Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2025-21298 — CVE-2025-21298에 대한 개념 증명 및 세부 정보 | Kitploit
도구/GitHubGitHub/ynwarcs/cve-2025-21298
Memory ForensicsVulnerability AnalysisExploitationReverse EngineeringBinary Analysis
GitHubynwarcs/cve-2025-21298

CVE-2025-21298

CVE-2025-21298에 대한 개념 증명 및 세부 정보

저장소 보기
197491년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

내용

이것은 CVE-2025-21298 - Windows OLE 원격 코드 실행 취약점(CVSS 9.8)에 대한 개념 증명입니다. 메모리 손상 PoC이며, 익스플로잇이 아닙니다.

전체 패치 차이(via ghidriff): LINK

취약점

취약점은 ole32.dll!UtOlePresStmToContentsStm에 있습니다. 이 함수의 목적은 OLE 저장소 내의 "OlePres" 스트림에 있는 데이터를 적절한 형식의 데이터로 변환하여 동일한 저장소의 "CONTENTS" 스트림에 삽입하는 것입니다. 이 함수는 저장소 객체에 대한 IStorage 포인터와 세 가지 다소 중요하지 않은 인수를 받습니다.

아래는 2025년 1월 패치의 차이점과 함께 함수의 구현을 볼 수 있습니다:

root@kitploit:~
__int64 __fastcall UtOlePresStmToContentsStm(IStorage *pstg, wchar_t *puiStatus, __int64 a3, unsigned int *lpszPresStm)
{
  struct IStorageVtbl *lpVtbl; // rax
  int v7; // r14d
+ bool IsEnabled; // al
  IStream *v10; // rcx
  bool v11; // zf
  struct IStorageVtbl *v12; // rax
  int v13; // ebx
  HRESULT v14; // eax
  const wchar_t *v15; // rdx
  IStream *pstmContents; // [rsp+40h] [rbp-19h] BYREF
  IStream *pstmOlePres; // [rsp+48h] [rbp-11h] BYREF
  tagFORMATETC foretc; // [rsp+50h] [rbp-9h] BYREF
  tagHDIBFILEHDR hdfh; // [rsp+70h] [rbp+17h] BYREF

  *lpszPresStm = 0;
  lpVtbl = pstg->lpVtbl;
  pstmContents = 0LL;
  v7 = 1;
  // Create a "CONTENTS" stream in the storage and store it into pstmContents
  if ( (lpVtbl->CreateStream)(pstg, L"CONTENTS", 18LL, 0LL, 0, &pstmContents) )
    return 0LL;
  // Immediately release pstmContents, we're not going to be using it right now
  (pstmContents->lpVtbl->Release)(pstmContents);
+ IsEnabled = wil::details::FeatureImpl<__WilFeatureTraits_Feature_3047977275>::__private_IsEnabled(&`wil::Feature<__WilFeatureTraits_Feature_3047977275>::GetImpl'::`2'::impl);
+ v10 = pstmContents;
+ v11 = !IsEnabled;
  v12 = pstg->lpVtbl;
+ if ( !v11 )
+   v10 = 0LL;
+ pstmContents = v10;
  (v12->DestroyElement)(pstg, L"CONTENTS");
  v13 = (pstg->lpVtbl->OpenStream)(pstg, &OlePres, 0LL, 16LL, 0, &pstmOlePres);// 2nd option to fail -> no OlePres stream
  if ( v13 )
  {
    *lpszPresStm |= 1u;
    if ( (pstg->lpVtbl->OpenStream)(pstg, L"CONTENTS", 0LL, 16LL, 0, &pstmContents) )
    {
      *lpszPresStm |= 2u;
    }
    else
    {
      (pstmContents->lpVtbl->Release)(pstmContents);
+     wil::details::FeatureImpl<__WilFeatureTraits_Feature_3047977275>::__private_IsEnabled(&`wil::Feature<__WilFeatureTraits_Feature_3047977275>::GetImpl'::`2'::impl);
    }
    return v13;
  }
  foretc.ptd = 0LL;
  v13 = UtReadOlePresStmHeader(pstmOlePres, &foretc, 0LL, 0LL);
  if ( v13 >= 0 )
  {
    v13 = (pstmOlePres->lpVtbl->Read)(pstmOlePres, &hdfh, 16LL);
    if ( v13 >= 0 )
    {
      v13 = OpenOrCreateStream(pstg, L"CONTENTS", &pstmContents);
      if ( v13 < 0 )
      {
        *lpszPresStm |= 2u;
        goto $errRtn_197;
      }
      if ( foretc.dwAspect == 4 )
      {
        *lpszPresStm |= 4u;
        v7 = 0;
        v13 = 0;
        goto $errRtn_197;
      }
      if ( foretc.cfFormat == 8 )
      {
        v14 = UtDIBStmToDIBFileStm(pstmOlePres, hdfh.dwSize, pstmContents);
LABEL_19:
        v13 = v14;
        goto $errRtn_197;
      }
      if ( foretc.cfFormat == 3 )
      {
        v14 = UtMFStmToPlaceableMFStm(pstmOlePres, hdfh.dwSize, hdfh.dwWidth, hdfh.dwHeight, pstmContents);
        goto LABEL_19;
      }
      v13 = -2147221398;
    }
  }
$errRtn_197:
  if ( pstmOlePres )
    (pstmOlePres->lpVtbl->Release)(pstmOlePres);
  // Release pstmContents if it still exists, we need to clean up
  if ( pstmContents )
    (pstmContents->lpVtbl->Release)(pstmContents);
  if ( foretc.ptd )
    CoTaskMemFree(foretc.ptd);
  if ( v13 )
  {
    v15 = L"CONTENTS";
    goto LABEL_31;
  }
  if ( v7 )
  {
    v15 = &OlePres;
LABEL_31:
    (pstg->lpVtbl->DestroyElement)(pstg, v15);
  }
  return v13;
}

문제는 pstmContents 변수에 있습니다. 처음에는 함수 시작 시 생성된 "CONTENTS" 스트림 객체에 대한 포인터를 저장하는 데 사용됩니다. 스트림은 생성 직후 파괴되고 pstmContents에 저장된 포인터는 해제됩니다(이는 coml2.dll!ExposedStream::~ExposedStream에서 메모리를 해제합니다). 그러나 변수에는 여전히 해제된 포인터가 남아 있습니다. 함수 후반부에서 이 변수는 다시 "CONTENTS" 스트림의 포인터를 저장하는 데 재사용될 수 있습니다. 이 때문에 함수 끝에는 변수에 포인터가 저장되어 있을 경우 해제하는 정리 코드가 있습니다. 그런데 이 코드는 UtReadOlePresStmHeader가 실패할 수 있다는 점을 고려하지 않습니다. 실패하면 pstmContents는 여전히 해제된 포인터를 가리키게 되고 정리 코드로 빠져들어 포인터를 다시 해제하게 됩니다. 따라서 이중 해제(double-free) 상황이 발생합니다.

패치 차이에서 볼 수 있듯이 Microsoft는 처음에 포함된 포인터가 해제된 후 pstmContents를 0으로 설정하여 이 문제를 수정했습니다.

재현

저장소에는 취약점을 재현하는 rtf 파일이 있습니다. MS Word에서 파일을 열어 테스트했지만 RTF 데이터를 파싱하는 다른 응용 프로그램(예: Outlook)으로도 테스트할 수 있습니다. OLE 개체를 포함하는 다른 형식을 통한 익스플로잇도 가능할 수 있지만 시도하지는 않았습니다.

동영상:

poc.webm

세부 사항

세부 사항은 나중에 공개하겠습니다.

도구 다운로드