一、漏洞概要
本文描述了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\0\0”开始:
1 2 3 4 5 |
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; "PE\0\0" IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; |
IMAGE_OPTIONAL_HEADER结构体的最后部分包含若干个类型为IMAGE_DATA_DIRECTORY的DataDirectory结构体:
1 2 3 4 5 6 7 8 9 10 11 |
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结构体的定义如下所示:
1 2 3 4 5 6 |
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 Files\Bitdefender\Bitdefender Threat Scanner\Antivirus_…\Plugins\”目录。
Bitdefender服务启动时,会解压cevakrnl.rv8模块,并将其加载为可执行代码。当处理经过签名的PE文件时,cevakrnl.rv8!sub_40ACFF0()函数就会被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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个字节的数据,并将该数据载入到堆缓冲区中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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表示形式。
1 2 3 4 5 6 7 8 |
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)才会被认为是有效字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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值:
1 2 3 4 5 6 7 |
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”值时,就会导致基于栈的缓冲区溢出漏洞。
1 2 3 4 5 6 7 8 |
-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()函数。
1 2 3 4 5 6 7 8 9 |
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]处数据作为当前对象传递给调用的函数。
1 2 3 4 |
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:
1 |
seg001:02F70BFA mov edi, [esi+eax*4] ; eax - fixed offset = 0x560 |
最终该数据会作为当前对象传递给sub_2F6F120()函数:
1 2 |
seg001:02F70D45 mov ecx, edi seg001:02F70D47 call sub_2F6F120 |
sub_2F6F120()最终会从某个指针中提取一个dword,这个指针有可能是攻击者构造的任意指针,这样会导致程序跳转到某个任意地址上。
1 2 3 4 5 |
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版中修复了该漏洞。
本文由 安全周 作者:空心 发表,转载请注明来源!
您必须[登录] 才能发表留言!