屌丝来谈Thunk Trick

0 篇文章 0 订阅

最近又冬眠了,时间很充裕。作为屌丝的我常常在互联网上找寻信息,自己也获得过很多人的帮助,无以回馈,所以想想就写些博客来。

Thunk是内联汇编中的一种技巧把戏,它代表着一串机器指令。从编程的角度来看,数据放在数据段,执行的指令放在代码段,用户无法修改某块内存的内容,把它变成机器码来用作指令执行。更何况操作系统对内存页面还有许多访问控制,一不留神把数据段中的内容当成指令操作和执行,操作系统就会毫不留情地抛一个0xC0000005 Access Violation的异常给你。即便是如此,但是Windows还是很自信地将本来属于系统一级的API导出给用户空间的程序调用,来操作内核的页面属性。这样对程序员来说就有更多灵活的空间可以发挥。VirtualAlloc VirtualProtect FlushInstructionCache VirtualFree等就是这一类函数。


废话不多说,先来定义一个代表机器指令的结构体:

#pragma pack(1)
struct _NiceThunk{
BYTE m_jump;//OxE9 jmp段内的直接跳转
DWORD m_offset;//标号偏移的相对值,直接加到EIP上去的
};



初始化:

void __stdcall InitThunk(_NiceThunk* pThunkImpl){
	pThunkImpl->m_jump = 0xE9;
	pThunkImpl->m_offset = (INT_PTR)ThunkFunc-(INT_PTR)(pThunkImpl+1);
}

这里有几点需要说明,首先是需要将数据格式对齐,指令的格式不对不能跳转,可以定义好了之后简单的测试下是否对齐assert(sizeof(_NiceThunk)==5)。另外,读MSDN可能发现pragma pack 和__declspec (align(#))似乎意思相似,但两者目的和作用是不一样的,不要搞混淆了。

然后从虚拟内存页上调拨一个页面分配_NiceThunk结构体,目的只有一个:能修改该页,并使它可以执行。

SYSTEM_INFO siSysInfo;
GetSystemInfo(&siSysInfo);
_NiceThunk* pThunkImpl = (_NiceThunk*)VirtualAlloc(NULL,siSysInfo.dwPageSize,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
InitThunk(pThunkImpl);
FlushInstructionCache(GetCurrentProcess(),pThunkImpl,sizeof(_NiceThunk));



最后只需要一个能让Thunk执行跳转的函数,该函数采用内联汇编编写,有关于MASM请参考MSDN文档:http://msdn.microsoft.com/zh-cn/library/4ks26t93(v=vs.90).aspx

void __stdcall ThunkFunc(int /*iArg*/){
	int iA = 0;
	iA++;
}
__declspec (naked) void __stdcall CallThunk(_NiceThunk* /*pThunkImpl*/){
	__asm{
		mov eax,[esp+4] _NiceThunk的地址
		mov ecx,0 
		push ecx  ThunkFunc参数入栈
		call eax 执行Thunk代码
		ret 4 参数出栈
	}
}
补充一句,Thunk可以作为函数指针使用,不信可以试试,这才是最大的Trick 大笑

最近也在看John Tang在codeproject上发表的文章:http://www.codeproject.com/Articles/27908/Thunk-and-its-uses可以提供更多的参考。

大半年之前记录学习的这段内容,由于例子引用的不充分太过浅显,虽然已经说明了__stdcall以及参数传递等部分内容,但还不够清晰的说明thunk的本质,天资过人的朋友那就另当别论啦。下面从另外一种角度说明thunk,然后可能会引出一些有关编译器和符号的东西。

typedef void  (__stdcall *CallThunkFunc)(int);
CallThunkFunc pfnCall = (CallThunkFunc)&(*pThunkImpl);
pfnCall(1);

咋看之下好像没什么奇怪。广大的屌丝朋友们,只要展开汇编新的风景将呈现在我们眼前:

//pfnCall(1) 调用thunk跳转到ThunkFunc
00100000  jmp  ThunkFunc (0EA11B3h) 00100000是分配thunk的虚存地址
//直接调用ThunkFunc(1)函数
call ThunkFunc(000711B3)
ThunkFunc:
000711B3  jmp   ThunkFunc (71460h)  
两种情况是不一样的。我和很多屌丝朋友们一起,第一次看到编译器对函数调用的反汇编,居然是这个样子时吃了一斤(吃得太晚了)。原来源程序中的函数定义的名称,会被编译器拿来作为一个标号(符号),函数的实现只是一个代码段,函数的调用就是一个跳转表的实现。综合pfnCall(1)不难形象的理解到:thunk的实现恰恰就是动态的构造了这个跳转表。话说回来,如果编译器对函数调用都用跳转表实现话,尼玛要是有人改了这个跳转表会怎样呢,请教各位屌丝朋友们……




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值