마지막 업데이트: 2026년 9월 16일

PPTX Reverse Engineering: Understanding PowerPoint Files Internally

역공학 PPTX 파일: 개발자를 위한 가이드

현대적인 프레젠테이션 데크는 투자자 피치부터 내부 분기별 지표까지 모든 것을 지원합니다. 하지만 텍스트를 프로그래밍 방식으로 추출하거나, 런타임에 템플릿을 교체하거나, 자동 슬라이드 생성기를 구축하거나, 기밀 프레젠테이션을 정제해야 했던 적이 있다면, 금방 깨달았을 것입니다: 표준 고수준 프레젠테이션 라이브러리는 예측할 수 없는 블랙 박스처럼 느껴질 수 있습니다.

python-pptx, Apache POI, 또는 OpenXML SDK와 같은 라이브러리가 한계에 도달하거나—문서화되지 않은 레이아웃 버그를 도입할 때—유일한 해결책은 직접 파고드는 것입니다. PowerPoint 프레젠테이션이 바이트와 스키마 수준에서 실제로 무엇인지 이해해야 합니다.

이번 심층 탐구에서는 .pptx 형식의 베일을 벗겨내고, 내부 구조를 풀어내며, 관계 그래프를 추적하고, 드로잉 계층을 해부하며, 원시 코드를 사용한 역공학, 검사 및 프레젠테이션 조작을 위한 실용적인 전략을 살펴볼 것입니다.

1. .pptx 파일은 실제로 무엇인가요?

핵심적으로, .pptx 파일은 1990년대의 고전적인 .ppt 형식과 같은 독점적인 단일 바이너리가 아닙니다. Microsoft가 Office Open XML(ECMA-376 및 ISO/IEC 29500)을 도입한 이후, 최신 Office 문서는 Open Packaging Conventions(OPC) 아카이브입니다.

쉽게 말해: .pptx 파일은 XML 문서와 미디어 자산을 결정적인 디렉터리 트리 구조로 정리한 zip 아카이브에 불과합니다.

표준 터미널 도구를 사용하면 몇 초 만에 이를 증명할 수 있습니다:

# Rename the extension and unpack it
cp presentation.pptx presentation.zip
unzip presentation.zip -d presentation_unpacked/
cd presentation_unpacked/
tree -L 2

결과적인 디렉터리 트리는 놀라울 정도로 일관되어 보입니다:

.
├── [Content_Types].xml
├── _rels/
│   └── .rels
├── docProps/
│   ├── app.xml
│   └── core.xml
└── ppt/
    ├── presentation.xml
    ├── _rels/
    ├── slides/
    ├── slideLayouts/
    ├── slideMasters/
    ├── theme/
    └── media/

각각의 시각 자산, 전환, 슬라이드 마스터 상속, 텍스트 상자 좌표, 그리고 벡터 그래픽은 모두 이 파일 계층 구조에 코드화되어 있습니다.

2. 패키지 해부: 주요 하위 시스템

프레젠테이션을 효과적으로 역공학하려면 각 최상위 구성 요소의 역할을 이해해야 합니다.

[Content_Types].xml

이것은 OPC 리더를 위한 진입 매니페스트입니다. 파일 확장자와 명시적인 내부 파트 이름을 표준화된 MIME/콘텐츠 유형에 매핑합니다. 새 슬라이드를 만들거나 이미지를 추가했지만 [Content_Types].xml에 선언하지 않으면 PowerPoint는 파일이 손상되었다고 선언하고 복구를 요청합니다.

예시 스니펫:

<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="xml" ContentType="application/xml"/>
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="png" ContentType="image/png"/>
  <Override PartName="/ppt/presentation.xml" 
            ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>
</Types>

_rels/ 관계 그래프

OpenXML에서 가장 중요한 개념 중 하나는 관계에 통한 간접성입니다. 파트는 원시 파일 경로를 직접 가리키는 경우가 거의 없습니다. 대신 파일(예: slide1.xml)은 형제 _rels 폴더에 위치한 연관 관계 파일(예: _rels/slide1.xml.rels)을 가집니다.

관계는 다음을 정의합니다:

  • 하이퍼링크 (r:id="rId2", TargetMode=“External”)
  • ppt/media/에 저장된 이미지 및 오디오 트랙
  • 레이아웃 상속 포인터 (slideLayout1.xml)
  • 노트 슬라이드, 댓글, 그리고 포함된 글꼴

docProps/ (메타데이터)

  • core.xml: Dublin Core 메타데이터—작성자, 제목, 생성 날짜, 수정 타임스탬프.
  • app.xml: 애플리케이션별 통계—PowerPoint 버전, 전체 슬라이드 수, 숨김 슬라이드 수, 단어 수, 프레젠테이션 템플릿 이름.

ppt/ (프레젠테이션 엔진)

실제 프레젠테이션이 위치하는 곳은 다음과 같습니다:

  • presentation.xml: 마스터 스파인입니다. 슬라이드 ID, 슬라이드 크기 차원, 노트 마스터 참조 및 기본 글꼴 설정을 등록합니다.
  • slides/: 개별 슬라이드(slide1.xml, slide2.xml 등).
  • slideLayouts/: 마스터 구조 프리셋(제목 슬라이드, 두 열, 섹션 헤더).
  • slideMasters/: 전역 스타일, 기본 팔레트, 배경 채우기 및 자리표시자 상속.
  • theme/: 색상 팔레트(강조 1~6, 어두움/밝음 변형)와 글꼴 스킴(주요/보조 글꼴).
  • media/: 원시 이미지(PNG, JPEG, SVG), 오디오 및 비디오 파일.

3. 슬라이드 해부: PresentationML (p:) 및 DrawingML (a:) 방언

ppt/slides/slide1.xml을 열면 두 개의 주요 XML 네임스페이스를 만나게 됩니다:

  1. PresentationML (p:): 구조적 프레젠테이션 요소(슬라이드, 도형 트리, 캔버스 그룹)를 관리합니다.
  2. DrawingML (a:): 타이포그래피, 기하학, 2D 좌표, 그라디언트 채우기 및 벡터 렌더링을 관리합니다.

표준 텍스트 도형이 어떻게 보이는지에 대한 간소화된 예시입니다:

<p:sp>
  <!-- 1. Non-visual shape properties (Identifiers, names) -->
  <p:nvSpPr>
    <p:cNvPr id="4" name="Title Box 1"/>
    <p:cNvSpPr>
      <a:spLocks noGrp="1"/>
    </p:cNvSpPr>
    <p:nvPr>
      <p:ph type="title"/>
    </p:nvPr>
  </p:nvSpPr>

  <!-- 2. Visual shape properties (Position, size, geometry) -->
  <p:spPr>
    <a:xfrm>
      <a:off x="1524000" y="1143000"/>
      <a:ext cx="9144000" cy="1828800"/>
    </a:xfrm>
    <a:prstGeom prst="rect">
      <a:avLst/>
    </a:prstGeom>
    <a:solidFill>
      <a:schemeClr val="accent1"/>
    </a:solidFill>
  </p:spPr>

  <!-- 3. Text Body (Paragraphs, runs, styling) -->
  <p:txBody>
    <a:bodyPr rtlCol="0" anchor="ctr"/>
    <a:lstStyle/>
    <a:p>
      <a:r>
        <a:rPr lang="en-US" sz="3200" b="1"/>
        <a:t>Mission Critical Architecture</a:t>
      </a:r>
    </a:p>
  </p:txBody>
</p:sp>

중요한 측정 단위: EMU와 포인트의 백분의 일

<a:xfrm> 안의 좌표 숫자를 확인하세요:

  • x="1524000"
  • cx="9144000"

이것은 영어 미터 단위 (EMUs) 입니다.

  • $1 \text{ inch} = 914,400 \text{ EMUs}$
  • $1 \text{ cm} = 360,000 \text{ EMUs}$
  • $1 \text{ pt} = 12,700 \text{ EMUs}$

EMUs는 정수를 사용하여 인치와 밀리미터 모두의 정확한 분수를 부동소수점 반올림 오류 없이 다양한 하드웨어 아키텍처에서 표현할 수 있게 합니다.

또한 폰트 크기를 확인하세요:

  • sz=\"3200\"32.00 pt를 의미합니다. DrawingML에서 폰트 크기는 포인트의 백분의 일 단위로 측정됩니다.

4. 상속 체인: 도형이 보이지 않는 스타일을 상속하는 이유

PPTX 파일을 역공학할 때 가장 흔한 함정 중 하나는 도형의 시각적 스타일이 해당 slideX.xml 내부에 완전히 선언되어 있다고 가정하는 것입니다.

실제로 OpenXML은 엄격한 4계층 계단식 상속 모델에 의존합니다:

[Theme: ppt/theme/theme1.xml]
[Slide Master: ppt/slideMasters/slideMaster1.xml]
[Slide Layout: ppt/slideLayouts/slideLayout1.xml]
[Slide: ppt/slides/slide1.xml]

slide1.xml의 텍스트 상자에 다음과 같이 포함되어 있다면:

<a:p>
  <a:r>
    <a:t>Revenue Projections</a:t>
  </a:r>
</a:p>

런( <a:r> )에 글꼴 패밀리, 명시적인 색상, 크기가 선언되어 있지 않습니다. PowerPoint가 이 텍스트를 어떻게 렌더링하는지 파악하려면 파서는 다음을 수행해야 합니다:

  1. 플레이스홀더 유형( <p:ph type="title"/>)을 식별합니다.
  2. slide1.xml.rels에 참조된 레이아웃을 읽습니다.
  3. slideLayout1.xml이 해당 플레이스홀더에 대한 스타일 오버라이드를 제공하는지 확인합니다.
  4. 기본 제목 텍스트 본문 스타일을 위해 slideMaster1.xml을 사용합니다.
  5. accent1 또는 tx1과 같은 색상 토큰을 theme1.xml에서 추적하여 16진수 색상 코드를 찾습니다.

이 상속 그래프를 건너뛰면 파서는 스타일을 잘못 읽고, 글꼴, 글꼴 크기 및 레이아웃 앵커가 누락됩니다.

5. 실용적인 리버스 엔지니어링 워크플로우

특정 PowerPoint 기능이 내부적으로 어떻게 작동하는지 조사해야 할 때(예: 모프 전환, 복잡한 표, 벡터 경로), 다음과 같은 실증적 접근 방식을 따르세요:

Step 1: 최소 “Diff Pair” 생성

  1. PowerPoint를 열고 빈 슬라이드를 만듭니다.
  2. before.pptx 파일로 저장합니다.
  3. 역공학하려는 정확한 단일 변경을 적용합니다 (예: 원에 그림자 효과를 추가하거나, 글머리표 스타일을 변경하거나, 삽입된 비디오를 삽입합니다).
  4. after.pptx 파일로 저장합니다.

Step 2: 두 아카이브 풀기

unzip before.pptx -d before/
unzip after.pptx -d after/

Step 3: XML 포맷팅

Office 아카이브 내부의 원시 XML은 일반적으로 들여쓰기와 줄바꿈 문자가 제거됩니다. 차이를 비교하기 전에 파일을 포맷하십시오:

find before/ after/ -name "*.xml" -exec xmllint --format {} --output {} \;

Step 4: 통합 Diff 실행

diff -uNr before/ after/ > changes.patch

changes.patch를 검토하면 PowerPoint가 도입한 정확한 태그 이름, 네임스페이스 속성 및 컨테이너 계층 구조를 알 수 있습니다. 이는 수천 페이지에 달하는 ECMA-376 문서를 뒤져보지 않고도 문서화되지 않았거나 모호한 스키마 속성을 가장 빠르게 발견하는 방법입니다.

6. 맞춤형 마이크로 엔진 구축: 풀기, 수정, 재패키징

때때로 Apache POI나 Microsoft OpenXML SDK와 같은 무거운 엔터프라이즈 종속성을 원하지 않을 때가 있습니다—특히 경량 서버리스 런타임(AWS Lambda, Cloudflare Workers, 엣지 노드)에서.

다음은 메모리 내 PPTX를 안전하게 압축 해제하고, 표준 라이브러리 도구를 사용해 사용자 정의 데이터를 삽입한 뒤 다시 패키징하는 방법을 보여주는 독립형 Python 패턴입니다:

import zipfile
import io
import xml.etree.ElementTree as ET

def modify_slide_title(input_pptx_bytes: bytes, new_title: str) -> bytes:
    input_zip = zipfile.ZipFile(io.BytesIO(input_pptx_bytes))
    output_buffer = io.BytesIO()
    
    with zipfile.ZipFile(output_buffer, "w", zipfile.ZIP_DEFLATED) as output_zip:
        for item in input_zip.infolist():
            content = input_zip.read(item.filename)
            
            # Target slide 1
            if item.filename == "ppt/slides/slide1.xml":
                namespaces = {
                    'p': 'http://schemas.openxmlformats.org/presentationml/2006/main',
                    'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'
                }
                
                # Register namespaces to preserve prefixes
                for prefix, uri in namespaces.items():
                    ET.register_namespace(prefix, uri)
                    
                root = ET.fromstring(content)
                
                # Find title placeholder text run
                for title_run in root.findall(".//p:sp[p:nvSpPr/p:nvPr/p:ph[@type='title']]//a:t", namespaces):
                    title_run.text = new_title
                    break
                
                content = ET.tostring(root, encoding="utf-8", xml_declaration=True)
            
            output_zip.writestr(item, content)
            
    return output_buffer.getvalue()

원시 바이트 수준에서 파일을 수정할 때 주의할 핵심 포인트:

  1. Namespace Preservation: XML 파서는 종종 접두사를 재작성합니다 (xmlns:pxmlns:ns0가 될 수 있습니다). 유효한 XML이지만, PowerPoint의 엄격한 내부 스키마 검증은 때때로 비표준 접두사 별칭을 거부합니다. 네임스페이스를 명시적으로 등록하십시오.
  2. ZIP Compression Methods: 표준 디플레이트 압축(zipfile.ZIP_DEFLATED)을 사용하여 파일을 다시 기록하도록 하세요.
  3. Stream Flushing: 바이트를 하위 스트림으로 전송하기 전에 zip 버퍼가 완전히 닫히고 플러시되는지 항상 확인하십시오.
  4. Preserve Relationships: 슬라이드를 제거하면 ppt/presentation.xml에서 해당 항목을 삭제하고, ppt/_rels/presentation.xml.rels에서 관계를 삭제하며, [Content_Types].xml에서 해당 콘텐츠 유형을 정리해야 합니다.

7. 성능 및 보안 고려사항

PPTX 파일을 리버스 엔지니어링하는 것은 단순히 슬라이드를 편집하는 것이 아니라, 시스템에 들어오는 내용을 감사하는 것입니다.

보안: 억단 웃음과 XXE

PPTX 파일은 XML을 파싱하기 때문에, 사용자 제출 프레젠테이션을 수집하는 서버 측 파이프라인은 다음과 같은 위험에 노출됩니다:

  • XML External Entity (XXE) Injection: /etc/passwd에 접근하거나 내부 클라우드 메타데이터 엔드포인트(http://169.254.169.254/)를 조회하려는 악성 XML.
  • Entity Expansion Attacks (Billion Laughs): 시스템 RAM을 고갈시키는 지수적인 엔티티 루프 공격.

Mitigation: 항상 XML 파서에서 resolve_entities, load_dtd, 그리고 외부 네트워크 해석을 비활성화하십시오 (예: Python에서 defusedxml 사용).

보안: 매크로 페이로드와 숨겨진 스트림

.pptx 확장자 아래에 위장된 .pptm 콘텐츠가 있는지 파일을 검사하십시오. 컴파일된 Visual Basic 코드를 포함하는 ppt/vbaProject.bin을 주의하십시오. 표준 .pptx 파일에서는 VBA 코드가 금지되어 있으며, 관계 파일에서 바이너리 페이로드 참조를 발견하면 즉시 격리 플래그를 트리거해야 합니다.

결론

.pptx 파일을 역공학하면 프레젠테이션 소프트웨어의 신비가 풀립니다. PowerPoint 파일이 좌표, 스키마 참조 및 XML 관계 트리로 구성된 단순한 zip 패키지임을 인식하면 기존 서드파티 추상화에 얽매이지 않게 됩니다.

슬라이드 생성 처리량을 최적화하든, 맞춤형 자동 정화 도구를 작성하든, 렌더링 오류를 해결하든, 기본 OpenXML 아키텍처를 직접 살펴보면 프레젠테이션 파이프라인을 완전히 제어할 수 있습니다.

자주 묻는 질문 (FAQ)

**Q: .pptx 파일을 표준 폴더로 변환하고 IDE에서 파일을 직접 편집할 수 있나요?

A1: 예, 아카이브를 추출하고 VS Code와 같은 편집기에서 XML을 편집한 뒤 디렉터리 내용을 다시 zip 압축하면 PowerPoint에서 다시 열 수 있습니다.

**Q: PowerPoint가 수동으로 수정한 PPTX 파일을 복구가 필요하다고 말하는 이유는 무엇인가요?

A2: 보통 잘못된 XML을 도입했거나, [Content_Types].xml에서 새로운 자산을 누락했거나, 연관된 .rels 파일에 남은 dangling reference가 있을 때 발생합니다.

**Q: PowerPoint는 도형 위치와 여백에 어떤 측정 단위를 사용하나요?

A3: PowerPoint는 영국 측정 단위(EMUs)를 사용하며, 1인치는 914,400 EMUs, 1포인트는 12,700 EMUs에 해당합니다.

**Q: 외부 라이브러리 없이 프레젠테이션에서 모든 이미지를 프로그래밍 방식으로 추출하려면 어떻게 해야 하나요?

A4: 표준 zip 유틸리티로 .pptx 파일을 열고 ppt/media/ 디렉터리 안에 있는 모든 바이너리 파일을 추출하면 됩니다.

**Q: 표준 XML 파서를 사용하여 사용자가 업로드한 PPTX 파일을 파싱하는 것이 안전한가요?

A5: 아니요, 파서를 강화하거나 defusedxml과 같은 안전 래퍼를 사용하여 XML 외부 엔터티(XXE) 및 zip-bomb 공격을 차단해야 합니다.

관련 항목