前言:这个样本是腾讯的笔试题,很遗憾当时没做好寄了,后来重新对样本进行梳理了一边,并整理成报告的形式分析给大家,也是对这段时间所学的知识做一个总结(因此会有很多不必要的操作)。同时在网上还能找到同类型样本的分析(在报告后面给出链接了),因此报告有些地方写得不是很详细。总之,萌新入门,如有错误,大佬轻喷。
拿到样本第一步,先对样本进行简单快速的分析,判断其是不是恶意程序与病毒,然后才能进行深入分析,不然折腾半天发现是正常程序就尬住了。
我们看这个样本
第一眼看到这个样本,至少可以提取到三个关键信息:1、谷歌图标;2、管理员权限;3、exe可执行程序。
这3点显然与样本标题格格不入,很显然,这是样本作者诱惑用户点击该程序的一种社会工程学手段,对于一些未经培训或疏忽大意的用户,在邮箱或其他地方收到该样本后并不会注意到它的异常,而是直接运行,然后就中招了。
在有了第一个疑点后,我们进一步确定该样本是不是恶意程序。通常来说,要快速判断一个样本(exe类)是不是恶意程序有那么几种方法(如果样本作者水平比较高,那么可能这些方法都看不出了问题):查看数字签名、查看可疑字符串、查看导出表以及在虚拟机中运行等等。如果这几种方法都不能直接确定该样本是恶意程序,那么—就要凭经验或者进行动静分析了。
我们来看看该样本的数字签名,使用的工具是微软提供的sigcheck,可以在微软官方下载。
上面的是我们的样本,下面是一个正常的GoogleChromePortable程序,可以看到在不能签名无法验证之外,其他与正常的程序基本一致,因此我们可以猜测这是一个被修改过(或是被伪装成)的GoogleChromePortable程序。
下面来看看该样本的字符串。使用的依旧是微软提供的strings工具。为了方便,我将提取的字符串长度设置为10以上,同时将输入重定向为txt文件。
由于字符串太多了,我就不一一截图了。
可以看到该样本导入了大量对文件操作的函数,由于我们在上面的分析中已经基本确定该程序是恶意的了,因此该样本导入这些函数一般存在2种可能:一是直接入侵,对受害者的电脑进行文件拷贝等操作,二是该程序只是一个安装程序或病毒前哨站,对受害人电脑继续检查以及为接下来的入侵打好基础(伪装自身或是关闭安全程序等等)
我们来看看该样本在虚拟机中运行的表现。我所使用的是vm虚拟机,win10 x64版。将样本拖入虚拟机,并使用火绒剑监控样本行为。
可以看到样本运行时有着非常多对注册表进行的操作,以及调用了很多重要的dll程序,但奇怪的是,程序运行并没有明显现象,只是有一个一闪而过的窗口,并且该样本的行为好。像并没有什么异常操作,仅仅依靠这些操作并不能将其判断为恶意程序,可是我们在之前的分析中已经基本确定这是一个恶意程序了呀。
还记得我们在1.2.2中做出的分析,该样本导入的大量的文件相关函数,但并没有用上,显然样本作者不会将这些函数无意义的导入程序(如果是为了混淆,那么就不止这些函数了)。
当时我在分析的时候认为这是样本通过某种方式隐藏了自身,但后来仔细研究过后我才发现这其实是我自身的设置问题。我在虚拟机运行该样本的时候,没有注意的一个细节,就是火绒将该程序报毒了,我当时以为这个报毒对我们的分析没有影响。后来发现,火绒报毒后就会对病毒进行一定的处理,从而导致病毒不能完全执行,因此我们用火绒剑也就监控不到相关行为。
现在我们把火绒剑打开后,将火绒本体关闭,并使虚拟机连上网,再看看样本做了什么。现在可以看到样本对文件以及网络方面做出的一系列动作了。
根据这些行为,我们很容易看到该样本的恶意操作,如对系统重要文件进行修改,连接网络创建隐藏文件等等。
在这些行为中我们还可以发现比较重要的一部分:
可以看到,样本在最后创建了一个可执行文件,这里我们先按下不表。
我们所有的分析都是建立在无壳的基础上的,注意不是混淆。
根据提示我们得知该样本是一个用NSIS打包制成的安装系统,同时根据脱壳提示,我们用7z打开该样本,得到如下文件
查阅相关信息我们得知这些dll文件是NSIS的打包文件。根据文件名称我们很容易能猜到这些文件的功能,至于这些文件有什么用,我们先放在一边。
在2.1.1的分析中,我们知道该样本其实是一个安装程序,这也可以通过分析样本的行为得知:
如上图所示,我们可以猜出样本从网上下载样本本体,并将其安装在C:\Users\Public\Videos\0aJz4Vu下,因此我们的深入分析还得从样本本体入手。
下载下来的文件如下:
我们先看看这些文件的详细信息,打开dat文件以及exe文件的详细信息
我们可以看到dat文件经过加密,同时发现exe文件有很详细的文件信息,看到这里的时候我初步怀疑exe是一个正常的程序,使用sigcheck检查后发现这其实就是一个正常的exe程序。
通过前面几步的分析不难猜出这是一个经典“白加黑“类型的恶意程序,即通过将自身伪装成正常程序的一部分以骗过安全程序的检查。而我们要研究的恶意程序也就隐藏在.dat .jpg .xml这几个文件中了(仔细检查会发现两个dat文件和exe文件其实是同一个文件)。
在上面的分析中我们知道,真正的恶意代码就隐藏在edge.jpg、edge.xml、vVehX.dat这三个文件中。在这三个文件中,我们首先来看vVehX.dat这个文件(在前面的分析中,我们得出这应该是一种白加黑类型的恶意程序,因此首先怀疑的是与vVehX.exe同名的数据文件),打开这个文件的数据,直接就能看到这其实是一个压缩包文件(PK是zip压缩包的文件头)
我们将后缀改为zip并解压,很遗憾,压缩包加了密码,因此我们还要再找到密码
想要找到密码,首先要找到密码在哪。我们不妨来分析一下:首先密码大概率是硬编码在代码中的,因为本地的exe程序是先要解出恶意代码本体,然后执行恶意代码才能连接上远程服务器。至于为什么不是反着来的,大家可以自己思考一些。
既然密码是硬编码在代码中的,那么很明显,密码一定是在vVehX.exe中,而不是2023财会人员薪资补贴调整新政策所需材料.exe中的。那么接下来我们就从vVehX.exe中找到密码。
首先我们查壳
经典的UPX压缩壳,网上有很多现成的脱壳工具,我就不一一赘述了。
脱壳后我们使用ida打开,查找字符串,首先我们查找关键字key,如果能直接找到那就--,很遗憾,作者并没有这么傻,但是没关系,在我浏览字符串的时候我看到一个关键字:
DynaZIP UnZIP,这是一个c++的压缩包库。但是,如果你认为我要从这个库的压缩和解压算法入手,找到该程序调用这个库的地方,然后顺藤摸瓜找到关键函数,并在OllyDbg中对关键函数下断点,然后单步调试找到密码的话,那你就错了。因为我想到了一个更好的方法:既然密码是硬编码在代码中的,那我就可以将文件中的字符串提取出来,作为密码本,让程序帮我们一个一个试。(当然,如果你说作者可能将密码拆开存储,在使用的时候才将密码组合,那就只能老老实实用第一种方法咯)。但很幸运,我的方法成功了。
我写了一个简单的python程序,用于暴力破解密码,具体见下:
虽然代码还存在许多问题,但我还是成功解开并取得密码
解开压缩包内容如下
根据我们第一次拆包的结果,首先我们将目光锁定在_TUProj.dat文件上,理由请参考前面的分析。打开这个文件,我们可以看到在文件开始有一段代码,下面是很长的未知定义
观察代码,很明显能看到关键点,一段很长的shellcode代码,这段代码也不难理解,大体上就是调用dll创建内容,将shellcode复制到内存并执行。那么就不难猜到我们要分析的重点就是这段shellcode代码。
这里我给出两种解法来分析这段shellcode代码:1、将这段shellcode代码转为二进制的形式,拖入ida让ida识别并转为伪c代码。这种方法的好处是操作简单,ida转化后的伪c代码也很容易阅读,缺点是ida需要特定插件或全功能版才能识别我们转换后的二进制代码,并且我们只能静态分析代码,需要比较深厚的代码功底。如果你想使用这种方法,这里有一位大佬的文章,我看了应该是同一份样本,可以参考一下:TrueUpdate白加黑木马分析保姆级教程 - VxerLee昵称已被使用 - 博客园 (cnblogs.com)
2、很显然这段shellcode是可以执行的,并且就是这段代码的核心,虽然我们不知道上图的代码是用什么语言写的,并且没头没尾,但是这并不妨碍我们用c++(py也可以,但需要多一步打包工作)重构并实现这段代码,下面是我用c++重构的代码,核心依旧是执行shellcode,编译运行后我们就得到了exe文件,这样我们就可以在ollydbg中调试该shellcode代码了。
我们在ollydbg中调试该程序,虽然我们并不知道这段shellcode代码具体是什么,但是我们可以猜到这段shellcode并不是恶意程序的本体,虽然这段shellcode看着挺长的,但远远不到能完整执行一个恶意程序的程度。因此我们可以猜测恶意程序的本体隐藏在那几个jpg文件或xml文件。(为什么要分析这些,因为我们需要找到shellcode的突破点,而不是一步一步的调试代码。在分析完成后,你应该知道我们要怎么突破这段shellcode代码了)
我们在ollydbg中给程序下以下关于文件操作的断点,还有我最爱的内存申请函数VirtualAlloc
啪的一下,很快我们就找到了第一个关键字-edge.xml ,注意右下角的堆栈
在这里我们往下单步步过,很快又看到一个关键字-malloc函数
我们将malloc函数,我们将函数的返回值(EAX)即申请的内存空间地址右键在数据窗口中跟随,继续往下调试,或在该地址下硬件断点。很快我们找到了读取文件数据的操作,并且刚才的空间也填满了数据
继续往下调试,很快我们有看到了疑似解密的操作
执行完这段代码,并且后面没有在对这些数据进行解密的操作后,我们得到了edge.xml文件解密后的数据
很明显这是一个exe文件,我们将数据保存下来
我们在010editor中检查保存下来的文件,我们在文件尾部可以看到一堆00后面跟着一些无用数据,这应该是ollydbg保存超范围了,删掉即可。(我已经删掉了,就不给你们截图了)。
继续回到我们的调试,继续往下执行代码,很快我们又找到一个关键点
可以看到这是edge.jpg文件的读取,重复刚才的操作,不过要注意这里会有多次内存分配,并且执行完成后很快就将内存权限移交,如果不注意很容易错过。
可以看到地址从10000000一路分配到了1005C000,这是最后一次分配,在这里我们将地址为10000000的内容dump下来
这样我们就得到了edge.jpg的解密文件。我们的调试就到此为止(代码并未结束,因为程序会创建新的线程执行解密后的文件),在这次调试中我们成功得到两个解密文件,接下来就是对这两个文件进行解析。
在解出这两个文件后,我仔细思考了一下调试过程,我们在调试得到第一个文件(edge.xml)后,继续运行程序,程序停到的并不是下一个文件的解密位置,而是中间夹有线程创建函数。当时我并没有意识到问题,到现在需要分析这两个文件的时候我才意思到,恶意代码的本体应该只有一份,或者说你并不会将一个完整的功能拆成两个exe文件(一般情况下)。因此,根据我们的分析与调试过程,我们可以猜测shellcode的实际功能只有一个,即解密出第一个文件,然后创建新的线程运行第一个解密文件,而第一个解密文件的功能则是解密出第二个文件并执行。(这也是我们在解密第二个文件的时候会出现无法读取内存的原因,因为该内存已被移交给新的线程执行)
验证方法也很简单,我们可以在编写c++代码时添加一个弹窗提示我们shellcode代码已经执行完成,或者用ida查看代码。
但是我懒,所以就不验证了,并且我们分析的目的也不是要确定这个,而是猜测这两个文件的功能,便于我们的分析(如果没有先前分析或执行程序,直接用ida查看代码是很痛苦的)。
我们用ida打开edgexml文件(也可以用ollydbg进行调试,不过我们现在是要进行代码分析,显然ida更好)。
进入到DllMain函数,可以发现两个关键函数
打开这两个函数,可以看到类似于解密代码,具体的分析我就不给你们做了,代码太长了。
我们用ida打开edgejpg文件。查看导出表,可以看到edge函数(DllEntryPoint啥也没有)
点进来我们就看到了该恶意程序的真正核心了。
第一部分是对一些文件进行操作
第二部分是一些反杀与反调试的操作
还记着我们在开始时分析该样本时无法看到相关行为,当时我以为时火绒的问题,看来是我错怪火绒了,样本确实通过某种方式在隐藏自己,不过当我们关掉火绒仅仅打开火绒剑时很神奇的避开了样本的检查手段(有种屎山代码以一种奇怪方式运行起来的感觉)。
接下来是一些创建线程的操作,这应该是创建多个线程以执行多种入侵/攻击行为。
接下来我们可以看到,样本在这里连接上远程服务器,这与我们之前在火绒剑看到的样本行为一致。
请注意,我只是挑了一部分比较重要的展示出来,并没有进行全代码解析,如果大家感兴趣可以自行分析。
键盘监控 聊天程序监控 电脑文件监控
2.4.3 至此我们就完整得到恶意程序的所有核心,以及各种恶意行为。我们的深入分析也就到处为止。
在本次样本分析中,我们从快速分析入手,通过观察,字符串,数字签名等方式快速判断样本的安全性,然后我们通过查壳以及样本行为分析成功找到样本释放出的文件,再后来我们通过暴力拆解的方式成功解出第一层包,最后我们通过c++重构代码,模拟样本行为,成功使用ollydbg对恶意程序进行调试并解出恶意程序本体。
在分析过程中我在网上找到了同一家族的样本分析TrueUpdate白加黑木马分析保姆级教程 - VxerLee昵称已被使用 - 博客园 (cnblogs.com),虽然样本是一样的,但我还是想办法使用我的方法对样本进行分析,对比这位大佬的分析,我的方法有很多取巧的成分,或者说我的分析方法不需要过多的脑力消耗,因此我认为我的样本分析还是很有参考意义的。
最后给出样本分析类型:Trojan.Win32.Dllhijack.TrueUpdate
样本:链接:https://pan.baidu.com/s/1ipc4-FS2osbvvIjd3r7uZw?pwd=lnvk
提取码:lnvk
import
zipfile
import
os
import
time
def
extract_with_password(zip_file, password_file, output_dir):
os.makedirs(output_dir, exist_ok
=
True
)
with
open
(password_file,
'r'
, encoding
=
'UTF-8'
) as pass_file:
passwords
=
pass_file.readlines()
start_time
=
time.time()
attempt_count
=
0
for
password
in
passwords:
password
=
password.strip()
if
not
password:
continue
attempt_count
+
=
1
try
:
with zipfile.ZipFile(zip_file,
'r'
) as zip_ref:
zip_ref.extractall(output_dir, pwd
=
password.encode(
'UTF-8'
))
end_time
=
time.time()
duration
=
end_time
-
start_time
print
(f
"解压成功,使用密码: {password}"
)
print
(f
"解密尝试次数: {attempt_count}"
)
print
(f
"解密耗时: {duration:.2f} 秒"
)
break
except
zipfile.BadZipFile:
print
(
"错误:文件不是有效的zip文件"
)
break
except
RuntimeError as e:
if
"Bad password"
in
str
(e):
print
(f
"密码错误: {password}"
)
else
:
continue
decrypt_encrypted_files(zip_file, output_dir, password)
def
decrypt_encrypted_files(zip_file, output_dir, password):
with zipfile.ZipFile(zip_file,
'r'
) as zip_ref:
for
file_info
in
zip_ref.infolist():
if
file_info.flag_bits &
0x01
:
file_info.flag_bits ^
=
0x01
try
:
zip_ref.extract(file_info, output_dir, pwd
=
password.encode(
'UTF-8'
))
except
Exception as e:
continue
if
__name__
=
=
"__main__"
:
zip_file
=
"压缩包路径"
password_file
=
"密码本路径txt"
output_directory
=
"输出路径"
extract_with_password(zip_file,password_file,output_directory)
import
zipfile
import
os
import
time
def
extract_with_password(zip_file, password_file, output_dir):
os.makedirs(output_dir, exist_ok
=
True
)
with
open
(password_file,
'r'
, encoding
=
'UTF-8'
) as pass_file:
passwords
=
pass_file.readlines()
start_time
=
time.time()
attempt_count
=
0
for
password
in
passwords:
password
=
password.strip()
if
not
password:
continue
attempt_count
+
=
1
try
:
with zipfile.ZipFile(zip_file,
'r'
) as zip_ref:
zip_ref.extractall(output_dir, pwd
=
password.encode(
'UTF-8'
))
end_time
=
time.time()
duration
=
end_time
-
start_time
print
(f
"解压成功,使用密码: {password}"
)
print
(f
"解密尝试次数: {attempt_count}"
)
print
(f
"解密耗时: {duration:.2f} 秒"
)
break
except
zipfile.BadZipFile:
print
(
"错误:文件不是有效的zip文件"
)
break
except
RuntimeError as e:
if
"Bad password"
in
str
(e):
print
(f
"密码错误: {password}"
)
else
:
continue
decrypt_encrypted_files(zip_file, output_dir, password)
def
decrypt_encrypted_files(zip_file, output_dir, password):
with zipfile.ZipFile(zip_file,
'r'
) as zip_ref:
for
file_info
in
zip_ref.infolist():
if
file_info.flag_bits &
0x01
:
file_info.flag_bits ^
=
0x01
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2024-3-28 11:43
被AnnXMY编辑
,原因: 图片不清晰,更新图片