DLL 线程本地存储

1.概览
.构造DLL   
   (1) 仅导出函数
      DLL 可以导出全局变量和类,但我们不建议这么做,建议导出函数。
   (2).lib
       每个 DLL 都有与之相对应的 .lib 文件 , 该文件中列出了 DLL 中导出的函数和变量的符号名
   (3) 指定要导出的函数名
       因为不同编译器的 Name mangle 规则不同,这就导致 DLL 不能跨编译器使用。
       有以下两种方法可以解决这个问题:
            1. .def 文件中指定要导出的函数名
            2. 在编译指中指定要导出的函数名:
                #pragma comment(linker, "/export:MyFunc=_MyFunc@8")
.DLL加载路径
     当需要加载一个 DLL 时,系统会依照下面的顺序去寻找所需 DLL 直到找到为止,然后加载,否则加载失败。
              (1) 当前可执行文件路径
              (2)GetWindowsDirectory 返回的Windows系统路径
              (3)16 位系统的路径 windows"system
              (4)GetSystemDirectory 返回的Windows系统路径
              (5) 当前进程所在路径
              (6)PATH 环境中所指定的路径
­
.创建/使用动态链接库
首先必须创建一个包含需要导出的符号的头文件,以便其他程序链接到该dll上:
// dllexample.h
#ifdef DLLEXAMPLE_EXPORTS // 在编译命令中已定义,所以实际用的是 __declspec(dllexport)
#define DLLEXAMPLE_API __declspec(dllexport)
#else
#define DLLEXAMPLE_API __declspec(dllimport)
#endif
DLLEXAMPLE_API int fnDllexample(void);
当其他应用包含该头文件,意图使用该dll的导出符号时,因为没有定义DLLEXAMPLE_EXPORTS,所以使用的是 __declspec(dllimport),这样编译器编译时便知道这是从外部引入的函数。在链接时,链接程序将生成导入表 (ImportAddressTable),该表罗列了所有调用到的函数,以及一个空白的对应地址。在程序执行时,加载器将动态的填入每个函数符号在本进 程中的地址,使得程序能正确的调用到dll中的函数上。
这种通过dll提供的.h和.lib文件进行链接dll的使用方式,称为 隐式链接 。用vc开发程序时,几乎所有的系统API调用都用了隐式链接。
.显式链接
在exe创建时不引用.lib文件中的符号,当然也不必包含.h头文件,而是由程序调用LoadLibrary(Ex)以及 GetProcAddress函数来获取每个需要使用的函数地址,从而进行dll中的函数调用,这种dll使用方法称为显式链接。显式链接时不生成对应 dll的IAT.
当决定不再使用该dll时,通过调用FreeLibrary来卸载。需要注意的是,同一个进程中共计调用LoadLibrary的次数要和调用FreeLibrary的次数相等,因为系统维护了一个使用计数,当计数为0时,才会真正的卸载该dll.
如果想确认一个dll是否已经被映射到进程空间中,尽量使用GetModuleHandle,最好不要冒然使用LoadLibrary(Ex).
GetProcAddress可以传递函数名或者序号(通过MAKEINTRESOURCE(2)来"制作"序号).
­
1.1动态加载DLL文件 LoadLibraryEx
HMODULE LoadLibraryEx( // 返回 DLL 加载到进程空间原首地址。
PCTSTR pszDLLPathName,
HANDLE hFile,
DWORD dwFlags );
dwFlags 可以有以下几个值
              (1) DONT_RESOLVE_DLL_REFERENCES
                                    建议永远不要使有这个值,它的存在仅仅是为了向后兼容、
                   更多内容请访问: http://blogs.msdn.com/oldnewthing/archive/2005/02/14/372266.aspx
              (2) LOAD_LIBRARY_AS_DATAFILE
                   把要加载的 DLL 文件以数据文件的形式加载到进程中。
                  GetModuleHandle GetProcAddress 返回 NULL
              (3) LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE
                   与前者相同,不同的时独占打开,禁止其它进程访问和修改该 DLL 中的内容。
              (4) LOAD_LIBRARY_AS_IMAGE_RESOURCE
                   不修改 DLL 中的 RVA ,以 image 的形式加载到进程中。常与 LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE 一起使用。
              (5) LOAD_WITH_ALTERED_SEARCH_PATH
                   修改 DLL 的加载路径
1.2 DLL 的加载与卸载
    (1) 加载
       不要在同一进程中,同时使用 LoadLIbrary LoadLibraryEx 加载同一 DLL 文件。
       DLL 的引用计数是以进程为单位的。 LoadLibrary 会把 DLL 文件加载到内存,然后映射到进程空间中。
       多次加载同一 DLL 只会增加引用计数而不会多次映射。当所有进程对 DLL 的引用计数都为 0 时,系统会在内存中释放该 DLL
    (2) 卸载
         FreeLibrary,FreeLibraryAndExitThread 对当前进程的 DLL 的引用计数减 1
    (3) GetProcAddress
         取得函数地址。它只接受 ANSI 字符串。
2.DLL的入口函数
      2.1 DllMain
              BOOL WINAPI DllMain(
              HINSTANCE hInstDll, "" 加载后在进程中的虚拟地址
              DWORD fdwReason, "" 系统因何而调用该函数
              PVOID fImpLoad "" 查看是隐工还是动态加载该 DLL
    DLLs DllMain 方法来初始化他们自已。 DllMain 中的代码应尽量简单,只做一些简单的初始化工作。
     不要在 DllMain 中调用 LoadLibrary,FreeLibrary Shell, ODBC, COM, RPC, socket 函数,从而避免不可预期的错误。
  2.2 fdwReason的值
    (1)DLL_PROCESS_ATTACH
       系统在为每个进程第一次加载该 DLL 时会,执行 DLL_PROCESS_ATTACH 后面的语句来初始化 DLL,DllMain 的返回值仅由它决定。
     系统会忽略 DLL_THREAD_ATTACH 等执行后 DllMain 的返回值。
     如果 DllMain 返回 FALSE, 系统会自动调用 DLL_PROCESS_DETACH 的代码并解除 DLL 文件中进程中的内存映射。         
    (2)DLL_PROCESS_DETACH
         如果 DLL 是因进程终止而卸载其在进程中的映射,那么负责调用 ExitProcess 的线程会调用 DllMain DLL_PROCESS_DETACH 所对应的代码。
         如果 DLL 是因 FreeLibrary FreeLibraryAndExitThread ,而卸载其在进程中的映射, 那么 FreeLibrary FreeLibraryAndExitThread 会负责调用 DllMain DLL_PROCESS_DETACH 所对应的代码。
         如果 DLL 是因 TerminateProcess 而卸载其在进程中的映射 , 系统不会调用 DllMain DLL_PROCESS_DETACH 所对应的代码。
    (3) DLL_THREAD_ATTACH
         若进程是先加载的 DLL, 后创建的线程
         那么在进程中创建新线程时 ( 主线程除外 ) ,系统会执行该进程已载的所有 DLL DllMain DLL_THREAD_ATTACH 对应的代码。
         若进程是先创建的线程 , 后加载的 DLL
         那么系统不会调用 DLL DllMain 中的代码。
     (4) DLL_THREAD_DETACH
         进程中的线程退出时,会先执行所有已加载 DLL DllMain DLL_THREAD_DETACH 所对应的代码。若该代码中有死循环,线程不会退出。               
2.3 同步化DllMain的调用
       同一时间只能有一个线程调用 DllMain 中的代码,所以下面的代码会导致死循环
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, PVOID fImpLoad) {
   HANDLE hThread;
   DWORD dwThreadId;
   switch (fdwReason) {
   case DLL_PROCESS_ATTACH :
      // The DLL is being mapped into the process' address space.
      // Create a thread to do some stuff.
      hThread = CreateThread(NULL, 0, SomeFunction, NULL,
         0, &dwThreadId);// CreateThread DLL_THREAD_ATTACH 中的代码,但是由于当前线程并未执行完毕 ,
      // 所以 DLL_THREAD_ATTACH 中的代码不会被执行,且 CreateThread 永无不会返回。
      // Suspend our thread until the new thread terminates.
      WaitForSingleObject(hThread, INFINITE);
­
      // We no longer need access to the new thread.
      CloseHandle(hThread);
      break;
­
   case DLL_THREAD_ATTACH :
      // A thread is being created.
      break;
­
   case DLL_THREAD_DETACH:
      // A thread is exiting cleanly.
      break;
­
   case DLL_PROCESS_DETACH:
      // The DLL is being unmapped from the process' address space.
      break;
   }
   return(TRUE);
}
­
3.延时加载DLL
(1) 延时加载 DLL 的限制
         延迟加载的D L L是个隐含链接的D L L,它实际上要等到你的代码试图引用D L L中包含的一个符号时才进行加载 ,它与动态加载不同。
4.已知的DLL (Known DLLs)
     位置: HKEY_LOCAL_MACHINE"SYSTEM"CurrentControlSet"Control"Session Manager"KnownDLLs
    LoadLibrary 在查找 DLL 会先去该位置查找有无相应的键值与 DLL 要对应,若有则根据链值去 %SystemRoot%"System32 加载键值对应的 DLL
     若无则根据默认规去寻找 DLL
5.Bind and Rebase Module
     它可以程序启动的速度。 ReBaseImage
­
DLL 注入和 API (DLL Injection and API Hooking)
1.概览
   每个进程都有自已独立的地址空间,一个进程不可能创建一个指向其它进程地址空间的指针。
   然而如果我们把自已的 DLL 注射到另一个进程的地址空间去,我们就可以在那个被注入的进程里为所欲为了。
   Subclass 同一进程中的窗体: http://msdn2.microsoft.com/en-us/library/ms649784.aspx .
2.用注册表注入DLL
     该方法适用于给 GUI 的程序注入 DLL
     所有的 GUI 应用程序在启动时都会加载 User32.dll ,而在 User32.dll DLL_PROCESS_ATTACH 代码根据注册表中的信息
来注入用户指定的DLL
注册表项 HKEY_LOCAL_MACHINE"Software"Microsoft"Windows NT"CurrentVersion"Windows"
中有两个值:
    LoadAppInit_Dlls :键值中指定要注入的 DLL  如 :c:"inject.dll
AppInit_Dlls :若其键值为 1, 则注入 LoadAppInit_Dlls 中指定的 DLL ,否则若为 0 则不注入。
     :
(1)LoadAppInit_Dlls 中的值是以空格或分号分隔的,所以 DLL 的路径中最好不要有空格,最后不指定路径,直接将 DLL 放到 windows 系统目录中。
(2) 用注册表注入 DLL 的方式有很大的局限性 ,Kernel32.dll Ntdll.dll 中有的函数才能调用
一.注入dll
1 .通过注册表项 HKEY_LOCAL_MACHINE/Software/Microsoft/Windows NT/CurrentVersion/Windows/AppInit_DLLs 来指定你的dll的路径,那么当一个GUI程序启动时就要加载User32.dll,而User32.dll将会检查这个值,如果有的话就 LoadLibrary该Dll。这个方法不好,因为大多数情况我们只需要针对性的注入,并且没办法注入到不使用User32.dll的进程中;
­
2 .用SetWindowsHookEx函数,并传递目标线程ID、需要挂载的Dll在本进程中的映射地址 (hInstance)、替换函数在本进程中的地址。这样,当被挂载进程的这个线程要执行相应的操作时(GETMESSAGE、键盘消息之类的),就会发 现已经安装了WH_XX,The system checks to see whether the DLL containing the GetMsgProc function is mapped into Process B's address space,如果还未映射该Dll,则强制LoadLibrary。然后系统调用hThisInstance + (GetMsgProc - hInstance),从而实现了事件的通知。这种方法的好处是可以针对某个进程安装Hook,缺点是容易被目标进程发现、同样只适用于GUI进程。如果 不再想使用挂钩了,那么需要调用UnhookWindowsHookEx,卸载Hook。
­
3 .使用远程线程注入Dll(Injecting a DLL Using Remote Threads)
这个方法比较好。流程是这样的:
?调用VirtualAllocEx,在目标进程保留一块内存,并提交,其长度是你要注入Dll的全路径长度nLen + 1,返回地址pv;
?调用WriteProcessMemory,在目标进程的pv处写入Dll的全路径,注意要添加/0结束符;
?获取 本进程 的LoadLibrary函数的地址,方法是调用pfn = GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA")——之所以获取本进程的地址,是因为kernel32.dll在每个进程的映射地址都相同,倘若不同,那么此方法则无效;
?调用HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0,  pfn, pv, 0, NULL)来创建远程线程,其实这个线程函数就是LoadLibrary函数,因此将执行映射Dll到目标进程的操作;
?调用VirtuallFreeEx(hProcessRemote, pv)释放提交的内存;
这便完成了dll注入。
缺点是不能用在windows98上。但是对于xp都要被微软抛弃的年代,windows98地影响不大了。
­
4 .披着羊皮的狼:使用特洛伊Dll来注入Dll(Injecting a DLL with a Trojan DLL)
其实就是替换某个目标进程要加载的a.dll,并把a.dll的所有引出函数用函数转发器在自己的dll引出。
­
5 .用调试函数插入Dll
ReadProcessMemory和WriteProcessMemory是windows提供的调试函数。如果在方法3中调用 WriteProcessMemory写入的不是字串而是精心编排好的机器指令,并且写在目标进程特定的地址空间,那么这段机器指令就有机会执行——而这 段机器指令恰好完成了LoadLibrary功能;
­
6 .其他方法(略)
­
二.挂接API(API Hooking)
其实,这是许多注入的Dll都愿意做的事情。
所谓挂接API就是在目标进程调用windows API之前,先执行我们的仿API函数,从而控制系统API的行为,达到特殊的目的。
我们的仿造函数必须与要替换的系统API有相同的型参表以及相同的返回值类型.
­
1 .改写系统API代码的前几个字节,通过写入jmp指令来跳转到我们的函数。在我们的函数里执行操作,可以直接返回一个值,也可以将系统API的前几个字节复原,调用系统API,并返回系统API的值——随便你想怎么做。
此方法的缺点是对于抢占式多线程的系统不太管用。
­
2.通过改写目标进程IAT中要调用的函数地址来达到目的。具体操作见书中示例
­
­
­
线程本地存储 (Thread-Local Storage)
例子 C / C + +运行期库要使用线程本地存储器( T L S)。由于运行期库是在多线程应用程序出现前的许多年设计的,因此运行期库中的大多数函数是用于单线程应用程序的。函数s t r t o k就是个很好的例子。
尽可能避免使用全局变量和静态变量
1.动态TLS
图21-1 用于管理T L S的内部数据结构
­
在创建线程时,进程会为当前创建的线程分配一个 void * 的数组作为 TLS 用。它用于存储只限当前线程可见的全局变量。
从而使进程中的每个线程都可以有自已的 ( 不能其它线程访问的 ) 全局变量。
TlsAlloc 在返回时会先把槽中的值置为 0 。每个线程至少有 64 个槽。
2.静态TLS
              __declspec(thread) 关键字用于声明,线程本地的全局变量。
               要求声明的变量必须是全局变量或静态变量。
3.Common API:
              TlsAlloc   TlsFree
              TlsSetValue   TlsGetValue
              __declspec(thread)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值