Write-ups/pwnable.xyz

Free Spirit - pwnable.xyz

dolphinlmg 2020. 2. 12. 02:46

Prob Info


Prob Info
Checksec

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *v3; // rdi
  signed __int64 i; // rcx
  int v5; // eax
  char v7[8]; // [rsp+8h] [rbp-60h]
  char *buf; // [rsp+10h] [rbp-58h]
  char nptr[48]; // [rsp+18h] [rbp-50h]
  unsigned __int64 v10; // [rsp+48h] [rbp-20h]

  v10 = __readfsqword(0x28u);
  setup();
  buf = (char *)malloc(0x40uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      _printf_chk(1LL, "> ");
      v3 = nptr;
      for ( i = 12LL; i; --i )
      {
        *(_DWORD *)v3 = 0;
        v3 += 4;
      }
      read(0, nptr, 0x30uLL);
      v5 = atoi(nptr);
      if ( v5 != 1 )
        break;
      __asm { syscall; LINUX - sys_read }
    }
    if ( v5 <= 1 )
      break;
    if ( v5 == 2 )
    {
      _printf_chk(1LL, "%p\n");
    }
    else if ( v5 == 3 )
    {
      if ( (unsigned int)limit <= 1 )
        _mm_storeu_si128((__m128i *)v7, _mm_loadu_si128((const __m128i *)buf));
    }
    else
    {
LABEL_16:
      puts("Invalid");
    }
  }
  if ( v5 )
    goto LABEL_16;
  if ( !buf )
    exit(1);
  free(buf);
  return 0;
}

psuedocode에 나오지 않은 부분이 여럿 있다. 먼저 1번 메뉴는 syscall read인데 어셈블리어로 보면 아래와 같다. 

.text:0000000000400849                 mov     rsi, [rsp+68h+buf] ; buf
.text:000000000040084E                 xor     rax, rax
.text:0000000000400851                 xor     rdi, rdi        ; fd
.text:0000000000400854                 mov     rdx, 20h        ; count
.text:000000000040085B                 syscall                 ; LINUX - sys_read

buf에 0x20바이트 만큼 read를 한다.

 

2번 메뉴는 _printf_chk인데 어셈블리어로 보면 아래와 같다.

.text:000000000040085F loc_40085F:                             ; CODE XREF: main+80↑j
.text:000000000040085F                 lea     rsi, aP         ; "%p\n"
.text:0000000000400866                 mov     rdx, r12
.text:0000000000400869                 mov     edi, 1
.text:000000000040086E                 xor     eax, eax
.text:0000000000400870                 call    __printf_chk
.text:0000000000400875                 jmp     short loc_4007F8

세번째 인자로 r12의 값을 주며 이전에 마지막으로 사용된 r12의 값을 찾아보면 아래와 같다.

.text:00000000004007C0                 push    r12
.text:00000000004007C2                 push    rbp
.text:00000000004007C3                 xor     ebp, ebp
.text:00000000004007C5                 push    rbx
.text:00000000004007C6                 sub     rsp, 50h
.text:00000000004007CA                 mov     rax, fs:28h
.text:00000000004007D3                 mov     [rsp+68h+var_20], rax
.text:00000000004007D8                 xor     eax, eax
.text:00000000004007DA                 lea     rbx, [rsp+68h+nptr]
.text:00000000004007DF                 lea     r12, [rsp+68h+buf]
.text:00000000004007E4                 call    setup
.text:00000000004007E9                 mov     edi, 40h        ; size
.text:00000000004007EE                 call    malloc
.text:00000000004007F3                 mov     [rsp+68h+buf], rax

이 부분에서 r12의 값이 buf의 주소를 담고 있음을 알 수 있다. 1번 메뉴를 선택하면 buf의 주소를 릭 할 수 있다.

 

3번 메뉴도 뭔가 이상한 함수로 나와있는데 어셈블리어로 보면 다음과 같다.

.text:0000000000400884                 mov     rax, [rsp+68h+buf]
.text:0000000000400889                 movdqu  xmm0, xmmword ptr [rax]
.text:000000000040088D                 movdqu  xmmword ptr [rsp+8], xmm0

movdqe는 16바이트를 그대로 mov하는 명령어이다. 즉 buf에 있는 16바이트를 v7에 덮어 쓴다는 것이다. v7을 16바이트 덮게 되면 그 아래에 존재하는 buf를 덮게 된다. 이후 2번 메뉴를 통해 덮은 주소에 값을 쓸 수 있다. Full RELRO가 적용되어있기 때문에 GOT는 덮지 못하지만 buf의 주소를 릭할 수 있기 때문에 그대로 ret을 덮어버리면 된다. 

 

하지만 ret을 덮고 프로그램을 종료시키려고 하면 아래 코드에 의해 세그폴트가 뜬다. 

free(buf);

buf를 ret 주소로 덮은 상태에서 종료시키면 free를 진행하면서 청크를 fastbin에 넣게 되는데 이때 청크 구조가 맞아야 한다. 이를 통과하기 위해 쓰기 가능한 영역에 fake chunk를 만들어주고 그 buf를 그 주소로 덮어주면 된다.