
CVE-2026-27280에 대한 심층 분석 및 개념 증명(PoC)으로, Adobe DNG SDK의 `dng_render_task::ProcessArea`에서 발생하는 out-of-bounds 쓰기(out-of-bounds write) 취약점입니다. Android의 이미지 디코딩 API를 통해 도달 가능하며, 근본 원인, 악용 가능성 평가, RCE 격차 분석을 포함합니다.
dng_render_task::ProcessArea의 경계를 벗어난 쓰기 (Adobe DNG SDK / Android)일반 DNG 파일에서 Android의 공개 이미지 디코딩 API를 통해 도달하며,
Galaxy S25에서 기기 자체의 /system/lib64/libdng_sdk.so 를 사용하여 재현되었습니다.
DNG_REQUIRE(srcArea.Size() == dstArea.Size()) + srcArea 인덱싱)#12 AImageDecoder_decodeImage /system/lib64/libjnigraphics.so
#11 android::ImageDecoder::decode /system/lib64/libhwui.so
#09 SkCodec::getPixels /system/lib64/libhwui.so
#08 SkRawCodec::onGetPixels /system/lib64/libhwui.so
#07 dng_render::Render() /system/lib64/libdng_sdk.so
#06 SkDngHost::PerformAreaTask /system/lib64/libhwui.so
#00 dng_render_task::ProcessArea /system/lib64/libdng_sdk.so <-- 취약 지점
ProcessArea는 타일 크기의 임시 버퍼를 srcArea에 매핑하지만 dstArea 좌표로 인덱싱합니다.
srcArea = dstArea + fSrcOffset이고 fSrcOffset은 파일의 DefaultCropOrigin — 실제 카메라 DNG에서는 항상 0이 아닌 값 — 이므로, 대상 포인터는 fSrcOffset * rowStep * pixelSize만큼 할당 범위를 벗어나 이동하며, 마스크 읽기-쓰기가 그 위치에 기록됩니다. 이는 변위(displacement) 이지 정수 오버플로우가 아니므로 거대한 할당이 필요 없습니다. 그래서 휴대폰에서도 도달 가능한 것입니다.
01-vulnerability/ 취약 및 수정 버전의 dng_render.cpp, 그리고 2471->2502 diff
02-poc/ 생성기, 로더, 프로브(소스) + 사전 빌드된 arm64 바이너리
03-samples/ DNG 파일들
04-evidence/ ASAN 로그, 기기 tombstone, 프로브 출력
05-analysis/ 근본 원인, 생성기, 인접 측정, RCE 격차 분석
이전 판정을 넘어서는 작업: 측정을 통해 두 가지 주장을 반증하고, "손상된 포인터가 사용되는지"의 격차를 해소하며, 체인이 중단되는 정확한 이유를 규명합니다. 먼저 06-rce-lab/README.md를 읽으십시오. evidence/*.md 파일은 실험별 보고서이고, src/ + apk/는 재현 가능한 하네스입니다.
adb push 02-poc/bin-arm64/poc_aimg 03-samples/trigger_natural_croporigin.dng /data/local/tmp/
adb shell "cd /data/local/tmp && chmod 755 poc_aimg && ./poc_aimg trigger_natural_croporigin.dng"
# -> Segmentation fault, tombstone frames inside libdng_sdk.so
arm64 로더를 재빌드하려면 Android 자체의 dng_sdk 플래그(Android.bp에서)가 필요합니다. 그렇지 않으면 구조체 레이아웃이 탑재된 라이브러리와 일치하지 않습니다:
-DUNIX_ENV=1 -DqDNGBigEndian=0 -DqDNGThreadSafe=1 -DqDNGUseLibJPEG=1 \
-DqDNGUseXMP=0 -DqDNGValidateTarget=1 -DqDNGUsingSanitizer=1 -DqLinux=1 -DqAndroid=1
이는 CVE-2026-27280 (DNG SDK 1.7.1 2471 및 이전 버전, CWE-787, CVSS 3.1 7.8 HIGH)에 해당하며, 2026-03-10에 게시되었습니다 — 버전 범위와 클래스로 일치하며, Adobe가 함수 이름을 명명한 것으로 확인된 것은 아닙니다.
실제 Android 디코딩 경로에서, 2026-01-01 패치가 적용된 기기에서 입증됨:
공격자가 선택한 대상으로의 경계를 벗어난 쓰기(4바이트 단위, 양방향, ASLR 독립적); AImageDecoder를 통한 일반 DNG에서 도달 가능, Samsung Gallery를 썸네일 로더에서 크래시시키는 것으로 시연됨; 인접 객체의 무성적 결정적 손상; free()에 도달하는 손상된 포인터(N1 해소); DNG 형식으로 표현된 힙 스프레이(ImageStats × SubIFD); 스프레이된 객체에 조준된 쓰기, 6/10 vs 0/10.
Android 경로에서 RCE는 아니며, 그 근본 이유는 하나입니다: 코드는 단일 값 0x3F800000만 쓸 수 있습니다. 모든 것은 여기서 파생됩니다 — 쓰기는 연속적이며(Scudo 헤더 위에 stride 없음), 쓸 0이 없고(길이/참조 카운트 손상 없음), 태그를 보존하는 부분 덮어쓰기는 정확히 하나의 고정 대상 오프셋을 가집니다. 하나의 값은 무작위화된 256MB 예약에 대한 단 한 번의 기회이며, 아무리 많은 스프레이 볼륨이나 동시성도 기회를 추가하지 않습니다. 데스크톱 SDK에서는 쓰기 가능한 집합이 256개 값 → ~67MB 대역에 걸쳐 256번의 기회이므로, 7.8이 거기서는 정확합니다.
이전 판정에서 철회됨 (05-analysis/05-exploitability-verdict.md), 측정으로 반증됨: "실제 디코더는 스프레이 프리미티브를 제공하지 않는다" 및 "단일 디코드 내에서 오버랩을 조종할 수 없다". 차단 요소는 PAC, BTI 또는 Scudo의 체크섬이 아닙니다 — 단일 쓰기 가능한 값이며, 이는 Adobe의 2502 수정이 srcArea로 인덱싱하여 근본적으로 제거하는 코드의 속성입니다.
작동하는 익스플로잇은 구축되지 않았으며, 여기에 게시된 것도 없습니다.
| 파일 | 용도 |
|---|
trigger_natural_croporigin.dng | 자연스러운 지오메트리 (DefaultCropOrigin = 8,8); AImageDecoder 크래시 발생 |
benign_reference.dng | origin이 0인 동일 파일 — 정상 디코딩, 힙 프로브에 사용 |
silent_corruption.dng | 작은 크기 클래스: 크래시 없이 경계를 벗어난 쓰기 (5/5) |
far_write_crash.dng | 큰 변위: 가시적으로 폴트 발생 |
| 파일 | 내용 |
|---|
01-root-cause.md | 버그, 수정, 쓰기의 형태 |
02-dng-generator.md | 트리거 파일 생성 방법, 두 가지 조정 변수, 함정 |
03-neighbours.md | 제어 가능성 수치, 버퍼 옆에 위치하는 것, 완화 조치 |
04-what-is-missing-for-rce.md | 입증된 것(P1-P8), 입증되지 않은 것(N1-N3), 남은 단계 |
05-exploitability-verdict.md | 이전 판정 — 06-rce-lab으로 대체됨, 기록용으로 유지 |
| 파일 | 내용 |
|---|
EXP1-stride-vs-scudo.md | stride vs Scudo 헤더: 추상적으로는 가능하지만 Android 경로에서는 불가능 (planes==1) |
EXP2-N1-corrupted-pointer-used.md | 손상된 포인터가 free()에 도달 — N1 해소 |
EXP2-real-process.md | 실제 Samsung Gallery 프로세스에서의 쓰기, 아무것도 심지 않음 |
EXP-imagestats-spray.md | 이전 판정이 불가능하다고 한 힙 스프레이, DNG 형식으로 표현됨 |
EXP-aimed-spray.md | 스프레이된 객체에 쓰기 조준: 6/10 vs 0/10 |
EXP-small-class-in-app.md | 실제 앱 프로세스 내에서 도달 가능한 코드 포인터 |
EXP-band-commit.md / EXP-concurrency.md | 대상 커밋이 실패하는 이유: 쓰기 가능한 값 1개 = 기회 1회 |
EXP-write-read-mismatch.md | 두 번째 결함 + ProcessArea 전체 소스 감사 |
EXP-value-1.0f.md | 1.0f-데이터 경로, 구조적으로 차단됨 |