OGame公式
所有结果取’整数'
飞行时间
a) 前往战场废墟:
(10 + (35.000 / Prozent * 开根(5000 / 速度))) / (24 * 60 * 60)
b) 在小星系内:
(10 + (35.000 / 负载百分数* 开根((1.000.000 + 星球距离 * 5000) / 速度))) / (24 * 60 * 60)
所有结果取’整数'
飞行时间
a) 前往战场废墟:
(10 + (35.000 / Prozent * 开根(5000 / 速度))) / (24 * 60 * 60)
b) 在小星系内:
(10 + (35.000 / 负载百分数* 开根((1.000.000 + 星球距离 * 5000) / 速度))) / (24 * 60 * 60)
首先说明,这个办法要离线升级包。把这个解压到一个目录,比如:d:\nod32
首先:在IIS里新建站点或虚拟目录;比如要通过 http://192.168.1.1 更新,那就新建站点,主机头名就设为 192.168.1.1 ,主目录指向“d:\nod32”(此处的目录为病毒库目录),然后在目录安全性那里设置匿名用户,不然就不是免ID的了。IIS的设置高手们都会,这里就不多说了。
C盘根目录下的boot.ini(隐藏文件)可以使用ANSI控制码来控制启动菜单显示出彩色。您可以根据以下短短的代码来为自己设计一个彩色的Windows启动菜单。这也有助于让您理解ANSI控制码及其转义,当然彩色的菜单也非常具有个性。
黑洞是密度超大的星球,吸纳一切,光也逃不了.(现在有科学家分析,宇宙中不存在黑洞,这需要进一步的证明,但是我们在学术上可以存在不同的意见)
补注:在空间体积为无限小(可认为是0)而注入质量接近无限大的状况下,磁场无限强化的情况下黑洞真的还有实体存在吗?
或物质的最终结局不是化为能量而是成为无限的场?
有兴在这次x'con交流会上认识白远方兄弟,这是他很早前写的文章,提到了很多东西,保存一下。
作者:白远方 (ID: baiyuanfan, baiyuanfan@163.com, baiyuanfan@hotmail.com)
June 18, 2007
关键字:rootkit,反主动防御,网络监控,ring0,mcafee8.5i,KIS6,ZoneAlarm Pro,实用级产品测试
目录:
反主动防御rootkit的产生背景及其必要性
反网络访问主动防御
反API钩子进程行为主动防御
反系统Notify进程行为主动防御
绕过监控进入ring0安装驱动
实用级反主动防御rootkit的通用性问题
反主动防御rootkit的产生背景及其必要性
当前随着新型木马,病毒,间谍软件对网络安全的威胁日益加重,传统的特征查杀型的安全产品和简单的封包过滤型防火墙已不能有效保护用户,因此各大安全公司纷纷推出自己的主动防御型安全产品,例如卡巴斯基kis6,mcafee8.5i,ZoneAlarm Pro等,这些产品应对未知的病毒木马都有很好的效果,若非针对性的作过设计的木马和rootkit,根本无法穿越其高级别防御。因此,反主动防御技术,作为矛和盾的另一方,自然被渗透者们提上日程;由于主动防御安全产品的迅速普及,为了不使后门木马被弹框报警,具有反主动防御能力的rootkit成为了一种必然选择。
反网络访问主动防御
几乎现在每个防火墙都具有应用程序访问网络限制功能。一个未知的程序反弹连接到外网,或者是在本地监听端口,基本上都会引起报警。而且对系统进程的行为也有了比较严格的审查,原先的注射代码到winlogon等系统进程,在向外反弹连接的方法,很多主动防御软件都会阻止了。
很多防火墙的应用程序访问网络限制,都可以通过摘除tcpip.sys上面的过滤驱动,并还原tcpip.sys的Dispatch Routines来绕过。据称这是因为在ndis层次取得进程id不方便而导致的。但是如果在一个实用级的rootkit里应用此方法则是不智之举,因为存在部分防火墙,如ZoneAlarm,其ndis过滤层必须和tdi过滤层协同工作,才会放行网络连接。至于ndis层次的中间层驱动的摘除,和NDIS_OPEN_BLOCK的还原,则是一项不太可能完成的任务,因为无法从原始文件中读取的方法,获得NDIS_OPEN_BLOCK的原始值;即使能够成功恢复ndis钩子,也不能保证系统可以正常运行,很可能会出现各种不明症状。
到现在为止,绕过应用程序访问网络限制最好的选择,还是那两个:简单的一个,注射代码到一个ie进程,用它反弹连接出来,访问外网;复杂的选择则是应用内核驱动,如ndis hook/添加新的ndis protocol,来实现端口复用,或者使用tdi client driver反弹连接。已经有很多木马和rootkit使用前者,因其简单易行,在实际开发中工程量小,出现问题的可能性也少得多,产品成熟的时间代价也小。但是目前很多的主动防御已经注意到这一点,并且在程序行为监控中严密防范了其他程序对ie的感染行为。
如图,想要使用僵尸IE访问网络的木马被拦截
反API钩子进程行为主动防御
接下来是主动防御系统的很重要的一部分:进程行为监控。该部分主动防御软件一般通过两种解决方案来执行,一是API钩子,二是windows支持的notify routine。
大量的主动防御安全软件,如KIS6,ZoneAlarm Pro,使用API钩子来监控进程的危险行为。如注射远程线程,启动傀儡IE,加载驱动,注册服务,修改敏感系统注册表键值等。但是作为一个rootkit,完全绕过这些操作,基本上是不可能的;于是摆放在面前的任务,就是如何击败这种主动防御。
对于特定种类的监控,总是有特定的方法可以绕过。比如注射远程线程,如果常用的CreateRemoteThread被监控了,可以尝试采用Debug API, SetThreadContext的方法绕过,也可以尝试采用hook其ntdll!ZwYieldExecution等频繁调用的函数来装载自己的DLL模块。 注册表监控,我的朋友xyzreg曾经写过系列文章,提出了很多种方法,包括RegSaveKey, Hive编辑等方法绕过卡巴斯基的注册表监控,其Hive编辑的方法目前仍未能有任何主动防御系统拦截。
但是从一个通用型,为实战设计的实用型rootkit来说,采用这些特定的技术并不是一个非常好的选择;因为这些技术可以保证对付一个主动防御软件,却不能保证通用,甚至通用性很差。而且针对每一个可能被主动防御拦截的行为,都采用一套特定的绕过技术,从工程代价上来讲,太过巨大,开发耗时,等其成熟更是不知道要多少时间来测试和更改。因此我们需要的一个相对涵盖范围广,能够解决绝大多数主动防御技术的解决方案。
针对API钩子实现的进程行为监控,一个较好的通用解决方案就是卸载所有安全软件所安装的API钩子。为兼容性和稳定起见,几乎所有的安全软件在安装API钩子时都会选择hook SSDT表,例如KIS6,ZoneAlarm Pro。我们如果能够进入ring0,就可以使用一个驱动程序,读取系统文件ntoskrnl.exe/ntkrnlpa.exe/ntkrpamp.exe,从中提出我们所希望的SSDT表的原始函数地址,替换被安全软件hook的地址,用此方法可以通用性很好的解决绝大多数的API钩子实现的进程行为监控。不过此方法有一个前提,就是事先必须绕过监控进入ring0。关于如何实现此前提,请阅读第五部分,“绕过监控进入ring0安装驱动”。
如图,ZoneAlarm Pro更改了大量的SSDT函数地址来监控程序行为。
反系统Notify进程行为主动防御
部分主动防御安全软件不仅仅是用API钩子,同时使用了微软提供的Notify Routine,来监视进程的行为。使用该技术的安全软件不是太多,但是也不至于少到一个实用级别rootkit可以忽略的程度。
以下几个微软DDK函数,PsSetCreateProcessNotifyRoutine,PsSetCreateThreadNotifyRoutine,PsSetLoadImageNotifyRoutine,被用作支持主动防御软件监控新进程的建立,新线程的建立,和一个新的模块被加载。处理该种类型的防御不能简单的清空NotifyRoutine就完事,因为系统本身,还有一些第三方正常模块和驱动,可能添加和使用该链表。
解决方案,一是可以先将使用了该技术的主动防御系统的驱动程序模块做一个列表出来,然后遍历这三条链表,找出地址指向这些驱动模块的项,再将这些项删除脱链。但是这需要对大量主动防御系统的研究和测试,并且通用型也不好。第二种方法,由于Notify Routine的监控力度要远弱于API钩子,因此在纯ring3将程序做一些小的改动,就可以越过这种类型的监控。
另外还有几个SDK函数,可以提供对文件和注册表的更改的notify。不能排除也有部分主动防御软件使用了它们。例如国产的超级巡警(AST.exe),使用了RegNotifyChangeKeyValue,做了对注册表敏感键值修改的事后警告提示。如果仅仅使用了API钩子清除技术,那么在此时就会被AST报警。和以上介绍的三个内核notify类似的也是,有不少正常的notify在被使用,不分青红皂白的全部卸载,会导致系统异常。
因此可见,Notify类监控虽然使用的不多,但是其对付的难度和需要的工程量,比API监控还要大。
如图,已经处理了API钩子监控的rootkit仍然被notify方式的AST报警。
绕过监控进入ring0安装驱动
这部分是重中之重。由于几乎每个主动防御系统都会监控未知驱动的加载和试图进入ring0的举动, 而我们在第一,第二和第三部分绕过主动防御要做的处理,都必须需要ring0权限。因此监控进入ring0,是一个独立的话题,也是我们实现前三个部分需要的条件。
直接添加注册表项,ZwLoadDriver安装驱动,是几乎要被任何主动防御系统报警。必须要采用一些隐蔽的或者是为人不知的方法。总结目前已经公布出来的进入ring0的办法,
有以下几种:
感染文件,例如win32k.sys,添加自己的代码到里面,启动的时候就会被执行。这种方法的优点是简单易行,稳定度和兼容性很好。但是最大的缺点就是必须重新启动以后,才能进入ring0,这是一个产品级别的后门所不能容忍的。而且微软自己的系统文件保护容易绕过,mcafee和卡巴斯基的文件监控可就不是那么容易了。
利用物理内存对象,来写入自己的代码到内核,并添加调用门来执行。这个是最早被人提出的不用驱动进入ring0的办法。因为出来的时间太长了,所以有以下一些问题:更新的操作系统内核不支持,如2003SP1;很多的主动防御系统会拦截,例如KIS6。所以这个办法也不理想。
利用ZwSystemDebugControl。这个代码在国外有人放出来过,利用它写内存,挂钩NtVdmControl,进入ring0。此法缺陷在于老的windows2000不被支持,最新的windows2003sp1上也取消了这个函数的此能力。不过好处在于,这个方法用的人少,基本上没有主动防御会注意到它,并进行拦截。
利用ZwSetSystemInformation的SystemLoadAndCallImage功能号加载一个模块进入ring0。这个方法提出来比较久了,但是因为用的人少,仍未被主动防御软件所重视。用得少的原因是,它不好用。它只能加载一个普通的模块到内核并且调用,却不是加载一个驱动,因此没有一个DriverObject。这导致了非常多的麻烦。因为要想使用这个办法,必须先用这个办法安装一个简单的内核模块,再用这个模块添加调用门等方式,执行代码清除主动防御的监视驱动安装的钩子,安装一个正常的驱动,才能最终完成任务。而且这个方法似乎对windows2003sp1以上的系统也无效。
因此,要想有一个相对完美的进入ring0解决方案,最好是寻找别人不知道或者使用很少的方法,或者将上面的有缺陷的方法做一个综合,用多种方法通过判断情况来选择使用。我在这里有一个新的思路提供给大家,微软新公布了一部分文档,关于HotPatch的使用。HotPatch可以在执行中修改系统中存在的用户态公用dll的内容,甚至是修改内核模块的内容。具体代码和细节,在这里我不能多说。
要想开发一个好的反主动防御rootkit,绕过监控进入ring0是必不可少的,然而这部分也是使用不成熟技术最多的,最容易出现严重问题的部分。作为一个负责任的实用级产品,一定要对这个部分作做详细的测试,来保证自己的产品不会在某些特殊的环境,比如64位CPU运行32位系统,多核处理器,HyperThread处理器上面,出现故障或者蓝屏。
实用级反主动防御rootkit的通用性问题
前文已述,本文的宗旨在于讨论一种实用级别rootkit开发的可行性。因此,工程量的大小,需要投入的人力,时间和金钱,也是我们需要考虑的内容。必须要考虑更好的兼容性通用性,和工程上的开发代价和稳定成熟周期不能无限大。因此,对于部分新技术,例如BiosRootkit,VirtualMachine-Rootkit,本文不做讨论,因为那些都属于如果要想做稳定通用,工程代价非常大,以至于他们只拥有技术上面的讨论价值,而不具备作为一个产品开发的可选解决方案的可能性。至少是目前来看是如此。
每个主动防御软件的原理和构造都是不相同的,因此不可能指望有某一种方法,从工程上可以解决一个主动防御系统,就可以无需测试的,保证无误的解决其他系统。因为这个原因,开发一个成熟稳定的反主动防御rootkit,必然要在兼容各种主动防御的系统的通用性上面下大功夫。按照不同的主动防御系统,在程序里switch case,应该是非常必要的,尽管绝大多数反主动防御代码原理上可以通用。基本上,在测试程序通用型的时候,常用的主动防御软件,是每种都要安装一个并且仔细测试的。
以下举例说明,几个常用主动防御系统各自需要注意的特点,这都是笔者在实际开发中遇到的比较典型的例子。
Mcafee8.5,该主动防御软件在最大化功能时会禁止在系统目录下创建可执行文件,光这一点就会让几乎全部rootkit安装失败,若非针对它做了设计。在这个系统下面,也不可能使用感染文件的方法来进入ring0。
KIS6,该系统会自动列举运行的隐藏进程,并且弹框警告。因此在这系统下,不太可能把自己的进程隐藏。而且它列举隐藏进程的手段很底层,很难绕过。
ZoneAlarm Pro,该系统下,如果一个其它的进程启动IE并且访问网络,安全报警仍然会以该进程本身访问网络为准执行,另外还会弹框警告,除非将自己的僵尸IE进程的父进程更改,或者不用IE来反弹连接。
国产的瑞星,总体来说这个系统的主动防御弱于国外产品,但是它特殊在于,会对IE作出非常严格的限制,默认不允许IE装载任何非系统的dll。因此在这个系统下基本不可能利用IE反弹。
其他的特殊情况还有很多。作为一个成熟产品开发者,这些都是必须要考虑的。
感谢:VXK(郭宏硕), xyzreg(张翼)。
不想让ime显示默认的窗口,只想用它的转换和选字功能,看过拿铁游戏论坛上的一个兄弟的一些代码,修正了一些我认为的bug,加入了一组控制函数,使得程序中可以显示一些button,玩家可以不必用热键就能切换输入法、全角/半角,中/英文标点。
//不知道这个能不能解决缩进的问题
#pragma comment ( lib, "imm32.lib" )
#include <windows.h>
#include <imm.h>
class CIme{
bool g_bIme; //ime允许标志
char g_szCompStr[ MAX_PATH ]; //存储转换后的串
char g_szCompReadStr[ MAX_PATH ];//存储输入的串
char g_szCandList[ MAX_PATH ]; //存储整理成字符串选字表
int g_nImeCursor; //存储转换后的串中的光标位置
CANDIDATELIST *g_lpCandList; //存储标准的选字表
char g_szImeName[ 64 ]; //存储输入法的名字
bool g_bImeSharp; //全角标志
bool g_bImeSymbol; //中文标点标志
void ConvertCandList( CANDIDATELIST *pCandList, char *pszCandList ); //将选字表整理成串
public:
CIme() : g_lpCandList( NULL ){ DisableIme(); } //通过DisableIme初始化一些数据
~CIme()
{
DisableIme();
if( g_lpCandList )
{
GlobalFree( (HANDLE)g_lpCandList );
g_lpCandList = NULL;
}
}
//控制函数
void DisableIme(); //关闭并禁止输入法,如ime已经打开则关闭,此后玩家不能用热键呼出ime
void EnableIme(); //允许输入法,此后玩家可以用热键呼出ime
void NextIme(); //切换到下一种输入法,必须EnableIme后才有效
void SharpIme( HWND hWnd ); //切换全角/半角
void SymbolIme( HWND hWnd );//切换中/英文标点
//状态函数
char* GetImeName(); //得到输入法名字,如果当前是英文则返回NULL
bool IfImeSharp(); //是否全角
bool IfImeSymbol(); //是否中文标点
void GetImeInput( char **pszCompStr, char **pszCompReadStr, int *pnImeCursor, char **pszCandList );
//得到输入法状态,四个指针任意可为NULL则此状态不回返回
//在pszCompStr中返回转换后的串
//在pszCompReadStr中返回键盘直接输入的串
//在pnImeCursor中返回szCompStr的光标位置
//在pszCandList中返回选字表,每项之间以\t分隔
//必须在消息中调用的函数,如果返回是true,则窗口函数应直接返回0,否则应传递给DefWindowProc
bool OnWM_INPUTLANGCHANGEREQUEST();
bool OnWM_INPUTLANGCHANGE( HWND hWnd );
bool OnWM_IME_SETCONTEXT(){ return true; }
bool OnWM_IME_STARTCOMPOSITION(){ return true; }
bool OnWM_IME_ENDCOMPOSITION(){ return true; }
bool OnWM_IME_NOTIFY( HWND hWnd, WPARAM wParam );
bool OnWM_IME_COMPOSITION( HWND hWnd, LPARAM lParam );
};
void CIme::DisableIme()
{
while( ImmIsIME( GetKeyboardLayout( 0 )))
ActivateKeyboardLayout(( HKL )HKL_NEXT, 0 );//如果ime打开通过循环切换到下一个关闭
g_bIme = false;
g_szCompStr[ 0 ] = 0;
g_szCompReadStr[ 0 ] = 0;
g_nImeCursor = 0;
g_szImeName[ 0 ] = 0;
g_szCandList[ 0 ] = 0;
}
void CIme::EnableIme()
{
g_bIme = true;
}
void CIme::NextIme()
{
if( !g_bIme )
return;
ActivateKeyboardLayout(( HKL )HKL_NEXT, 0 );
}
void CIme::SharpIme( HWND hWnd )
{
ImmSimulateHotKey( hWnd, IME_CHOTKEY_SHAPE_TOGGLE );
}
void CIme::SymbolIme( HWND hWnd )
{
ImmSimulateHotKey( hWnd, IME_CHOTKEY_SYMBOL_TOGGLE );
}
void CIme::ConvertCandList( CANDIDATELIST *pCandList, char *pszCandList ) //转换CandidateList到一个串,\t分隔每一项
{
unsigned int i;
if( pCandList->dwCount < pCandList->dwSelection )
{
pszCandList[ 0 ] = 0;
return;
}
//待选字序号超出总数,微软拼音第二次到选字表最后一页后再按PageDown会出现这种情况,并且会退出选字状态,开始一个新的输入
//但微软拼音自己的ime窗口可以解决这个问题,估计微软拼音实现了更多的接口,所以使用了这种不太标准的数据
//我现在无法解决这个问题,而且实际使用中也很少遇到这种事,而且其它标准输入法不会引起这种bug
//非标准输入法估计实现的接口比较少,所以应该也不会引起这种bug
for( i = 0; ( i < pCandList->dwCount - pCandList->dwSelection )&&( i < pCandList->dwPageSize ); i++ )
{
*pszCandList++ = ( i % 10 != 9 )? i % 10 + '1' : '0';//每项对应的数字键
*pszCandList++ = '.';//用'.'分隔
strcpy( pszCandList, (char*)pCandList + pCandList->dwOffset[ pCandList->dwSelection + i ] );//每项实际的内容
pszCandList += strlen( pszCandList );
*pszCandList++ = '\t';//项之间以'\t'分隔
}
*( pszCandList - 1 )= 0;//串尾,并覆盖最后一个'\t'
}
bool CIme::OnWM_INPUTLANGCHANGEREQUEST()
{
return !g_bIme;//如果禁止ime则返回false,此时窗口函数应返回0,否则DefWindowProc会打开输入法
}
bool CIme::OnWM_INPUTLANGCHANGE( HWND hWnd ) //ime改变
{
HKL hKL = GetKeyboardLayout( 0 );
if( ImmIsIME( hKL ))
{
HIMC hIMC = ImmGetContext( hWnd );
ImmEscape( hKL, hIMC, IME_ESC_IME_NAME, g_szImeName );//取得新输入法名字
DWORD dwConversion, dwSentence;
ImmGetConversionStatus( hIMC, &dwConversion, &dwSentence );
g_bImeSharp = ( dwConversion & IME_CMODE_FULLSHAPE )? true : false;//取得全角标志
g_bImeSymbol = ( dwConversion & IME_CMODE_SYMBOL )? true : false;//取得中文标点标志
ImmReleaseContext( hWnd, hIMC );
}
else//英文输入
g_szImeName[ 0 ] = 0;
return false;//总是返回false,因为需要窗口函数调用DefWindowProc继续处理
}
bool CIme::OnWM_IME_NOTIFY( HWND hWnd, WPARAM wParam )
{
HIMC hIMC;
DWORD dwSize;
DWORD dwConversion, dwSentence;
switch( wParam )
{
case IMN_SETCONVERSIONMODE://全角/半角,中/英文标点改变
hIMC = ImmGetContext( hWnd );
ImmGetConversionStatus( hIMC, &dwConversion, &dwSentence );
g_bImeSharp = ( dwConversion & IME_CMODE_FULLSHAPE )? true : false;
g_bImeSymbol = ( dwConversion & IME_CMODE_SYMBOL )? true : false;
ImmReleaseContext( hWnd, hIMC );
break;
case IMN_OPENCANDIDATE://进入选字状态
case IMN_CHANGECANDIDATE://选字表翻页
hIMC = ImmGetContext( hWnd );
if( g_lpCandList )
{
GlobalFree( (HANDLE)g_lpCandList );
g_lpCandList = NULL;
} //释放以前的选字表
if( dwSize = ImmGetCandidateList( hIMC, 0, NULL, 0 ))
{
g_lpCandList = (LPCANDIDATELIST)GlobalAlloc( GPTR, dwSize );
if( g_lpCandList )
ImmGetCandidateList( hIMC, 0, g_lpCandList, dwSize );
} //得到新的选字表
ImmReleaseContext( hWnd, hIMC );
if( g_lpCandList )ConvertCandList( g_lpCandList, g_szCandList );//选字表整理成串
break;
case IMN_CLOSECANDIDATE://关闭选字表
if( g_lpCandList )
{
GlobalFree( (HANDLE)g_lpCandList );
g_lpCandList = NULL;
}//释放
g_szCandList[ 0 ] = 0;
break;
}
return true;//总是返回true,防止ime窗口打开
}
bool CIme::OnWM_IME_COMPOSITION( HWND hWnd, LPARAM lParam ) //输入改变
{
HIMC hIMC;
DWORD dwSize;
hIMC = ImmGetContext( hWnd );
if( lParam & GCS_COMPSTR )
{
dwSize = ImmGetCompositionString( hIMC, GCS_COMPSTR, (void*)g_szCompStr, sizeof( g_szCompStr ));
g_szCompStr[ dwSize ] = 0;
}//取得szCompStr
if( lParam & GCS_COMPREADSTR )
{
dwSize = ImmGetCompositionString( hIMC, GCS_COMPREADSTR, (void*)g_szCompReadStr, sizeof( g_szCompReadStr ));
g_szCompReadStr[ dwSize ] = 0;
}//取得szCompReadStr
if( lParam & GCS_CURSORPOS )
{
g_nImeCursor = 0xffff & ImmGetCompositionString( hIMC, GCS_CURSORPOS, NULL, 0 );
}//取得nImeCursor
if( lParam & GCS_RESULTSTR )
{
unsigned char str[ MAX_PATH ];
dwSize = ImmGetCompositionString( hIMC, GCS_RESULTSTR, (void*)str, sizeof( str ));//取得汉字输入串
str[ dwSize ] = 0;
unsigned char *p = str;
while( *p )PostMessage( hWnd, WM_CHAR, (WPARAM)(*p++), 1 );//转成WM_CHAR消息
}
ImmReleaseContext( hWnd, hIMC );
return true;//总是返回true,防止ime窗口打开
}
char* CIme::GetImeName()
{
return g_szImeName[ 0 ]? g_szImeName : NULL;
}
bool CIme::IfImeSharp() //是否全角
{
return g_bImeSharp;
}
bool CIme::IfImeSymbol() //是否中文标点
{
return g_bImeSymbol;
}
void CIme::GetImeInput( char **pszCompStr, char **pszCompReadStr, int *pnImeCursor, char **pszCandList )
{
if( pszCompStr )
*pszCompStr = g_szCompStr;
if( pszCompReadStr )
*pszCompReadStr = g_szCompReadStr;
if( pnImeCursor )
*pnImeCursor = g_nImeCursor;
if( pszCandList )
*pszCandList = g_szCandList;
}
//由于微软拼音实现了很多自己的东西,CIme和它的兼容性有些问题
//1、在函数ConvertCandList中所说的选字表的问题
//2、函数GetImeInput返回的szCompReadStr显然经过了加工而不是最初的键盘输入
// 它的每个可组合的输入占以空格补足的8byte,且新的不可组合的输入存为0xa1
// 我们可以在输入法名字中有子串"微软拼音"时,只显示末尾的一组8byte,如果有0xa1就什么都不显示,也可以直接用TextOut显示所有的
//******************************************************************//
//
// 做这个东西的时候得到了论坛上网友的热心帮助,整理之后再送给大家
// 这是个在DX程序下使用系统输入法的解决方案,可能有little bug :-)
// 不过还没发现,如果发现了,告诉我啊 jerrywang@163.net
// 程序中使用的CHK()CHKB()是为了监测内存泄漏,可以去掉,CTTFFONT
// 为显示信息用,可以用其他方法替换如 TxtOut() 等
//
//******************************************************************//
//////////////////////////////////////////////////////////////////////
//
// IM.h: CIM class (使用系统)输入法类
// 2001/4/30 Write by Jerry Wang
// 感谢大大鱼的帮助
// Need Lib: imm32.lib
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_IM_H__6887B165_972D_4D17_8A75_FE07930CE59C__INCLUDED_)
#define AFX_IM_H__6887B165_972D_4D17_8A75_FE07930CE59C__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define _CIM_MAXINPUTCHARNUMBER 24 //最多输入的字节数
#include "FindMe.h"
class CIM
{
private:
CTTFFont ttffont; //显示信息
LPCANDIDATELIST m_lpCandList; //输入法候选文字列表
LPSTR m_lpszImeInput; //指向IME输入的文字字符串指针
LPSTR m_lpszCurInputLanguageDesc; //指向当前输入语言描述的指针
char m_cCandidateList[255]; //候选文字列表缓冲区
char m_cInput[64]; //输入的字母
BOOL CandidateToString( LPCANDIDATELIST lpCandidateList ); //转换候选文字列表到字符串
public:
CIM();
virtual ~CIM();
LPSTR GetResultString( void ); //取得输入的字符串
void UpdateShow( ); //显示输入法信息
LPSTR GetCurInputLanguageDesc( ); //取得应用程序当前使用语言的描述
void ClearCandidateList( void ); //清除输入法文字候选列表
BOOL GetCandidateList( HWND hWnd ); //取得输入法文字候选列表
BOOL ImeIsOpen( void ); //输入法是否打开
void OnChar( TCHAR ch ); //处理WM_IME_CHAR消息
void OnImeNotify( HWND hWnd,WPARAM wParam ); //处理WM_IME_NOTIFY消息
void OnImeComposition( HWND hWnd, LPARAM lParam ); //处理WM_IME_COMPOSITION消息
};
#endif // !defined(AFX_IM_H__6887B165_972D_4D17_8A75_FE07930CE59C__INCLUDED_)
//*********************************************************************************************//
//////////////////////////////////////////////////////////////////////
//
// IM.cpp: CIM class (使用系统)输入法类
// 2001/4/30 Write by Jerry Wang
// 感谢大大鱼的帮助
// Need Lib: imm32.lib
//
//////////////////////////////////////////////////////////////////////
#include "imm.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CIM::CIM()
{
m_lpszImeInput = new char[_CIM_MAXINPUTCHARNUMBER];
ZeroMemory( m_lpszImeInput,_CIM_MAXINPUTCHARNUMBER );
*m_lpszImeInput = '\0';
*m_cInput = '\0';
ttffont.Create( "黑体",15,RGB(255,255,255) );
GetCurInputLanguageDesc();
}
CIM::~CIM()
{
ZeroMemory( m_lpszImeInput,_CIM_MAXINPUTCHARNUMBER );
CHKB( delete m_lpszImeInput );
if( m_lpszCurInputLanguageDesc != NULL )
CHKB( delete m_lpszCurInputLanguageDesc );
}
void CIM::OnImeComposition(HWND hWnd, LPARAM lParam)
{
if (lParam & GCS_RESULTSTR)
{
HIMC hIMC; //输入设备上下文
DWORD dwLen;
LPSTR lpResultStr;
hIMC = ImmGetContext(hWnd); //取得输入上下文
if (!hIMC)
return;
dwLen = ImmGetCompositionString(hIMC,GCS_RESULTSTR,NULL,0L);
dwLen+=1;
if(dwLen)
{
lpResultStr = new char[ dwLen ];
//// 缓冲区已经满了
if( strlen( m_lpszImeInput ) + dwLen > _CIM_MAXINPUTCHARNUMBER - 2 )
{
MessageBeep( 0 );
return;
}
ZeroMemory( lpResultStr ,dwLen );
if (lpResultStr)
{
ImmGetCompositionString(hIMC,GCS_RESULTSTR, lpResultStr,dwLen);
strcat( m_lpszImeInput,lpResultStr );
}
delete lpResultStr;
}
ImmReleaseContext(hWnd,hIMC); //释放输入上下文
}
}
void CIM::OnImeNotify(HWND hWnd, WPARAM wParam)
{
DWORD dwCommand = (DWORD) wParam;
switch( dwCommand )
{
case IMN_CHANGECANDIDATE:
GetCandidateList( hWnd );
break;
case IMN_CLOSECANDIDATE:
ClearCandidateList();
break;
case IMN_OPENCANDIDATE:
GetCandidateList( hWnd );
break;
}
}
void CIM::OnChar( TCHAR ch )
{
int len = strlen( m_lpszImeInput );
if( ImeIsOpen() ) //输入法打开状态
{
}
else //输入法关闭状态
{
if( ch >= 32 && ch < 128 && len < _CIM_MAXINPUTCHARNUMBER - 1 ) //输入的是英文字母
{
*( m_lpszImeInput + len ) = ch;
*( m_lpszImeInput + len + 1) = '\0';
}
}
if( ch == 8 ) //BackSpace字符
{
if( len == 0 ) //字符串长度为零
return;
if( len == 1 ) //只有一个字符
{
*m_lpszImeInput = '\0';
return;
}
BYTE cc1,cc2;
cc1 = *(m_lpszImeInput + len -1); //分离字节
cc2 = *(m_lpszImeInput + len -2);
if( cc1 > 0xA0 && cc2 > 0xA0 ) //中文双字节的每个字节都>0xA0
*( m_lpszImeInput + len -2 ) = '\0';
else //是英文字符(单字节)
*( m_lpszImeInput + len -1 ) = '\0';
}
}
BOOL CIM::GetCandidateList(HWND hWnd)
{
DWORD dwSize;
HIMC hIMC;
ZeroMemory( m_cCandidateList,sizeof(m_cCandidateList) );
if( m_lpCandList )
{
delete m_lpCandList;
m_lpCandList = NULL;
}
if( GetKeyboardLayout(0)==0 )
{
return FALSE;
}
hIMC = ImmGetContext(hWnd); //取得输入上下文
if(hIMC == NULL)
{
return FALSE;
}
ZeroMemory( m_cCandidateList,sizeof(m_cCandidateList) );
if(dwSize = ImmGetCandidateList(hIMC,0x0,NULL,0))
{
m_lpCandList = (LPCANDIDATELIST)new char[dwSize];
if(m_lpCandList)
{
ImmGetCandidateList(hIMC,0x0,m_lpCandList,dwSize);
CandidateToString(m_lpCandList);
}
}
ImmReleaseContext(hWnd,hIMC);
return TRUE;
}
void CIM::ClearCandidateList()
{
if(m_lpCandList)
{
delete m_lpCandList;
m_lpCandList = NULL;
}
ZeroMemory( m_cCandidateList,sizeof( m_cCandidateList ) );
}
LPSTR CIM::GetCurInputLanguageDesc()
{
HKL hKL = GetKeyboardLayout(0);
if( m_lpszCurInputLanguageDesc != NULL )
CHKB( delete m_lpszCurInputLanguageDesc ); //删除先 ^o^
int lengh = ImmGetDescription(hKL,NULL,0); //取得描述的长度
CHK( m_lpszCurInputLanguageDesc = new char[ lengh ] );
if( lengh )
{
ImmGetDescription(hKL,m_lpszCurInputLanguageDesc,lengh);
}
else
{
strcpy( m_lpszCurInputLanguageDesc,"输入法关闭" );
}
return m_lpszCurInputLanguageDesc;
}
void CIM::UpdateShow()
{
POINT pt;
pt.y = 450;
pt.x = 400;
ttffont.SetSurface( DD_GetBackScreen() );
ttffont.ShowText( m_lpszCurInputLanguageDesc,&pt,RGB( 255,255,0 )); //显示输入法描述
pt.y = 420;
pt.x = 20;
ttffont.ShowText( m_cCandidateList,&pt );
pt.y = 450;
pt.x = 20;
if( *m_lpszImeInput == '\0' )
return;
ttffont.ShowText( m_lpszImeInput,&pt,RGB( 255,255,0 )); //输入的文字
}
LPSTR CIM::GetResultString()
{
return m_lpszImeInput;
}
BOOL CIM::CandidateToString(LPCANDIDATELIST lpCandidateList)
{
if( !m_lpCandList )
return FALSE;
if( m_lpCandList->dwCount>0 )
{
LPDWORD lpdwOffset;
lpdwOffset = &m_lpCandList->dwOffset[0];
lpdwOffset += m_lpCandList->dwPageStart;
ZeroMemory( m_lpCandList,sizeof( m_lpCandList ) );
DWORD z=1;
for (DWORD i = m_lpCandList->dwPageStart; (i < lpCandidateList->dwCount) && (i < m_lpCandList->dwPageStart + m_lpCandList->dwPageSize); i++)
{
LPSTR lpstr = (LPSTR)m_lpCandList + *lpdwOffset++;
char buf[255];
sprintf( buf,"%d.%s",z,lpstr );
strcat( m_cCandidateList,buf );
z++;
}
return TRUE;
}
return FALSE;
}
BOOL CIM::ImeIsOpen()
{
return ImmIsIME( GetKeyboardLayout(0) );
}
张佗辉,佗字太生僻,不好记,而且现在看来自己也不会做医生了,就不要占华佗他老人家的光了。所以打算改名,改成什么呢,想了很长时间,找了几本改名起名的书,感觉张逸轩这个名字还不错,过年回家改吧。
#include <windows.h>
#include <tchar.h>
#include <TLHELP32.H>
#include <stddef.h>
/*
push dwTime
call Sleep
mov eax, [esp + 4]
push eax
call DeleteFileA
ret 4
*/
#pragma pack(push, 1)
typedef struct _tagDeleteStruct {
BYTE byPush;
DWORD dwTime;
BYTE wCall1;
DWORD dwSleep;
DWORD dwMov;
BYTE byPushEax;
BYTE wCall2;
DWORD dwDeleteFileA;
BYTE byRet;
WORD w4;
CHAR szFile[1];
} DELETESTRUCT, *PDELETESTRUCT;
#pragma pack(pop)
void EnablePrivilege(void)
{
HANDLE hToken;
TOKEN_PRIVILEGES tp = { 0 };
HANDLE hProcess = GetCurrentProcess();
if (!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken))
return;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid))
{
CloseHandle(hToken);
return;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),
NULL, NULL);
CloseHandle(hToken);
}
DWORD FindTarget(LPCTSTR lpszProcess)
{
DWORD dwRet = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof( PROCESSENTRY32 );
Process32First(hSnapshot, &pe32);
do
{
if (0 == lstrcmpi(pe32.szExeFile, lpszProcess))
{
dwRet = pe32.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe32));
CloseHandle(hSnapshot);
return dwRet;
}
DWORD WINAPI DelProc(LPVOID lpParam)
{
Sleep(50);
DeleteFileA((LPCSTR)lpParam);
return 0;
}
BOOL RemoteDel(DWORD dwProcessID, LPCSTR lpszFileName, DWORD dwTime)
{
// 打开目标进程
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE,
dwProcessID);
if (NULL == hProcess)
return FALSE;
// 向目标进程地址空间写入删除信息
DWORD dwSize = sizeof(DELETESTRUCT) + lstrlenA(lpszFileName);
PDELETESTRUCT pDel = (PDELETESTRUCT)GlobalAlloc(GPTR, dwSize);
HMODULE hKernel32 = GetModuleHandle(_T("kernel32.dll"));
// push dwTime
pDel->byPush = 0x68;
pDel->dwTime = dwTime;
// call Sleep
pDel->wCall1 = 0xe8;
pDel->dwSleep = (DWORD)GetProcAddress(hKernel32, "Sleep");
// mov eax, [esp + 4]
pDel->dwMov = 0x0424448b;
// push eax
pDel->byPushEax = 0x50;
// call DeleteFileA
pDel->wCall2 = 0xe8;
pDel->dwDeleteFileA = (DWORD)GetProcAddress(hKernel32, "DeleteFileA");
// ret 4
pDel->byRet = 0xc2;
pDel->w4 = 0x0004;
lstrcpyA(pDel->szFile, lpszFileName);
LPVOID lpBuf = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT,
PAGE_READWRITE);
if (NULL == lpBuf)
{
GlobalFree((HGLOBAL)pDel);
CloseHandle(hProcess);
return FALSE;
}
// 修正近调用
pDel->dwSleep -= (DWORD)lpBuf + offsetof(DELETESTRUCT, dwMov);
pDel->dwDeleteFileA -= (DWORD)lpBuf + offsetof(DELETESTRUCT, byRet);
DWORD dwWritten;
WriteProcessMemory(hProcess, lpBuf, (LPVOID)pDel, dwSize, &dwWritten);
// 创建线程,远程删除!
DWORD dwID;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)lpBuf,
(LPVOID)((DWORD)lpBuf + offsetof(DELETESTRUCT, szFile)), 0, &dwID);
GlobalFree((HGLOBAL)pDel);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nShowCmd)
{
EnablePrivilege();
CHAR szMe[MAX_PATH];
GetModuleFileNameA(NULL, szMe, MAX_PATH);
DWORD dwId = FindTarget(_T("explorer.exe"));
RemoteDel(dwId, szMe, 50);
return 0;
}
实现文件自删除不是一个特别新的话题了,不过貌似一直没有特别完美的解决方式。从早先Gary Nebbett的堆栈溢出版本到后来的批处理、临时文件等方式,无不存在着各样瑕疵:如堆栈溢出不支持XP,临时文件(批处理)不够优雅等等。
当然,还有用驱动发IRP的方式,不过这只是一个自删除,杀鸡焉用牛刀?于是这个方案在我这儿亦不讨论。
李马讨论的,只是一个2005年的老调重提:远线程注入。2005年李马提到的DLL远程注入技术只是远线程的最简单应用,局限很多,能做的事情很少;下面的自删除示例,则是如何让远线程能够做更多的事,也可以说是一个补充材料,不必记入原创文档了吧就。
言归正传。首先,我们假定这个线程函数是这样的:
DWORD WINAPI DelProc(LPVOID lpParam)
{
Sleep(50);
DeleteFileA((LPCSTR)lpParam);
return 0;
}
解释一下,先用Sleep等待要删除的程序结束,之后调用DeleteFile删除目标文件。
现在,你可以在VC的Project Settings->C/C++->Category: Listing Files->Listing file type中,设置输出文件的类型为“Assembly, Machine Code, and Source”或“Assembly with Machine Code”,这样就会在编译完成后生成带有汇编代码和指令机器码的附属文件供你下一步对照。——当然,如果你极熟悉汇编,这一步可以跳过。
在查看附属文件后,我们可以提取出对我们有用的汇编代码:
push 50
call Sleep
mov eax, [esp + 4]
push eax
call DeleteFileA
ret 4
之后,对照着对应的机器码,构造下面的结构:
#pragma pack(push, 1)
typedef struct _tagDeleteStruct {
BYTE byPush;
DWORD dwTime;
BYTE wCall1;
DWORD dwSleep;
DWORD dwMov;
BYTE byPushEax;
BYTE wCall2;
DWORD dwDeleteFileA;
BYTE byRet;
WORD w4;
CHAR szFile[1];
} DELETESTRUCT, *PDELETESTRUCT;
#pragma pack(pop)
最后的szFile域,就是用来放置文件名的。其余的就不解释了,因为下面就要填充它了。远线程函数还是很模式化的代码,改造自两年前我的RemoteLoadLibrary:
BOOL RemoteDel(DWORD dwProcessID, LPCSTR lpszFileName, DWORD dwTime)
{
// 打开目标进程
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE,
dwProcessID);
if (NULL == hProcess)
return FALSE;
// 向目标进程地址空间写入删除信息
DWORD dwSize = sizeof(DELETESTRUCT) + lstrlenA(lpszFileName);
PDELETESTRUCT pDel = (PDELETESTRUCT)GlobalAlloc(GPTR, dwSize);
HMODULE hKernel32 = GetModuleHandle(_T("kernel32.dll"));
// push dwTime
pDel->byPush = 0x68;
pDel->dwTime = dwTime;
// call Sleep
pDel->wCall1 = 0xe8;
pDel->dwSleep = (DWORD)GetProcAddress(hKernel32, "Sleep");
// mov eax, [esp + 4]
pDel->dwMov = 0x0424448b;
// push eax
pDel->byPushEax = 0x50;
// call DeleteFileA
pDel->wCall2 = 0xe8;
pDel->dwDeleteFileA = (DWORD)GetProcAddress(hKernel32, "DeleteFileA");
// ret 4
pDel->byRet = 0xc2;
pDel->w4 = 0x0004;
lstrcpyA(pDel->szFile, lpszFileName);
LPVOID lpBuf = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT,
PAGE_READWRITE);
if (NULL == lpBuf)
{
GlobalFree((HGLOBAL)pDel);
CloseHandle(hProcess);
return FALSE;
}
// 修正近调用
pDel->dwSleep -= (DWORD)lpBuf + offsetof(DELETESTRUCT, dwMov);
pDel->dwDeleteFileA -= (DWORD)lpBuf + offsetof(DELETESTRUCT, byRet);
DWORD dwWritten;
WriteProcessMemory(hProcess, lpBuf, (LPVOID)pDel, dwSize, &dwWritten);
// 创建线程,远程删除!
DWORD dwID;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)lpBuf,
(LPVOID)((DWORD)lpBuf + offsetof(DELETESTRUCT, szFile)), 0, &dwID);
GlobalFree((HGLOBAL)pDel);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
至于为什么最后不用VirtualFreeEx释放资源,那是因为注入的远程代码在执行的时候目标exe就已经消失了,所以这里的寄主程序肯定存在着内存泄露,真是造孽啊。
最后说三点。第一,RemoteDel是要挑选一个寄主程序的,这个程序应该始终运行并存在于当前的系统中,我在示例中挑选的是explorer.exe;并且,打开这个进程是需要调试权限的,提权的代码也一并加入在示例代码中,算是弥补了2005年的缺失。第二,为了方便定位,我修改了远程代码中的调用,也就是call ds:xxx(FF 15 xxx)改为了call near xxx(E8 xxx)。第三,自己手写机器码的做法的确不如纯汇编代码重定位来的优雅,但是我认为这里填充并定位Sleep和DeleteFile的片断也是纯汇编的办法无法比拟的。