ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Game - pwnable.xyz
    Write-ups/pwnable.xyz 2020. 2. 12. 21:33

    Prob Info


    Prob Info
    Checksec

     

    int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
    {
      const char *v3; // rdi
      signed int v4; // eax
    
      setup();
      v3 = "Shell we play a game?";
      puts("Shell we play a game?");
      init_game();
      while ( 1 )
      {
        while ( 1 )
        {
          print_menu(v3, argv);
          v3 = "> ";
          printf("> ");
          v4 = read_int32();
          if ( v4 != 1 )
            break;
          (*((void (**)(void))cur + 3))();
        }
        if ( v4 > 1 )
        {
          if ( v4 == 2 )
          {
            save_game();
          }
          else
          {
            if ( v4 != 3 )
              goto LABEL_13;
            edit_name();
          }
        }
        else
        {
          if ( !v4 )
            exit(1);
    LABEL_13:
          v3 = "Invalid";
          puts("Invalid");
        }
      }
    }


    init_game()

    char *init_game()
    {
      char *result; // rax
    
      saves = (__int64)malloc(0x20uLL);
      cur = (char *)find_last_save(32LL);
      printf("Name: ");
      read(0, cur, 0x10uLL);
      result = cur;
      *((_QWORD *)cur + 3) = play_game;
      return result;
    }

    saves에 malloc으로 힙을 할당하고 find_last_save함수로 마지막 힙을 cur에 저장한다. 이후 cur에 이름을 0x10바이트 입력받고 힙의 마지막 8바이트에 play_game함수의 주소를 넣는다.

     

    bss

    .bss:0000000000602100 ; __int64 saves[5]
    .bss:0000000000602100 saves           dq ?                    ; DATA XREF: find_last_save+1E↑o
    .bss:0000000000602100                                         ; find_last_save+3E↑o ...
    .bss:0000000000602108                 dq ?
    .bss:0000000000602110                 dq ?
    .bss:0000000000602118                 dq ?
    .bss:0000000000602120                 dq ?

    init_game 함수가 끝나면 아래와 같다.

    pwndbg> x/10gx &cur
    0x6020e0 <cur>: 0x0000000000603010      0x0000000000000000
    0x6020f0:       0x0000000000000000      0x0000000000000000
    0x602100 <saves>:       0x0000000000603010      0x0000000000000000
    0x602110 <saves+16>:    0x0000000000000000      0x0000000000000000
    0x602120 <saves+32>:    0x0000000000000000      0x0000000000000000
    pwndbg> x/6gx 0x603000
    0x603000:       0x0000000000000000      0x0000000000000031
    0x603010:       0x6161616161616161      0x6161616161616161
    0x603020:       0x0000000000000000      0x0000000000400aca

    이를 기준으로 간단하게 구조체를 만들었다.

    00000000 game            struc ; (sizeof=0x20, align=0x8, copyof_6)
    00000000 name            db 16 dup(?)
    00000010 score           dq ?
    00000018 f_play          dq ?                    ; offset
    00000020 game            ends

    구조체에 함수 포인터가 있으므로 이것을 덮으면 된다.

     

    ssize_t edit_name()
    {
      size_t v0; // rax
    
      v0 = strlen(cur->name);
      return read(0, cur, v0);
    }

    현재 게임의 이름 부분을 strlen으로 길이를 구해 그만큼 read로 입력받는다. 함수포인터를 덮어야 하는데, 함수 포인터와 name 사이에 socre가 존재해 strlen을 이용하면 함수포인터를 덮을 수 없다. 일단 play_game함수를 보자.

     

    unsigned __int64 play_game()
    {
      __int16 v0; // dx
      __int16 v1; // dx
      __int16 v2; // dx
      __int16 v3; // dx
      int fd; // [rsp+Ch] [rbp-124h]
      int v6; // [rsp+10h] [rbp-120h]
      unsigned int buf; // [rsp+14h] [rbp-11Ch]
      unsigned int v8; // [rsp+18h] [rbp-118h]
      unsigned __int8 v9; // [rsp+1Ch] [rbp-114h]
      char s; // [rsp+20h] [rbp-110h]
      unsigned __int64 v11; // [rsp+128h] [rbp-8h]
    
      v11 = __readfsqword(0x28u);
      fd = open("/dev/urandom", 0);
      if ( fd == -1 )
      {
        puts("Can't open /dev/urandom");
        exit(1);
      }
      read(fd, &buf, 0xCuLL);
      close(fd);
      v9 &= 3u;
      memset(&s, 0, 0x100uLL);
      snprintf(&s, 0x100uLL, "%u %c %u = ", buf, ops[v9], v8);
      printf("%s", &s);
      v6 = read_int32();
      if ( v9 == 1 )
      {
        if ( buf - v8 == v6 )
          v1 = LOWORD(cur->score) + 1;
        else
          v1 = LOWORD(cur->score) - 1;
        LOWORD(cur->score) = v1;
      }
      else if ( v9 > 1 )
      {
        if ( v9 == 2 )
        {
          if ( buf / v8 == v6 )
            v2 = LOWORD(cur->score) + 1;
          else
            v2 = LOWORD(cur->score) - 1;
          LOWORD(cur->score) = v2;
        }
        else if ( v9 == 3 )
        {
          if ( v8 * buf == v6 )
            v3 = LOWORD(cur->score) + 1;
          else
            v3 = LOWORD(cur->score) - 1;
          LOWORD(cur->score) = v3;
        }
      }
      else if ( !v9 )
      {
        if ( v8 + buf == v6 )
          v0 = LOWORD(cur->score) + 1;
        else
          v0 = LOWORD(cur->score) - 1;
        LOWORD(cur->score) = v0;
      }
      return __readfsqword(0x28u) ^ v11;
    }

    내용이 많은데, 이중에서 그냥 score가 -1점이 되는 부분이 존재하는것만 확인했다. 0점에서 -1점을 하면 0xffffffffffffffff이 될 줄 알았는데 잘 보니 LOWORD, 즉 하위 2바이트만 사용해 0xffff가 된다. 이 함수 외에는 score를 건들 수 없으니 다음 함수를 살펴봤다.

     

    int save_game()
    {
      game *v0; // rcx
      __int64 v1; // rdx
      __int64 v2; // rdx
      game *v3; // rax
      signed int i; // [rsp+Ch] [rbp-4h]
    
      for ( i = 1; i <= 4; ++i )
      {
        if ( !saves[i] )
        {
          saves[i] = malloc(0x20uLL);
          v0 = saves[i];
          v1 = *&cur->name[8];
          *v0->name = *cur->name;
          *&v0->name[8] = v1;
          saves[i]->score = SLOWORD(cur->score);
          saves[i]->f_play = play_game;
          v2 = i;
          v3 = saves[v2];
          cur = saves[v2];
          return v3;
        }
      }
      LODWORD(v3) = puts("Not enough space.");
      return v3;
    }

    saves에서 빈칸을 찾고 그곳에 새로운 힙을 저장하고 새로은 힙에 cur의 내용을 복사한다. score를 복사하는 부분을 어셈블리어로 다시 봐봤다.

    mov     rax, cs:cur
    movzx   eax, word ptr [rax+10h]
    movsx   rax, ax
    mov     [rdx], rax

    cur->score를 eax에 넣고 (rax = 0x000000000000ffff) movsx rax, ax를 통해 rax가 0xffffffffffffffff이 된다. 이 값을 새로운 힙에 저장해 원래 원하던 값으로 만들 수 있다. 

    이후 edit_name을 진행하면 총 0x18 바이트 + 함수포인터 길이 만큼 덮을 수 있으므로 win 함수의 주소로 덮어주면 된다.

    'Write-ups > pwnable.xyz' 카테고리의 다른 글

    SUS - pwnable.xyz  (0) 2020.02.13
    fspoo - pwnable.xyz  (0) 2020.02.13
    l33t-ness - pwnable.xyz  (0) 2020.02.12
    Jmp table - pwnable.xyz  (0) 2020.02.12
    TLSv00 - pwnable.xyz  (0) 2020.02.12

    댓글

Designed by Tistory.