能力值:
( LV2,RANK:10 )
|
-
-
2 楼
对C不太熟,但是你的代码里似乎没有显式的对堆栈进行操作,唯一觉得奇怪的地方就是mainCRTStartup写在print里面了
姑且猜一猜mainCRTStartup的汇编代码
push ebp
mov ebp,esp
pop ebp
ret
正常的执行的话这个函数在执行到ret的时候会做的事情是pop eip,因为call是压入返回地址到栈顶后跳转的,所以ret也会对堆栈进行操作,弹出返回地址到eip
但是你的函数没有call就顺序执行到了这个mainCRTStartup,这样栈顶的地址我不确定到底会是什么的,因为我不知道c启动函数到底干了什么...
最简单的还是丢调试器里看看就明白了
|
能力值:
( LV2,RANK:10 )
|
-
-
3 楼
#include<stdio.h>
extern "C" int __cdecl mainCRTStartup(void);
void print()
{
#pragma comment(linker, "/entry:print")
#pragma comment(linker, "/SECTION:.text,ERW")
#pragma comment(lib, "msvcrt.lib")
int mainCRTStartup();
void main();
__asm
{
MOV EAX, OFFSET main
MOV BYTE PTR[EAX], 0xB8 //MOV EAX, 0x
MOV DWORD PTR[EAX+1], OFFSET SHOWSTRING //将printf语句地址放在eax+1处
MOV WORD PTR[EAX+5], 0xE0FF // JMP EAX:FFE0
}
mainCRTStartup();
__asm
{
pop ebp
ret
}
SHOWSTRING:
printf("hello,world!\n");
__asm ret 16
}
void main()
{
__asm nop
__asm nop
__asm nop
__asm nop
__asm nop
}
这个程序应该如上面修改一下吧。
加了一个pop ebp,
和一个ret 16
加了5个nop是因为程序一开始修改main()函数的指令为jmp printf,但是要给main()函数至少5个字节的空间,否则修改main()函数就会破坏紧跟main()函数的代码。所以留出5个nop为指令空间占位。
pringf后面加个ret 16是因为printf实际是main跳转过来,带有main的4个参数,而且printf执行完后,编译器认为整个print函数完毕会自动生成pop ebp,因为入口处自动生成了push ebp,而此处不应该pop ebp ,所以执行ret 16代替编译器自动生成的pop ebp,ret.
mainCRTStartup();执行完以后加入 ret指令实际是没有用处的,因为mainCRTStartup()不会返回到这个ret指令处执行,而是执行了exitprocess系统退出函数了。假设mainCRTStartup();执行完后返回ret处,那么为了堆栈平衡,要先pop ebp,因为一开始就执行了pop ebp指令。
|
能力值:
( LV2,RANK:10 )
|
-
-
4 楼
引用:
pringf后面加个ret 16是因为printf实际是main跳转过来,带有main的4个参数,而且printf执行完后,编译器认为整个print函数完毕会自动生成pop ebp,因为入口处自动生成了push ebp,而此处不应该pop ebp ,所以执行ret 16代替编译器自动生成的pop ebp,ret.
改正:
因为是控制台程序,只有2个参数,所以应该改成ret 8,即
SHOWSTRING:
printf("hello,world!\n");
__asm ret 8
}
|
能力值:
( LV2,RANK:10 )
|
-
-
5 楼
再加一句吧,那个nop实际要至少7个nop,然而main()自动生成xor eax,eax和ret指令,占用了2,3个字节,因此5个nop是不会出错的。
|
能力值:
( LV2,RANK:10 )
|
-
-
6 楼
看雪还是看雪,逛看雪的人还真是不一般。佩服,分析的很好。没想到真的是堆栈平衡问题 ,感谢你的回答。我这里还有一点疑问:
1)“加了5个nop是因为程序一开始修改main()函数的指令为jmp printf,但是要给main()函数至少5个字节的空间,否则修改main()函数就会破坏紧跟main()函数的代码。所以留出5个nop为指令空间占位。”
经过调式,可以得到,这个main函数地址处的第一个指令其实是条jmp指令,其后是一大堆的nop指令而已。此面试题的题意是不要在main函数里面显示的加代码。
2)需要保证什么条件,才能使得启动函数正常返回。有时我调试的时候,根本就没有从 mainCRTStartup();返回。而是直接退出了。(这得好好分析这个函数)
3)我觉得要保持堆栈平衡,需要构造一些清栈的代码,并起到绕过编译器所加的检查代码。当然,要是能使得完全通过debug下的各个检查就更好了
再次感谢你的回答,给了提供了非常关键的思路。
|
能力值:
( LV6,RANK:90 )
|
-
-
7 楼
不知道aait 是否调试了程序 ,你的测试demo在vs2008过不了,
我调试了一下 我的编译工具是vs2008,
废话不多讲 直接上代码吧
#include<stdio.h>
extern "C" int __cdecl mainCRTStartup(void);
void print()
{
#pragma comment(linker, "/entry:print")
#pragma comment(linker, "/SECTION:.text,ERW")
#pragma comment(lib, "msvcrt.lib")
int mainCRTStartup();
void main();
__asm
{
MOV EAX, OFFSET main
MOV BYTE PTR[EAX], 0xE9 //MOV EAX, 0x
MOV ecx,OFFSET SHOWSTRING;
sub ecx, offset main;
sub ecx,5
MOV DWORD PTR[EAX+1], ecx //将printf语句地址放在eax+1处
}
mainCRTStartup();
__asm
{
ret
}
SHOWSTRING:
printf("hello,world!\n");
__asm
{
ret
}
}
void main()
{
}
|
能力值:
( LV2,RANK:10 )
|
-
-
8 楼
回复7楼,你不修改楼主的源代码肯定是要出错了。主要是因为程序修改main()函数,而main()函数只有2到3,4个字节,所以修改main()函数会冲掉紧跟着main()函数的某个子程序的某些字节,肯定就会崩溃了。我调试了,好象紧跟着main()函数的是缓冲区安全检查函数secutity_cookie,破坏了这个函数入口处的几个字节肯定是要崩溃的。所以应该在main()函数里面加5个到7个nop.给修改main()函数留出指令空间,应该是7个字节的指令空间。
另外printf执行完后,楼主源代码没有ret返回,而是编译器自动生成pop ebp,ret指令,这个pop ebp是不队的,
堆栈不平衡了,所以手工添加ret 8指令,代替编译器自动生成的pop ebp,ret.
以上修改我是经过调试器调试了的。请使用我修改的代码编译。
因为printf一闪而过,所以最好在printf后面加一个getcha();
|
能力值:
( LV2,RANK:10 )
|
-
-
9 楼
#include<stdio.h>
extern "C" int __cdecl mainCRTStartup(void);
void print()
{
#pragma comment(linker, "/entry:print")
#pragma comment(linker, "/SECTION:.text,ERW")
#pragma comment(lib, "msvcrt.lib")
int mainCRTStartup();
void main();
__asm
{
MOV EAX, OFFSET main
MOV BYTE PTR[EAX], 0xB8 //MOV EAX, 0x
MOV DWORD PTR[EAX+1], OFFSET SHOWSTRING //将printf语句地址放在eax+1处
MOV WORD PTR[EAX+5], 0xE0FF // JMP EAX:FFE0
}
mainCRTStartup();
__asm
{
pop ebp
ret
}
SHOWSTRING:
printf("hello,world!\n");
__asm ret 8
}
void main()
{
__asm nop
__asm nop
__asm nop
__asm nop
__asm nop
__asm nop
__asm nop
}
|
能力值:
( LV2,RANK:10 )
|
-
-
10 楼
这个答案在vs2008下还是通不过。不过你们的回答给了我很好的思路。非常谢谢你们的回答。如果可以,希望能再改改。这个堆栈平衡很有问题。
1)尤其是怎样构造是的main函数正常返回,使得crt启动函数正常调用main函数,并正确清理资源返回。
2)要保持堆栈平衡,需要构造一些清栈的代码,并起到绕过编译器所加的检查代码。当然,要是能使得完全通过debug下的各个检查就更好了
3)main函数起始处只是一系列int 3指令罢了,不用nop吧!并在main里面nop貌似没啥作用!因为我们的__asm
{
MOV EAX, OFFSET main
MOV BYTE PTR[EAX], 0xB8 //MOV EAX, 0x
MOV DWORD PTR[EAX+1], OFFSET SHOWSTRING //将printf语句地址放在eax+1处
MOV WORD PTR[EAX+5], 0xE0FF // JMP EAX:FFE0
}根本就不是将mov eax,OFFSET SHOWSTRING与jmp eax指令码写到我们所见到的main函数里面。而只是写在了jmp指令处。
|
能力值:
( LV6,RANK:90 )
|
-
-
11 楼
非要我说脏话,你为什么不测试一下我的代码啊
你难道眼睛有问题吗?
真的看不出,我给出的代码和你原来代码的变化吗?
汗 以后打死不回这样的帖子了,
|
能力值:
( LV2,RANK:10 )
|
-
-
12 楼
|
能力值:
( LV2,RANK:10 )
|
-
-
13 楼
问最后一个问题:两种跳转方式
MOV EAX, OFFSET main
MOV BYTE PTR[EAX], 0xE9 //构造jmp 相对地址 指令并放在main函数处
MOV ecx,OFFSET SHOWSTRING;
sub ecx, offset main;
sub ecx,5
MOV DWORD PTR[EAX+1], ecx //将printf语句地址放在eax+1处
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//MOV EAX, OFFSET main
//MOV BYTE PTR[EAX], 0xB8 //MOV EAX, 0x
//MOV DWORD PTR[EAX+1], OFFSET SHOWSTRING //将printf语句地址放在eax+1处
//MOV WORD PTR[EAX+5], 0xE0FF // JMP EAX:FFE0
怎么下面构造的跳转会出问题呢?能不能分析下??elianmeng,aait
|
能力值:
( LV2,RANK:10 )
|
-
-
14 楼
[QUOTE=xwl刘晓伟;1107119]问最后一个问题:两种跳转方式
MOV EAX, OFFSET main
MOV BYTE PTR[EAX], 0xE9 //构造jmp 相对地址 指令并放在main函数处
MOV ecx,OFFSET SHOWSTRING;
s...[/QUOTE]
你并没有看懂我以前的回答, 7个nop的作用,或者用7个int 3也可以。第一种跳转占用5个字节,第二种跳转占用7个字节,如果直接main()是空的话,main()编译以后只有2个或者3个字节,没有冲掉紧跟main()的代码是侥幸,如果稳妥一点,还是写入7个空代码吧。
|
能力值:
( LV2,RANK:10 )
|
-
-
15 楼
跟进去,可以看到,只是一系列jmp指令而已。并没有真正到main函数处
妈的,我不会贴图片,图片挂了。网页太不方便了。
|
能力值:
( LV2,RANK:10 )
|
-
-
16 楼
原题目中貌似没extern "C"一句
原题:
#include "stdio.h" void print() { * }
void main() { }
在*号处加一段代码,显示出"hello,world".
换一种思路的解法:
#include "stdio.h"
void print()
{
}
void main()
{
printf("Hello world.\n");
#define main test
}
void main()
{
}
加粗部分为新加入代码
|
能力值:
( LV2,RANK:10 )
|
-
-
17 楼
十分感谢你的回答!!用宏是能很好的解决那个面试题,也可以用全局变量或静态变量在main之前初始化的特性来解答此面试题。甚至可以用writeFile来代替printf来输出到控制台。(其实printf应该也是调用的此函数,并且此函数不是c运行库的函数,而是windows库函数)。但是我这里提问的是,我贴出来的做法的问题出在哪里?
|
|
|