ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CHAPTER 3.Viva_La_Vida MakingFilm
    0x.STORY_TELLER/MakingFilm 2018. 9. 3. 18:49

    CHAPTER 3.Viva_La_Vida MakingFilm


    BoB7기 내부 CTF 문제로 제작한 heap overflow 문제이다. 워낙 잘하는 사람이 많기 때문에 어떤 방식으로 제작할까를 고민해보다가 여러 구조체를 겹쳐 할당하여 포인터 형식으로 참조하도록 설계하였고, 그 과정에서 unsorted bin과 fastbin을 활용하여 prev_inuse flag를 해제하는 것과, double free를 통한 consolidate, overflow를 통한 prev_size 오버라이트, uaf를 통한 fake chunk 생성을 통한 unlink로 구조체를 덮어서 got_overwring를 통하여 릭 및 익스가 가능하다.


    글로벌 변수는 다음과 같이 구성되어있다. cavalry는 기사의 name, salary, salary length, in_use 비트에 대한 정보를 가지고 있고, root_cavalry는 처음 할당되는 청크가 가지는 특수한 구조체로 name, salary, secret, salry-secret length, in_use 정보를 포함하고 있다.


    Cavalry 는 더블 포인터 형식으로 최대 8개까지 할당이 되도록 하였고, 그 동적할당 영역의 오프셋을 토대로 cavalry를 할당하여 정보에 접근한다. info_root_cavalry 부분이 root_cavalry 구조체의 정보가 할당되는 영역인데 bss 영역을 사용하도록 하였고, 해당 청크의 주소는 global_root_cavalry에 저장이 되며, global_cavalry의 관리 청크의 첫 주소값으로 저장되게 된다.


    struct cavalry{
       char *name;
       char *salary;
       unsigned long popularity[2]; // [0] -> strlen, [1] -> in_use
    };

    struct root_cavalry{
       char *name;
       char *salary;
       char *secret;
       unsigned long popularity[3]; // [0] -> strlen salary, [1] -> strlen secret, [1] -> in use
    };

    long overflow = 0;
    long root_limit = 0;
    char unlink_here=NULL;
    struct root_cavalry *global_root_cavalry;
    struct cavalry **global_cavalry;
    unsigned long info_root_cavalry[6];


    메뉴는 6개로 나뉘어져있는데, 순서는 제멋대로 할당을 하였기 때문에 푸는 사람으로 하여금 직접 함수를 분석하여 함수의 순서를 추측해야 된다. 또한 바이너리가 출력 값이 거의 없기 때문에 입력에 실패하였더라도 알 수 없다는 특징이 있다.


    Cavalry_count 변수가 기사 청크의 할당 개수를 카운트하고 info_cavalry_count 변수가 할당된 청크 구조체의 내용이 사용자로부터 입력이 되었는지를 판단하는 역할을 한다. fastbin은 fastbin크기의 청크가 할당되면 세팅이 되는데, 문제는 설계상 패스트빈 double free를 제한하기 위해서 1번만 할당이 되도록 하기 위해서 flag 값으로 설정하였다.


    int main()
    {  
       global_cavalry = (struct cavalry**)calloc((FULL+1), sizeof(struct cavalry*));
       allocate_root();
       int fastbin=0; // only one fastbin allocation
       int cavalry_count = 0;
       int info_cavalry_count = 0;
       int choice = 0;
       int breadwinner = 0; // menu choice 한 번만 출력
       int exit_menu = 0;

       setvbuf(stdin, 0, 2, 0);
       setvbuf(stdout, 0, 2, 0);

       prologue();

       while(choice != 101)
      {
           selection(&choice, &breadwinner);
           getchar();
           breadwinner++;
           switch(choice)
          {
               case 8:     // global_cavalry 할당
                   allocate_global(global_cavalry, &cavalry_count);
                   break;
               case 13:    // cavalry 정보 할당
                   allocate_cavalry(global_cavalry, &cavalry_count, &info_cavalry_count, &fastbin);
                   break;
               case 54:    // leak name and change bit
                   show_name(global_cavalry, &info_cavalry_count);
                   break;
               case 24:    // free -> double free trigger
                   free_cavalry(global_cavalry, &info_cavalry_count, &fastbin);
                   break;    
               case 21:    // unlink here
                   modify_salary(global_cavalry, &info_cavalry_count);
                   break;
               case 1:     // unlink trigger method here
                   allocate_salary(global_cavalry, &info_cavalry_count);
                   break;
               default:    // 10 wrong input
                   exit_menu++;
                   if(exit_menu>10)
                       epilogue();
                   break;
          }

      }


    8번을 입력하면 cavarly 관리 청크에 구조체를 할당할 수 있는데, 첫 할당일 경우 root_cavalry를 할당해주고, 그 다음부터는 일반적인 cavalry를 할당해준다.


    void allocate_root()
    {
       global_root_cavalry = info_root_cavalry;
    }

    void allocate_global(struct cavalry **global_cavalry, int *cavalry_count)
    {
       if(*cavalry_count > FULL)
      {
           return;
      }

       if(*cavalry_count == 0)
      {
           global_cavalry[*cavalry_count] = global_root_cavalry;
           *cavalry_count += 1;
           return;
      }

       global_cavalry[*cavalry_count] = (struct cavalry*)calloc(1, sizeof(struct cavalry));
       *cavalry_count += 1;
    }


    13번을 입력하면 cavalry 구조체의 내용을 할당할 수 있는데, name의 경우 allocate_name_buf에 미리 할당된 내용을 인덱스에 맞게 할당을 해주고 salary는 사용자로부터 입력받은 문자열의 크기를 strlen한 값을 malloc을 해준다. 만약 첫 청크인 root의 경우 secret이라는 청크가 추가적으로 할당되는데 malloc(4000)으로 고정된 값을 가진다. 이를 통해 사용자는 salary의 크기는 결정할 수 있지만, 그 다음 secret에 대한 부분은 정적인 크기로 할당이 되기 때문에 특정 기법으로 익스를 할 때 안되는 경우가 발생하게 된다. 만약 fastbin 크기로 할당이 된 경우에는 fastbin의 flag가 1로 세팅되기 때문에 동일한 크기인 0x100으로 할당이 되는데 이를 통해 fastbin_dup 과 같은 기법을 사용할 수 없게 제한이 된다. memcpy하는 동안에는 정확한 크기만을 사용하기 때문에 따로 overflow가 발생하거나 하지는 않는다. salary에 대한 부분이 할당이 되면 popularity[0]에 salary로 할당한 길이를 저장하고, [1]에는 in_use를 1로 세팅을 해준다.


    void allocate_cavalry(struct cavalry **global_cavalry, int *cavalry_count, int *info_cavalry_count, int *fastbin)
    {
       char *allocate_name_buf[8] = {"root", "knight1", "knight2", "knight3", "knight4", "knight5", "knight6", "knight7"};
       char salary[4096];
       int popularity=0;

       memset(salary, 0, 4096);

       if(*cavalry_count != *info_cavalry_count+1 || *cavalry_count > FULL)
      {
           return;
      }

       if(*cavalry_count == 1) // root 할당
      {
           global_root_cavalry->name = malloc(16);
           strcpy(global_root_cavalry->name, allocate_name_buf[*info_cavalry_count]);
           printf(">>");
           read(0, salary, 4096);
           if(strlen(salary)<=15)
          {
               popularity=strlen(salary);
          }
           else
          {
               popularity = strlen(salary)-strlen(salary)%16;
          }

           if(popularity <= 120)
               *fastbin = 1;

           global_root_cavalry->salary = malloc(popularity);
           memset(global_root_cavalry->salary, 0, popularity);
           memcpy(global_root_cavalry->salary, salary, popularity);

           global_root_cavalry->secret = malloc(4000);
           memset(global_root_cavalry->secret, 0 , 4000);

           global_root_cavalry->popularity[0] = popularity;
           global_root_cavalry->popularity[1] = 4000;
           global_root_cavalry->popularity[2] = 0x1;

           *info_cavalry_count += 1;
           return;
      }


       global_cavalry[*info_cavalry_count]->name=malloc(16);
       strcpy(global_cavalry[*info_cavalry_count]->name, allocate_name_buf[*info_cavalry_count]);
       printf(">>");
       read(0, salary, 4096);
       if(strlen(salary)<16)
      {
           popularity = strlen(salary);
      }
       else
      {
           popularity = strlen(salary)-strlen(salary)%16;
      }

       if(popularity <= 120)
      {
           if(*fastbin > 0) // fastbin이면 0x100으로 임의 할당
          {
               global_cavalry[*info_cavalry_count]->salary = malloc(0x100);
               memset(global_cavalry[*info_cavalry_count]->salary, 0, 0x100);
               memcpy(global_cavalry[*info_cavalry_count]->salary, salary, popularity);
               global_cavalry[*info_cavalry_count]->popularity[0] = 0x100;
               global_cavalry[*info_cavalry_count]->popularity[1] = 1;
               *info_cavalry_count += 1;
               return;
          }
      }

       if(popularity <= 120)
             *fastbin = 1;

       global_cavalry[*info_cavalry_count]->salary = malloc(popularity);
       memset(global_cavalry[*info_cavalry_count]->salary, 0, popularity);
       memcpy(global_cavalry[*info_cavalry_count]->salary, salary, popularity);
       global_cavalry[*info_cavalry_count]->popularity[0] = popularity;
       global_cavalry[*info_cavalry_count]->popularity[1] = 1;

       *info_cavalry_count += 1;
    }


    54번은 show_name 함수 부분인데 사용자가 할당한 청크인지를 choice를 입력받아서 확인을 한 뒤에 root 즉, 첫번째로 할당한 청크인 경우에는 구조체의 in_use 비트를 0또는 1로 세팅을 해준다. 그리고 19950610이라는 제작자의 생일을 입력하게 되면 overflow 플래그 값에 따라 0x50바이트 이하의 fastbin일 경우에 다음 청크의 prev_size부분에 값을 입력할 수 있도록 8바이트 만큼을 오버플로우를 일으킬 수 있게 strlen을 8만큼 증가시켜준다. 이를 통해 fastbin_dup_consolidate가 트리거 되면 unlink를 할 수 있는 조건이 성립되게 된다.


    void show_name(struct cavalry **global_cavalry, int *info_cavalry_count)
    {
       int choice;
       int change_bit;
       printf(">>");
       scanf("%d", &choice);
       getchar();

       if(choice >= *info_cavalry_count || choice<0)
      {
           return;
      }

       if(choice!=0 && global_cavalry[choice]->popularity[1] != 1)
      {
           return;
      }

       if(choice == 0)
      {
           printf(">>%s\n", global_root_cavalry->name);

           if(strcmp(global_root_cavalry->name, "root")==0)
          {
                   printf(">>");
                   scanf("%d", &change_bit);
                   getchar();

               if(change_bit!=19950610)
              {
                   epilogue();
              }

               if(overflow == 0)
              {
                   if(global_root_cavalry->popularity[0]<=0x50)
                       global_root_cavalry->popularity[0] += 0x8;
                   overflow += 1;
              }

               if(global_root_cavalry->popularity[2]) // root in_use set -> double free
                   global_root_cavalry->popularity[2] = 0;
               else
                   global_root_cavalry->popularity[2] = 1;
          }

           return;
      }

       printf(">>");
       printf("%s\n", global_cavalry[choice]->name);
    }


    24번은 cavalry 청크를 free해 줄 수 있는데 popularity의 in_use 부분을 체크하고 salary 부분의 청크를 free해준다.


    void free_cavalry(struct cavalry **global_cavalry, int *info_cavalry_count, int *fastbin)
    {
       int choice;
       printf(">>");
       scanf("%d", &choice);
       getchar();

       if(choice >= *info_cavalry_count || choice<0)
      {
           return;
      }

       if(choice == 0)
      {
           if(global_root_cavalry->popularity[2] != 1)
          {
               return;
          }

           if(global_root_cavalry->popularity[0]<=120)
          {
               *fastbin = 0;
          }

           free(global_root_cavalry->salary);
           global_root_cavalry->popularity[2] = 0;

      }

       if(global_cavalry[choice]->popularity[1] != 1)
      {
           return;
      }

       else
      {
           free(global_cavalry[choice]->salary); // free
           global_cavalry[choice]->popularity[1] = 0; // in_use set to 0
      }
    }


    21번 메뉴는 salary 부분을 변경할 수 있는데, size를 체크하기 때문에 overflow가 발생하지 않지만, 아까 +8된 값만큼 적을 수 있기 때문에 fastbin 한정, secret의 prev_size를 덮을 수 있다, 또한 free 여부를 체크하지 않기 때문에 uaf가 발생하여 free된 청크들의 값을 수정할 수 있는 취약점이 존재한다.


    void modify_salary(struct cavalry **global_cavalry, int *info_cavalry_count)
    {
      int choice;
      int size;
      char buf[4096];

      memset(buf, 0, 4096);

      printf(">>");
      scanf("%d", &choice);
      getchar();

       if(choice >= *info_cavalry_count || choice<0)
      {
           return;
      }

       if(choice == 0)
      {
           if(global_root_cavalry->popularity[2] != 1)
          {
               return;
          }

           size = global_root_cavalry->popularity[0];
           if(size>=4096)
               size=4095;
           read(0, buf, size+1);
           memcpy(global_root_cavalry->salary, buf, size);

           return;
      }

       if(global_cavalry[choice]->popularity[1] != 1)
      {
           return;
      }

       size = global_cavalry[choice]->popularity[0];
       if(size>=4096)
           size = 4095;
       read(0, buf, size+1);
       memcpy(global_cavalry[choice]->salary, buf, size);
    }


    1번 메뉴는 특수 메뉴로 salary가 free되 있는 경우에 다시 할당을 해준다. 익스플로잇 과정에서 필요한 과정이기에 따로 함수가 추가하였다.


    void allocate_salary(struct cavalry **global_cavalry, int *info_cavalry_count)
    {
       int choice;
       printf(">>");
       scanf("%d", &choice);
       getchar();

       if(choice != 0 )
           epilogue();

       if(root_limit == 1)
      {
           free(global_root_cavalry->secret);
      }

       if(root_limit > 1)
      {
           epilogue();
      }

       root_limit += 1;

       if(global_root_cavalry->popularity[2] == 0)
      {
           global_root_cavalry->salary = malloc(48);
           global_root_cavalry->popularity[2] = 1;
      }

    }


    결국에 secret의 prev_size를 덮을 수 있고, uaf 취약점이 발생하며, bss 영역에 root_cavalry 구조체를 할당하기 때문에 힙 트릭을 적절히 활용하면 익스가 가능하다.

    '0x.STORY_TELLER > MakingFilm' 카테고리의 다른 글

    CHAPTER 2.HalloweenDay MakingFilm  (0) 2018.04.11
    CHAPTER 1. Nymph's_fault MakingFilm  (0) 2018.02.28

    댓글

Designed by Tistory.