我们有知道
调试器,在调试程序时会设断在OEP(修改第一个字节0xcc)。我在想,使用什么编程技术,代码可以在OEP前被执行。在网上找了些资料,在论坛上看到许多大牛,使用静态TLS做了好多有趣的事,今天自己也来终结下,,呵呵
1. 什么是TLS?
TLS是Thread Local
Storage(线程局部存储)的简称,是一项解决多线程内部变量使用问题的技术。用于将某些数据和一特定线程关联起来,即,这些数据为关联线程所独有
(私有)。在多线程编程中, 同一个变量, 如果要让多个线程共享访问, 那么这个变量可以使用关键字volatile进行声明;
而如果一个变量不想被多个线程共享访问, 那么就应该使用TLS。
2. 如何使用TLS编程?
TLS使用非常简单, 只要对变量声明时使用__declspec(thread)修饰就可以了。例如:
__declspec(thread) int g_nData = 0;
3.多线程中使用TLS的例子(动态)
相关API
Windows TLS的API: TlsAlloc, TlsFree, TlsSetValue, TlsGetValue。(动态TLS)
● DWORD TlsAlloc(); //(若要使用动态T L S,首先必须调用TlsAlloc函数)
这个函数命令系统对进程中的位标志进行扫描,并找出一个FREE标志。然后系统将该标志从FREE改为INUSE,并且TlsAlloc返回位数组中的标志的索引。DLL(或APP)通常将该索引保存在一个全局变量中,因为它的值是每个进程而不是每个线程使用的值。
● BOOL TlsSetValue( //将一个值放入线程的数组中
DWORD dwTlsIndex,
PVOID pvTlsValue);
● PVOID TlsGetValue(DWORD dwTlsIndex); //要从线程的数组中检索一个值
● BOOL TlsFree(DWORD dwTlsIndex); //当在所有线程中不再需要保留TLS槽时
例子:
#include <windows.h>
#include "stdio.h"
#define ThreadCound 4 //创建线程个数
DWORD dwTlsIndex;
//全局变量
int iNUM_OF_CALL_COMMON=0;
int iNUM_OF_CALL_THREAD=0;
VOID CommonFunc(VOID)
{
LPVOID lpvData;
// Retrieve a data pointer for the current thread.
iNUM_OF_CALL_COMMON++;
lpvData = TlsGetValue(dwTlsIndex);
if ((lpvData == 0) && (GetLastError() != ERROR_SUCCESS))
exit(0);
// Use the data stored for the current thread.
printf("common: thread %d: lpvData=%lx\n",
GetCurrentThreadId(), lpvData);
Sleep(5000);
}
DWORD WINAPI ThreadFunc(VOID)
{
LPVOID lpvData;
// Initialize the TLS index for this thread.
iNUM_OF_CALL_THREAD++;
lpvData = (LPVOID) LocalAlloc(LPTR, 256);
if (! TlsSetValue(dwTlsIndex, lpvData))
exit(0);
printf("thread %d: lpvData=%lx\n", GetCurrentThreadId(), lpvData);
CommonFunc();
// Release the dynamic memory before the thread returns.
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != 0)
LocalFree((HLOCAL) lpvData);
return 0;
}
void main(void)
{
DWORD dwThreadId;
HANDLE hThread[ThreadCound];
int i;
//Allocate a TLS index
if ((dwTlsIndex =TlsAlloc()) == TLS_OUT_OF_INDEXES)
{
exit(0);
}
for (i = 0; i<ThreadCound; i++)
{
hThread[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc, NULL, 0, &dwThreadId);
if(hThread == NULL)
{
exit(0);
}
}
for (i = 0; i < ThreadCound; i++)
{
WaitForSingleObject(hThread, INFINITE);
}
TlsFree(dwTlsIndex);
printf("The nums of thread is: %d\n",iNUM_OF_CALL_THREAD);
printf("The nums of call is: %d\n",iNUM_OF_CALL_COMMON);
}
以上是动态TLS在多线程中的使用.
而利用静态线程局部存储可以玩弄调试器甚至可以感染一些pe.
静态线程局部存储是在Windows的PE/COFF可执行文件格式中所支持的静态线程局部存储。即在PE头中数据目录表中的第10项,这一项指向的是Tls目录,Tls目录的结构如下:
IMAGE_TLS_DIRECTORY32 STRUCTStartAddressOfRawData dd ?EndAddressOfRawData dd ?AddressOfIndex
dd ?AddressOfCallBacks dd ?SizeOfZeroFill
dd ?Characteristics
dd ?IMAGE_TLS_DIRECTORY32 ENDS比较重要的是该结构的第四项,是指向Tls
CallBack的数组,该数组里面存放了一个或多个TLS回调函数.而TLS回调函数要执行大概经历以下三个步骤:1.程序在链接时,连接器要在PE文
件的目录表创建TLS目录2.在创建线程时,PE加载器会从TEB(当前线程环境块,通过FS寄存器获取)中获取指向TLS回调函数数值的指针(TEB偏
移2Ch).3.查看TLS 回调函数数组是否为空,不为空时加载器会依次执行回调函数。
静态TLS代码;
#include "stdio.h"#include <windows.h>//告诉编译器在PE文件中要创建TLS目录#pragma
comment(linker,
"/INCLUDE:__tls_used")//TLS_CallBack()函数,第二个参数决定了函数在那种情况下(DllMain函数一样)
/* DLL_PROCESS_ATTACH:是指新进程创建时,初始化主线程时执行
DLL_PROCESS_DETACH: 是指在进程终止时执行
DLL_THREAD_ATTACH: 是指创建新线程时,但是不包括主线程
DLL_THREAD_DETACH: 是指在所有线程终止时执行,但是同样不包括主线程*/void go_anit(){
ExitProcess(0);}void __stdcall my_tls_callback(PVOID h, DWORD reason,
PVOID pv){ //仅在进程初始化创建主线程执行 if (reason ==
DLL_PROCESS_ATTACH) { //检查OEP
IMAGE_DOS_HEADER *DosHeader = (IMAGE_DOS_HEADER*)GetModuleHandle(NULL);
IMAGE_NT_HEADERS *NtHeader =
(IMAGE_NT_HEADERS*)(((DWORD)DosHeader)+ (DWORD)DosHeader->e_lfanew);
BYTE *ope =
(PBYTE)(NtHeader->OptionalHeader.AddressOfEntryPoint +
(DWORD)DosHeader); if (*ope == 0xcc) {
go_anit(); } }}/*
下面创建一个TLS段 ".CRT$XLB"的含义是:".CRT"表明是使用C RunTime机制 $后面的XLB中,
X表示随机的标识 L表示TLS Callback section B可以被换成B到Y的任意一个字母,.
$是给连接器的。*/#pragma data_seg(".CRT$XLB")//定义多个TLS_CallBack
PIMAGE_TLS_CALLBACK p_Tls_CallBack[] = {my_Tls_CallBack1,
my_Tls_CallBack2,my_Tls_CallBack3, 0};PIMAGE_TLS_CALLBACK p_Tls_CallBack
= my_tls_callback;#pragma data_seg()void main(){ printf("Hello
TLS\n"); getchar();}
这种方法就能真真的玩弄调试器吗?显然不是的,首先我们来看下创建一个进程的过程1.打开将要在该进程中执行的映像文件。
首先操作系统找到执行的Windows映像然后创建一个内存区对象,以便后面将它映射到新的进程地址空间中。
2.创建Windows执行体进程对象。
接下来操作系统调用内部的系统函数NtCreateProcess来创建一个Windwos执行体进程对象。具体步骤是:
(1)建立EPROCESS
(2)创建初始的进程地址空间(3)初始化内核进程块KPROCESS
(4)结束进程地址空间的创建过程
(5)建立PEB
(6)完成执行体进程对象的创建过程
3.创建初始线程(栈、堆执行环境初始化及执行线程体对象)。
这时候Windows执行体进程对象已经完全建立完成,但它还没有线程所以无法执行,接下来系统调用NtCreateThread来创建一个挂起的新线程它就是进程的主线程体。
4.通知Windows子系统新进程创建了(子系统是操作系统的一部分它是一个协助操作系统内核管理用户态/客户方的一个子系统具体的进程为
Csrss.exe)。
接下来操作系统通过客户态(Kernel32.dll)给Windows子系统(Csrss)发送一个新进程线程创建的数据消息,让子系统建立自己的进程线程管理块。当
Csrss接收到该消息时候执行下面的处理:
*复制一份该进程和线程句柄
*设置进程优先级
*分配Csrss进程块
*把新进程的异常处理端口绑定到Csrss中,这样当该进程发生异常时,Csrss将会接收到异常消息
*分配和初始化Csrss线程块
*把线程插入到进程的线程列表中
*把进程插入到Csrss的线程列表中
*显示进程启动光标
5.开始执行初始线程(如果创建时候指定了线程的CREATE_SUSPENDED状态则线程暂时挂起不执行)。
到这里进程环境已经建立完毕进程中开始创建的主线程到这里获得执行权开始执行线程
6.在新进程和线程环境中完成地址空间的初始化(比如加载必须的DLL和库),然后开始到进程入口执行。
到这步实质是调用ldrInitializeThunk来初始化加载器,堆管理器NLS表TLS数组以及临界区结构,并且加载任何必须要的DLL并且用
DLL_PROCESS_ATTACH功能代码来调用各DLL入口点,最后当加载器初始化例程返回到用户模式APC分发器时进程映像开始在用户模式下执行,然后它调用线程启动函数开始执行。
操作系统会抛出一个CreateProcessEvent事件,早于Tls_CallBack函数,同时ExceptionEvent事件也是早于
Tls_callback的所以我们可以设置调试器第一次断点“系统断点” 本实验我用的Immunity Debugger调试器,
OD相似选项->调试选项->事件->设置第一次暂停于 “系统断点” 单步跟中会发现 对当前进程信息设置
file:///C:/Users/asus/AppData/Local/youdao/ynote/images /C357E1C9D1184437819E2C6A7919ADB8/clipboard.png到此跟进,,,查了好多资料也不知道这个函数是啥 子,,求大牛指导
file:///C:/Users/asus/AppData/Local/youdao/ynote/images /DCE533091FD64FE48C52145B1484E6F6/clipboard.png之后见call跟进,,,到ZwContiune函 数, 继续执行线程,他的一个参数是CONTEXT指针,找到Context->EIP
file:///C:/Users/asus/AppData/Local/youdao/ynote/images/2D7F6F8AC69F46B293E3ED5CC47F167E/clipboard.png出现OPE地址
file:///C:/Users/asus/AppData/Local/youdao/ynote/images/000F6F5D807640FB92E5BFC4E562AF28/clipboard.png
跟进call eax
file:///C:/Users/asus/AppData/Local/youdao/ynote/images/98213D4D5ACD48A3A6C4BF957917E3B1/clipboard.pngcall edx 则进入OEP
file:///C:/Users/asus/AppData/Local/youdao/ynote/images/6A1D98036EB64140BAEF6A1F7B67F792/clipboard.png
file:///C:/Users/asus/AppData/Local/youdao/ynote/images
/05EAE8B3357D45CCA440A3082C120B11/clipboard.png这个过程真的还很模糊,很多疑问,不知道进入OEP之
前的那些函数具体的干了啥,,,,求指导