标题: 【原创】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: 我用的控制台编译,之前我就说过了)。脚本如下形式:
@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. 头文件
int main(int argc, char* argv)
{
return 0;
}
以上代码直接在控制台编译通过很正常。那以下呢?
int main(int argc, char* argv)
{
printf(“hello world!\n”);
return 0;
}
编译器会拒绝编译,显示printf函数未定义,通常的做法是加入stdio.h头文件。可是,如果不加这个头文件呢,可否有方案解决这个文件?答案是:有。
在调用printf之前加入声明、编译时加入msvcrt.lib即可。
#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语言版)
.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可以导出自己想要的任何名字,还可以选择不导出名字)
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文件:
#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的方法去调用这个函数了,再在上面的代码中加入如下代码:
SetLayeredWindowAttributes proc _dwNumber1:DWORD,
_dwNumber2:DWORD,
_dwNumber3:DWORD,
_dwNumber4:DWORD
xor eax,eax
ret
SetLayeredWindowAttributes endp
导出函数也相应的加一个SetLayeredWindowAttributes。
编译出user32.lib文件,重新命名为myuser32.lib
再写一个窗体程序,直接加入SetLayeredWindowAttributes声明,myuser32.lib库 后调用,编译通过。
#pragma comment(lib,"myuser32.lib")
#pragma comment(lib,"user32.lib")
#include
#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)
#pragma once
#ifndef __NTDLL__HH_
#define __NTDLL__HH_
#pragma comment(lib, "ntdll")
#include
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