Visual Studio 2015编译EncodePointer函数的问题
来源:互联网 发布:网络摄像机方案 编辑:程序博客网 时间:2024/06/06 11:24
在编程工具方面,我是个偏好使用最新工具的强迫症晚期患者。但对于大我数程序员来讲,采用新工具能够获得最大的编程效率,这也无可非议。Visual Studio 2015是个好东西,尤其我常用来开发驱动程序,可以节省不省时间和精力。但Visual Studio从2010开始就无法编译出可在Windows XP SP1平台上运行的程序了,现在也依然如此。作为商业软件的开发,如果不考虑Windows XP SP1以下的平台,可能有些不妥,尤其如果安全软件的话。所以,我设计了一种解决方法,供大家参考。
一、基本方法
之所以新版Visual Studio编译的程序无法在XPSP1上运行,是因为C运行时和MFC框架中大量使用了EncodePointer和DecodePointer这两个新版Windows上才有的位于kernel32.dll中的API函数。网上的方法大概有三种:
1、退回老版本的VisualStudio,至少是2008;
2、安装两个版本的VisualStudio,在新版中采用老版本的编译工具集;
3、自己设计一套哑函数代替出问题的这两个函数。
前两种方法实际上是一种。而但三种方法中,在程序的安全性上是有些问题的。EncodePointer和DecodePointer之所以出现,是为了防范利用对象指针进行的攻击。在支持这两个函数的系统上不使用它们似乎也有不妥。于是我想在第三种方法上进行一下改进。
自己在代码中实现另一套EncodePointer和DecodePointer,是基本思路。但如何让链接器知道链接时该选择哪套函数呢?如果使用C在EXE工程中实现这两个函数,我们就没有任何机会了——链接器总是先链接kernel32.lib而无情地抛弃自己定义的函数。倒是可以自己实现一个包含这两个函数的lib库,并在链接EXE时在“附加依赖项”中将自己的 lib 调到 kernel32.lib 之前(实际上只要将自己的lib放入了“附加依赖项”中,它就会在默认库之前得到链接)。还在
我采用了网上可以查到的一种方法,在EXE工程中编译一个asm文件。因为汇编语言文件生成的obj文件链接总是先于默认库的。
对了,编译时不要忘记将工程设置中的“所需最低版本”设为5.01。
二、x86架构
x86架构下的asm文件代码如下:
.model flatPUBLIC__imp__EncodePointer@4, __imp__DecodePointer@4.data__imp__EncodePointer@4dd dummy_encode__imp__DecodePointer@4dd dummy_decodeEXTERNDEF__imp__LoadLibraryA@4 : DWORDEXTERNDEF__imp__GetProcAddress@8 : DWORDEXTERNDEF __imp__ShowMsg:DWORD ; CPP 文件中定义 void ShowMsg() 函数kernel32dll db 'kernel32.dll',0encode db 'EncodePointer',0decode db 'DecodePointer',0.codedummy proc mov eax, dword ptr [esp+4] ret 4dummy endpdummy_encode proc; call [__imp__ShowMsg] push offset kernel32dll call [__imp__LoadLibraryA@4] test eax, eax jz @F push offset encode push eax call [__imp__GetProcAddress@8] test eax, eax jz @F mov dword ptr [__imp__EncodePointer@4], eax jmp eax@@: mov eax, offset dummy mov dword ptr [__imp__EncodePointer@4], eax jmp eaxdummy_encode endpdummy_decode proc……dummy_decode endpend代码中省略了dummy_decode的内容,因为它与dummy_encode极其相似。
首先__imp__EncodePointer@4是我伪造EncodePointer引入表的特殊变量名称,其中存储了自己实现的EncodePointer函数dummy_encode的地址。编译好的EXE的C运行时在调用EncodePointer时就会调用到dummy_encode中来。在这里首先调用LoadLibraryA装载kernel32.dll,然后调用GetProcAddress获得EncodePointer的函数地址。如果成功获得的话,就将该地址放入__imp__EncodePointer@4中并直接跳转到其上继续执行,而下一次调用将会直接转发到真正的EncodePointer上。如果没有EnocdePointer,则将__imp__EncodePointer@4置为dummy并跳转执行,这样下一次调用就不用耗费时间去查询了。通过修改__imp__EncodePointer@4改变EncodePointer调用地址的方法可以有效地适应多线程并发执行的情况。如果dummy_encode有多个线程重入,它们都将去查询真实的EncodePointer地址,“mov dword ptr [__imp__EncodePointer@4], eax”的指令会被多次执行,但因其是原子操作,多次操作不会产生任何副作用。
调试时可能有些麻烦,所以在注释行中我调用了另一个CPP文件中的void ShowMsg()函数,而其中只有一行__debugbreak()调用。请注意名称上的特定样式,它可以很好的工作!
还需注意的一点是, Visual Studio 2015似乎不支持应用程序的asm文件编译,需要针对文件采自定义编译的方式进行,编译的命令行如下:
cd /d $(ProjectDir)$(IntDir)ml /c "%(FullPath)"输出内容为:
$(ProjectDir)$(IntDir)%(Filename).obj另外需要将链接器的 “强制文件输出”选项设置为“仅限多次定义的符号 (/FORCE:MULTIPLE)”。
三、x64架构
XP SP1居然有64位的!虽然是鸡肋版本,但为了系统的完整兼容,我决定也进行一下尝试。虽然这样做,工程设置的“所需最低版本”至少是6.00,但我猜想它是可以在XP SP1上运行的。我的代码是这样的:
PUBLIC__imp_EncodePointer, __imp_DecodePointer.data__imp_EncodePointerdq dummy_encode__imp_DecodePointerdq dummy_decodeEXTERNDEF__imp_LoadLibraryA : QWORDEXTERNDEF__imp_GetProcAddress : QWORDEXTERNDEF__imp_ShowMsg : QWORD ; CPP 文件中定义 void ShowMsg() 函数kernel32dll db 'kernel32.dll',0encode db 'EncodePointer',0decode db 'DecodePointer',0.codedummy proc mov rax, rcx retdummy endpdummy_encode proc mov qword ptr [rsp+8], rcx sub rsp, 28h ;(1); mov rax, qword ptr [__imp_ShowMsg]; call rax lea rcx, [kernel32dll]; push rcx (*) mov rax, qword ptr[__imp_LoadLibraryA] call rax ; poprcx (*) test rax, rax jz @F lea rdx, [encode]; push rdx (*) mov rcx, rax; push rcx (*) mov rax, qword ptr [__imp_GetProcAddress] call rax; pop rcx (*); pop rdx (*) test rax, rax jz @F mov qword ptr [__imp_EncodePointer], rax add rsp, 28h ;(2) mov rcx, qword ptr [rsp+8] jmp rax@@: lea rax, [dummy] mov qword ptr [__imp_EncodePointer], rax add rsp, 28h ;(3) mov rcx, qword ptr [rsp+8] jmp raxdummy_encode endpdummy_decode proc……dummy_decode endpend
与x64架构下非常相似,我就不重复说明了。值得注意的是函数的命名规则略有变化。还有就是x64函数调用约定的情况。x64架构的子函数头四个参数通过rcx、rdx、r8、r9来传递,堆栈也至少要预留20h的空间,再加入为了使返回地址在16字节边界上对齐,需要另加上8字节空间,因此需要28h字节的空间。如果子函数参数多于4个,则空间要更多,且要在16字节边界上对齐,如果子数有5个参数,则总共需要下面这么多的空间:
30h(参数) + 8h(用于返回地址对齐) = 38h
最开始因为我对x64函数调用约定的误解(即使传递的参数少于4个,也要至少预留4个参数的位置),我并没有采用(1)、(2)、(3)的方式预留参数空间,而是采用了(*)处更为紧凑的方法。这种方法一般情况下是没有问题的,但在这里则不然。为了排错我耗费了很多时间,这里记录下来好让后来人不要犯同样的错误。
(*)的方法LoadLibrary和GetProcAddress函数调用完成后总是将dummy_encode的返回地址修改掉,但自信自己的代码没有问题,我逆向了一下LoadLibraryA和GetProcAddress发现,它们的实质代码第一行居然干了这个:
LoadLibraryA:mov [rsp+10h],rbxGetProcAddress:mov [rsp+18h],rsirsp+8h是第1个参数的位置,而rsp+10h和rsp+18h分别对应了第2个参数和第3个参数的位置。但LoadLibraryA哪里来的第二个参数,而GetProcAddress又哪里来的第3个呢?它们正对应了dummy_encode的返回地址!访问参数以外的堆栈空间的理由是什么,我实在猜不出,不会是为采用ROP的shellcode编写制造一些麻烦吧?
另外,x64下对asm文件编译的命令行为:
cd /d $(ProjectDir)$(IntDir)ml64 /c "%(FullPath)"
四、总结
这种方法可以有效地提高Visual Studio 2015编译出来的程序的兼容性,同时避免了安全性上的降低。但也有人认为费了这么大力气才解决这么小的一个问题没有必要。无所谓,通过这个试验长了点儿知识总还是好的!- Visual Studio 2015编译EncodePointer函数的问题
- visual studio 2008编译libnids的问题
- Visual Studio 2008的Vmware插件导致Visual Studio编译出错并异常退出的问题
- FFmpeg在Visual Studio环境下的编译问题
- 在Visual Studio中编译Linux的一些问题
- Visual Studio 2015编译wxWidgets
- Visual Studio 2015 编译gflags
- Visual Studio的函数unsafe报错问题
- Visual Studio 编译libimobiledevice问题简介
- visual studio 编译问题unresolved external symbol
- visual studio 编译中的小问题
- json在visual studio 2015上面的编译以及使用
- visual studio 2005下xvid的编译
- 提高Visual Studio的编译速度
- visual studio 编译参数的意思
- 使用Visual Studio cl编译的步骤
- Visual Studio 2008转Visual Studio 2010的模板问题
- 【IDE-Visual Studio】编译出错(chenlu-2):传参时访问私有的构造函数
- Linux下获取进程状态
- 单选框 change 事件 。 单选框点击事件,切换不同的table。
- web项目启动初始化java Demo
- 好马应不应该吃回头草?
- 九幽史程博:助力国内开发者借Win10东风出海
- Visual Studio 2015编译EncodePointer函数的问题
- 统计无符号整型数的二进制码中‘1’的个数
- Linux应用服务器搭建手册——weblogic安装
- 把byte[]转换为String
- python 安装 setuptools Compression requires the (missing) zlib module 的解决方案
- 判断两个边平行于坐标轴的矩阵相交
- spring框架学习(四)自动装配
- MyEclipse 引用 jar外包的方法
- TTL和CMOS的区别