这道题的总体思路就是uaf + fsb, 开始以为是libc2.29的uaf + tcache double free, 后来发现没有show导致没有办法leak出libc地址,最后无从下手。后来还是从别人的博客里学到神奇的姿势。
题目描述
pwnable.tw上的一道题。
Partial RELRO,got表可以改,基本上是利用改got表来getshell了。
1 | Arch: amd64-64-little |
题目提供了四个功能
1 | 1. alloc |
其中alloc功能相当于malloc一个chunk,但是chunk的size<=0x78
, 且chunk的地址会放在bss段的heap数组上,heap数组允许至多两个成员。接受完data输入后会在末尾补上0,这里存在一个off by null,但是没有什么用。
realloc功能为更改chunk的size以及更新content内容,这里末尾不会自动补0.值得注意的是,当size为0时,相当于触发free但是对应的bss上的数组中的指针不会清0,于是这里有一个uaf可以利用。
free功能就是同时free掉chunk以及将heap数组中的指针清0
相关知识点
libc2.29的tcache double free的检测机制
1 | //glibc-2.27 |
多了一个key
的成员变量。这个key
的作用是什么?
1 | //glibc-2.27 |
当chunk被free到tcache中时,key
会被置为第一个chunk的地址,也就是tcache的结构chunk。当chunk从tcache中取出来时,key
会被设置成NULL。总而言之,就是通过key
来表明这个chunk是否在tcache中。
而这个key
也是libc2.29中提供的对tcache的额外的检查,即在将一个chunk放入tcache时,会检查该chunk的key
是否等于tcache结构体的地址,如果是,则进一步检查tcache中是否已有地址相同的chunk,从而触发double free的检查机制。
然而仍然有绕过检查的方法,就是key
不等于tcache结构体的位置即可,而这往往需要配合uaf或者chunk overlap同时利用才能做到。
realloc(ptr, size)函数
总的来说,realloc函数在size不同的情况下,可以达到malloc,edit,free三种功能:
ptr == 0
: malloc(size)ptr != 0 && size == 0
: free(ptr)ptr != 0 && size == old_size
: edit(ptr)ptr != 0 && size < old_size
: edit(ptr) and free(remainder)ptr != 0 && size > old_size
: new_ptr = malloc(size); strcpy(new_ptr, ptr); free(ptr); return new_ptr;
利用思路
- 利用uaf在tcache不同size的链表中放置一个
atoll_got
的chunk - 利用其中一个指向
atoll_got
的chunk更改atoll_got
为printf_plt
,这样在调用atoll
时,就会调用printf
从而构造出一个格式化字符串漏洞,利用这个漏洞可以leak出栈上的libc地址,这里选择leak__libc_start_main
。 - 利用另一个指向
atoll_got
的chunk将atoll_got
再改成system
,注意因为此时atoll
是printf
,所以在调用alloc时,需要输入的Index和Size不是直接输入数字,而是通过输入的string的长度来通过printf返回的值间接传给Index和Size。由于read的长度限制在16,因此注意这里tcache的相关chunk必须是(可以通过”%xc”来控制printf的返回值。)size=0x20
,否则无法利用。 - 最后再输入
/bin/sh\x00
调用atoll
来执行system("/bin/sh");
getshell即可。
exp
1 | context.log_level = "debug" |
小结
- 将
atoll
改为printf
确实是神奇的姿势(我没遇到过),从这个思路上看,貌似还可以引入本来不存在的漏洞来利用。 - 调试的时候gef好像有点问题,peda可以用。