Adobe에서 Encoding이 다른 두 URL을 연결하는 과정에서 Out Of Bound Read / Write와 Heap Buffer OverFlow가 발생하는 취약점
Exodus Blog에서 확인한 결과 취약한 함수가 호출한 함수는 URL 연결을 위해 strncat
함수를 호출한다.
strncat
함수는 외부에서 import해 사용한다.
.rdata
segment에 존재하며,.rdata
segment에 저장된 이름을 이용한다. [그림 1] .rdata 영역에서 "strncat"
문자열을 확인하고, strncat
함수의 상호 참조 함수를 확인한 모습
[그림 2] [그림 1]에서 찾은 strncat
함수의 상호 참조 함수의 상호 참조 함수를 확인한 모습
[그림 2]을 보면 strncat
함수에서 상호 참조하며 취약한 함수, ExploitPoint
를 찾아내었다.
취약한 함수는 1 byte 값과 UTF-16BE BOM의 일부인
"\xFF"
문자를 비교하는 연산을 수행한다.
"\xFF"
와 1 byte 레지스터를 비교하는 OpCode를 얻어, 해당 OpCode가 존재하는 함수를 탐색하며 취약한 함수를 찾았다.ExploitPoint
를 찾은 모습Exodus 블로그를 훑어본 결과, 취약한 함수는 많은 명령을 보유하고 있는 것을 확인하였다.
또한 IDA는 분석가의 코드로 바이너리 분석을 자동화해주는 기능을 가지고 있다.
IDA Script를 이용해 각 함수의 insruction 수를 구하고, 많은 instruction을 가진 함수들을 탐색하면 취약한 함수를 찾을 수 있으리라 판단했다.
나는 IDC Script를 이용해 instruction이 400개 초과인 함수만 추려 취약한 함수, ExploitPoint
를 찾았다.
#include <idc.idc>
static main() {
auto func, end, count, inst;
func = 0;
Message("================ START =================\n");
for(func = NextFunction(func); func != BADADDR; func = NextFunction(func)) {
if(func != -1) {
end = GetFunctionAttr(func, FUNCATTR_END);
count = 0;
inst = func;
while(inst < end) {
count++;
inst = FindCode(inst, SEARCH_DOWN | SEARCH_NEXT);
}
if(count > 400) {
Message("%s contains %d instructions\n" , Name(func), count);
}
} else {
//Message("No function found at location %x", func);
}
}
}
[그림 4] IDC IDA Script로 취약한 함수, ExploitPoint
를 찾은 모습
__int16 __cdecl ExploitPoint(wchar_t *Source, CHAR *lpString, char *String, _DWORD *a4, int *a5)
{
__int16 v5; // di
wchar_t *concatURL_addr; // ebx
CHAR *lpString_copy; // eax
CHAR v8; // dl
__int64 len_lpString; // rax
wchar_t *Source_copy; // ecx
__int64 len_Source; // rax
int v12; // eax
int totallen_Source; // eax
int len_Source_notUTF; // eax
CHAR *allocadr_Source; // eax
wchar_t *v16; // ecx
int totallen_lpString; // eax
int len_lpString_notUTF; // eax
CHAR *allocadr_lpString; // eax
int v20; // eax
int v21; // edx
int v22; // edx
_DWORD *v23; // eax
int v24; // ecx
int *v25; // eax
int v26; // ecx
int v27; // eax
int v28; // ecx
int v29; // eax
wchar_t *v30; // ecx
int v31; // eax
int len_allocaddr; // eax
int v33; // eax
int v34; // ecx
int v35; // edx
wchar_t *v37; // [esp-4h] [ebp-F4h]
unsigned int v38; // [esp-4h] [ebp-F4h]
wchar_t *v39; // [esp-4h] [ebp-F4h]
unsigned int v40; // [esp-4h] [ebp-F4h]
unsigned int v41; // [esp-4h] [ebp-F4h]
int v42[7]; // [esp+Ch] [ebp-E4h] BYREF
int v43; // [esp+28h] [ebp-C8h]
int v44; // [esp+2Ch] [ebp-C4h]
int v45; // [esp+30h] [ebp-C0h]
int v46; // [esp+34h] [ebp-BCh]
wchar_t *v47; // [esp+38h] [ebp-B8h]
__int64 v48; // [esp+3Ch] [ebp-B4h]
int v49; // [esp+4Ch] [ebp-A4h]
int v50[3]; // [esp+50h] [ebp-A0h] BYREF
int v51; // [esp+5Ch] [ebp-94h]
int v52; // [esp+60h] [ebp-90h]
int v53; // [esp+64h] [ebp-8Ch]
int v54; // [esp+68h] [ebp-88h]
int v55; // [esp+6Ch] [ebp-84h]
int v56; // [esp+70h] [ebp-80h]
int v57; // [esp+74h] [ebp-7Ch]
int v58; // [esp+78h] [ebp-78h]
char *v59; // [esp+7Ch] [ebp-74h]
__int64 v60; // [esp+80h] [ebp-70h]
__int64 v61; // [esp+88h] [ebp-68h]
int v62; // [esp+90h] [ebp-60h]
int v63[3]; // [esp+94h] [ebp-5Ch] BYREF
int v64; // [esp+A0h] [ebp-50h]
int v65; // [esp+A4h] [ebp-4Ch]
int v66; // [esp+A8h] [ebp-48h]
int v67; // [esp+ACh] [ebp-44h]
int v68; // [esp+B0h] [ebp-40h]
int v69; // [esp+B4h] [ebp-3Ch]
int v70; // [esp+B8h] [ebp-38h]
int v71; // [esp+BCh] [ebp-34h]
void *v72; // [esp+C0h] [ebp-30h]
__int128 v73; // [esp+C4h] [ebp-2Ch]
int v74; // [esp+D4h] [ebp-1Ch]
int iMaxLength[2]; // [esp+D8h] [ebp-18h]
LPCSTR allocadr_lpString_copy; // [esp+E0h] [ebp-10h]
LPCSTR allocadr_Source_copy; // [esp+E4h] [ebp-Ch]
int v78[2]; // [esp+E8h] [ebp-8h] BYREF
allocadr_Source_copy = 0;
allocadr_lpString_copy = 0;
v5 = 1;
*(_QWORD *)v78 = 0i64;
*(_QWORD *)iMaxLength = 0i64;
concatURL_addr = 0;
v49 = 0;
v62 = 0;
v74 = 0;
if(!a5) return 0;
*a5 = 0;
// [1-1] relative URL 길이 측정
lpString_copy = lpString;
if(lpString && *lpString && (v8 = lpString[1]) != 0 && *lpString == (CHAR)0xFE && v8 == (CHAR)0xFF) {
len_lpString = ((__int64 (__cdecl *)(CHAR *))strlen_UTF16BE)(lpString);
v78[1] = len_lpString;
if ((HIDWORD(len_lpString)&(unsigned int)len_lpString) == -1) {
LABEL_9:
*a5 = -2;
return 0;
}
lpString_copy = lpString;
} else {
v78[1] = v78[0];
}
// [1-2] base URL 길이 측정
Source_copy = Source;
if(!Source || !lpString_copy || !String || !a4) {
*a5 = -2;
goto LABEL_86;
}
if(*(_BYTE *)Source != 0xFE)
goto LABEL_25;
if(*((_BYTE *)Source+1) == 0xFF) {
len_Source = ((__int64 (__cdecl *)(wchar_t *))strlen_UTF16BE)(Source);
iMaxLength[1] = len_Source;
if((HIDWORD(len_Source)&(unsigned int)len_Source) == -1)
goto LABEL_9;
Source_copy = Source;
v12 = iMaxLength[1];
} else {
v12 = iMaxLength[0];
}
if(*(_BYTE *)Source_copy == 0xFE && *((_BYTE *)Source_copy+1) == 0xFF) {
totallen_Source = v12 + 2;
} else {
LABEL_25:
len_Source_notUTF = (int)custom_strlen((LPCSTR)Source_copy);
Source_copy = v37;
totallen_Source = len_Source_notUTF + 1;
}
iMaxLength[1] = totallen_Source;
// [2-1] base URL을 새로운 heap에 저장
allocadr_Source = (CHAR *)((int (__usercall *)@<eax>(wchar_t *@<ecx>, int, int))calloc_guess)(Source_copy, 1, totallen_Source);
allocadr_Source_copy = allocadr_Source;
if(!allocadr_Source) {
*a5 = -7;
return 0;
}
((void (__usercall *)(unsigned int@<ecx>, wchar_t *, wchar_t *, int))custom_strncpy)(v38, (wchar_t *)allocadr_Source, Source, iMaxLength[1]);
if(*lpString==(CHAR)0xFE && lpString[1]==(CHAR)0xFF) {
totallen_lpString = v78[1] + 2;
} else {
len_lpString_notUTF = (int)custom_strlen(lpString);
v16 = v39;
totallen_lpString = len_lpString_notUTF + 1;
}
v78[1] = totallen_lpString;
// [2-2] relative URL을 새로운 heap에 저장
allocadr_lpString = (CHAR *)((int (__usercall *)@<eax>(wchar_t *@<ecx>, int, int))calloc_guess)(v16, 1, totallen_lpString);
allocadr_lpString_copy = allocadr_lpString;
if(!allocadr_lpString) {
*a5 = -7;
LABEL_86:
v5 = 0;
goto LABEL_87;
}
((void (__usercall *)(unsigned int@<ecx>, wchar_t *, wchar_t *, int))custom_strncpy)(v40, (wchar_t *)allocadr_lpString, (wchar_t *)lpString, v78[1]);
if(!(unsigned __int16)check_modify_URL((int)allocadr_Source_copy, iMaxLength[1], a5) || !(unsigned __int16)check_modify_URL((int)allocadr_lpString_copy, v78[1], a5)) {
goto LABEL_86;
}
// [3] URL 관련 연산 수행
v20 = URLparse_process((CHAR *)allocadr_Source_copy, v42);
if(v20 || (v20 = URLparse_process((CHAR *)allocadr_lpString_copy, v50)) != 0) {
*a5 = v20;
goto LABEL_86;
}
if(!*(_BYTE *)Source || (v21 = v42[0], v50[0] != 5) && v50[0] != v42[0]) {
v35 = sub_25802FAC((int)v50);
v23 = a4;
v24 = v35 + 1;
if(v35 + 1 > *a4) goto LABEL_44;
*a4 = v35;
v25 = v50;
goto LABEL_82;
}
if(*lpString) {
v26 = v55;
v63[1] = v42[1];
v63[2] = v42[2];
v27 = v51;
v63[0] = v42[0];
v73 = 0i64;
if(!v51 && !v53 && !v55) {
if(sub_25803155(v50)) {
v28 = v44;
v64 = v42[3];
v65 = v42[4];
v66 = v42[5];
v67 = v42[6];
v29 = v43;
if(v49 == 1) {
v29 = v43 + 2;
v28 = v44 - 1;
v43 += 2;
--v44;
}
v69 = v28;
v68 = v29;
v70 = v45;
if(v58) {
if (*v59 != '/') {
// [4] 연결된 URL을 저장하기 위한 새로운 heap 할당
concatURL_addr = (wchar_t *)((int (__usercall *)@<eax>(wchar_t *@<ecx>, int, int))calloc_guess)((wchar_t *)(v58 + 1), 1, v58 + 1 + v46);
if(!concatURL_addr) {
v23 = a4;
v24 = v58 + v46 + 1;
goto LABEL_44;
}
if(v46) {
// [5] base URL을 [4]에서 할당한 heap memory에 저장
((void (__usercall *)(unsigned int@<ecx>, wchar_t *, wchar_t *, int))custom_strncpy)(v41, concatURL_addr, v47, v46 + 1);
if (*((_BYTE *)concatURL_addr + v46 - 1) != '/') {
v31 = ((int (__usercall *)@<eax>(wchar_t *@<ecx>, char *, int))sub_25818D6E)(v30, (char *)concatURL_addr, '/');
if(v31) *(_BYTE *)(v31 + 1) = 0;
else *(_BYTE *)concatURL_addr = 0;
}
}
// [6] relative URL을 [5]에서 저장한 base URL 뒤에 이어 붙임 : 취약점 발생
if(v58) {
len_allocaddr = (int)custom_strlen((LPCSTR)concatURL_addr);
((void (__usercall *)(uintptr_t@<ecx>, char *, char *, int))custom_strncat)(v58 + 1, (char *)concatURL_addr, v59, v58 + 1 + len_allocaddr);
}
sub_25802E0C((int)concatURL_addr, 0);
v71 = (int)custom_strlen((LPCSTR)concatURL_addr);
v72 = concatURL_addr;
goto LABEL_75;
}
v71 = v58;
v72 = v59;
} else {
v71 = v46;
v72 = v47;
if((_DWORD)v60) goto LABEL_75;
*(_QWORD *)&v73 = v48;
}
LABEL_74:
if((_DWORD)v73) {
LABEL_77:
if ( (int)v61 > 0 ) *((_QWORD *)&v73 + 1) = v61;
v34 = sub_25802FAC((int)v63);
if(v34+1 > *a4) {
*a4 = v34 + 1;
goto LABEL_45;
}
*a4 = v34;
v25 = v63;
goto LABEL_82;
}
LABEL_75:
if((int)v60 > 0) *(_QWORD *)&v73 = v60;
goto LABEL_77;
}
v26 = v55;
v21 = v42[0];
v27 = v51;
}
v64 = v27;
v65 = v52;
v66 = v53;
v67 = v54;
v33 = v56;
if(v62 == 1) {
v26 += 2;
v33 = v56 - 1;
v55 = v26;
--v56;
}
v69 = v33;
v68 = v26;
v71 = v58;
v70 = v57;
v72 = v59;
if(v57) goto LABEL_75;
v78[1] = 0;
if(!sub_25802C93(v21, &v78[1])) goto LABEL_75;
v70 = v78[1];
goto LABEL_74;
}
v22 = sub_25802FAC((int)v42);
v23 = a4;
v24 = v22 + 1;
if(v22+1 > *a4) {
LABEL_44:
*v23 = v24;
LABEL_45:
*a5 = -3;
goto LABEL_86;
}
*a4 = v22;
v25 = v42;
LABEL_82:
sub_25803194((int)v25, String);
LABEL_87:
if(allocadr_Source_copy)
(*(void (__cdecl **)(LPCSTR))(dword_25824098 + 12))(allocadr_Source_copy);
if(allocadr_lpString_copy)
(*(void (__cdecl **)(LPCSTR))(dword_25824098 + 12))(allocadr_lpString_copy);
if(concatURL_addr)
(*(void (__cdecl **)(wchar_t *))(dword_25824098 + 12))(concatURL_addr);
return v5;
}
Parameter
_BYTE *Source
: baseURL_BYTE *lpString
: relative URLExploit에 필요한 부분만 나타내면 ExploitPoint 함수는 크게 아래의 동작을 거친다.
URL 길이 측정
[1-1] relative URL 길이 측정 | [1-2] base URL 길이 측정
URL을 heap에 저장
[2-1] base URL을 새로운 heap에 저장 | [2-2] relative URL을 새로운 heap에 저장
URL 관련 연산 수행 (Exploit과 연관 X)
연결된 URL을 저장하기 위한 새로운 heap 할당
base URL을 [4]에서 할당한 heap memory에 저장
relative URL을 [5]에서 저장한 base URL 뒤에 이어 붙임 : 취약점 발생
char *string
: 길이를 계산할 UTF16-BE로 encoding된 문자열int __cdecl strlen_UTF16BE(char *string)
{
char *p_string_i0; // eax
char string_i1; // cl
int length; // esi
char string_i0; // bl
char *p_string_i1; // eax
p_string_i0 = string;
if(!string || *string != -2 || string[1] != -1) return -1;
string_i1 = 0;
length = 0;
do {
string_i0 = *p_string_i0;
p_string_i1 = p_string_i0 + 1;
if(!p_string_i1) break;
string_i1 = *p_string_i1;
p_string_i0 = p_string_i1 + 1;
if(!string_i0) goto LABEL_10;
if(!string_i1) break;
length += 2;
} while ( p_string_i0 );
if(string_i0) return -1;
LABEL_10:
if(!string_i1) return length;
else return -1;
}
LPCSTR lpString
: UTF-16BE로 encoding된 문자열의 주소 (32 bit)int __cdecl strlen_UTF16BE_(char *string)
{
char *v1; // ecx
int i; // edx
char v4; // al
v1 = string;
if(!string) return 0;
for(i = 0; ; i += 2) {
v4 = *v1;
v1 += 2;
if (!v4 && !*(v1-1)) break;
}
return i;
}
wchar_t *Destination
: 복사할 주소wchar_t *Source
: 복사할 문자열(의 주소)unsigned int iMaxLength
: 복사할 길이deststr
(복사할 공간; 복사된 장소)wchar_t *__cdecl custom_strncpy(wchar_t *Destination, wchar_t *Source, unsigned int iMaxLength)
{
wchar_t *result; // eax
int pExceptionObject; // [esp+Ch] [ebp-4h] BYREF
if(!Destination || !Source || !iMaxLength) {
(*(void (__thiscall **)(_DWORD, int))(dword_258240A4 + 4))(*(_DWORD *)(dword_258240A4 + 4), 1073741827);
pExceptionObject = 0;
CxxThrowException(&pExceptionObject, (_ThrowInfo *)&_TI1H);
}
if(*(_BYTE *)Source == 0xFE && *((_BYTE *)Source+1) == 0xFF)
return wcsncpy(Destination, Source, iMaxLength >> 1);
result = (wchar_t *)lstrcpynA((LPSTR)Destination, (LPCSTR)Source, iMaxLength);
*((_BYTE *)Destination + iMaxLength - 1) = 0;
return result;
}
char *Destination
, char *Source
, int maxlength
custom_strncat
함수는 Destination
뒤에 Source
를 붙인다.custom_strcat(Destination, Source);
호출하여 문자열 연결을 진행한다.int __cdecl custom_strncat(char *Destination, char *Source, int maxlength)
{
int result; // eax
LPCSTR pExceptionObject; // [esp+10h] [ebp-4h] BYREF
if(!Destination || !Source || !maxlength) {
(*(void (__thiscall **)(_DWORD, int))(dword_258240A4 + 4))(*(_DWORD *)(dword_258240A4 + 4), 1073741827);
pExceptionObject = 0;
CxxThrowException(&pExceptionObject, (_ThrowInfo *)&_TI1H);
}
pExceptionObject = custom_strlen(Destination);
if(&custom_strlen(Source)[(int)pExceptionObject] <= (const CHAR *)(maxlength-1)) {
custom_strcat(Destination, Source);
return 1;
} else {
strncat(Destination, Source, maxlength - (_DWORD)pExceptionObject - 1);
result = 0;
Destination[maxlength - 1] = 0;
}
return result;
}
Parameter
base URL이 UTF16-BE로 encoding 되었을 경우, base URL 뒤에 relative URL을 "\x00\x00"
이 나올 때까지 2 byte 씩 복사한다.
base URL이 UTF16-BE로 encoding 되지 않았을 경우, lstrcatA 함수를 호출해 base URL 뒤에 relative URL을 "\x00"
이 나올 때까지 1 byte 씩 복사한다.
LPSTR __cdecl custom_strcat(LPSTR lpString1, LPCSTR lpString2)
{
int len_lpString1; // eax
LPCSTR p_lpString2_i2; // edx
CHAR *concatpoint; // ecx
CHAR lpString2_i2; // al
CHAR lpString2_i3; // bl
int pExceptionObject; // [esp+10h] [ebp-4h] BYREF
if(!lpString1 || !lpString2) {
(*(void (__thiscall **)(_DWORD, int))(dword_258240A4 + 4))(*(_DWORD *)(dword_258240A4 + 4), 1073741827);
pExceptionObject = 0;
CxxThrowException(&pExceptionObject, (_ThrowInfo *)&_TI1H);
}
if(*lpString1 == (CHAR)0xFE && lpString1[1] == (CHAR)0xFF) {
len_lpString1 = (int)custom_strlen(lpString1);
p_lpString2_i2 = lpString2 + 2;
concatpoint = &lpString1[len_lpString1];
do {
do {
lpString2_i2 = *p_lpString2_i2;
p_lpString2_i2 += 2;
*concatpoint = lpString2_i2;
concatpoint += 2;
lpString2_i3 = *(p_lpString2_i2 - 1);
*(concatpoint - 1) = lpString2_i3;
} while(lpString2_i2);
} while(lpString2_i3);
} else {
lstrcatA(lpString1, lpString2);
}
return lpString1;
}
UTF16-BE로 encoding된 base URL과 ANSI로 encoding된 relative URL이 연결될 때,
ANSI URL을 UTF16-BE로 인식하여 취약점이 발생한다.
그 결과, ANSI로 encoding된 relative URL의 Null Terminator인
"\x00"
을 넘어 UTF-16BE의 Null Terminator인"\x00\x00"
이 나올 때까지 문자열 복사, 붙여넣기를 진행한다.
[그림 5] Root Cause 모식도
Adobe root cause.pdf의 PPT slide를 넘기며 Root Cause를 시각적으로 확인할 수 있다.
THE IDA PRO BOOK 2ND EDITION (Chris Eagle)
피드백은 언제나 환영합니다