//时:1:01 2011-5-1
//空:ia32 cpu + win xp sp3 + vc6.0 + windbg
//人:by reduta
//事:理解windows api的机制
一、windows api地址定位机制
众所周知,windows api函数有windows的一些关键的dll函数所导出,因此这里从库说起
为什么引入库?在编程的过程中人们发现很多程序使用相同的代码,为了方便编程,人们将这些代码提取出来即为库
静态链接是什么?静态链接是将库和程序合二为一,这在链接时有链接器完成。
为什么引入动态链接库?因为静态库是将程序和库合二为一,这样每个程序都有一个库的副本,并不能实现运行时库的共享,另一方面不方便对库进行更新。
使用动态链接库需要解决的问题?使用动态链接库要解决四个问题,使用哪个库?库在哪?使用库中的哪些函数?所需库函数的地址在哪?
1、需要哪个库------IMAGE_IMPORT_DESCRIPTOR的Name域指明
2、库在哪?------dll重定向文件夹(即*.exe.local 文件夹)---knowdlls-----当前目录---win/sys/set环境变量中的库
3、需要库中的哪些函数---IMAGE_IMPORT_DESCRIPTOR的int来指明
4、所需库函数的地址----eat中查找,iat中调用 /*/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//时:2011-5-1
//空:IA32 + Winxp sp3 + vc6.0 (unicode编码)
//人:by reduta
//事:理解windows api函数(pe观点)
*/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define USE_UNICODE
#include "head.h"
struct PEMAP
{
HANDLE hFile;
HANDLE hMap;
LPVOID lpBase;
}pe = {NULL};
//宏函数
#define FIND_PE_HEADER(lpBase) \
PIMAGE_DOS_HEADER pdos = (PIMAGE_DOS_HEADER)lpBase; \
PIMAGE_NT_HEADERS pnt = (PIMAGE_NT_HEADERS)((PBYTE)lpBase + pdos->e_lfanew);\ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL Constrcutor(TCHAR *szPeFile);
VOID Deconstructor();
BOOL IsPe(LPVOID lpBase);
DWORD RvaToOffset(LPVOID lpBase,DWORD dwRva,PDWORD dwSecOffset = NULL);
VOID ShowImportInfo(LPVOID lpBase);
VOID ShowExportInfo(LPVOID lpBase);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int _tmain(int ac,TCHAR **pav)
{
//参数不对
if (ac!=3)
{
cout<<"*.exe import pefile"<<endl
<<"*.exe export pefile"<<endl;
return 0;
}
//构造出错
if (!Constrcutor(pav[2]))
{
return 0;
}
//不是pe
if (!IsPe(pe.lpBase))
{
cout<<"the file not pe!"<<endl;
return 0;
}
if (CMP_STR(3,1,import))
{
ShowImportInfo(pe.lpBase);
}
if (CMP_STR(3,1,EXPORT))
{
ShowExportInfo(pe.lpBase);
}
//析
Deconstructor();
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL Constrcutor(TCHAR *szPeFile)
{
//fileobj
pe.hFile = CreateFile(szPeFile,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
if (pe.hFile==INVALID_HANDLE_VALUE)
{
LAST_ERROR(CreateFile);
return FALSE;
}
//mapobj
pe.hMap = CreateFileMapping(pe.hFile,NULL,PAGE_READONLY,0,0,NULL);
if (pe.hMap==NULL)
{
LAST_ERROR(CreateFileMapping);
CloseHandle(pe.hFile);
return FALSE;
}
//viewobj
pe.lpBase = MapViewOfFile(pe.hMap,FILE_MAP_READ,0,0,0);
if (pe.lpBase==NULL)
{
LAST_ERROR(MapViewOfFile);
CloseHandle(pe.hMap);
CloseHandle(pe.hFile);
return FALSE;
}
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//析
VOID Deconstructor()
{
UnmapViewOfFile(pe.lpBase);
CloseHandle(pe.hMap);
CloseHandle(pe.hFile);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//判断是否为PE
BOOL IsPe(LPVOID lpBase)
{
FIND_PE_HEADER(lpBase);
return (pdos->e_magic!=IMAGE_DOS_SIGNATURE || pnt->Signature!=IMAGE_NT_SIGNATURE) ? FALSE : TRUE;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//计算节偏移
DWORD RvaToOffset(LPVOID lpBase,DWORD dwRva,PDWORD dwSecOffset /* = NULL */)
{
FIND_PE_HEADER(lpBase);
PIMAGE_SECTION_HEADER pfirst = (PIMAGE_SECTION_HEADER)((PBYTE)lpBase + pdos->e_lfanew +
sizeof IMAGE_NT_SIGNATURE + sizeof IMAGE_FILE_HEADER + pnt->FileHeader.SizeOfOptionalHeader);
DWORD dwOffset = 0;
for (int inx=0; inx!=pnt->FileHeader.NumberOfSections; ++inx)
{
if (inx==pnt->FileHeader.NumberOfSections - 1)
{
dwOffset = pfirst[inx].VirtualAddress - pfirst[inx].PointerToRawData;
break;
}
if (dwRva >= pfirst[inx].VirtualAddress && dwRva < pfirst[inx+1].VirtualAddress)
{
dwOffset = pfirst[inx].VirtualAddress - pfirst[inx].PointerToRawData;
break;
}
}
if (dwSecOffset!=NULL)
{
*dwSecOffset = dwOffset;
}
return dwRva - dwOffset;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//输出导入表信息
VOID ShowImportInfo(LPVOID lpBase)
{
FIND_PE_HEADER(lpBase);
PIMAGE_IMPORT_DESCRIPTOR piid = (PIMAGE_IMPORT_DESCRIPTOR)((PBYTE)lpBase +
RvaToOffset(lpBase,pnt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
char *szDllName = NULL;
PDWORD pint = NULL;
PIMAGE_IMPORT_BY_NAME pfunc = NULL;
while (piid->OriginalFirstThunk)
{
szDllName = (char *)((PBYTE)lpBase + RvaToOffset(lpBase,piid->Name));
cout<<"^_^_____________________"<<szDllName<<"______________________^_^"<<endl;
pint = (PDWORD)((PBYTE)lpBase + RvaToOffset(lpBase,piid->OriginalFirstThunk));
while (*pint)
{
if ((*pint & IMAGE_ORDINAL_FLAG32)==IMAGE_ORDINAL_FLAG32)
{
cout<<"ordinal import:"<<hex<<*pint<<endl;
}
else
{
pfunc = (PIMAGE_IMPORT_BY_NAME)((PBYTE)lpBase + RvaToOffset(lpBase,*pint));
cout<<"name import hint:"<<pfunc->Hint<<" name:"<<pfunc->Name<<endl;
}
++pint;
}
++piid;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID ShowExportInfo(LPVOID lpBase)
{
FIND_PE_HEADER(lpBase);
PIMAGE_EXPORT_DIRECTORY pexd = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)lpBase +
RvaToOffset(lpBase,pnt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));
PDWORD pent = (PDWORD)((PBYTE)lpBase + RvaToOffset(lpBase,pexd->AddressOfNames));
PWORD pordinal = (PWORD)((PBYTE)lpBase + RvaToOffset(lpBase,pexd->AddressOfNameOrdinals));
PDWORD peat = (PDWORD)((PBYTE)lpBase + RvaToOffset(lpBase,pexd->AddressOfFunctions));
for (int inx=0; inx!=pexd->NumberOfNames; ++inx)
{
cout<<"name:"<<(char *)((PBYTE)lpBase + RvaToOffset(lpBase,pent[inx]))<<" ordinal:"<<
hex<<pordinal[inx] + pexd->Base<<" addr"<<peat[pordinal[inx]]<<endl;
}
}
///////////////////////////////////#END#////////////////////////////////////////////////////////////////////////////
二、windows api的系统调用机制
1、何为操作系统?
理解操作系统的概念-----操作系统是硬件与用户之间的一层媒介程序,既然是硬件和软件之间的一层媒介程序,因此它要完成的任务不外乎两点。
其一有效管理硬件,其二提供用户接口,管理硬件不外乎 CPU调度管理 进线程管理 内存管理 文件系统管理 I/O管理等等,提供的用户接口不外乎
控制台(consonle)、图形界面(GUI)以及API函数接口。
针对某个系统的编程主要是API的调用,此时从应用程序角度来说,操作系统就是一个大的程序运行库。即对API函数的调用就是操作系统功能的
一次调用。
2、windows系统调用机制
//理论
旧的windows系统调用借助于int 0x2e,类似于linux中的0x80,有它产生一个中断,而后CPU调用0x2e所对应的中断处理程序,即KiSystemService,有KiSystemService
根据调用的服务序号查询 系统服务描述符表(System service descriptor table SSDT)或者(System service descriptor Table shadow)后者用于图形界面,实现
在win32k.sys中。
//调试--windbg中执行!idt /a 0x2e即可查看到该中断向量号所对应的中断处理程序
kd> !idt /a 0x2e
Dumping IDT:
2e: 8088374a nt!KiSystemService
//ia32中关于系统调用时的内容
//调用时
1、performs an access rights check(privilege check).
执行访问特权检查
2、temporarily saves(internally) the current contents of the ss esp cs and eip registers。
临时保存当前特权级中的ss esp cs和eip寄存器内容。
3、loads the segment selector and stack pointer for the new stack(that is,the stack for the privilege being called) from the tss into the ss and esp
registers and switches to the new stack.
从TSS(任务状态段中)取出ss和esp寄存器的内容,为新栈加载段选择子和栈指针。(新栈指的是特权切换后的栈,就windows来说就是系统调用后的ring0中的系统栈). 4、pushes the temporarily saved ss and esp values for the calling procedure's stack onto the new stack。
将临时保存的ss和esp值压入到新栈中(就windows说临时的保存值指的是ring3环中的context)。
5、copies the parameters from the calling procedure's stack to the new stack,a value in the call gate descritor determines how many parameters to
copy to the new stack.
复制系统调用的参数到新栈中,在门描述符中的一个值决定了要复制多少个参数。
6、pushes the temporarily saved cs and eip values for the calling procedure to the new stack.
将临时保存的cs和eip值压入新栈。
7、loads the segment slector for the new code segment and the new instrcution pointer from the call gate into the cs and eip registers,repectively.
从调用门中将CS和eip寄存器的值分别加载到新的段选择子和新eip中。
8、begins execution of the called procedure at the new privilege level。
在新特权级中开始执行调用。 //通过上面的过程可以看出系统栈中的内容排布为
ring3 ss //ring3的ss段选择子
ring3 esp //ring3的esp
---------------------------//保存ring3的线程栈
arg..... //ring3 call的参数
---------------------------//保存ring3线程调用时的参数
ring0 cs //ring0的cs段选择子
ring0 eip //ring0的eip
---------------------------//系统调用的开始。
与之相关的关键数据结构即为tss(任务状态段和门描述符)。与之相关的关键操作是特权访问检查。
//返回
1、performs a privilege check.
执行特权检查
2、restores the cs and eip register to their values point to call.
保存cs和eip寄存器的值到先前的call过程中。
3、if the ret instruction has an optional n argument,increments the stack pointer by the number of bytes specified with the n operand to release
parameters from the stack. if the call gate descriptor specifies that one or more parameters be copied from one stack to the other,a ret n instrcution
must be used to release bytes occupied on each stack by the parameters,on a retun ,the processor increments esp by n for each stack to step
over(effectively remove ) these parameters from the stacks.
如果ret指令后面有可选的n参数(类似ret 1/2/3),n指的是以字节为单位,指明从栈中需要释放的空间。如果调用门描述符指明一个或多个参数被复制(从一个栈到另
一个栈),ret n指令必须在每一个栈中释放n字节,在一个返回中,处理器给esp增加n从而步过栈上的参数。
4、restores the ss and esp registers to their values prior to the call,which causes a switch back to the stack of the calling procedure.
重新保存ss和esp寄存器的值到先前那个引发调用的call中。
5、if the ret instruction has an optionl n argument,increments the stack pointer by the number of bytes specified with the n operand to release
parameters from the stack(see explanation in step3).
如果ret指令后有可选的n参数,栈指针esp增加n用于释放栈上的参数(参考第3步)。
6、resumes execution of the calling procedure
释放系统调用。 //理论
旧的系统调用方法涉及到内存的访问,因此它是不效率的为此intel提供了sysenter/sysexit(ia32)和syscall/sysret(ia64)指令用于快速调用。具体的指令使用在
(ia32开发手册Volume.2B 中)。 //IA32中关于sysenter/sysexit的描述
//sysenter
1、loads the segment selector from the ia32_sysenter_cs into
the cs register
加载ia32_sysenter_cs到cs寄存器.
2、loads the instruction pointer from the ia32_sysenter_eip into
the eip register.
加载ia32_sysenter_eip到eip寄存器
3、adds 8 the value in ia32_sysenter_cs and loads it into the ss
register.
在ia32_sysenter_cs中的值加8,将和加载到ss寄存器.
4、loads the stack pointer from the ia32_sysenter_esp into the esp
register.
加载ia32_sysenter_esp到esp寄存器
5、switches to privilege level 0
切换到0环。
6、clear the vm flag in the efalgs register ,if the flag is set.
如果虚拟8086模式开启,该指令会将其清除。
7、begins executing the selected system procedure.
开始执行选择的系统过程。
IA32_SYSENTER_CS 0x174
IA32_SYSENTER_ESP 0x175
IA32_SYSENTER_EIP 0x176
//sysexit
1、add 16 to the value in ia32_sysenter_cs and loads the sum into the
cs slector register.
ia32_sysenter_cs加上16,把和加载到cs段选择子寄存器中。
2、loads the instruction pointer from the edx register into the eip
register
从edx寄存器取指令指针保存到eip寄存器中。
3、adds 24 to the value in ia32_sysenter_cs and loads the sum into
the ss selector register.
ia32_sysenter_cs加24并将和保存到ss段选择子寄存器。
4、loads the stack pointer from the ecx register into the esp reg.
从ecx中取栈针保存到esp寄存器。
5、swiches to the privilege level 3
切换到ring3.
6、begins executing the user code at the eip address.
在eip处开始执行用户代码。 //源
#define USE_UNICODE
#include "head.h"
int _tmain(int ac,TCHAR **pav)
{
__asm int 3
HANDLE hFile = CreateFile(_TT(cmd.exe),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
if (hFile!=INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
}
return 0;
} //调试
调试器中设置好应用程序的符号表,在虚拟机中运行程序,程序中断到windbg中。
001b:00401578 cc int 3 //wmain函数中断处
001b:00401579 8bf4 mov esi,esp
001b:0040157b 6a00 push 0
001b:0040157d 6a00 push 0
001b:0040157f 6a03 push 3
001b:00401581 6a00 push 0
001b:00401583 6a01 push 1
001b:00401585 6800000080 push 80000000h
001b:0040158a 681c304700 push 47301Ch
001b:0040158f ff15e8414800 call dword ptr ds:[4841E8h]
....................................
kd> bp ntdll!ntcreatefile //下ring3中的createfile断点
kd> g //跑起程序
//反汇编窗口中的代码
.....................
001b:7c956ddf b827000000 mov eax,27h ;服务序号(指的是SSDT中的)
001b:7c956de4 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300) ;系统服务分发函数 KiFastSystemCall
001b:7c956de9 ff12 call dword ptr [edx] ds:0023:7ffe0300={ntdll!KiFastSystemCall (7c9585e8)}
001b:7c956deb c22c00 ret 2Ch ;返回地址
kd> t //跟进KiFastSystemCall
//KiSystemCall系统服务分发器的代码
01b:7c9585e8 8bd4 mov edx,esp //保存返回地址到edx中,执行完ring0的过程返回到该地址处
001b:7c9585ea 0f34 sysenter kd> rdmsr 176 //取sysenter进入ring0后的eip地址---(详见IA32-2b参考手册)
msr[176] = 00000000`80883810
kd> bp 80883810 //在进入ring0的eip地址处下断
kd> g //执行到ring0入口 kd> rdmsr 174 //取cs段选择子
msr[174] = 00000000`00000008 //其特权进行了变更
kd> dg 8
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0008 00000000 ffffffff Code RE 0 Bg Pg P Nl 00000c9a
kd> rdmsr 175 //取esp
msr[175] = 00000000`f78a7000
kd> r //取当前寄存器的内容对比查询到的数值
eax=00001141 ebx=00000000 ecx=00000023 edx=0007fc74 esi=0000000d edi=0000000e
eip=80883817 esp=f78a6ffc ebp=0007fcc0 iopl=0 nv up di pl nz ac po cy
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000013
nt!KiFastCallEntry+0x7:
80883817 0fa1 pop fs
//反汇编窗口
nt!KiFastCallEntry: ;调用nt!KiFastCallEntry函数
80883810 b923000000 mov ecx,23h ;ecx=0x23
80883815 6a30 push 30h ;
80883817 0fa1 pop fs ;完成fs寄存器的赋值操作
80883819 8ed9 mov ds,cx ;设置ds段选择子
8088381b 8ec1 mov es,cx ;设置es段选择子
...........................................................
(KiFastEntry的参考代码在reactos\ke\i386\trap.asm中) //理解快速调用(系统调用需要三步走,其一保存ring3,其二、运行ring0,其三、返回ring3
//与快速调用相关的三个ring3函数
kd> u ntdll!kifastsystemcall
ntdll!KiFastSystemCall:
7c9585e8 8bd4 mov edx,esp //这里取到了ring3下的esp
7c9585ea 0f34 sysenter //进入借助于 ia32_sysenter_msr寄存器完成设置CS/SS/EIP的问题
ntdll!KiFastSystemCallRet:
7c9585ec c3 ret //返回ring3的地址
7c9585ed 8da42400000000 lea esp,[esp]
7c9585f4 8d642400 lea esp,[esp]
1、保存ring3 --ring3的栈在进入时保存 ring3的返回地址在内核初始化时保存于kuser_shared_data中,这个结构是一个共享结构,在ring3和ring0都存在。
ring3中比较典型的代表是GetTickCount(),反汇编该API即会发现它是直接操作的kuser_shared_data中的数据成员
kd> dt _kuser_shared_data
ntdll!_KUSER_SHARED_DATA
........................................
+0x2f0 TraceLogging : Uint4B
+0x2f8 TestRetInstruction : Uint8B
+0x300 SystemCall : Uint4B
+0x304 SystemCallReturn : Uint4B //在内核初始化时已经将ring3要返回的地址保存于此处了
+0x308 SystemCallPad : [3] Uint8B
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : Uint8B
.......................................
2、运行ring0---运行ring0这借助于IA32_SYSENTER_MSR寄存器,IAT32_SYSENTER_EIP保存有内核运行的eip,IA32_SYSENTER_CS保存有选择子,其值为0x8,
+8即0x10则为SS段选择子。然后进入KiFastCallEntry()进行相关服务的执行。
3、返回--就是sysexit指令的功能了。 三、小结
存储程序原理决定了程序的两种状态---存储态(文件态)和运行态(线程态),以套子理论可知,api函数也存在这两种特点。文件态以pe结构为基础,运行态以系统调用
为根基。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课