-
-
[原创]格式化字符串打出没有回头路(下)——回头望月
-
发表于: 2024-5-28 08:52 49232
-
在我的前篇文章的最后提出了以下问题。在只有一次的格式字符串过程中,如果采用-z noww
的编译选项该如何处理,代码如下。
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
|
#include #include #include #define BUFLEN 0x60 int init_func(){
setvbuf (stdin,0,2,0);
setvbuf (stdout,0,2,0);
setvbuf (stderr,0,2,0);
return 0;
} int dofunc(){
char buf[BUFLEN];
puts ( "input" );
read(0, buf, BUFLEN);
printf (buf);
_exit(0);
return 0;
} int main(){
init_func();
dofunc();
return 0;
} // gcc -z now fmt_st.c -o fmt_strx64 |
题目存在的困难如下
- 保护模式为
Full RELRO
,不能攻击fini_array
。 - 程序只能执行一次,因为执行的是
_exit
函数,所以也无法进行EOP的攻击。 - 开启PIE,程序加载地址随机。
保护情况如下
1
2
3
4
5
|
Arch: amd64 - 64 - little
RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled |
2.printf
函数源码分析
要攻击printf
函数内部栈,就需要对函数进行更进一步的源码了解。以下以glibc2.31
为例,其他版本差距不大。
1.概述
printf
涉及主要有3个函数__vfprintf_internal buffered_vfprintf printf_positional
,其中,buffered_vfprintf
是关闭缓冲区才需要调用的函数,printf_positional
时需要定位时才调用的函数(类似于%4$p
),函数对字符的处理并没有像IO_FILE
一样使用虚表,而是使用了goto
这种反人类的编程语句,并且为c语言标准预留了所有的可执行虚表空间,如果以后增加标准格式可以快速实现。在关闭缓冲区后一般的调用过程如下。
1 |
printf => __vfprintf_internal => buffered_vfprintf => __vfprintf_internal => printf_positional
|
2.准备工作
1.跳转程序的处理逻辑
printf
使用的是goto
来进行跳转,它定义了一个跳表数组来表示格式化字符,其中字符所对应的数字是由stepX_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
|
// /stdio-common/vfprintf-internal.c static const uint8_t jump_table[] =
{
/* ' ' */ 1, 0, 0, /* '#' */ 4,
0, /* '%' */ 14, 0, /* '\''*/ 6,
0, 0, /* '*' */ 7, /* '+' */ 2,
0, /* '-' */ 3, /* '.' */ 9, 0,
/* '0' */ 5, /* '1' */ 8, /* '2' */ 8, /* '3' */ 8,
/* '4' */ 8, /* '5' */ 8, /* '6' */ 8, /* '7' */ 8,
/* '8' */ 8, /* '9' */ 8, 0, 0,
0, 0, 0, 0,
0, /* 'A' */ 26, 0, /* 'C' */ 25,
0, /* 'E' */ 19, /* F */ 19, /* 'G' */ 19,
0, /* 'I' */ 29, 0, 0,
/* 'L' */ 12, 0, 0, 0,
0, 0, 0, /* 'S' */ 21,
0, 0, 0, 0,
/* 'X' */ 18, 0, /* 'Z' */ 13, 0,
0, 0, 0, 0,
0, /* 'a' */ 26, 0, /* 'c' */ 20,
/* 'd' */ 15, /* 'e' */ 19, /* 'f' */ 19, /* 'g' */ 19,
/* 'h' */ 10, /* 'i' */ 15, /* 'j' */ 28, 0,
/* 'l' */ 11, /* 'm' */ 24, /* 'n' */ 23, /* 'o' */ 17,
/* 'p' */ 22, /* 'q' */ 12, 0, /* 's' */ 21,
/* 't' */ 27, /* 'u' */ 16, 0, 0,
/* 'x' */ 18, 0, /* 'z' */ 13
};
#define CHAR_CLASS(Ch) (jump_table[(INT_T) (Ch) - L_(' ')]) |
以step0_jumps
为例,其中'REF (width)
与REF (form_unknown)
偏移为8,所以jump_table
中1-9
代表的值都是8。REF (width)
则代表真实的地址差。
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
|
static JUMP_TABLE_TYPE step0_jumps[30] = \
{ \
REF (form_unknown), \
REF (flag_space), /* for ' ' */ \
REF (flag_plus), /* for '+' */ \
REF (flag_minus), /* for '-' */ \
REF (flag_hash), /* for ' \
REF (flag_zero), /* for '0' */ \
REF (flag_quote), /* for '\'' */ \
REF (width_asterics), /* for '*' */ \
REF (width), /* for '1'...'9' */ \
REF (precision), /* for '.' */ \
REF (mod_half), /* for 'h' */ \
REF (mod_long), /* for 'l' */ \
REF (mod_longlong), /* for 'L', 'q' */ \
REF (mod_size_t), /* for 'z', 'Z' */ \
REF (form_percent), /* for '%' */ \
REF (form_integer), /* for 'd', 'i' */ \
REF (form_unsigned), /* for 'u' */ \
REF (form_octal), /* for 'o' */ \
REF (form_hexa), /* for 'X', 'x' */ \
REF (form_float), /* for 'E', 'e', 'F', 'f', 'G', 'g' */ \
REF (form_character), /* for 'c' */ \
REF (form_string), /* for 's', 'S' */ \
REF (form_pointer), /* for 'p' */ \
REF (form_number), /* for 'n' */ \
REF (form_strerror), /* for 'm' */ \
REF (form_wcharacter), /* for 'C' */ \
REF (form_floathex), /* for 'A', 'a' */ \
REF (mod_ptrdiff_t), /* for 't' */ \
REF (mod_intmax_t), /* for 'j' */ \
REF (flag_i18n), /* for 'I' */ \
};
# define JUMP_TABLE_BASE_LABEL do_form_unknown # define REF(Name) &&do_##Name - &&JUMP_TABLE_BASE_LABEL |
当程序中进行JUMP (*++f, step0_jumps);
跳转时,则调到step0_jumps[8]
处所代表的值,则是程序的真实偏移地址。
1
2
3
4
5
6
7
8
9
10
11
12
|
JUMP (*++f, step0_jumps); # define JUMP(ChExpr, table) \ do \
{ \
const void *ptr; \
spec = (ChExpr); \
ptr = NOT_IN_JUMP_RANGE (spec) ? REF (form_unknown) \
: table[CHAR_CLASS (spec)]; \
goto *ptr; \
} \
while (0)
#endif |
同时,程序使用LABEL
来定义跳转位置,并设置参数。
1
2
3
4
|
#define LABEL(Name) do_##Name LABEL (flag_space): space = 1;
JUMP (*++f, step0_jumps);
|
2.程序输出
程序的输出主要使用outstring
,最终还是调用IO_FILE
的xsputn
函数。代码在/stdio-common/vfprintf-internal.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
|
# define PUT(F, S, N) _IO_sputn ((F), (S), (N)) #define outchar(Ch) \ do \
{ \
const INT_T outc = (Ch); \
if (PUTC (outc, s) == EOF || done == INT_MAX) \
{ \
done = -1; \
goto all_done; \
} \
++done; \
} \
while (0)
#define outstring(String, Len) \ do \
{ \
assert (( size_t ) done <= ( size_t ) INT_MAX); \
if (( size_t ) PUT (s, (String), (Len)) != ( size_t ) (Len)) \
{ \
done = -1; \
goto all_done; \
} \
if (__glibc_unlikely (INT_MAX - done < (Len))) \
{ \
done = -1; \
__set_errno (EOVERFLOW); \
goto all_done; \
} \
done += (Len); \
} \
while (0)
|
需要注意的是在glibc2.34
后实现了outstring_func
函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# define PUT(F, S, N) _IO_sputn ((F), (S), (N)) static inline int
outstring_func ( FILE *s, const UCHAR_T *string, size_t length, int done)
{ assert (( size_t ) done <= ( size_t ) INT_MAX);
if (( size_t ) PUT (s, string, length) != ( size_t ) (length))
return -1;
return done_add_func (length, done);
} #define outstring(String, Len) \ do \
{ \
const void *string_ = (String); \
done = outstring_func (s, string_, (Len), done); \
if (done < 0) \
goto all_done; \
} \
while (0)
|
程序还有一个填充输出函数,类似于%100a
在输出前填充的空格,
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
|
static inline int
pad_func ( FILE *s, CHAR_T padchar, int width, int done)
{ if (width > 0)
{
ssize_t written;
#ifndef COMPILE_WPRINTF written = _IO_padn (s, padchar, width);
#else written = _IO_wpadn (s, padchar, width);
#endif if (__glibc_unlikely (written != width))
return -1;
return done_add_func (width, done);
}
return done;
} #define PAD(Padchar) \ do \
{ \
done = pad_func (s, (Padchar), width, done); \
if (done < 0) \
goto all_done; \
} \
while (0)
|
其中_IO_padn
在/libio/iopadn.c
中,最终还是调用IO_FILE
的xsputn
函数。
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
|
ssize_t _IO_padn ( FILE *fp, int pad, ssize_t count)
{ char padbuf[PADSIZE];
const char *padptr;
int i;
size_t written = 0;
size_t w;
if (pad == ' ' )
padptr = blanks;
else if (pad == '0' )
padptr = zeroes;
else
{
for (i = PADSIZE; --i >= 0; )
padbuf[i] = pad;
padptr = padbuf;
}
for (i = count; i >= PADSIZE; i -= PADSIZE)
{
w = _IO_sputn (fp, padptr, PADSIZE);
written += w;
if (w != PADSIZE)
return written;
}
if (i > 0)
{
w = _IO_sputn (fp, padptr, i);
written += w;
}
return written;
} libc_hidden_def (_IO_padn) |
3.跳表
程序中定义了2组共4个跳表,分别是STEP0_3_TABLE
和STEP0_4_TABLE
,跳表是按照字符顺序进行。例如,第一个跳表基本上所有的都有定义,到了第四个跳表则定义很少,如下。
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
|
#define STEP4_TABLE \ /* Step 4: processing format specifier. */ \
static JUMP_TABLE_TYPE step4_jumps[30] = \
{ \
REF (form_unknown), \
REF (form_unknown), /* for ' ' */ \
REF (form_unknown), /* for '+' */ \
REF (form_unknown), /* for '-' */ \
REF (form_unknown), /* for ' \
REF (form_unknown), /* for '0' */ \
REF (form_unknown), /* for '\'' */ \
REF (form_unknown), /* for '*' */ \
REF (form_unknown), /* for '1'...'9' */ \
REF (form_unknown), /* for '.' */ \
REF (form_unknown), /* for 'h' */ \
REF (form_unknown), /* for 'l' */ \
REF (form_unknown), /* for 'L', 'q' */ \
REF (form_unknown), /* for 'z', 'Z' */ \
REF (form_percent), /* for '%' */ \
REF (form_integer), /* for 'd', 'i' */ \
REF (form_unsigned), /* for 'u' */ \
REF (form_octal), /* for 'o' */ \
REF (form_hexa), /* for 'X', 'x' */ \
REF (form_float), /* for 'E', 'e', 'F', 'f', 'G', 'g' */ \
REF (form_character), /* for 'c' */ \
REF (form_string), /* for 's', 'S' */ \
REF (form_pointer), /* for 'p' */ \
REF (form_number), /* for 'n' */ \
REF (form_strerror), /* for 'm' */ \
REF (form_wcharacter), /* for 'C' */ \
REF (form_floathex), /* for 'A', 'a' */ \
REF (form_unknown), /* for 't' */ \
REF (form_unknown), /* for 'j' */ \
REF (form_unknown) /* for 'I' */ \
}
|
3.主要函数分析
1.printf
典型的GUN
软链接,调用__vfprintf_internal
,在/stdio-common/printf.c
中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// /stdio-common/printf.c int __printf ( const char *format, ...)
{ va_list arg;
int done;
va_start (arg, format);
// 主要函数
done = __vfprintf_internal (stdout, format, arg, 0);
va_end (arg);
return done;
} #undef _IO_printf ldbl_strong_alias (__printf, printf );
ldbl_strong_alias (__printf, _IO_printf); |
2.__vfprintf_internal
(vfprintf
)
简单宏定义,即为vfprintf
函数,在/stdio-common/vfprintf-internal.c
中。
1
2
|
# define vfprintf __vfprintf_internal int vfprintf ( FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
|
vfprintf
经过一系列参数定义、格式化字符定义、检测及非格式化字符输出之后就进入格式化字符处理流程,在每一个处理流程过程中设置相应的参数。
1
2
3
|
LABEL (flag_space): space = 1;
JUMP (*++f, step0_jumps);
|
大部分跳转我们并不在意,只重点说明以下几个,LABEL (form_unknown)
就执行结束了,也就是遇到非格式化字符串就是啥都不干。
1
2
3
4
5
6
7
8
|
LABEL (form_unknown): if (spec == L_( '\0' ))
{
/* The format string ended before the specifier is complete. */
__set_errno (EINVAL);
done = -1;
goto all_done;
}
|
当存在$
时则执行goto do_positional
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
- [原创]反序列化的前生今世 9945
- [原创]gdb在逆向爆破中的应用 3592
- [原创]EOP编程 11297
- [原创]格式化字符串打出没有回头路(下)——回头望月 49233
- [原创]格式化字符串打出没有回头路(上) 20623