CAOV Write up

Osori·2021년 3월 23일
0

pwnable.tw 의 CAOV 문제이다. 처음 풀어보는 c++문제이다.
일단 보호기법은 PIE 빼고 다 걸려있다. 오랜만에 엄청난 삽질을 한 문제다.

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      NO PIE (0x400000)

취약점 및 분석

일단 기본적으로 키(string)과 value(long)을 저장하고 출력하는 프로그램이다.
소스코드를 줘서 소스코드만 본다고 문제가 풀리지 않는다. 왜인지는 모르겠지만, IDA로 까보면 edit 함수에 이상한 녀석이 있다.

unsigned __int64 Edit()
{
  __int64 v0; // rax
  __int64 v1; // rax
  __int64 old[6]; // [rsp+0h] [rbp-80h]
  char v4; // [rsp+30h] [rbp-50h]
  unsigned __int64 v5; // [rsp+68h] [rbp-18h]

  v5 = __readfsqword(0x28u);
  default(old);
  copy((__int64)&v4, (__int64)old, (const char **)data); //v4 is what???
  destroy((__int64)&v4); 
  sub_401B1A(data);
  v0 = std::operator<<<std::char_traits<char>>(&std::cout, "\nYour data info before editing:");
  std::ostream::operator<<(v0, (__int64)&std::endl<char,std::char_traits<char>>);
  sub_401D70(old);
  v1 = std::operator<<<std::char_traits<char>>(&std::cout, "\nYour data info after editing:");
  std::ostream::operator<<(v1, (__int64)&std::endl<char,std::char_traits<char>>);
  sub_401D70((__int64 *)data);
  destroy((__int64)old);
  return __readfsqword(0x28u) ^ v5;
}

대체 v4라는 녀석이 왜 복사생성자에 들어가있는지도 모르겠지만, 어쨌든 소멸자가 호출될 때 *v4의 값을 컨트롤 가능하면 원하는 곳에서 free가 가능하다. 왜냐면 소멸자(함수)가 아래와 같기 때문이다. delete도 결국은 free함수로 귀결된다. (이유는 뒤에 나온다.)

_DWORD *__fastcall destroy(__int64 a1)
{
  if ( *(_QWORD *)a1 )
    operator delete[](*(void **)a1);
  *(_QWORD *)a1 = 0LL;
  *(_QWORD *)(a1 + 8) = 0LL;
  *(_QWORD *)(a1 + 16) = 0LL;
  return init_time((_DWORD *)a1);
}

그리고 이 값을 어떻게 컨트롤 할지는 당연히 이전에 edit_name을 하는 부분에서 컨트롤이 가능할 것이다. 비슷한 스택프레임에 존재를 하기 때문에 취약점을 생각하면 당연하다.

위 그림들을 보면 알겠지만 edit_name의 src 배열이 Data.edit 의 v4보다 0x60바이트 만큼 앞서있다. 따라서 우리는 이를 활용해서 fake로 free를 할 수 있다.

이렇게 보면 왜 뜬금없이 name을 받으라고 했는지도 알 수 있다. fake 청크를 만들라는 것이다.

libc leak

그냥은 leak할 수 없다. 모든 입력뒤에는 null문자가 들어가기 때문이다. 여기서 너무 뻘짓을 많이 했다. 먼저 name 부분에 0x70사이즈의 fake chunk를 만들어서 free 해준다.

우리는 name의 fd를 변조할 수 있기 때문에 전역 포인터 변수 Data의 값을 덮을 수 있는 곳을 할당받아야 하고, 이 위치는 다음과 같다.

변조 후 heap 상황은 아래 사진과 같게 된다.

우리는 Data 객체의 멤버를 마음대로 할 수 있으니까, key의 위치에 got등을 넣어서 leak을 하면 된다.

(참고로 해당 libc에서 printf의 오프셋이 0x55800 이라서 leak이 정상적으로 안되니 다른 got를 사용하자)

쉘 획득

이후에는 double free를 이용해서 __malloc_hook에 원가젯을 넣어주면 된다.

여담

왜 이 취약점이 발생했는지 좀 궁금해서 분석을 해보았는데, 결론은 = 연산자 오버로딩 자체에서 일어난 취약점인것 같다. 연산자 오버로딩에서는 이 소스코드처럼 직접 대입을 하는 것이 아니라 임시객체를 반환하는 형식으로 코드를 작성해야한다. 즉 복사생성자가 할일을 연산자 오버로딩에 사용하면 안된다. 그리고 이는 개발자가 어떻게 코드를 작성하는지에 따라 다른데, = 연산자 오버로딩은 별로 좋은 습관은 아닌 것 같다. 편할지는 모르겠지만, 명시적으로 ToString(), ToInt() 같은 메서드를 사용하는게 좋은 습관인 듯 하다.

profile
해킹, 리버싱, 게임 좋아합니다

0개의 댓글