破解LanStar技术揭秘

来源:互联网 发布:c语言while循环的用法 编辑:程序博客网 时间:2024/05/17 02:28

破解LanStar技术揭秘


教你怎么爆LanStar菊花(嗯~我是绅士) By HingC

如果不想看技术的可以直接拉到最下面下载你懂的1.22

有新修正版V1请看你懂的1.22修正V1版通用于LanStar7.0 8.0 8.1

 

一些黑历史:

         上电脑课时和好同(基)学(友)Dota团战时老师突然来一句:”请同学们暂停一下” OMGWhat the fuck! fuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuck!!! 直接痿掉,然后就是各种队友骂。我们也只能眼睁睁看老师广播幻灯片。难道就没有什么办法了吗?

         有的同学很有反抗革(和)命(谐)精神,在上课前Ctrl+Alt+Del打开任务管理器,发现了罪魁祸首:进程LanClt.exe好~结束之。

 蓝色的小鸟,就是你了

果断结束进程之~好~点击确定

可是结果很令人失望
又有些人不甘心上下转转,发现了个好东西:

有个叫CtrlProcess.exe的进程,看名字嘛,就知道是控制进程的东东,好~先结束他,然后再结束LanClt.exe,耶!LanClt挂掉了,终于摆脱了控制了。之前我根据这个写了个脚本

可是我们殊不知这只是噩梦的开始,在教师的监控端能看见有家伙下线了:


呵呵~居然敢强行结束。老师接下来的话就是:“HingC同学~赶快给我连接上去,我给你3秒,不连上去就给我到外面上课,3…2…1…”同学们在老师的逼迫下又从新连接,乖乖上课了。事情就到这里结束了。记得当年我把这个小脚本发给了5个同学,然后就杯具了,想爆菊花反被老师爆菊。本文终。


































说好的绅士呢!!!

好正文开始

我这大绅士怎么能任人调戏呢,应该是我去**


首先呢,所谓知己知彼方能百战百胜,第一步Google一下看有没什么有用资料        


什么有用的都没有

那就我们自己动手,来观察一下我们的对手LanClt.exe。来到LanStar的安装目录下,发现了一大堆文件。


有一大堆exe,dll什么的,没有sys和drv文件,说明该程序的所有的操作均在ring3下完成,没有进入内核态,那么我破解也不需要rootkit了。再仔细看看,发现有个exe文件

好~打开看看


看到这里,我就想,这个东西应该是一个有关屏幕显示的模块,这是一个破解的关键,下文会提到,然后就在C:\WINDOWS\system32目录下发现了

好~复制保存起来。再用冰刃IceSword看看LanClt的加载模块


嗯~的确没有sys和drv文件,验证了我上面的猜想(不过后来分析发现ScreenX.dll会调用驱动,不过是和屏幕有关的,没多大关系)。又回想起在老师广播的时候呢,屏幕会同步老师的屏幕,而且鼠标和键盘都锁死了。锁死鼠标和键盘?在用户态下做到这一点因该是通过底层全局键盘和鼠标钩子实现的,打开我的一个消息钩子工具,把LanClt.exe加载的WH_KEYBOARD_LL钩子和WH_MOUSE_LL钩子卸载掉



那么这个时候LanClt就不能锁死我们的键盘鼠标了,然后开启广播。按一下Win键,没有拦截,然后在工具栏里发现了个窗口RenderWindow

广播的原理呢就是在这么一个窗口里绘制图像,可是他是强行置顶的并且是全屏幕的,也就是说你的屏幕还是被一个窗口覆盖,,没有最小化也不能关闭,这个时候你什么还是都干不了。于是我就想办法把这个窗口干掉,用SendMessage API给窗口发送消息,可是WM_CLOSE和WM_DESTROY都不起作用,然后试了下WM_HIDE好~窗口不见了,可是鼠标还是会跟着动。


那么在用户态下能模拟鼠标的也只有通过系统API实现,查查Google和MSDN发现有两个函数SetCursorPos和mouse_event。

都说羊毛出在羊身上,最直接的方法就是到LanClt和组件的文件中去找。进行系统逆向,看看相关组件的调用和导出。找到一开始说的ScreenX.dll它和屏幕广播有关,查看到处函数,发现在ScreenX.dll中导入了SetCursorPos。


那就直接进行API hook,采用消息钩子的方式进行远程注入,程序在接到特定消息后先经钩子函数处理,也就是要加载一个特定的DLL到进程内存中去

我写了个DLL,下面是API Inline Hook类

class APIHook{public:APIHook(void* pTGfunc,void* pRPfunc,SIZE_T sizeOfCode=5);~APIHook();BOOL hookFunc();  //Hook函数BOOL unHookFunc();//恢复Hook函数BOOL motifyFunc(unsigned char *lpBuffer,SIZE_T nSize);BOOL recoverFunc();void resetTarget(void* pTGfunc,void* pRPfunc);private:bool isHook;bool isMotify;BYTE *bakCode;void *pTargetFunc;void *pReplaceFunc;SIZE_T motifySize; SIZE_T maxnMotifySize;};APIHook::APIHook(void* pTGfunc,void* pRPfunc,SIZE_T sizeOfCode){pTargetFunc=pTGfunc,pReplaceFunc=pRPfunc;isHook=false,isMotify=false;bakCode=new(BYTE[sizeOfCode]);maxnMotifySize=sizeOfCode;if(pTargetFunc){ReadProcessMemory((void *)GetCurrentProcess(),\                                  pTargetFunc,\                                  bakCode,\                                  sizeOfCode,\                                  NULL);}//读取函数头保存以便恢复}APIHook::~APIHook(){delete bakCode;}BOOL APIHook::hookFunc(){if(!isHook&&pReplaceFunc&&pTargetFunc){BYTE AsmCode[5];  //改写函数头5字节AsmCode[0] = 0xE9;  //JMP指令,机械码为0xE9__asm{mov ecx,this;mov eax,[ecx].pReplaceFuncmov ebx,[ecx].pTargetFuncsub eax,ebxsub eax,5mov dword ptr[AsmCode+1],eax}  //JMP pReplaceFunc直接跳转到我们自己的函数里isHook=true;return WriteProcessMemory((void *)GetCurrentProcess(),\                                          pTargetFunc,\                                          AsmCode,\                                          5,\                                          NULL);                //将指令写入函数代码的内存中,头5字节改为JMP pReplaceFunc}return false;}BOOL APIHook::unHookFunc(){if(isHook&&pTargetFunc){isHook=false;return WriteProcessMemory((void *)GetCurrentProcess(),\pTargetFunc,\bakCode,\5,\NULL);//恢复hook}return false;}VOID WINAPI myMouse_event(  _In_  DWORD dwFlags,  _In_  DWORD dx,  _In_  DWORD dy,  _In_  DWORD dwData,  _In_  ULONG_PTR dwExtraInfo)  //我们自己的Mouse_event,一定要和mouse_event定义成一样,保证堆栈平衡{return;  //什么都不做直接退出}BOOL WINAPI mySetCursorPos(  _In_  int X,  _In_  int Y) //我们自己的SetCursorPos{return true;  //什么都不做直接退出}APIHook hookSetCursorPos(SetCursorPos,mySetCursorPos);  APIHook hookmouse_event(mouse_event,myMouse_event);

调用方法hookFunc();进行API inline hook

好~把代码写到DllMain的case DLL_PROCESS_ATTACH:里然后写个钩子函数

LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam){return CallNextHookEx(pHookCBT, nCode, wParam, lParam);  //什么都不做,直接传递消息}

调用的时候直接用SetWindowsHookEx函数加载GetMsgProc就可以了,然后在广播之后FindWindow(NULL,"RenderWindow") 然后SendMessage WM_HIDE就可以了。


但是我们不知道什么时候开启广播,虽说可以设定一个定时器,每隔一段时间FindWindow一次,如果返回非NULL则SendMessage WM_HIDE。但测试的时候发现在SendMessage发出消息后窗口隐藏后程序仍会在屏幕上绘制图像,而且这个方法很不稳定。

回到ScreenX.dll因为它和屏幕广播有关,查看导出函数:


发现了一个叫SX_Render_SetVisible的函数,看名字是设置RenderWindow的是否显示的,好~打开OllyDbg在SX_Render_SetVisible上按F2设下断点运行之。


得到反汇编代码如下

MOV EAX,DWORD PTR SS:[ESP+8]MOV ECX,DWORD PTR SS:[ESP+4]PUSH EAXPUSH ECXMOV ECX,4FEA68CALL 004ED1E0RETN

分析一下:堆栈[ESP+4] [ESP+8]是两个参数,这个函数将在MOV ECX,4FEA68(一个全局变量)后参赛传递给了地址为004ED1E0的一个未导出的函数。函数原型如下:

DWORD __cdecl SetVisible(  _In_ DWORD x,  _In_ DWORD y);

发现在广播前会调用该函数两次第一次是x=0;y=0第二次是x=0;y=1。在第二次调用后进入广播模式。其中在上图中把EAX寄存器改为0后继续运行发现不进入广播模式。结束广播时再调用一次,参数依然是x=0;y=0。由此我们推出第一次调用是检查并隐藏之前的广播窗口,第二次调用显示窗口。其中参数y是隐藏/显示标志参数。

那么就直接Hook掉SX_Render_SetVisible这个函数。为了保证程序正确运行我们只需修改SX_Render_SetVisible参数传递过程就可以了,数一数到MOV ECX,4FEA68一共10字节,那么将函数头10字节修改为

PUSH 0PUSH 0NOPNOPNOPNOPNOPNOP

两个PUSH 0一共4字节,用6个NOP指令(空指令)来填充剩余的内存。得到机械码如下:

BYTE AsmCode[]="\x6a\x00\x6a\x00\x90\x90\x90\x90\x90\x90";

写入机械码到函数头部内存的代码如下:

BOOL APIHook::motifyFunc(unsigned char *lpBuffer,SIZE_T nSize){if(isMotify){recoverFunc();}if(pTargetFunc&&!isMotify&&nSize<=maxnMotifySize){isMotify=true;motifySize=nSize;return WriteProcessMemory((void *)GetCurrentProcess(),\                                          pTargetFunc,\                                          lpBuffer,\                                          nSize,\                                          NULL);                  //将nSize字节的lpBuffer写入指定内存}return 0;}BOOL APIHook::recoverFunc(){if(isMotify&&pTargetFunc){return WriteProcessMemory((void *)GetCurrentProcess(),\                                          pTargetFunc,\                                          bakCode,\                                          motifySize,\                                          NULL);}return 0;}void APIHook::resetTarget(void* pTGfunc,void* pRPfunc){pTargetFunc=pTGfunc,pReplaceFunc=pRPfunc;ReadProcessMemory((void *)GetCurrentProcess(),\                          pTargetFunc,\                          bakCode,\                          maxnMotifySize,\                          NULL);}APIHook motifySetVisible(NULL,NULL,10);  //声明类void setMotifySetVisible()  //修改SetVisible函数{HINSTANCE SXDLLhandle;SXDLLhandle=GetModuleHandle("ScreenX.dll");//获得ScreenX.dll句柄if(SXDLLhandle){pSetVisible=(PSXSV)GetProcAddress(SXDLLhandle,"SX_Render_SetVisible");                //得到函数地址motifySetVisible.resetTarget((LPVOID)pSetVisible,NULL);motifySetVisible.motifyFunc((unsigned char*)\                    "\x6a\x00\x6a\x00\x90\x90\x90\x90\x90\x90",10); //修改之}}

如果我们自己想要切换进入广播模式(比如有老师走过来看的时候)只需要自己压入参数调用004ED1E0即可,代码如下:

DWORD __cdecl SetVisibleBak(  _In_ DWORD x,  _In_ DWORD y)  //切换广播模式,y=0隐藏,y=1显示{__asm //由于函数头字节被修改,因此要自己压入参数{push retAddr//压入返回EIPpush ypush x//压入x,y参数mov eax,pSetVisibleadd eax,0Ah  //+10字节,跳过函数头jmp eax  //jump to function!//SetVisible中调用的函数是__stdcall型的,不用平衡堆栈//可SetVisible却是__cdecl调用类型的,真令人纠结retAddr:}}

到这里我们已经掌握了LanStar的广播机制了,通过Hook SX_Render_SetVisible和SetCursorPos通过调用SetVisibleBak我们可以随意进出广播模式。


如果很不幸老师不广播了,而是关你机怎么办呢,这招经常用来对付那些下课后迟迟不肯离开的同学。


而且坑爹的是你按关闭键他就直接关你机了,这一点是绝对不能容忍的。让我来干掉他。说到关机,那么无非就是调用ExitWindowsEx这个函数,原型如下

BOOL WINAPI ExitWindowsEx(    _In_  UINT uFlags,    _In_  DWORD dwReason);

有两个参数uFlags, dwReason 8字节。返回非0代表成功,不管那么多,直接修改函数之

APIHook motifyExitWindowsEx(ExitWindowsEx,NULL,8);motifyExitWindowsEx.motifyFunc((unsigned char*)"\xb8\x01\x00\x00\x00\xc2\x08\x00",8);

其中BYTEAsmCode[]="\xb8\x01\x00\x00\x00\xc2\x08\x00";

是如下代码的机械码

MOV EAX 1  //返回1表成功RET 0x08    //返回并出栈2个参数


我想看看这个窗体的具体实现,于是便给LanClt.exe的所有调用ExitWindowsEx的地方下了断点:


然后来到这里:


发现是个case结构,那么就上下看看case其他情况看看有什么好东西,往上拉,不远处就发现了好东西:


是有关黑屏的代码,在教室机上可以按黑学生机的屏。然后就是这个样子:


这种事情太没节操了,果断屏蔽之,进入黑屏关键函数0x00401f50处

将其中一段汇编分析了一下,结果写在上图右边的注释里了,根据上图最后一句汇编RETN 0C,得出该函数有三个参数。原型如下

void SetBlackScreen(    _In_ UINT uFlags,    _In_ HWND hWnd,  //显示图片的资源号    _In DWORD unknow);

发现他先隐藏了任务栏再画窗口。那么不要紧我们只要把任务栏显示回来,然后隐藏黑屏窗口最后退出就可以了。

汇编代码如下:

MOV EBP,DWORD PTR DS:[411638]  //ShowWindowPUSH 0PUSH DWORD PTR DS:[ECX+20]CALL EBPPUSH 1PUSH DWORD PTR DS:[ ECX+28]CALL EBPRETN 0Ch

机械码:

BYTE AsmCode[]="\x8B\x2D\x38\x16\x41\x00\x6A\x00\xFF\x71\x20\xFF\xD5\x6A\x01\xFF\x71\x28\

xFF\xD5\xC2\x0C\x00";

这个函数0x00401f50是没有导出的函数,要进行Hook必须知道地址,虽然可以硬编码0x00401f50这个地址,但是系统不一样或发生重定位或系统堆栈随机化就不管用了,还会导致软件崩溃。我们可以根据函数头部的一些特征来寻找函数。下列是我从左边的内存视图中数出来的一些特征码

pSetBlackScreen+0 == 0x53

pSetBlackScreen+7 ==0x46

pSetBlackScreen+8 ==0x20

pSetBlackScreen+10 ==0xFF

pSetBlackScreen+13 ==0x16

pSetBlackScreen+18 ==0x28

pSetBlackScreen+22 ==0x84

pSetBlackScreen+23 ==0x94

pSetBlackScreen+25 ==0x00

根据这些特征扫描LanClt.exe的模块内存就能定位到函数指针pSetBlackScreen,代码如下:

HMODULE WINAPI getBlackScreenFunc()  //获得未导出函数SetBlackScreen地址{HINSTANCE lanHandle=GetModuleHandle("lanclt.exe");  //模块基址if(!lanHandle)return NULL;DWORD OldProtect;MODULEINFO lanMoudleInfo;VirtualProtectEx(GetCurrentProcess(), \                         lanHandle,\                         1,\                         PAGE_EXECUTE_READWRITE,\                         &OldProtect);GetModuleInformation(GetCurrentProcess(),\                             lanHandle,\                             &lanMoudleInfo,\                             sizeof(lanMoudleInfo));          //获取模块大小    BYTE *lanBaseAddr=(BYTE*)lanHandle;DWORD Offset=1;for(;Offset<lanMoudleInfo.SizeOfImage;Offset++)  //扫描特征码{if(*(lanBaseAddr+Offset+sizeof(BYTE)*0)==0x53\&&*(lanBaseAddr+Offset+sizeof(BYTE)*7)==0x46\&&*(lanBaseAddr+Offset+sizeof(BYTE)*8)==0x20\&&*(lanBaseAddr+Offset+sizeof(BYTE)*10)==0xFF\&&*(lanBaseAddr+Offset+sizeof(BYTE)*13)==0x16\&&*(lanBaseAddr+Offset+sizeof(BYTE)*22)==0x84\){break;  //发现函数}}VirtualProtectEx(GetCurrentProcess(),lanHandle,1,OldProtect,NULL);if(Offset<lanMoudleInfo.SizeOfImage-1){return (HMODULE)(lanBaseAddr+Offset);}else{return NULL;}}

得到地址后就进行修改:

void setMotifySetBlackScreen()  //修改SetBlackScreen函数{motifySetBlackScreen.resetTarget((LPVOID)getBlackScreenFunc(),NULL);BYTE AsmCode[]="\x8B\x2D\x38\x16\x41\x00\x6A\x00\\                 xFF\x71\x20\xFF\xD5\x6A\x01\xFF\x71\x28\xFF\xD5\xC2\x0C\x00";motifySetBlackScreen.motifyFunc(AsmCode,23);}


回到一开始说的键盘鼠标锁的问题上,不可能每次都依赖工具,然后用眼找出钩子然后卸载把,那么我们就来屏蔽LanClt的消息钩子安装,那么当然是Hook安装钩子的函数SetWindowsHookEx。使用已经写好的类很容易就实现了:

HHOOK WINAPI mySetWindowsHookEx(  _In_  int idHook,  _In_  HOOKPROC lpfn,  _In_  HINSTANCE hMod,  _In_  DWORD dwThreadId)  //我们自己的SetWindowsHookEx{TCHAR *curProcessName=new(TCHAR[128]);getProcessName(curProcessName);if ((idHook==WH_KEYBOARD_LL||idHook==WH_MOUSE_LL)&&inTargetProcess()){return 0;}else//正常调用{HHOOK result;//因为函数头字节被修改,函数头的push ebp mov;ebp,esp要自己实现__asm{ mov eax,dword ptr[dwThreadId] push eax  mov ecx,dword ptr[hMod] push ecx  mov edx,dword ptr[lpfn] push edx  mov eax,dword ptr[idHook] push eax//压入参数push retAddr//压入返回EIPpush ebpmov ebp,esp//函数头保存堆栈mov eax,SetWindowsHookExadd eax,05hjmp eax//jump to function!retAddr:mov result,eax}return result;}}APIHook hookSetWindowsHookEx(SetWindowsHookEx,mySetWindowsHookEx);
调用hookSetWindowsHookEx.hookFunc();即可


如果LanClt的钩子已经安装上去了这么办?我们知道在钩子函数中,最后若要将消息往下传递要调用CallNextHookEx该函数的第一个参数便是该钩子的句柄,那么这个句柄就一定以全局变量的方式存在一个地方。

我在mySetWindowsHookEx中将LanClt安装的WH_KEYBOARD_LL钩子和WH_MOUSE_LL钩子的函数地址用MessageBox弹出来得到0x00422560和0x00422860两个钩子函数地址,用OllyDbg犯汇编查看函数尾部的CallNextHookEx调用


同理得到钩子句柄存放在[0x430020]和[0x430018]中,这个地址不是绝对的正如上文所说,发生重定位和堆栈随机化就不管用了,绝对地址只存在在代码中,知道LanClt的基址为0x00400000,那么这两个的相对地址(RAV,relative virtual address)就是0x30020和0x30018。访问这两个变量时用基址+RAV即可。代码如下:

void unHookKBLLandMSLL() //卸载LanClt安装的键盘和鼠标钩子{HINSTANCE lanHandle=GetModuleHandle("lanclt.exe");  //获得基址if(!lanHandle)return ;DWORD *KBLL_Hook_Handle=(DWORD *)((DWORD)lanHandle+0x30020);          //[0x00430020]钩子句柄DWORD *MSLL_Hook_Handle=(DWORD *)((DWORD)lanHandle+0x30018);          //[0x00430018]钩子句柄if(*KBLL_Hook_Handle){UnhookWindowsHookEx((HHOOK)*KBLL_Hook_Handle);  //卸载之*KBLL_Hook_Handle=NULL;}if(*MSLL_Hook_Handle){UnhookWindowsHookEx((HHOOK)*MSLL_Hook_Handle);  //卸载之*MSLL_Hook_Handle=NULL;}return ;}

这里的处理有点不当,请看修正

另外我玩了一下教师端发现在远程信息里还有个远程结束进程的功能:


怎么能让别人远程结束本地进程呢,LanClt调用的是TerminateProcess来结束进程的,那么hook掉就可以了:

BOOL WINAPI myTerminateProcess(  _In_  HANDLE hProcess,  _In_  UINT uExitCode){return true; //什么都不做}APIHook hookTerminateProcess(TerminateProcess,myTerminateProcess);hookTerminateProcess.hookFunc();

到这里LanStar就被华丽的爆菊了,我们想怎么样就这么样,出入自由,不再受任何人的干扰,可以和基友来战个痛快。


还有一些具体的细节处理,线程通信和应用代码就看我的源码吧。

最后说说我所维护的一个叫“你懂的”软件,看了本文那软件干什么的你懂的。


之前是用VB6写的通过关闭LanStar特定的网络活动线程来屏蔽广播,通过判断LanClt的线程启动时间和切换数来进行屏蔽,效果很不好,判断逻辑略恶心。半年之后的今天我又重启了这个项目,制作了本人的最后一款“你懂的”是“你懂的1.22”,1.22之前的所有版本均是使用线程判断方式编写,很不稳定。如果要使用请用1.22,本软件不再进行发布但会修改Bug。

之前的版本由于原理的限制只能做到保持在线而不能接受广播,在教师机上看是这样的:


很明显广播中HingC2号机使用了旧版的“你懂的”,如果老师细心点的话你的菊花就不保了。最新1.22就没有这类问题,而且旧版的还不能接受文件传输。

话说接着别人的项目写就骂别人,接着自己的项目写就骂自己,的确是这样,半年后打开自己写的代码,第一感觉就是WTF,我写的都是些什么啊啊啊!


还有给我亲爱的电脑老师提醒一下,LanStar这款软件是06年的,太老了,还是换换吧,也请对本文所写的内容进行一些测试和分析,谢谢。


你懂的1.22下载

有新修正版本请看你懂的1.22修正版
如果程序有bug导致不测我表示抱歉

1.22及旧版源码下载


注:本文只是技术研究与讨论,没有任何恶意,不要拿来干坏事哦~I'm HingC