—— 检测SigFlip在PE的签名里嵌入的shellcode,以及嵌入方法改进
前言
之前看到 @w8ay实现的白加黑生成器(https://x.hacking8.com/post-430.html),感觉这个做法非常的棒,但是 SigFlip
的做法真的隐蔽吗?真的不容易被查杀吗?
刚好最近在研究PE的文件签名结构和签名,那今天就详细讲解一下签名相关的内容,以及如何改进SigFlip
实现更深层次的隐藏。
原理回顾
对原理比较了解的可直接跳过这一节。
看一下微软给的PE文件签名的结构,这张图非常经典,虽然只是一个微软签名省略了大部分细节的概括图,但是有助于我们了解一些最基本的信息。
首先可以看到图中灰色区域标注的内容是不参与文件签名hash计算的,并且可以看到Attribute Certificate table
的位置是由 PE头中的Data Directories
中某一项指定的,Data Directories
共有16项,每一项的结构都是:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
总共8个字节,指定了对应的数据在文件中的偏移以及大小。Data Directories
的Security项就指向了其签名数据。
签名数据的数据结构是:
typedef struct _WIN_CERTIFICATE {
DWORD dwLength;
WORD wRevision;
WORD wCertificateType; // WIN_CERT_TYPE_xxx
BYTE bCertificate[ANYSIZE_ARRAY];
WIN_CERTIFICATE, *LPWIN_CERTIFICATE;
-
• dwLength:表明签名的数据的总体大小,一般是等于
_IMAGE_DATA_DIRECTORY
的size字段。 -
• wRevision:表明当前签名信息的版本。
-
• wCertificateType:指定接下来的签名的类型
-
• bCertificate:Byte数组,存储的就是签名的具体数据。
最值得注意的是bCertificate
的字节大小要求8字节对齐。
SigFlip的代码实现
从上面分析的不参与文件校验的部分可以得知,只有_WIN_CERTIFICATE
部分是可以用来隐藏数据的,具体我们直接看SigFlip的代码实现。
//Get IMAGE_DIRECTORY_ENTRY_SECURITY field and retrieve the RVA and SIZE of the Certificate Table (WIN_CERTIFICATE).
_CertTableRVA = _optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY + _DT_SecEntry_Offset].VirtualAddress;
_CertTableSize = _optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY + _DT_SecEntry_Offset].Size;
_wCert = (LPWIN_CERTIFICATE)((BYTE*)_peBlob + _CertTableRVA);
memcpy((((BYTE*)_peBlob + _CertTableRVA) + _wCert->dwLength), _rpadding, strlen(_rpadding));
//update dwLength and Cert Table Entry Size.
printf("[+]:Updating OPT Header Fields/Entries n");
_wCert->dwLength += strlen(_rpadding);
_ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY + _DT_SecEntry_Offset].Size += strlen(_rpadding);
//update checksum
printf("[+]:Calculating/Updating the new OPTHeader checksumn");
checksum = PEChecksum(_peBlob, _fSize);
_ntHeader->OptionalHeader.CheckSum = checksum;
首先获取到_WIN_CERTIFICATE
的位置和大小,然后把数据padding到签名中,并扩充为8字节对齐,最后更新PE文件的checksum
和_WIN_CERTIFICATE
的大小。
检测方法
SigFlip
的检测是非常简单,我们抛开插入shellcode时的标识起始字xFExEDxFAxCExFExEDxFAxCE
不谈,只看插入的内容,可以看到SigFlip
仅仅修改了_IMAGE_DATA_DIRECTORY
中的Size
字段,但是并没有修改_WIN_CERTIFICATE
中的dwLength
,两个长度不一致就可以很轻易的发现隐藏在签名中的数据:
pe_obj = pefile.PE(filepath)
security_entry = pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_SECURITY"]
sig_off = pe_obj.OPTIONAL_HEADER.DATA_DIRECTORY[security_entry].VirtualAddress
sig_len = pe_obj.OPTIONAL_HEADER.DATA_DIRECTORY[security_entry].Size
pkcs7Data = b""
dirty = b""
if sig_off == 0 or sig_len == 0:
return pkcs7Data,dirty
# 取出和 security 相关的数据
with open(filepath, 'rb') as fh:
fh.seek(sig_off)
sig_raw_data = fh.read(sig_len)
begin = 0
new_p = self.WIN_CERTIFICATE()
c.memmove(c.addressof(new_p),sig_raw_data,c.sizeof(self.WIN_CERTIFICATE))
pkcs7Data = sig_raw_data[begin+8: begin+ new_p.dwLength ]
begin += new_p.dwLength
dirty = sig_raw_data[begin:]
return pkcs7Data,dirty
此时就会有人想,如果我把_WIN_CERTIFICATE
的dwLength
也修改了,那是不是就足够隐蔽了呢?要解答这个问题,只简单的看一下上面的微软的签名概略图是远远不够的,我们需要深入了解签名数据中每一段的含义和作用,在下一节会进行详细的讲解。
改进SigFlip的思路
在进行这一节之前,我们应该先大概了解一下ASN.1
格式标准和DER(Distinguished Encoding Rules)
编码规范,可以阅读如下的参考文章:
-
• https://blog.csdn.net/zhaoruixiang1111/article/details/84191682
-
• https://www.cnblogs.com/nathanyang/p/9951282.html
简单来讲每一个结构的数据都是用如下三个部分来描述的:
-
• Identifer: 表述数据类型
-
• Length:表示后面接的数据长度
-
• Contents:就存放着需要的数据
比如我们对字符串[email protected]
进行编码:
16 0d 74 65 73 74 31 40 72 73 61 2e 63 6f 6d
indentify length 字符串的Ascii码
微软的前面数据其实是 pkcs#7
加密消息标准的数据,是使用满足ASN.1
的编码标准,使用DER
编码规范进行存储的数据其ASN.1的定义如下:
ContentInfo ::= SEQUENCE {
contentType ContentType,
content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
}
其中ContentType
表示了Content
的具体数据格式,有如下的可选值:
'1.2.840.113549.1.7.1': 'data',
'1.2.840.113549.1.7.2': 'signed_data',
'1.2.840.113549.1.7.3': 'enveloped_data',
'1.2.840.113549.1.7.4': 'signed_and_enveloped_data',
'1.2.840.113549.1.7.5': 'digested_data',
'1.2.840.113549.1.7.6': 'encrypted_data',
'1.2.840.113549.1.9.16.1.2': 'authenticated_data',
'1.2.840.113549.1.9.16.1.9': 'compressed_data',
'1.2.840.113549.1.9.16.1.23': 'authenticated_enveloped_data',
'1.3.6.1.4.1.311.2.1.4': 'spc_indirect_data_content'
在这里contentType
的obj是1.2.840.113549.1.7.2
,表明content
的内容是signedData
。
看到这里你就找到了上一节的问题的答案,由于DER编码数据中存储有自身的length,所以即便我们修改了 dwLength,依然无法实现数据隐藏,这里的长度校验依然会让隐藏数据无处遁形。
我们继续向下分析,SignedData的数据格式定义如下:
SignedData ::= SEQUENCE {
version Version,
digestAlgorithms DigestAlgorithmIdentifiers,
contentInfo ContentInfo,
certificates
IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
Crls
IMPLICIT CertificateRevocationLists OPTIONAL,
signerInfos SignerInfos
}
ContentInfo
的内容是参与签名计算的,不能修改。certificates
里面存储的是x509格式的证书文件,其实是可以把自己的shellcode编码为一个证书存在这里,但是这相对来讲是比较有难度的。另外Crls
是一个可选成员,在这里微软并没有使用这个成员,所以按道理是可以用来隐藏数据的,不过还是不够隐蔽,我们继续看,SignerInfos
的格式定义分别如下:
SignerInfos ::= SET OF SignerInfo
SignerInfo ::= SEQUENCE {
version Version,
issuerAndSerialNumber IssuerAndSerialNumber,
digestAlgorithm DigestAlgorithmIdentifier,
authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
encryptedDigest EncryptedDigest,
unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
主要看unauthenticatedAttributes
,这是一个可变长数组,微软定一个此数组存在一个元素时以及两个元素时的含义,但是到底是存在一个元素还是两个元素是看具体的签名签发者决定的,所以这个数组是可以用来隐藏数据的,我们只需要定义一个公共使用的obj_id,就可以避免被发现。
持续关注本微信公众号或知识星球,SigFlip-plus
版本的相关的代码实现会在后续公开。
参考文档
-
• https://3gstudent.github.io/%E9%9A%90%E5%86%99%E6%8A%80%E5%B7%A7-%E5%9C%A8PE%E6%96%87%E4%BB%B6%E7%9A%84%E6%95%B0%E5%AD%97%E8%AF%81%E4%B9%A6%E4%B8%AD%E9%9A%90%E8%97%8FPayload
-
• https://ti.dbappsecurity.com.cn/info/2163
安全的矛与盾
欢迎大家加入知识星球”安全的矛与盾”,本星球是一个既讲攻击也讲防御、开放的、前沿的安全技术分享社区。在这里你不仅可以学习到最新的攻击方法与逃避检测的技术,也可以学到最全面的安全防御体系,了解入侵检测、攻击防护系统的原理与实践。站在攻与防不同的视角看问题,提高自己对安全的理解和深度,做到: 知攻、知守、知进退;有矛、有盾、有安全。
现知识星球开启内测邀请,关注此微信公众号可领取5折优惠券,欢迎大家加入!
今日推荐阅读
原文始发于微信公众号(安全的矛与盾):听说你在用SigFlip在PE签名里嵌入shellcode?