大家好,我本次分享的内容是从海量PE样本中挖掘伪造图标类恶意程序。
众所周知,很多恶意程序会将自己的图标伪装成常见的word图标、文件夹图标以及一些常用程序的图标。比如文件夹exe病毒会伪装成文件夹图标,以及最近一段时间爆发的WannaRen 勒索病毒就会释放出everything软件的图标来迷惑用户去点击。所以,我就萌发了从海量PE样本中提取图标来挖掘伪造图标类恶意程序的想法。
对于单个PE样本,很容易提取其图标,一种常见的方法就是使用Resource Hacker。但对于大量的样本,我们应该怎么提取呢?
对于使用Python的研究人员来讲,有多种方法可以用来提取PE文件的图标。
第一种方法是使用windows提供的API函数,ExtractIconEx函数可以从exe、dll PE文件中获得图标的句柄,拿到PE文件的图标句柄后,使用GetDC函数,该函数可以获得指定窗口或整个屏幕显示设备上下文(Divice Context,DC)的句柄,实现截图画图,并将其保存为bmp位图。在Python中,我们可以通过调用win32gui中的ExtractIconEx方法(需要安装pywin32)提取图标,然后使用win32ui模块中GetDC相关的截图画图函数生成图片。
代码如下:
import win32ui
import win32gui
def main():
large, small = win32gui.ExtractIconEx("tests\WeChat.exe",0)
win32gui.DestroyIcon(small[0])
hdc = win32ui.CreateDCFromHandle( win32gui.GetDC(0) )
hbmp = win32ui.CreateBitmap()
hbmp.CreateCompatibleBitmap( hdc, 32, 32 )
hdc = hdc.CreateCompatibleDC()
hdc.SelectObject( hbmp )
hdc.DrawIcon( (0,0), large[0] )
hbmp.SaveBitmapFile( hdc, "save.bmp" )
if __name__ == '__main__':
main()
其中,因为ExtractIconEx函数返回一个大图标(3232)和一个小图标(3232)的句柄,我们只需要大图标的句柄,所以需要将小图标进行销毁。CreateDCFromHandle从句柄中得到设备上下文,之后,创建32*32大小的兼容位图,再将兼容位图句柄设置到兼容内存设备DC上,然后绘图并保存为save.bmp。
若想保存为png等格式,可以在调用ExtractIconEx后,使用PyQt,将其保存为png图片格式。
这个方法方便简单,但是存在一个缺点,就是ExtractIconEx方法仅仅返回3232和1616的大小两个图标的句柄。然而很多时候PE样本的图标并不仅仅是这两个大小,所以需要一个更好的方法来提取更多的图标。
另外一个方法是使用一些开源的项目,比如https://github.com/firodj/extract-icon-py,该项目基于Python的pefile模块,之后再使用pillow模块进行画图,保存为png格式。其中用到的pefile模块是Python中专门用来解析PE文件的库。该模块的功能十分强大,我们有必要对其进行了解。首先通过一个demo来了解一下使用方法:
import pefile
PEfile_Path = r"C:\test.exe"
pe = pefile.PE(PEfile_Path)
for importeddll in pe.DIRECTORY_ENTRY_IMPORT:
print importeddll.dll
即可输出该PE文件的导入表。关于PE文件格式的更多内容请参阅《windows PE权威指南》。
通过上述方法我们可以成功获取到PE文件的图标,那么我们怎么知道它是不是伪装成一些常见软件的图标呢?我们通过肉眼很容易分辨他是不是伪装成常用软件的图标了,但对于程序来说,可能就没那么容易了。通过分析我们人的思维过程:首先我们已经熟知了一些常见软件的图标,然后再与当前PE文件的图标进行对比,如果两者十分相似就认为当前PE文件是伪装成了常用软件的图标。与之类比,我们的程序也分为两个步骤,
第一步,提取并存储一些常见软件的图标。
第二步,将当前PE文件的图标与常见软件图标进行相似度对比,如果相似度较高,则认为当前PE文件伪装成了常用软件的图标。
首先,我们开始提取并存储一些常见软件的图标,最常见的图标是windows系统自带的图标,这些图标我们应该怎么获取呢?这些图标全部都存在一个文件中,那就是shell32.dll,所以我们只需要将这个dll文件的所有图标提出来即可获得系统自带的图标。之后可再自行搜集一些可能会被伪装的软件图标。
第二步,将两个图片进行对比,计算两者的相似度。有很多算法可以计算两者相似度,网上也有很多教程,这里我们采用将图像的相似度计算转化为直方图的距离计算的算法:
from PIL import Image
# 转换图片的大小,并使用convert()方法将其转为RGB颜色模式,
# 常见的modes 有 “L” (luminance) 表示灰度图像, “RGB” 表示真彩色图像, and “CMYK” 表示出版图像。
def make_regalur_image(img, size = (64, 64)):
return img.resize(size).convert('RGB')
def hist_similar(lh, rh):
assert len(lh) == len(rh)
print sum(1 - (0 if l == r else float(abs(l - r))/max(l, r)) for l, r in zip(lh, rh))/len(lh)
def calc_similar(li, ri):
return hist_similar(li.histogram(), ri.histogram())
def calc_similar_by_path(lf, rf):
li, ri = make_regalur_image(Image.open(lf)), make_regalur_image(Image.open(rf))
return calc_similar(li, ri)
if __name__ == '__main__':
calc_similar_by_path('1.JPG','2.JPG')
首先,我们有必要把所有的图片都统一到特别的规格,在这里我选择是的64*64的分辨率,之后计算两者直方图的距离。具体的原理可查阅参考文献4。
通过这种方式我们即可得到两个图标的相似度,经过小规模测试,假如一个PE文件图标伪装成了常用软件的图标,两者相似度为100%。
经过测试,这种方法的速度不够快,因为涉及大量的IO操作,所以,为了能够大批量提取样本,对于这种IO密集型的操作,非常适合采用多线程的方法。通过使用Python的threading模块和Queue模块即可完成此任务,同时,我们通过使用生产者消费者模型,利用生产者不断的生成要提取的PE文件路径,将其放入队列中,使用消费者从队列中不断的取出PE文件路径,再利用上述方法提取图标,计算相似度,进而实现多线程加速。
参考资料:
msdn文档
windows黑客编程技术详解
https://www.codeproject.com/Articles/9303/Get-icons-from-Exe-or-DLL-the-PE-way
https://blog.youkuaiyun.com/gumanren/article/details/83810210
https://blog.youkuaiyun.com/gzlaiyonghao/article/details/2325027?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-9&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-9