pwn1
本题的逻辑和线上的EasyCoin还是很像的,但是有些蜜汁逻辑真的看着难受。
基本逻辑
regist函数在ptr数组中找到一个空闲的位置,ptr中每个地址都是一个0x130大小的块,注册后的结构如下:
1 | struct user{ |
login函数简单粗暴的查找username,然后登入。
view_profile打印出用户名、年龄和描述信息。
update更新用户名、年龄和描述信息,非常坑的是,更新用户名时只能再输入strlen(username)长度的字符。
friend函数可以删除或者添加一个friend。删除时,沿着next找到要删除的user。添加时,沿着next找到最后一个结点并添加。忍住不吐槽这个破函数……忍住……. 这里可以添加自己为friend
send函数发送msg,每次send之前要构造一个msg结构体,msg的结构体如下:
1 | struct msg{ |
这里用到的strdup函数相当于:
1 | p = malloc(strlen(buf)); |
view_msg和logout比较简单,这里就不列出了。
漏洞分析
和EasyCoin一样,本题也可以添加自己为好友,next指针指向自己,删除好友时,相当于释放自己这块内存。而释放后没有将指针置零,UAF漏洞能够泄露地址和覆写got表。
漏洞利用
泄露libc地址
释放一个user后,它的name字段被覆写为FD,该指针指向main_arena+88。
覆写got表
当重新注册一个新用户时,首先在ptr数组中找一个闲置的块,然后按照输入的size,malloc一个块作为name的空间。由于刚刚释放了一个user,unsorted bin中已有一个大小为0x130的块。当输入的size小于0x130时,就会从unsorted bin上分配给name。
此时在name中写入puts@got的地址,即相当覆写已释放的user的name地址为puts@got地址,此时我们再update已释放user,就能向puts@got写入one_gadget地址了。
艰苦的心路历程
虽然本题利用很简单,但是!!我踩了好多坑!!
思路一:把已释放user的name字段覆写为malloc_hook的地址,再update,试图向malloc_hook中写入one_gadget。然而!update时用到了strlen!而strlen(&__malloc_hook)的值为0,也就是根本写不进去
思路二:泄露libc也可以通过environ变量泄露栈地址,覆写函数返回地址为one_gadget。再一次被update阻拦了,栈中的返回地址长度都为3(如0x400ebe)。又想是否能够修改main函数的返回地址?发现main函数是通过exit退出的,没有leave;ret环节。
思路三:向FD所指的main_arena+88中写入got表地址,由于main_arena+88恰好是记录top地址的位置,覆写成got表,之后的malloc会从got表中分配并可以覆写got表。然而,update还必须要输入age,age处对应了BK指针,为了保证能够正常分配块,age也应该保持main_arena+88,但是update对age进行了截断,只复制后四字节然后自行填充,就不能把完整的地址写入BK处。
利用脚本
1 | from pwn import * |
pwn2
pwn2还是挺简单的,存在数组越界漏洞,而且该数组还是在bss段上,因此可以泄露stderr的地址进而泄露libc地址;另外bss离got也很近,可以覆写got表。
附上脚本
1 | from pwn import * |