ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TLSv00 - pwnable.xyz
    Write-ups/pwnable.xyz 2020. 2. 12. 03:53

    Prob Info


    Prob Info
    Checksec

     

    int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
    {
      signed int v3; // eax
      signed int v4; // ST0C_4
    
      setup(*(_QWORD *)&argc, argv, envp);
      puts("Muahaha you thought I would never make a crypto chal?");
      generate_key(0x3F);
      while ( 1 )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            print_menu();
            printf("> ");
            v3 = read_int32();
            if ( v3 != 2 )
              break;
            load_flag();
          }
          if ( v3 > 2 )
            break;
          if ( v3 != 1 )
            goto LABEL_12;
          printf("key len: ");
          v4 = read_int32();
          generate_key(v4);
        }
        if ( v3 == 3 )
        {
          print_flag();
        }
        else if ( v3 != 4 )
        {
    LABEL_12:
          puts("Invalid");
        }
      }
    }

    크립토 문제라고 한다. 일단 키 생성 부분부터 확인하면 아래와 같다.

    unsigned __int64 __fastcall generate_key(signed int a1)
    {
      signed int i; // [rsp+18h] [rbp-58h]
      int fd; // [rsp+1Ch] [rbp-54h]
      char s[72]; // [rsp+20h] [rbp-50h]
      unsigned __int64 v5; // [rsp+68h] [rbp-8h]
    
      v5 = __readfsqword(0x28u);
      if ( a1 > 0 && (unsigned int)a1 <= 0x40 )
      {
        memset(s, 0, 0x48uLL);
        fd = open("/dev/urandom", 0);
        if ( fd == -1 )
        {
          puts("Can't open /dev/urandom");
          exit(1);
        }
        read(fd, s, a1);
        for ( i = 0; i < a1; ++i )
        {
          while ( !s[i] )
            read(fd, &s[i], 1uLL);
        }
        strcpy(key, s);
        close(fd);
      }
      else
      {
        puts("Invalid key size");
      }
      return __readfsqword(0x28u) ^ v5;
    }

    인자로 받은 키 길이만큼 /dev/urandom에서 읽어들이고 key에 strcpy로 복사한다. 키값을 읽어올 때 널바이트는 무시하도록 만들어서 길이만큼 복사는 되지만 문자열 함수이므로 맨 뒤에 널바이트가 붙게 된다. 

     

    __int64 print_flag()
    {
      __int64 result; // rax
    
      puts("WARNING: NOT IMPLEMENTED.");
      result = (unsigned __int8)do_comment;
      if ( !(_BYTE)do_comment )
      {
        printf("Wanna take a survey instead? ");
        if ( getchar() == 121 )
          do_comment = (__int64 (*)(void))f_do_comment;
        result = do_comment();
      }
      return result;
    }

    플래그 출력 함수 부분이다. 그런데 기능이 구현되지 않아 대신 설문조사 함수를 do_comment에 저장하고 호출해준다. 그런데 이 함수의 주소를 보면 아래와 같은데, 실제 플래그를 출력해주는 함수와 근접하다.

    .text:0000000000000B1F ; __unwind {
    .text:0000000000000B1F                 push    rbp
    .text:0000000000000B20                 mov     rbp, rsp
    .text:0000000000000B23                 sub     rsp, 40h
    .text:0000000000000B27                 mov     rax, fs:28h
    .text:0000000000000B30                 mov     [rbp+var_8], rax
    .text:0000000000000B34                 xor     eax, eax
    .text:0000000000000B36                 lea     rdi, aEnterComment ; "Enter comment: "
    .text:0000000000000B00 real_print_flag proc near
    .text:0000000000000B00 ; __unwind {
    .text:0000000000000B00                 push    rbp
    .text:0000000000000B01                 mov     rbp, rsp
    .text:0000000000000B04                 lea     rsi, flag
    .text:0000000000000B0B                 lea     rdi, format     ; "%s"
    .text:0000000000000B12                 mov     eax, 0
    .text:0000000000000B17                 call    printf
    .text:0000000000000B1C                 nop
    .text:0000000000000B1D                 pop     rbp
    .text:0000000000000B1E                 retn
    .text:0000000000000B1E ; } // starts at B00
    .text:0000000000000B1E real_print_flag endp

    게다가 끝 주소도 00으로 끝나서 PIE가 걸려있어도 주소 끝부분만 널바이트로 덮어주면 3번 메뉴로 실제 플래그 출력 함수를 호출할 수 있다. 여기서 앞서 설명한 generate_key함수의 strcpy가 off-by-one을 가능하게 해주는 것을 알 수 있다. 먼저 3번 메뉴를 한번 호출해서 do_comment에 f_do_comment 함수의 주소를 적고 다시 generate_key를 호출하면 off-by-one을 통해 do_comment가 가리키는 함수를 실제 플래그 함수로 바꿀 수 있다.

     

    여기까지 진행하면 실제 플래그를 출력할 수 있는데, 이 플래그는 xor 암호화가 되어있다. 플래그 형식이 "FLAG{}" 이므로 이런 형식이 나오도록 하면 된다. 키 길이를 1로 하면 (0은 불가) 키가 아래와 같이 될 것이다.

    key : | ?? | 00 | ?? | ?? | ... | ?? |

    그렇다면 1번째 인덱스의 L은 "L" ^ 0 을 진행해 그대로 L이 될 것이다. 이런 방식으로 1번째부터 플래그 길이만큼 하나씩 brute-forcing으로 구하면 된다. 플래그를 %s로 출력해주기 때문에 암호화를 하다 널바이트가 나오면 플래그가 끊길 수 있으니 범위를 나눠서 구하면 된다.

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

    l33t-ness - pwnable.xyz  (0) 2020.02.12
    Jmp table - pwnable.xyz  (0) 2020.02.12
    Free Spirit - pwnable.xyz  (0) 2020.02.12
    two targets - pwnable.xyz  (0) 2020.02.12
    xor - pwnable.xyz  (0) 2020.02.11

    댓글

Designed by Tistory.