翻译:興趣使然的小胃
预估稿费:110RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
一、漏洞概要
本文描述了Bitdefender PE引擎中存在的一个缓冲区溢出漏洞。
Bitdefender提供了“反恶意软件(antimalware)”引擎,该引擎可以集成到其他安全厂商的产品中,Bitdefender在自家产品中(如Bitdefender Internet Security 2017及以下版本)也使用了该引擎。在安全产品的众多功能中,反恶意软件引擎是核心功能,用于扫描潜在的恶意便携式可执行文件(portable executable,PE)。
二、漏洞细节
PE文件可以使用X.509证书进行签名。签名机制可确保可执行文件内容没被篡改,且文件来自于可信来源。
证书信息存放在PE数据的某个目录中,该目录由IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER字段进行定义。
PE文件中的IMAGE_NT_HEADERS结构体以特征字符“PE”开始:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; "PE"
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
IMAGE_OPTIONAL_HEADER结构体的最后部分包含若干个类型为IMAGE_DATA_DIRECTORY的DataDirectory结构体:
WORD Magic
BYTE MajorLinkerVersion
...
DWORD LoaderFlags
DWORD NumberOfRvaAndSizes
IMAGE_DATA_DIRECTORY DataDirectory[16]
----------------------------------------------------
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // RVA of the data
DWORD Size; // Size of the data
};
DataDirectory[4]代表的是IMAGE_DIRECTORY_ENTRY_SECURITY,指向一个包含WIN_CERTIFICATE结构体的列表。VirtualAddress字段指的是文件偏移量,而不是RVA(相对虚拟地址,Relative Virtual Address)。
WIN_CERTIFICATE结构体的定义如下所示:
typedef struct _WIN_CERTIFICATE {
DWORD dwLength;
WORD wRevision;
WORD wCertificateType;
BYTE bCertificate[ANYSIZE_ARRAY];
} WIN_CERTIFICATE, *PWIN_CERTIFICATE;
vsserv.exe是Bitdefender的系统服务,该进程会自动扫描PE文件,通过cevakrnl.rv8模块分析PE文件的数字签名。cevakrnl.rv8模块是一个压缩模块,位于“%ProgramFiles%Common FilesBitdefenderBitdefender Threat ScannerAntivirus_…Plugins”目录。
Bitdefender服务启动时,会解压cevakrnl.rv8模块,并将其加载为可执行代码。当处理经过签名的PE文件时,cevakrnl.rv8!sub_40ACFF0()函数就会被调用。
cevakrnl.rv8:040AE691 lea eax, [ebp+var_2C]
cevakrnl.rv8:040AE694 push eax ; &(ebp-0x2C) - object placed on the stack
cevakrnl.rv8:040AE695 call sub_40ACFF0 ; call here
cevakrnl.rv8!sub_40ACFF0() extracts the IMAGE_DIRECTORY_ENTRY_SECURITY offset and size fields.
cevakrnl.rv8:040ACFF0 sub_40ACFF0 proc near ; CODE XREF: sub_40AE5C0+D5p
cevakrnl.rv8:040ACFF0
...
cevakrnl.rv8:040AD007 mov edi, [ebp+arg_0]
...
cevakrnl.rv8:040AD025 mov eax, [edi+4] ; eax = IMAGE_NT_HEADERS
cevakrnl.rv8:040AD025 ; contains at
cevakrnl.rv8:040AD025 ; offset 0x0: DWORD Signature ("PE");
cevakrnl.rv8:040AD025 ; offset 0x4: IMAGE_FILE_HEADER FileHeader;
cevakrnl.rv8:040AD025 ; offset 0x18: IMAGE_OPTIONAL_HEADER32 OptionalHeader;
cevakrnl.rv8:040AD028 mov [ebp+arg_0_bkup], edi
cevakrnl.rv8:040AD02E mov [ebp+numofcrcs], ecx
cevakrnl.rv8:040AD034 mov [ebp+var_1F0], ecx
cevakrnl.rv8:040AD03A mov esi, [eax+9Ch] ; attribute certificate size
cevakrnl.rv8:040AD03A ; OptionalHeader.DataDirectory+0x24
cevakrnl.rv8:040AD03A ; = IMAGE_DIRECTORY_ENTRY_SECURITY.Size
cevakrnl.rv8:040AD040 mov edx, [eax+98h] ; attribute certificate offset
cevakrnl.rv8:040AD040 ; OptionalHeader.DataDirectory+0x20
cevakrnl.rv8:040AD040 ; = IMAGE_DIRECTORY_ENTRY_SECURITY.Offset
cevakrnl.rv8:040AD040 ; "Points to a list of WIN_CERTIFICATE structures, defined in WinTrust.H"
程序会从先前定义的偏移量处读取不超过0x2400个字节的数据,并将该数据载入到堆缓冲区中。
cevakrnl.rv8:040AD092 cmp esi, 2400h ; maximum size
cevakrnl.rv8:040AD098 jbe short @max
cevakrnl.rv8:040AD09A mov esi, 2400h
cevakrnl.rv8:040AD09F @max: ; CODE XREF: sub_40ACFF0+A8j
...
cevakrnl.rv8:040AD0C4 lea eax, [ebp+var_1C4]
cevakrnl.rv8:040AD0CA push eax ; int
cevakrnl.rv8:040AD0CB push esi ; size
cevakrnl.rv8:040AD0CC
cevakrnl.rv8:040AD0CC loc_40AD0CC: ; CODE XREF: sub_40ACFF0+CEj
cevakrnl.rv8:040AD0CC mov ebx, [ebp+buf]
cevakrnl.rv8:040AD0D2 mov edi, [ebp+arg_0_bkup]
cevakrnl.rv8:040AD0D8 push ebx ; buf
cevakrnl.rv8:040AD0D9 push edx ; offset
cevakrnl.rv8:040AD0DA push edi ; int
cevakrnl.rv8:040AD0DB call readatoffset ; read all structures
cevakrnl.rv8:040AD0DB ; typedef struct _WIN_CERTIFICATE {
cevakrnl.rv8:040AD0DB ; DWORD dwLength;
cevakrnl.rv8:040AD0DB ; WORD wRevision;
cevakrnl.rv8:040AD0DB ; WORD wCertificateType;
cevakrnl.rv8:040AD0DB ; BYTE bCertificate[ANYSIZE_ARRAY];
cevakrnl.rv8:040AD0DB ; } WIN_CERTIFICATE,*LPWIN_CERTIFICATE;
经过一些无关紧要的操作后,Bitdefender开始在待处理数据中搜索X.509中的“organizationName”属性。程序会搜索0x0A045503这个dword来定位该属性,这个dword是organizationName OID 2.5.4.10的ASN.1表示形式。
cevakrnl.rv8:040AD320 @startloop: ; CODE XREF: sub_40ACFF0+326j
cevakrnl.rv8:040AD320 ; sub_40ACFF0+728j
cevakrnl.rv8:040AD320 mov ecx, [ebp+buf]
cevakrnl.rv8:040AD326 mov eax, [ecx+esi] ; current dword
cevakrnl.rv8:040AD329 lea ebx, [ecx+esi]
cevakrnl.rv8:040AD32C mov [ebp+var_208], ebx
cevakrnl.rv8:040AD332 cmp eax, 0A045503h ; 55:04:0A = X.509 "id-at-organizationName" attribute
cevakrnl.rv8:040AD337 jz short @found
当程序找到“organizationName”时,该字段对应的字符串值会经某个调用传递给负责计算CRC32校验码的函数,该函数会返回该字符串经反转处理(即按位取非(NOT))后的CRC32校验值。
请注意,在“organizationName”中,只有可打印的ASCII字符(0x20-0x7E)才会被认为是有效字符。
cevakrnl.rv8:040AD3B8 @found: ; CODE XREF: sub_40ACFF0+347j
cevakrnl.rv8:040AD3B8 ; sub_40ACFF0+357j
cevakrnl.rv8:040AD3B8 mov bl, [ecx+esi+5] ; value string length
cevakrnl.rv8:040AD3BC movzx eax, bl
cevakrnl.rv8:040AD3BF mov [ebp+var_20C], eax
cevakrnl.rv8:040AD3C5 add eax, 6
cevakrnl.rv8:040AD3C8 add eax, esi
cevakrnl.rv8:040AD3CA mov [ebp+var_1E8], 0
cevakrnl.rv8:040AD3D4 mov [ebp+var_40], 0
cevakrnl.rv8:040AD3D8 mov [ebp+savedcrc], 0
cevakrnl.rv8:040AD3E2 mov [ebp+after_value_string], eax ; offset to next data
...
cevakrnl.rv8:040AD444 mov eax, [ebp+buf]
cevakrnl.rv8:040AD44A add eax, 6
cevakrnl.rv8:040AD44D mov [ebp+edi+var_40], 0
cevakrnl.rv8:040AD452 add eax, esi ; offset + 6
cevakrnl.rv8:040AD452 ; points to value string
cevakrnl.rv8:040AD454 push edi ; length of string
cevakrnl.rv8:040AD455 push eax ; Organization in certificate
cevakrnl.rv8:040AD456 call crc32 ; crc32
cevakrnl.rv8:040AD45B add esp, 8 ; this returns ~crc32
cevakrnl.rv8:040AD45B ; ~crc32("31TZnp") = 0xdeadbeef
如果之前没处理过该CRC值:
cevakrnl.rv8:040AD480 @checkduplicate: ; CODE XREF: sub_40ACFF0+488j
cevakrnl.rv8:040AD480 ; sub_40ACFF0+4A0j
cevakrnl.rv8:040AD480 cmp [ebp+ecx*4+crc32results], eax ; array of already saved CRCs
cevakrnl.rv8:040AD487 jz @duplicate
cevakrnl.rv8:040AD48D inc ecx
cevakrnl.rv8:040AD48E cmp ecx, ebx
cevakrnl.rv8:040AD490 jb short @checkduplicate
该值会存放在某个大小为8个dwords的本地栈数组中。对于每个不同的CRC值,这个数据的索引都会相应地增加,但程序却没有检查数组的大小限制。这样一来,如果程序在处理过程中遇到数量足够的不同的“organizationName”值时,就会导致基于栈的缓冲区溢出漏洞。
-000001B8 crc32results dd 8 dup(?)
-00000198 var_198 db 256 dup(?)
...
cevakrnl.rv8:040AD51E mov eax, [ebp+savedcrc]
cevakrnl.rv8:040AD524 mov [ebp+ebx*4+crc32results], eax ; buffer overflow
cevakrnl.rv8:040AD524 ; [ebp+ebx*4-0x1B8] = eax
cevakrnl.rv8:040AD52B inc ebx
cevakrnl.rv8:040AD52C mov [ebp+numofcrcs], ebx
攻击者可以利用这个漏洞将大量任意数据覆盖到栈中。通过逆向CRC32算法可知,我们可以构造某个ASCII字符串,生成我们需要的CRC值,从而将任意数据写入栈中。
虽然存在该漏洞的函数会在返回时检查某个cookie值,我们还是可以在函数返回之前,将某个对象放置于栈中,从而实现代码执行。
该对象作为第一个参数传递给存在漏洞的函数,位于0x1C偏移处(PoC中该值更改为0xdeadbeef)的字段会被传递给global_function0()函数。
cevakrnl.rv8:040AD750 mov ebx, [ebp+arg_0_bkup] ; ebx points to the stack of the caller function,
; which is above crc32results
...
cevakrnl.rv8:040AD785 push 0
cevakrnl.rv8:040AD787 push 1
cevakrnl.rv8:040AD789 push 41C40Eh
cevakrnl.rv8:040AD78E push 6
cevakrnl.rv8:040AD790 push dword ptr [ebx+1Ch] ; corrupted
cevakrnl.rv8:040AD793 call global_function0
global_function0()函数会调用sub_2F70B90(),并将[0xdeadbeef+0x22C]处数据作为当前对象传递给调用的函数。
seg001:02F5D69F mov ecx, [ecx+22Ch] ; crash here
seg001:02F5D69F ; ecx is controlled
seg001:02F5D6A5 push [ebp+arg_4]
seg001:02F5D6A8 call sub_2F70B90
sub_2F70B90()函数会从当前对象指针中提取一个dowrd:
seg001:02F70BFA mov edi, [esi+eax*4] ; eax - fixed offset = 0x560
最终该数据会作为当前对象传递给sub_2F6F120()函数:
seg001:02F70D45 mov ecx, edi
seg001:02F70D47 call sub_2F6F120
sub_2F6F120()最终会从某个指针中提取一个dword,这个指针有可能是攻击者构造的任意指针,这样会导致程序跳转到某个任意地址上。
seg001:02F6F132 mov eax, [edi+4]
seg001:02F6F135 push ebx
seg001:02F6F136 push dword ptr [esi+4]
seg001:02F6F139 push edi
seg001:02F6F13A call eax
能否跳转到任意地址取决于攻击者能否将构造的内容存放到某个固定的地址中。攻击者可以通过堆喷射(heap spraying)技术实现这一目标。根据Bitdefender引擎的复杂度,我们认为这种可能性是存在的。
三、其他说明
感谢独立安全研究员Pagefault将该漏洞报告给SecuriTeam安全披露项目。
Bitdenfender已经在7.71417版中修复了该漏洞。