printf / fprintf 메모리 구조

dandb3·2024년 5월 20일
0

pwnable

목록 보기
23/25

printf와 fprintf의 소스코드를 비교해 보자.

printf

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

fprintf

int
__fprintf (FILE *stream, const char *format, ...)
{
  va_list arg;
  int done;

  va_start (arg, format);
  done = __vfprintf_internal (stream, format, arg, 0);
  va_end (arg);

  return done;
}

보면 알 수 있지만, 사실상 둘은 똑같다고 볼 수 있다.
그냥 stream 인자로 stdout을 주는 것인지, user-defined 값으로 주는 것인지의 차이일 뿐이다.
앞으로 fprintf를 기준으로 분석할 것이다.
먼저 인자들이 메모리에 어떻게 쌓이는지 확인해 보자.

Dump of assembler code for function __fprintf:
   0x00007ed91ede0bd0 <+0>:     endbr64 
   0x00007ed91ede0bd4 <+4>:     sub    rsp,0xd8
   0x00007ed91ede0bdb <+11>:    mov    QWORD PTR [rsp+0x30],rdx
   0x00007ed91ede0be0 <+16>:    mov    QWORD PTR [rsp+0x38],rcx
   0x00007ed91ede0be5 <+21>:    mov    QWORD PTR [rsp+0x40],r8
   0x00007ed91ede0bea <+26>:    mov    QWORD PTR [rsp+0x48],r9
   0x00007ed91ede0bef <+31>:    test   al,al
   0x00007ed91ede0bf1 <+33>:    je     0x7ed91ede0c2a <__fprintf+90>
   0x00007ed91ede0bf3 <+35>:    movaps XMMWORD PTR [rsp+0x50],xmm0
   0x00007ed91ede0bf8 <+40>:    movaps XMMWORD PTR [rsp+0x60],xmm1
   0x00007ed91ede0bfd <+45>:    movaps XMMWORD PTR [rsp+0x70],xmm2
   0x00007ed91ede0c02 <+50>:    movaps XMMWORD PTR [rsp+0x80],xmm3
   0x00007ed91ede0c0a <+58>:    movaps XMMWORD PTR [rsp+0x90],xmm4
   0x00007ed91ede0c12 <+66>:    movaps XMMWORD PTR [rsp+0xa0],xmm5
   0x00007ed91ede0c1a <+74>:    movaps XMMWORD PTR [rsp+0xb0],xmm6
   0x00007ed91ede0c22 <+82>:    movaps XMMWORD PTR [rsp+0xc0],xmm7
   0x00007ed91ede0c2a <+90>:    mov    rax,QWORD PTR fs:0x28
=> 0x00007ed91ede0c33 <+99>:    mov    QWORD PTR [rsp+0x18],rax
   0x00007ed91ede0c38 <+104>:   xor    eax,eax
   0x00007ed91ede0c3a <+106>:   lea    rax,[rsp+0xe0]
   0x00007ed91ede0c42 <+114>:   xor    ecx,ecx
   0x00007ed91ede0c44 <+116>:   mov    rdx,rsp
   0x00007ed91ede0c47 <+119>:   mov    QWORD PTR [rsp+0x8],rax
   0x00007ed91ede0c4c <+124>:   lea    rax,[rsp+0x20]
   0x00007ed91ede0c51 <+129>:   mov    DWORD PTR [rsp],0x10
   0x00007ed91ede0c58 <+136>:   mov    DWORD PTR [rsp+0x4],0x30
   0x00007ed91ede0c60 <+144>:   mov    QWORD PTR [rsp+0x10],rax
   0x00007ed91ede0c65 <+149>:   call   0x7ed91edf5860 <__vfprintf_internal>
   0x00007ed91ede0c6a <+154>:   mov    rcx,QWORD PTR [rsp+0x18]
   0x00007ed91ede0c6f <+159>:   xor    rcx,QWORD PTR fs:0x28
   0x00007ed91ede0c78 <+168>:   jne    0x7ed91ede0c82 <__fprintf+178>
   0x00007ed91ede0c7a <+170>:   add    rsp,0xd8
   0x00007ed91ede0c81 <+177>:   ret    
   0x00007ed91ede0c82 <+178>:   call   0x7ed91eeaec90 <__stack_chk_fail>
End of assembler dump.

앞으로 format string의 인자들을 format string의 표기를 따라 1$, 2$, ... 이런 식으로 표기할 것이다.

  • <__fprintf + 11> ~ <__fprintf + 26>
    1$, 2$, 3$, 4$rsp + 0x30부터 순서대로 메모리에 저장해 준다.
  • <__fprintf + 31> ~ <__fprintf + 82>
    함수 호출 전에 mov eax, 0x0를 호출해 주므로, __fprintf + 31 ~ __fprintf + 82 까지는 jmp된다.
  • <__fprintf + 90> ~ <__fprintf + 99>
    rsp + 0x18에 canary가 저장된다.
  • <__fprintf + 104> ~ <__fprintf + 119>
    rsp + 0x85$부터가 저장된 주소가 저장된다.
  • <__fprintf + 124> ~ <__fprintf + 144>
    rsp에 0x10이 저장된다.
    rsp + 0x4에 0x30이 저장된다.
    rsp + 0x10에 rsp + 0x20이 저장된다.
    typedef struct {
       unsigned int gp_offset;
       unsigned int fp_offset;
       void *overflow_arg_area;
       void *reg_save_area;
    } va_list[1];
    va_list 구조체는 위와 같은데, rsp의 위치에 va_list가 저장된다고 생각하면 된다.

그 후에 __vfprintf_internal 함수가 호출된다.

__vfprintf_internal 함수가 호출되기 전의 메모리 구조를 확인해 보면 다음과 같다.

-------va_list-------
rsp + 0x0     0x10
rsp + 0x4     0x30
rsp + 0x8     rsp + 0xe0 (5$부터의 주소)
rsp + 0x10    rsp + 0x20
---------------------
rsp + 0x18    canary
rsp + 0x20    ??
rsp + 0x28    ??
rsp + 0x30    1$
rsp + 0x38    2$
rsp + 0x40    3$
rsp + 0x48    4$
rsp + 0x50

...

rsp + 0xd8    ret_addr
rsp + 0xe0    5$
rsp + 0xe8    6$

...

여기까지만..

profile
공부 내용 저장소

0개의 댓글