MicroSoft编译器你不可不知的秘密

:  【原创】MicroSoft编译器你不可不知的秘密
: 黑猫①号
:  2012-12-15 23:33
转载请注明出处  :  http://blog.csdn.net/jha334201553/article/details/8300153

注:

1.  本文是写给调用dll原理不太清楚,以及不知道怎么调用未声明但是已经导出的windows API(例如Native API),大牛自动略过本文。

2.  一些基础的知识不会介绍,例如什么是函数调用约定(如:__stdcall、__cdecl、__fastcall等),为什么要加extern  “C” 、 #ifdef  __cplusplus。也不会介绍一些很容易百度(谷歌)到的知识。


用控制台直接编译程序

以下演示内容全部通过控制台编译,在开始之前先大概的说下环境(加这部分是因为本文内容就是有关导出函数环境的O(∩_∩)O~ )。

VC6其实可以免安装使用的,将bin、lib、include文件夹( 如果想要alt、mfc功能,那把他们的文件也一起 )直接复制到U盘中走到哪带到哪,再写个脚本设置下环境变量,就可以随心编译。(PS: 我用的控制台编译,之前我就说过了)。脚本如下形式:

  1. @echo off  
  2. set path =%path%;%cd%\bin;  
  3. setinclude=%cd%\Include;%cd%\MFC\include;%cd%\ATL\Include;  
  4. setlib=%cd%\lib;%cd%\MFC\Lib;  
  5. cmd.exe  
@echo off
set path =%path%;%cd%\bin;
setinclude=%cd%\Include;%cd%\MFC\include;%cd%\ATL\Include;
setlib=%cd%\lib;%cd%\MFC\Lib;
cmd.exe


头文件、lib、dll是怎么回事

a.       头文件

  1. int main(int argc, char* argv)  
  2. {  
  3.         return 0;  
  4. }  
int main(int argc, char* argv)
{
        return 0;
}

以上代码直接在控制台编译通过很正常。那以下呢?

  1. int main(int argc, char* argv)  
  2. {  
  3.         printf(“hello world!\n”);  
  4.         return 0;  
  5. }  
int main(int argc, char* argv)
{
        printf(“hello world!\n”);
        return 0;
}

编译器会拒绝编译,显示printf函数未定义,通常的做法是加入stdio.h头文件。可是,如果不加这个头文件呢,可否有方案解决这个文件?答案是:有。

在调用printf之前加入声明、编译时加入msvcrt.lib即可。

  1. #pragma  comment(lib, "MSVCRT")   
  2. #ifdef  __cplusplus   
  3. extern "C" {  
  4. #endif   
  5.    
  6. int printf( const char* format , ...  );  
  7. #ifdef  __cplusplus   
  8. };  
  9. #endif   
  10.    
  11. int main(int argc, char* argv)  
  12. {  
  13.         printf("hello world!\n");  
  14.         return 0;  
  15. }  
#pragma  comment(lib, "MSVCRT")
#ifdef  __cplusplus
extern "C" {
#endif
 
int printf( const char* format , ...  );
#ifdef  __cplusplus
};
#endif
 
int main(int argc, char* argv)
{
        printf("hello world!\n");
        return 0;
}

命令行定位到文件所在位置,然后输入  cl  *.cpp   即可编译出exe程序来(虽然有些警告信息o(╯□╰)o  )

提示:  .h头文件中是各种函数的声明部分,使程序在编译的时候可以找到函数,这样编译阶段就不会报undefined错误了。而lib文件呢,是在链接的时候根据编译阶段生成的obj文件中指明的label到lib文件中找这个函数,如果lib为静态库(是指代码在lib中,而没有生成dll,见示例代码 ),则连接器会把lib中的代码复制到可执行文件中,如果lib是动态库,则从lib文件中找这个函数的导出名与跟这个lib库一起编译出来的动态库名称,写入导入表(动态库中其实只需要两个参数,一个dll名称,一个原函数名称)。


b.       Lib与dll文件

自己写一个user32.dll动态库调用里面的MessageBoxA调用会是什么情况呢?

User32.asm内容(以下为汇编版本,在下面的下载包中有个C语言版)

  1. .386  
  2.            .model     flat,stdcall  
  3.            option     casemap :none  
  4.     .code  
  5. ;DLL入口  
  6. DllEntry    proc   _hInstance,_dwReason,_dwReserved  
  7.                     mov   eax,1  
  8.                     ret  
  9. DllEntry    Endp  
  10. ; 导出函数  
  11. MessageBoxA  proc        _dwNumber1:DWORD,  
  12.                          _dwNumber2:DWORD,  
  13.                          _dwNumber3:DWORD,  
  14.                          _dwNumber4:DWORD  
  15.                     xor   eax,eax  
  16.                     ret  
  17. MessageBoxA  endp  
  18.    
  19. End            DllEntry  
.386
           .model     flat,stdcall
           option     casemap :none
    .code
;DLL入口
DllEntry    proc   _hInstance,_dwReason,_dwReserved
                    mov   eax,1
                    ret
DllEntry    Endp
; 导出函数
MessageBoxA  proc        _dwNumber1:DWORD,
                         _dwNumber2:DWORD,
                         _dwNumber3:DWORD,
                         _dwNumber4:DWORD
                    xor   eax,eax
                    ret
MessageBoxA  endp
 
End            DllEntry

User32.def文件内容(一些人喜欢用__declspec( dllexport ) 导出函数,我喜欢用def文件申明,这样我的dll可以导出自己想要的任何名字,还可以选择不导出名字)

  1. EXPORTS  
  2.                MessageBoxA  
EXPORTS
               MessageBoxA

编译命令     ml /c  /coff  /Cp  *.asm 

                     link  /section:.bss,S  /dll  /def:user32.def  /subsystem:windows  *.obj

生成一个user32.lib,将此文件重命名为myuser32.lib (防止于编译器库中的user32.lib 冲突(原user32.lib 后面有用))。

再创建一个测试的cpp文件:

  1. #pragma comment(lib,"myuser32.lib")   
  2.    
  3. #ifdef __cplusplus   
  4. extern "C" {  
  5. #endif   
  6.           
  7. int __stdcall MessageBoxA(unsigned long,  
  8.                           char*,  
  9.                           char*,  
  10.                           unsigned long );  
  11.    
  12. #ifdef __cplusplus   
  13. };  
  14. #endif   
  15.    
  16. int main()  
  17. {  
  18.          MessageBoxA(0, "你好""标题栏", 0);  
  19.          return 0;  
  20. }  
#pragma comment(lib,"myuser32.lib")
 
#ifdef __cplusplus
extern "C" {
#endif
        
int __stdcall MessageBoxA(unsigned long,
                          char*,
                          char*,
                          unsigned long );
 
#ifdef __cplusplus
};
#endif
 
int main()
{
         MessageBoxA(0, "你好", "标题栏", 0);
         return 0;
}

编译命令: cl  *.cpp

运行下编译出来的程序,呀!糟糕,为什么运行的时候出现了一个MessageBox对话框呢?难道没有调用我们自己写的user32.dll么?但是链接的时候明明是链接我们的lib文件的呀!!!这时候你是不是觉得很奇怪?这个问题留给你了(跟PE格式、系统有关)。(可以参考win loader (注:不是boot loader,是可执行文件加载器))


调用库lib中没有的函数

记得网上一直流传着一个函数叫 SetLayeredWindowAttributes 可以改变窗体,可是调用这个函数的方法都是先LoadLibrary 再 GetProAddress 地址以后调用的,那么可以利用上面调用MessageBoxA的方法去调用这个函数了,再在上面的代码中加入如下代码:

  1. SetLayeredWindowAttributes      proc        _dwNumber1:DWORD,  
  2.                                             _dwNumber2:DWORD,  
  3.                                             _dwNumber3:DWORD,  
  4.                                             _dwNumber4:DWORD  
  5.                                                                                               
  6.                                    xor   eax,eax  
  7.                                    ret  
  8. SetLayeredWindowAttributes      endp  
SetLayeredWindowAttributes      proc        _dwNumber1:DWORD,
                                            _dwNumber2:DWORD,
                                            _dwNumber3:DWORD,
                                            _dwNumber4:DWORD
                                                                                            
                                   xor   eax,eax
                                   ret
SetLayeredWindowAttributes      endp

导出函数也相应的加一个SetLayeredWindowAttributes

编译出user32.lib文件,重新命名为myuser32.lib


再写一个窗体程序,直接加入SetLayeredWindowAttributes声明,myuser32.lib库 后调用,编译通过。

  1. #pragma comment(lib,"myuser32.lib")   
  2. #pragma comment(lib,"user32.lib")   
  3.    
  4. #include <windows.h>   
  5.    
  6. #ifdef __cplusplus   
  7. extern "C" {  
  8. #endif   
  9.    
  10. long WINAPI SetLayeredWindowAttributes(   HWND  hWnd,  
  11.                                           int   cr,  
  12.                                           unsigned char bAlpha,  
  13.                                           unsigned long dwFlags);  
  14. #ifdef __cplusplus   
  15. };  
  16. #endif   
  17. //…… 略  
#pragma comment(lib,"myuser32.lib")
#pragma comment(lib,"user32.lib")
 
#include <windows.h>
 
#ifdef __cplusplus
extern "C" {
#endif
 
long WINAPI SetLayeredWindowAttributes(   HWND  hWnd,
                                          int   cr,
                                          unsigned char bAlpha,
                                          unsigned long dwFlags);
#ifdef __cplusplus
};
#endif
//…… 略

完整的示例代码见下载包(MicroSoft编译器你不可不知的秘密\自己构建lib文件调用\窗口透明

 

调用Native API

所谓的应用层Native API就是ntdll导出的函数。这些函数大部分是直接通过调用门进入系统内核运行的,在ring3中是比较底层的函数(或许自己利用int 0x2e、sysenter进入内核才算底层吧),对于杀毒软件的主动防御有很好的免杀作用(比如ReadProcessMemory,CreateRemoteThread替换成更底层的NtReadVirtualMemory、NtCreateThread可以解决ring3的主动防御、导入表查杀)。所以Native API在安全方面有着重要的作用。

微软一直强调不要调用Native API 函数,以后可能会没有这个函数或者参数改变,在ring3可以完全不必担心这个问题,因为在驱动中早有Zw函数是文档化的了,而Zw函数众所周知是跟Nt函数同参数的,所以ntdll中的函数发生变化可能性非常小,再者如果参数变化也无妨,最多就本程序奔溃,不会影响到系统。

下载包中带了几个ntdll.lib文件(MicroSoft编译器你不可不知的秘密\直接调用ntdll函数\ntdll),可以接加入工程链接,如果发现这个lib中没有的函数,那么利用上面说的方法自己创建一个ntdll.lib文件导入工程。

例子代码中有对NtAllocateVirtualMemory的调用,其他函数方法类似,关于函数的声明直接到wdk文档中找,或者微软在线帮助(把函数名改成Zw开头,如ZwAllocateVirtualMemory)

  1. #pragma once   
  2. #ifndef __NTDLL__HH_   
  3. #define __NTDLL__HH_   
  4.    
  5. #pragma comment(lib, "ntdll")   
  6. #include<windows.h>   
  7.    
  8. typedef unsigned longULONG_PTR;  
  9. #define NTSTATUS  LONG   
  10.    
  11. #ifdef  __cplusplus   
  12. extern"C"{  
  13. #endif   
  14.    
  15. NTSTATUS   
  16. WINAPI  
  17. NtAllocateVirtualMemory(  
  18.                          HANDLE  ProcessHandle,  
  19.                          PVOID*  BaseAddress,  
  20.                          ULONG_PTR ZeroBits,  
  21.                          PSIZE_T  RegionSize,  
  22.                          ULONG  AllocationType,  
  23.                          ULONG Protect  
  24.                        );  
  25.    
  26. #ifdef  __cplusplus   
  27. };  
  28. #endif   
  29.    
  30. #endif // __NTDLL__HH_  
#pragma once
#ifndef __NTDLL__HH_
#define __NTDLL__HH_
 
#pragma comment(lib, "ntdll")
#include<windows.h>
 
typedef unsigned long* ULONG_PTR;
#define NTSTATUS  LONG
 
#ifdef  __cplusplus
extern"C"{
#endif
 
NTSTATUS 
WINAPI
NtAllocateVirtualMemory(
                         HANDLE  ProcessHandle,
                         PVOID*  BaseAddress,
                         ULONG_PTR ZeroBits,
                         PSIZE_T  RegionSize,
                         ULONG  AllocationType,
                         ULONG Protect
                       );
 
#ifdef  __cplusplus
};
#endif
 
#endif // __NTDLL__HH_
可以写个ntdll.h的头文件,以后要用ntdll函数的时候就添加这个头文件,没有的函数就继续加进去,这样这个头文件就日渐的完善了。不过像CreateRemoteThread这种函数在ntdll中实现还是比较蛋疼的,要想让这个函数跟原来的运行效果一样还要下一点功夫才行,涉及的函数涉及的数据相当多,不过奖品也是很丰盛的,你用你自己实现方法,360就不会吭声了。


本文档下载地址:http://ishare.iask.sina.com.cn/f/35074156.html

示例程序下载地址:http://ishare.iask.sina.com.cn/f/35074546.html

                                       http://kuai.xunlei.com/d/gW0GA9itjFfNUAQA5ac

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值