这是从别人博客上摘的一段C嵌汇编码
( http://www.cppblog.com/kevinlynx/archive/2011/01/02/137886.html
)
__declspec(naked)
void caller(void* pfn,
)
{
__asm
{
pop eax;
add eax, 3;
xchg dword ptr[esp], eax;
push eax;
ret;
}
}
下面是调用方法
void print_str( const char *s ){ printf( "%s\n", s );}{ ...caller( print_str, "a string" ); __asm add esp, 4
...}
原作者讲了一些基础,这里就不提了
看了一遍,发现 "ADD EAX, 3" 的用法有点奇怪(我相信搞破解的人一定比较熟悉,但正常的程序不会这么写。)
初看 EAX 是地址,+3是很危险的,但仔细一看,发现代码是为了从最外层主调函数一路穿越"caller" 直达 print_str,这里牵涉到一个重要问题,就是在CALL指令时,会有将“CALL指令下一条地址压栈”的操作,那么代码思路很明了了,就是为了要造出 调用print_str时,ESP(+0) 指向 caller(..)调用的下一个地址。
第一关已经顺利搞定,但又碰到个问题,由于 print_str 的入参是可变的,所以必须用 cdecl调用,那RET之后 如何平栈呢? 如果直接跳到 caller下一条地址,就丧失了平栈的机会,最终会在某个主调函数上被微软的 stack cookie捕获抛个SEH。
这里就用到文章开头提到的 ADD EAX, 3。
必须要造一个环境,让 caller 调用完成后,给个机会清理现场。于是乎,caller之后就有了 ADD ESP, 4。其实这里的4是与print_str的入参数目相关的,每个参数要多加 4字节,如此一来,整个代码就理顺了。
那为什么 是 ADD EAX, 3呢? 应该是预估出一条ADD指令占用多少长度,和具体的环境有关。因为没看INTEL手册,这里只能认为ADD 寄存器+WORD的长度是3个字节。我用VC试验了一下,的确是如此,我也尝试了ADD 寄存器+DWORD,长度变为了5个字节。