《编程高手箴言》中的一段30行左右的程序,百思不得其解,请帮帮忙

jcwKyl 2008-06-04 07:15:28
请大家先看看代码,我的问题在代码后面。
程序源代码如下:

#include <windows.h>

PROC lpAddr = MessageBoxA;

PROC SetHook(LPCSTR lpModuleName, PROC lpFuncAddr, PROC pNewProc) {
LPBYTE lpByte;
LPDWORD lpAddr;
DWORD dwAddr;

lpByte = (LPBYTE)lpFuncAddr;
if(lpByte[0] == 0xff && lpByte[1] == 0x25) {
lpAddr = (LPDWORD)(&lpByte[2]);
dwAddr = lpAddr[0];
lpAddr = dwAddr;
dwAddr = lpAddr[0];

WriteProcessMemory(GetCurrentProcess(), lpAddr,
&pNewProc, sizeof(DWORD), NULL);
return (PROC)dwAddr;
}
return NULL;
}

int APIENTRY MyMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
int ret = 0;
if(lpAddr) {
ret = lpAddr(NULL, "new", "new", MB_OK);
}
return ret;
}

int main() {
lpAddr = SetHook("user32.dll", lpAddr, MyMessageBox);
MessageBoxA(NULL, "old", "old", MB_OK);
return 0;
}


程序类似于API HOOK,main中调用MessageBoxA时给的参数是"old",运行后显示的对话框文本是"new"。这就是效果。现在我的问题是:
上述程序稍微改动一下,主要是,在main函数调用SetHook之前再次使用
lpAddr = MessageBoxA
赋一次值,这个程序就不会有上述的运行效果了。所以自己写了个打印内存的函数,有以下五种情形,自己百思不得其解:
(dumpMem是自己写的一个函数,第一个参数是内存地址,后面的参数不用管,这个函数输出时首先输出地址值,然后输出这个地址处的内存内容,下面是用16进制输出的内存内容,最后输出与这些内存内容相对应的ASCII文本)
情形一:
PROC lpAddr = MessageBoxA;
int main() {
dumpMem(lpAddr, 16, 16, UNIT_BYTE, 16);
return 0;
}
输出:
004015D2 ff 25 ac a2 42 0 cc cc cc cc cc cc cc cc 55 8b .%..B.........U.

情形二:
PROC lpAddr = MessageBoxA;
int main() {
dumpMem(MessageBoxA, 16, 16, UNIT_BYTE, 16);
return 0;
}
输出:
77D5050B 8b ff 55 8b ec 83 3d 1c 4 d7 77 0 74 24 64 a1 ..U...=...w.t$d.

情形三:
int main() {
PROC lpAddr = MessageBoxA;
dumpMem(lpAddr, 16, 16, UNIT_BYTE, 16);
return 0;
}
输出:
77D5050B 8b ff 55 8b ec 83 3d 1c 4 d7 77 0 74 24 64 a1 ..U...=...w.t$d.

情形四:
PROC lpAddr;
int main() {
lpAddr = MessageBoxA;
dumpMem(lpAddr, 16, 16, UNIT_BYTE, 16);
return 0;
}
输出:
77D5050B 8b ff 55 8b ec 83 3d 1c 4 d7 77 0 74 24 64 a1 ..U...=...w.t$d.

情形五:
PROC lpAddr = MessageBoxA;
int main() {
lpAddr = MessageBoxA;
dumpMem(lpAddr, 16, 16, UNIT_BYTE, 16);
return 0;
}
输出:
77D5050B 8b ff 55 8b ec 83 3d 1c 4 d7 77 0 74 24 64 a1 ..U...=...w.t$d.
...全文
340 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
jcwKyl 2008-06-08
  • 打赏
  • 举报
回复
呵呵,确实,这种东西只是一个硬编码。把DEBUG模式改成RELEASE模式,或者加上编译器优化选项,或者把那个生成的EXE文件绑定一下,那个例子就得不到期望的结果了。
jcwKyl 2008-06-08
  • 打赏
  • 举报
回复
呵呵,确实,这种东西只是一个硬编码。把DEBUG模式改成RELEASE模式,或者加上编译器优化选项,那个例子就得不到期望的结果了。确实是只能作为DEMO啊。
jcwKyl 2008-06-06
  • 打赏
  • 举报
回复
呵呵,发现自己问了个挺无聊的问题。我的原来的问题是为什么全局变量并且就地赋值居然能把原来高效调用的函数形式变成低效的调用。我想这可能和编译器产生高效调用和低效调用的原因有关。可能是因为全局变量初始化在加载器遍历导入表并导入各个模块之前进行,这个时候函数名这个符号没有定义,找不到函数体的存放地址,所以只能把所有对该函数的调用都集中到那个JMP上去。可是JMP后面的地址就是IAT中的一个地址,而高效调用形式中的CALL DWORD PTR 后面的地址也是IAT中的一个地址,不明白为什么不能直接使用CALL DWORD PTR这样的调用形式。
好了,谢谢你们。
kitsudo 2008-06-06
  • 打赏
  • 举报
回复
出现这个情况完全是因为作者的原因
if(lpByte[0] == 0xff && lpByte[1] == 0x25) {
lpAddr = (LPDWORD)(&lpByte[2]);
dwAddr = lpAddr[0];
lpAddr = dwAddr;
dwAddr = lpAddr[0];

WriteProcessMemory(GetCurrentProcess(), lpAddr,
&pNewProc, sizeof(DWORD), NULL);
return (PROC)dwAddr;
}
这段代码实际上只能演示作者所表达的那种状态
而实际上要达到这个效果
只要改变__imp__MessageBoxA@16 (0040d244)的跳转而已
这个只能说明了作者的代码真的只能是当个DEMO了
泛用性很差啊
呵呵
凌乱1980 2008-06-06
  • 打赏
  • 举报
回复
我在VC++2003上测试了LZ的例子,发现情形一的地址是77D5050B啊,和LZ说的情况不一样,奇怪。
jcwKyl 2008-06-05
  • 打赏
  • 举报
回复
我想知道的是情形一为什么和后面四种情形不一样,全局变量初始化究竟有什么玄机?
使用反汇编器调试可以看到,MessageBoxA的函数体地址应该放在后面四种情形所输出的77D5050B地址处。情形一的0xff, 0x25是CALL 指令的操作码。那一段代码的原理就是找到这条CALL指令,然后把CALL后面的地址改成MyMessageBox的地址,所以在main中调用MessageBoxA时实际上是调用的MyMessageBox。
fflush 2008-06-05
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 jcwKyl 的回复:]
感谢六楼的朋友。你说的加一句对lpAddr的调用,是指这样吗?
PROC lpAddr = MessageBoxA;
int main() {
lpAddr(NULL, "test", "test", MB_OK);
dumpMem(lpAddr, 6, 16, UNIT_BYTE, 16);
return 0;
}
我这样试了一下,好像dumpMem的值没有改变。

另外,原来的帖子中说0xff,0x25是CALL直接寻址的操作码,那里错了,这是JMP指令直接寻址的操作码。
在《编程高手箴言》中对这一段代码的解释说:“微软为了高效,…
[/Quote]
lz自己不是给出解释了吗
jcwKyl 2008-06-05
  • 打赏
  • 举报
回复
呵呵,谢谢你。向你学习。
lin_style 2008-06-05
  • 打赏
  • 举报
回复
没变?
我是这样想的
首先API的地址是固定的,全局变量在编译时就确定,但是库还没导入,所以PROC lpAddr = MessageBoxA; 这句中的lpAddr只是个暂时的空量。等有用到他的时候才会去寻找MessageBoxA的地址。
既然还是不一样,在下才疏学浅。献丑了。匿。。
jcwKyl 2008-06-05
  • 打赏
  • 举报
回复
感谢六楼的朋友。你说的加一句对lpAddr的调用,是指这样吗?
PROC lpAddr = MessageBoxA;
int main() {
lpAddr(NULL, "test", "test", MB_OK);
dumpMem(lpAddr, 6, 16, UNIT_BYTE, 16);
return 0;
}
我这样试了一下,好像dumpMem的值没有改变。

另外,原来的帖子中说0xff,0x25是CALL直接寻址的操作码,那里错了,这是JMP指令直接寻址的操作码。
在《编程高手箴言》中对这一段代码的解释说:“微软为了高效,直接使用了导入的API的地址来调用它,而把JMP过程省去了。可以通过声明全局变量的方法来让它调用Windows API的时候产生JMP的过程”。
也就是说,以前调用MessageBoxA的形式是:
CALL DWORD PTR [_imp__MessageBoxA]; _imp_MessageBoxA是IAT中的地址。
现在使用了一个全局变量lpAddr并就地初始化为MessageBoxA后,调用MessageBoxA的形式变成了:
CALL 0x00401502
00401502: JMP 77D5050B
上面的00401502是个中间过渡的跳板地址。
lin_style 2008-06-05
  • 打赏
  • 举报
回复
情形一:
PROC lpAddr = MessageBoxA; //预编译确定,这个时候MessageBoxA的DLL还没在内存中,未传给lpAddr所以lpAddr是取不到值的
int main() {
//如果这里加一句对lpAddr的调用,那么dumpMem值可能会改变,等待楼主实验,如果会变就证明我的想法是对的,到时候再详细说明
dumpMem(lpAddr, 16, 16, UNIT_BYTE, 16);
return 0;
}
输出:
004015D2 ff 25 ac a2 42 0 cc cc cc cc cc cc cc cc 55 8b .%..B.........U.

情形二:
PROC lpAddr = MessageBoxA;
int main() {
dumpMem(MessageBoxA, 16, 16, UNIT_BYTE, 16); //这里直接寻址
return 0;
}
输出:
77D5050B 8b ff 55 8b ec 83 3d 1c 4 d7 77 0 74 24 64 a1 ..U...=...w.t$d.

情形三:
int main() {
PROC lpAddr = MessageBoxA; //运行确定
dumpMem(lpAddr, 16, 16, UNIT_BYTE, 16);
return 0;
}
输出:
77D5050B 8b ff 55 8b ec 83 3d 1c 4 d7 77 0 74 24 64 a1 ..U...=...w.t$d.

情形四:
PROC lpAddr;
int main() {
lpAddr = MessageBoxA; //同上
dumpMem(lpAddr, 16, 16, UNIT_BYTE, 16);
return 0;
}
输出:
77D5050B 8b ff 55 8b ec 83 3d 1c 4 d7 77 0 74 24 64 a1 ..U...=...w.t$d.

情形五:
PROC lpAddr = MessageBoxA;
int main() {
lpAddr = MessageBoxA;
dumpMem(lpAddr, 16, 16, UNIT_BYTE, 16);
return 0;
}
zjw6861982 2008-06-05
  • 打赏
  • 举报
回复
顶你,一起学习ing
gezichong 2008-06-04
  • 打赏
  • 举报
回复
没看明白..
  • 打赏
  • 举报
回复
sf
???
iu_81 2008-06-04
  • 打赏
  • 举报
回复
情形一应该是正常保存了 MessageBoxA的地址
其他情况 MessageBoxA的地址已经改变了,如果没有改变的话,就没有必要来保存

69,336

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧