MEMZ 这个上古毒物我两三年前就下载了它的样本,然后把它扔到硬盘的某个角落。今天重新给它翻出来,看看它的源代码到底是怎么样的。
首先 ,样本: MEMZ_virus.zip (密码:MEMZ!virus
工具:IDA Pro v7.0
IDA7.0.zip
(Index不想写w
解压,发现目录里有两个文件,一个.bat
,另一个.exe
很明显.exe
就是病毒样本,那么.bat
什么用的?
打开
可以看到,为了逃避检测,它还特地把执行内容写到js
里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//获取组件 f=new ActiveXObject("Scripting.FileSystemObject"); //打开文件x i=f.getFile("x").openAsTextStream(); //创建base64解密对象 x=new ActiveXObject("MSXml2.DOMDocument").createElement("Base64Data"); x.dataType="bin.base64"; //读取x中所有数据,并解密 x.text=i.readAll(); o=new ActiveXObject("ADODB.Stream"); o.type=1; o.open(); o.write(x.nodeTypedValue); //将解密内容写入z.zip z=f.getAbsolutePathName("z.zip"); o.saveToFile(z); s=new ActiveXObject("Shell.Application"); //解压z.zip得到MEMZ.zip s.namespace(26).copyHere(s.namespace(z).items()); o.close(); i.close(); |
很明显这个bat是用来生成exe病毒文件的。
就一个base64
加密竟然直接绕过了360????
直接用IDA
打开MEMZ.exe
,分析源代码
程序出口点为start
函数,跟进
然后直接一个 F5 下去
观察一下函数的流程。
首先获取了程序的启动参数,类似于int main(int argc,char**argv)
,只不过用Windows API GetCommandLine()
的方式获取。
然后再判断参数是否存在,存在的话再判断是不是为/watchdog
,是的话创建一个新线程执行sub_40114A()
创建完新线程后,调用了RegisterClassEx
注册窗口和CreateWindowEx
新建窗口。
但是这里的参数有点奇怪
RegisterClassEx
的定义
ATOM WINAPI RegisterClassEx(
_In_ const WNDCLASSEX *lpwcx
);
参数应该为WNDCLASSEX*
而不是SHELLEXECUTEINFO*
有可能是IDA
反编译出错,也有可能是作者的混淆(可能性更大)
总之,到MSDN
上找这两的定义
注意程序中的调用为RegisterClassExA((const WNDCLASSEXA *)&pExecInfo.lpVerb);
所以SHELLEXECUTEINFO.lpVerb
及以下与WNDCLASSEX.cbSize
及以下的参数一一对应。(项数也刚好相同,大小也相等sizeof(*void)=sizeof(int)=sizeof(DWORD)=4
)
分析一下参数就可以发现,它创建了一个类名为hax
的窗口,且此窗口的回调函数为sub_4010000
,其余什么也没有。
跟进此函数
在winuser.h
里面可以看到关于Message
系列常数定义的值
WM_CLOSE
对应窗口关闭请求,WM_ENDSESSION
对应关机时关闭窗口请求
且如果传给当前窗口的消息不为其中任一时,窗口信息转为DefWindowProc
处理。
否则调用函数sub_401021
跟进此函数
函数一开始就创建了 20 个线程执行同样这个函数,这样会变成无限创建线程,如果没有后续处理系统会卡死。
上面这个是我一开始的想法。其实它是错的。
这20个线程执行的不是这个函数的开头,而是一段IDA没能F5出来的代码,在这里
(意义不明,下图看得出堆栈也是平衡的,不知道为什么IDA错误
没有办法,我们直接对着汇编分析。
首先push esi
,意义不明。
然后调用GetCurrentThreadId
获取当前线程ID,保存在eax
中。
接着SetWindowHookEx(idHook=5,lpfn=offset_fn,hmod=0,dwId);
查阅MSDN
,
即所有的窗口事件将会被传送到fn
所指向的函数处理,跟进
再次查询MSDN
,code=3
即创建窗口事件
跟进函数sub_401A55
发现这是个生成随机数的函数。
观察原函数,生成的随机数都返回给了v4 (LPARAM)
,所以这段代码实现了随机设置此句柄上的窗口位置。
之后调用了MessageBox(hWnd=0,lpText,Caption="MEMZ",0x1010=MB_OK|MB_ICONERROR|MB_SYSTEMMODAL)
来显示对话框。
最后使用UnhookWindowHookEx
卸载之前的钩子。
MessageBox
中的内容lpText
则是随机以下内容
回到原函数,创建对话框后
首先获取(LoadLibrary
)ntdll.dll
中的RtlAdjustPrivilege
和NtRaiseHardError
函数,如果这两个函数都能成功获取的话,就先调用v4(RtlAdjustPrivilege)
,再调用v6(NtRaiseHardError)
但是如果有一获取不成功,就是用原始的方法提升至SeShutdownPrivilege
权限,最后调用ExitWindowsEx
强制关机。
RtlAdjustPrivilege
是一个MSDN
未公开的函数,因为它可以做到一行提权,完美替代以前使用传统的OpenProcessToken -> LookupPrivilegeValue -> AdjustTokenPrivileges
麻烦方法。
此函数定义NTSTATUS RtlAdjustPrivilege
(
ULONG Privilege, //Privilege [In] Privilege index to change.
BOOLEAN Enable, //Enable [In] If TRUE, then enable the privilege otherwise disable.
BOOLEAN CurrentThread, //CurrentThread [In] If TRUE, then enable in calling thread, otherwise process.
PBOOLEAN Enabled //
Enabled [Out] Whether privilege was previously enabled or disabled.
)
19 = 0x13 = SE_SHUTDOWN_PRIVILEGE
即关机权限
同样,NtRaiseHardError
也为ntdll.dll
中MSDN
未公开的函数,它的功能是引发一次蓝屏(与普通蓝屏不同,这个蓝屏一般由硬件错误引起,区别如图
)。
函数定义NTSYSAPI NTSTATUS NTAPI NtRaiseHardError
(
IN NTSTATUS ErrorStatus,
IN ULONG NumberOfParameters,
IN PUNICODE_STRING UnicodeStringParameterMask OPTIONAL,
IN PVOID *Parameters,
IN HARDERROR_RESPONSE_OPTION ResponseOption,
OUT PHARDERROR_RESPONSE Response
);
使用例子:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 |
typedef /*__success(return >= 0)*/ LONG NTSTATUS; typedef NTSTATUS *PNTSTATUS; #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) typedef struct _LSA_UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING; typedef enum _HARDERROR_RESPONSE_OPTION { OptionAbortRetryIgnore, OptionOk, OptionOkCancel, OptionRetryCancel, OptionYesNo, OptionYesNoCancel, OptionShutdownSystem } HARDERROR_RESPONSE_OPTION, *PHARDERROR_RESPONSE_OPTION; typedef enum _HARDERROR_RESPONSE { ResponseReturnToCaller, ResponseNotHandled, ResponseAbort, ResponseCancel, ResponseIgnore, ResponseNo, ResponseOk, ResponseRetry, ResponseYes } HARDERROR_RESPONSE, *PHARDERROR_RESPONSE; //获取函数地址....... int nEn = 0; RtlAdjustPrivilege(0x13, TRUE, FALSE, &nEn); HARDERROR_RESPONSE reResponse; NtRaiseHardError(0xC000021A,0,0,0,OptionShutdownSystem,&reResponse); |
(也可以参考:https://blog.csdn.net/AcceZn/article/details/54670776
即可引起一次0xC000021A蓝屏。(注:代码中NtRaiseHardError
的第一个参数为负数是因为signed int
的溢出)
别忘了,在创建窗口前还创建了一个线程执行函数sub_40114A()
继续跟进
这个函数流程是一目了然。
首先用GetProcessImageFileName
获取当前运行文件名,然后利用tlhelp32.h
里的CreateToolhelp32Snapshot
结合Process32First
和Process32Next
遍历所有进程,也获取这些进程的文件名,并寻找与当前进程名相等的数量。如果数量v4
比之前的数量v7
还少(说明有进程被杀死),直接调用函数sub_401021
,即之前的蓝屏/关机函数。
总结一下目前为止的流程:从程序启动开始,如果参数带有/watchdog
,则创建一个线程判断是否有当前病毒程序被杀死,有的话直接蓝屏/关机。之后创建一个窗口,如果检测到窗口被关闭或系统将要关闭,手动创建多个线程蓝屏/关机。
回到主程序,继续往下看流程。(注:此时还在有参数存在的条件内,即有参数但不为/watchdog
,因为在之前创建窗口后使用while
死循环获取并处理窗口消息。
连续调用CreateFile
和WriteFile
尝试向\\.\PhysicalDrive0
即0号磁盘设备写入扇区内容。中间那一大段就是获取写入内容,这一段就是覆盖系统MBR
的过程。
首先打开当前磁盘目录下的note.txt
,写入以下内容,完成后调用notepad
将其打开,即显示了这段信息。
之后循环创建线程执行函数sub_401A2B
然后进入while() Sleep
永久阻塞状态
中间这一段有点难分析,我们先看接下来的程序。(注:此时为无参数内
一堆警告信息。
都确认之后
首先通过ShellExecuteEx
创建5个带/watchdog
参数的程序(作用如上分析),再执行一个带/main
参数的程序,还SetPriorityClass (0x80u=HIGH_PRIORITY_CLASS)
设置最高优先级后本进程退出。
现在整个程序的流程都大概清楚了:先是用户点击,此时无参数,显示两个警告,都确认后创建5个/watch
进程和一个/main
进程并退出。/watchdog
进程只是检测是否有自己的进程被杀死或者要关机了,那个时候直接蓝屏/关机。/main
进程才是执行主要功能的进程,先是覆盖磁盘MBR
,然后开始搞事搞事(好像确实是真的搞事)。
回到刚刚参数/main
(现在已经知道是它了)执行的地方,现在来详细分析这段调用(循环创建线程执行函数sub_401A2B
)的功能。
跟进sub_401A2B()
发现这个函数只是简单地调用了以参数lpThreadParameter
传进来的函数,并且只要函数调用成功的话这个函数的两个参数都自加,10sec后又重新执行一遍。
所以问题的所在不是这个函数,而是参数v9
回到原函数,分析一下流程:首先v8=0; v9=(DWORD *)&off_405130
然后Sleep() v9
指向的空间的第二个DWORD
字节的数据大小。
接着CreateThread() v9
指向的空间第一个DWORD
指向的函数。
最后v9
越过两个DWORD
字节,并且阻塞10msec后继续。
看得出来,最重要的就是在off_405130
处的数据了。
函数都直接硬编码了在里面,我们一个一个分析。
来到sub_4014FC
(函数只有一个参数,我也不知道为什么
先是sub_401A55()
随机数到v2
,然后ShellExecute (&lpFile)[v2 % 0x2E]
,即随机打开以下文件
来到sub_40156D
先是GetCursorPos
获取指针位置,然后疯狂在一定范围内随机,最后SetCursorPos
,实现了鼠标不断抖动跟喝了脉动一样的效果。
来到sub_4017A5
也很明显,先是随机了一定范围内的数作为SendInput
的参数(1=INPUT_KEYBOARD
),且这个随机数作为可视ASCII
被模拟发送至键盘。('0'=48 '0'+42=90='Z'
)
来到sub_4016A0
随机了一个数作为PlaySound
的参数(&pszSound)[v1 % 3]
,即随机播放下列声音
来到sub_4015D4
先用GetDesktopWindow()
获取了顶层桌面的句柄,再用GetWindowDC()
获取了桌面的窗口设备上下文(DC),接着用GetWindowRect
获取桌面的大小,最后使用BitBlt()
绘制图形并ReleaseDC()
关闭写入。
BitBlt()
参数中的0x330008=NOTSRCCOPY
即“
Copies the inverted source rectangle to the destination.”(MSDN)
,就是实现了将整个桌面进行反色显示的功能。(一个F5刷新即可复原)
来到sub_40162A
发现创建了一个新线程执行sub_401994
,跟进
功能与之前分析的类似。
来到sub_401866
首先使用GetSystemMetrics()
获取ICO
支持大小,如下
然后同样的获取桌面DC
,鼠标位置。之后用LoadIcon()
读取ICO
图片,在鼠标位置上调用DrawIcon
绘制ICO
图片后,再随机生成位置绘制ICO
图片。(同样F5刷新可去除)
注:0x7F01=32513=IDI_ERROR 0x7F03=32515=IDI_EXCLAMATION
来到sub_401688
使用了EnumChildWindows()
获取桌面上所有子窗口,并且用EnumFunc()
函数回调,跟进。
使用GlobalAlloc()
分配堆空间,作为SendMessageTimeout()
的一参数,同时设置Msg
,超时时间为0x64=100msec
Msg=0xD
,开始先获取窗口中所有文本,存到v2
,然后将v2
代入sub_401AA0
,最后Msg=0xC
重新设置窗口的文本为处理后的v2
。跟进此函数。
对不起根本不想看,自己去试过之后知道这个函数作用是颠倒字符串,同STL
中reverse()
一样。
这个函数功能就是颠倒桌面子窗口中所有文本框里的字符串。
(注:这里的桌面不仅仅是explorer.exe
,还有其子进程,就是所有通过双击打开的程序。
来到sub_4017E9
StretchBlt()
和BitBlt()
的区别就是前者会根据Dst
放缩图像,后者只是单纯拷贝。
StretchBlt()
中参数0xCC0020=SRCCOPY
,即将整个桌面缩小50,50,100,100
实现了无限循环缩小屏幕的效果。(同样,F5刷新可复原)
来到sub_4016CD
生成一堆随机数,然后再一定范围内随机复制图像,造成混乱的效果(同样F5可去除)
至此所有的功能都分析完了。
总结: 双击打开病毒文件时,首先弹出俩警告框,点取消直接退出,点确定则创建五个参数/watchdog
和一个参数/main
的进程。/watchdog
进程检测是否有病毒进程被关闭或者将要关机,有的话弹出20个提示框,然后蓝屏/关机。/main
进程首先修改磁盘MBR
,然后用记事本弹出信息,最后循环执行打开程序/播放声音/反色屏幕/放图片/不断缩放屏幕/随意复制屏幕内容等操作并堵塞。
感觉除了改MBR
其它好像都不会怎么样
所以帮它把改MBR
去掉就安全了
源代码,要去掉CreateFile
并且不触发ExitProcess
首先把底下的jnz
改成jmp
然后。。。。。。。。。。。
再来到这里
同样把jnz
换成jmp
再然后。。。。。。。。。。。。
然后很完美
前面删的太爽一不小心把这个给删了
弄回去就好了
还有这个也要弄回去(手打就成这样了,不过不影响
然后写入
一份无毒无害安全的MEMZ.exe
就弄好了
。。。。。。虽然运行不正常
别了别了别这样换,重新替换一次,这次只修改jmp
以及call
以及push
调用
然后就成功的去掉了写MBR
的功能。
因为它实在是太安全了,以至于360都报毒了
修改版(去除写MBR
,安全无害,可以在真机上运行):memz_edit.zip(密码:memz!funny
(PS:从上面的源代码分析看得出来,其实只要taskkill /f /im MEMZ.exe
就好了,什么都不会发生
(可以直接写一个检测按键的程序,xx键按下while(1)if(GetAsyncKeyState(...))
直接全部关闭MEMZ.exe
,是完美安全的做法。
总感觉把写MBR
去了这程序乐趣就少了一半
所以我特地把MBR
及NC
代码提出来:dump.bin(如果只有mbr.bin是不够的
直接用WinHex
等工具覆盖写到虚拟机的磁盘偏移量0
就可以了。
为了方便写入,我还特地写了个小工具 WriteMBR.exe(源代码:WriteMBR.zip
直接把.bin
文件拖进去就可以了。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。本来弄到这里很开心很快乐
直到我看到了这个 https://github.com/Leurak/MEMZ
MMP*******************************************************************************************************************************
MD有源代码我还分析个**
。。。。。。。等等,其实这波不亏啊。
因为在Windows上配置编译环境比TM直接改汇编还难
好了,源代码有是有,但是我也不会改
只好自己琢磨这也弄个骚东西出来。
推荐下载地址: av_full.rar (无打包,多文件
(注:Win7以上可能无法运行! av_packed_full.rar (打包版本,单文件
(注:无压缩 av_packed_full.exe
(注:精简警告:可能运行不正常!!! av_packed.exe av_packed.rar
注:安全无毒警告! http://r.virscan.org/language/zh-cn/report/58c20ab6372e192bf370238cb13bbbdb
至于360sd………………………….大家都知道的。(QVM和KVM存在意义不明????)
声明:本程序并不会篡改任何系统文件(重启后就跟没运行过一样),可以放心的在真机上食用。
源代码: av_source.zip (build with VC6 on win7x64
特别鸣谢:6332812(虽然是自己拿来用的w
顺带BUG:XP上自行关掉静音,谢谢合作
还有我在Win7上写的,界面是这样的:
如果拿到XP上运行界面变成这样知道一下意思一下就行哈
NB
好文章,很清晰!
大佬我有个疑问,如何把jnz修改成jmp
qwq
不错不错!可惜无毒版MEMZ的链接好像没了,原本想玩一下
上次丢数据的时候给弄没了,只存了 av_source.zip 的备份。如果想玩的话,可以自己编译一份 MEMZ,或者闲着无聊也可以拿 IDA 啥的改一改ww
如何编译啊
qwq
用 VC++ 之类的编译源代码,剩下的资源文件根据说明生成打包就可以了。不过实际上可能会有点麻烦,自己弄编译环境的话可能不会比 IDA 里 NOP 几下快(
Ohhhhhhhhhh!
感谢您的用心,我是一只路过猫