[하루한줄] mpengine: asprotect embedded runtime dll memory corruption

URL

Target

  • Windows Defender

Explain

Windows Defender는 여느 안티바이러스와 같이 수 많은 포맷을 분석 및 검사할 수 있는 메커니즘을 가지고 있습니다. 2018년, 마이크로소프트는 Defender를 샌드박싱한다고 밝혔지만 디폴트로 설정되어 있지는 않습니다. 따라서 Defender에서 파일을 분석할 때, 파일 파서, 언패커, 인터프리터 그리고 에뮬레이터는 여전히 SYSTEM 권한으로 실행됩니다.

이 취약점은 Denfender에서 ASProtect로 패킹된 바이너리를 언패킹할 때 발생합니다. Defender에서는 패킹된 바이너리를 분석할 때, 패커의 파일 포맷를 바탕으로 바이너리에서 정보를 파싱하는게 아니라 에뮬레이터를 통해 바이너리를 실행하여 바이너리가 알아서 언패킹하도록 유도합니다. ASProtect는 언패킹 과정에서 내재된 DLL을 파일시스템에 떨구고, 해당 DLL을 에뮬레이터로 활용하여 메인 바이너리 안에 있는 바이트 코드를 실행하면서 언패킹을 진행합니다. 에뮬레이터로 활용되는 DLL 역시 패킹이 되어 있지만 특별한 검사는 하지 않습니다. 취약점은 바로 이 DLL의 IMAGE_SECTION_HEADER 구조체를 파싱할 때 발생합니다.

NtHdr = (IMAGE_NT_HEADERS *)((char *)ImagePtr + ImagePtr->e_lfanew);
NtHdr->OptionalHeader.SizeOfImage = this->ImageSize;
NtHdr->OptionalHeader.ImageBase = this->ImageBase;
NtHdr->OptionalHeader.AddressOfEntryPoint = this->EntryPoint;
NtHdr->OptionalHeader.SizeOfHeaders = 4096;
TotalSects = this->LastSection - this->FirstSection) / sizeof(EMBEDDED_SECTION);
// ...
do
{
  SectHdrs->VirtualAddress = *(int *)((char *)&this->FirstSection->VirtualAddress + Offset);
  SectHdrs->PointerToRawData = *(int *)((char *)&this->FirstSection->VirtualAddress + Offset);
  //  IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE
  SectHdrs->Characteristics = 0xE0000020;
  CurrSect = (EMBEDDED_SECTION *)((char *)this->FirstSection + Offset);
  if ( SectNum >= NumSects )
    EndAddress = this->ImageSize;
  else
    EndAddress = CurrSect[1].VirtualAddress;
  VirtualSize = EndAddress - CurrSect->VirtualAddress;
  SectHdrs->Misc.VirtualSize = VirtualSize;
  SectHdrs->SizeOfRawData = VirtualSize;
  StringCchPrintfA((char *)SectHdrs, 8u, ".sect%d", SectNum);
  memcpy_s_0(
    &this->ImagePtr[*(int *)(&this->FirstSection->VirtualAddress + Offset)],
    this->ImageSize - *(int *)(&this->FirstSection->VirtualAddress + Offset),
    *(void **)(&this->FirstSection->PointerToData + Offset),
    *(int *)(&this->FirstSection->SectSize + Offset));

위 코드에서는 DLL에 저장된 RVA(Relative Virtual Address)를 아무런 검증 없이 가져옵니다. 그런 다음, 이를 마지막 memcpy에서 버퍼의 오프셋으로 사용하는데, 이 때 임의로 넣은 RVA 값을 통해 원하는 오프셋을 읽거나 쓸 수 있습니다. 익스플로잇이 될 경우, 이 작업은 에뮬레이터에서 진행되기 때문에 에뮬레이터 밖의 메모리를 읽거나 쓸 수 있습니다.