
MIME 헤더 파싱 버그를 사용하여 힙 오버플로 및 vftable 덮어쓰기를 통한 EIP 제어를 달성하는 Outlook 2019 제로클릭 취약점 CVE-2020-1349용 익스플로잇.
해당 취약점은 한글 Windows 10 1909 x64, 코드페이지 949에 설치된 Outlook 2019 (16.0.12624.20424)에서 발생합니다.

이 취약점을 이용해 ASLR을 우회하지는 못했지만, 부팅 시 고정되는 시스템 라이브러리의 주소를 사용하여 계산기를 띄우는 Outlook 2019 32비트 익스플로잇을 작성했습니다. 또한 이 취약점은 제로 클릭 취약점입니다. "outlook.com" 계정 이외의 메일 서비스 계정이 IMAP을 사용하여 Outlook 2019와 연결되어 있으면 메일 수신만으로 트리거됩니다.
v26 = IsDBCSLeadByte(*v13);
v12 = 0;
if ( v26 )
{
LODWORD(v5) = v5 - 1;
*v11++ = *v13++;
--v8;
}
LODWORD(v5) = v5 - 1;
*v11++ = *v13++;
--v8;
goto LABEL_51;
[OUTLMIME!CloseAllSockets+0x50804 의사 코드]
크래시가 발생하며 취약점이 존재하는 부분은 위와 같습니다. 파싱된 To 헤더 또는 From 헤더의 문자열이 멀티바이트일 때, 파싱할 남은 문자열을 카운트하는 변수가 2만큼 감소하고, 변수가 1일 때 2만큼 감소하면서 정수 언더플로우가 발생합니다.
if ( v23 != '=' )
{
if ( v23 == 34 )
v15 = 6;
goto LABEL_48;
}
v15 = 1;
}
if ( v15 )
goto LABEL_40;
[OUTLMIME!CloseAllSockets+0x50804 의사 코드]
LABEL_40:
v24 = v13[v14];
if ( v24 )
{
if ( v5 == v14 )
goto LABEL_52;
if ( v8 != v14 )
{
v25 = IsDBCSLeadByte(v24);
v12 = 0;
if ( v25 )
++v14;
++v14;
}
}
goto LABEL_51;
}
[OUTLMIME!CloseAllSockets+0x50804 의사 코드]
LABEL_51:
if ( v5 <= v14 )
goto EXIT_0;
}
[OUTLMIME!CloseAllSockets+0x50804 의사 코드]
if ( v15 )
{
if ( v15 == 1 )
{
v15 = 2;
v21 = v13[v14];
v22 = v14 + 1;
if ( v21 != '?' )
v22 = v14;
if ( v21 != '?' )
v15 = v12;
v14 = v22;
}
[OUTLMIME!CloseAllSockets+0x50804 의사 코드]
else if ( v15 > 1 )
{
if ( v15 <= 3 )
{
v18 = v13[v14];
v19 = v14 + 1;
if ( v18 != '?' )
v19 = v14;
v14 = v19;
v20 = v15 + 1;
if ( v18 != '?' )
v20 = v15;
v15 = v20;
}
[OUTLMIME!CloseAllSockets+0x50804 의사 코드]
if ( v15 == 4 )
{
if ( v13[v14] == '?' )
v15 = 5;
LABEL_40:
v24 = v13[v14];
if ( v24 )
{
if ( v5 == v14 )
goto LABEL_52;
if ( v8 != v14 )
{
v25 = IsDBCSLeadByte(v24);
v12 = 0;
if ( v25 )
++v14;
++v14;
}
}
goto LABEL_51;
}
[OUTLMIME!CloseAllSockets+0x50804 의사 코드]
if ( v15 == 5 )
{
v16 = v14;
v17 = v14 + 1;
v30 = v13[v16];
if ( v30 == '=' )
{
*v11++ = 34;
--v8;
}
memcpy(v11, v13, v17);
v13 += v17;
v11 += v17;
LODWORD(v5) = v5 - v17;
v8 -= v17;
if ( v30 == '=' )
{
*v11++ = 34;
--v8;
}
v12 = 0;
v14 = 0;
v15 = 0;
goto LABEL_51;
}
[OUTLMIME!CloseAllSockets+0x50804 의사 코드]
else
{
v23 = v13[v14];
if ( v23 != '=' )
{
if ( v23 == '"' )
v15 = 6;
goto LABEL_48;
}
v15 = 1;
}
if ( v15 )
goto LABEL_40;
[OUTLMIME!CloseAllSockets+0x50804 의사 코드]
LABEL_48:
v26 = IsDBCSLeadByte(*v13);
v12 = 0;
if ( v26 )
{
LODWORD(v5) = v5 - 1;
*v11++ = *v13++;
--v8;
}
LODWORD(v5) = v5 - 1;
*v11++ = *v13++;
--v8;
goto LABEL_51;
[OUTLMIME!CloseAllSockets+0x50804 의사 코드]
그러나 위 버그를 이용해 힙 오버플로우를 트리거할 때, 다시 루프를 빠져나오는 조건을 만족시킬 수 없어 원하는 만큼 부분 덮어쓰기를 수행할 수 없었습니다. 따라서 루프를 빠져나오는 특정 조건을 만족하는 입력을 찾아야 했습니다. 위와 같은 이유로 주변 코드 부분을 엄격하게 감사한 결과, 루프를 빠져나올 수 있는 입력값을 찾는 대신 특수 문자열을 처리할 때 인덱스 증가나 쓰기 없이 무한 루프에 빠지는 버그를 발견했습니다.

힙 스프레이와 힙 펑수이를 수행한 후, 위 버그를 이용해 무한 루프에 걸리지 않은 다른 스레드에서 실행 중인 객체의 vftable을 덮어씀으로써 EIP 하이재킹에 성공했습니다.