近期都很忙,突发奇想在二进制✌()的引导下想完成这一道所谓的中等题😊😊😊(在这之前补充一下,聊到了凌晨 4 点,现在身心俱疲。)
此前一直做一些简单的板子题度日,堆只看不碰,栈只挑简单的做,真的可恶啊!
07-15:tips
本题主要是允许栈迁移,允许利用 read 语句多次写入,因此构成了任意写和任意 rop 执行。
本题的难点在于
1. 没有 pop rdi 用 csu 解决。
2. 没有 syscall,通过改写 read 的 got 表为附近的指令的来构造 syscall。
3.rax 控制,通过 read 读入的实际大小返回值为 rax 构造。
因此本题的思路,任意写布置 rop,修改 got 表,然后执行 rop,getshell。
# 1.read 原程序
只有一个 read,明显栈溢出,至于怎么溢出,额,待会细琐。当然这里一眼看过去,shellcode 是最好想到的啊,我们简单看看保护和 vmmap 看看有没有
1 | 解释 |
显然,这里没有像静态链接程序固定带有的 mprotect 函数,可以去修改内存页的权限为 rwxp,(再调用 read 函数将 pwntools 生成的 shellcode 代码注入到 addr 中,之后再将 read 函数返回地址写为 addr 地址,调用 shellcode,获得 shell),而且就算可以改,也没办法泄露 addr,这个想法被 pass 了。
# 2. 一个可行的 idea - 拓展到栈上的 ret2dl
以前只是见过,今天来实操一下。😋,当然 t1d 说这个题用它做不出来。
# ret2dl 介绍
对于 NO RELRO 的情况,我们可以利用 read 函数修改 .dynamic
段中的 .dynstr
节的地址,将其修改为我们可控制的地址。然后我们可以在这个地址上构造一个伪造的 fake_dynstr
,将其中的某个字符串替换为 system
函数的字符串。
接下来,我们可以调用 .dl_fixup
函数,它会解析我们修改的字符串所对应的原函数。 _dl_fixup
函数根据字符串(也就是函数名)来索引函数,因此最终会解析 system
函数。
无论是 32 位还是 64 位,实现这个过程都相对简单。具体步骤如下:
1 | 找到.dynamic段并获取.dynstr节的地址(STRTAB的d_ptr)。 |
注:如果是 FULL RELERO
,程序在运行之前就已经调用了 ld.so
将所需的外部函数加载完成,程序运行期间不进行动态加载,因此,在程序的 got
表中, link_map
和 dl_runtime_resolve
函数的地址都为 0
可以参考:【精选】ret2dlresolve 超详细教程 (x86&x64)-CSDN 博客
# 本题的具体实践
显然,除了上述方法中通过调用 system("/bin/sh")
和打 one_gadget
来 getshell
以外,还有一种常见方式就是通过触发 syscall 软中断来 getshell,然而,本题的 ELF 源文件中并没有 syscall
这个 gadget
,并且,我们又无法泄露 libc 信息,用 libc 中的 syscall
,因此,我们需要想办法创造出 syscall
这个 gadget。
在 t1d 的指引下,我去回顾了一遍 read,write 这些函数。参考文章:ctf 中关于 syscall 系统调用的简单分析 - 知乎 (zhihu.com)
1 | read(): |
其实,在 libc 中,read,write,close,alarm 等等这些函数只是对系统调用进行了简单的封装,在这些函数中都存在 syscall 这个 gadget,且 syscall 一般离函数的开始地址都很近,故可以将这些函数的 got 表改为 syscall 的地址,从而触发系统调用。
同样我们可以具体看看 ida 里面 read 的汇编:本题的不知道为啥没显示 syscall,显示的是 call😋,下面是博客里的,看着更清晰一点。
1 | 将read的系统调用号 0 赋值给 rax |
😋, 要是所有题目都可以直接去把 read 的 got 表改成 syscall,再修改参数执行就好了()
好了,我们知道,在 64 位中,要想成功 getshell,需要控制的寄存器如下:
1 | rax = 0x3b |
因此针对本题的思路如下:
我们可以用 ret2csu 先改写 read 的 got 表为 syscall 的地址,有了 syscall 后,再由 read 读入的字节数控制 rax 寄存器(同时读入 /bin/sh 字符串),并用 ret2csu 控制 rdi,rsi,rdx 三个参数,最后调用触发 syscall 即可。t1d 说的大致相同,那我先去实搓了,回来接着写,万一还有坑,做不出来就是🤡了。
2023-11-17 10:53:07
2023-11-18 19:31:07
# 3. 小丑归来🤡
本题目的难点在栈迁,,,,(对我这个菜🐕来说)
断断续续鏖战 1 天,在师傅的敲打下,该碰的坑都碰,先总结得失:
1. 做题要动态调试,一下午可能跑了两三次吧,画表格布栈、打草稿,等等,这些都不如动调来的快
2. 栈迁移的技巧:直接把返回地址写成 read 的 lea rax, [rbp+buf]
,把 rbp 改成想迁移到的地址,这个地址有考究,待会认真说。
3. 巧妙利用 read 实现任意写,布链子。
待会说,自闭了,板子题只是基础,要多见新题,基础打牢,多调题目。
整体来说,对这个 “简单” 的做法还是存在一些疑惑,等弄懂了回来更新。
# 1)栈迁移
一些学习的博客:
栈迁移的原理 && 实战运用 - ZikH26 - 博客园 (cnblogs.com)
哎,少看,多实践
针对本题具体的是迁移到 read 的虚拟栈,em,还要再学习一下回来具体补充
# 2)ROP 链
像 ret2syscall 一样,直接调用 read_plt
# 3)用 csu 改 got
当然这里是和 4 结合在一起的,用 read 控制输入为 0x3b 调节 rax 实现改 got 同时调用 syscall 的作用
# 4.exp
1 | #seccomp-tools dump ./pwn.pwn看哪些被禁用了 |