吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8394|回复: 24
收起左侧

[调试逆向] 反调试技术实现小结

  [复制链接]
buzhifou01 发表于 2020-5-16 16:03

前言


随着软件逆向工程技术的快速发展,针对各种软件的分析破解技术层出不穷,动态调试技术也在不断的发展中,那么在这种环境下,就非常需要反调试技术了,反调试成了一种防止破解的一种手段,很好得使用反调试,可以保护软件。接下来,给大家分享一下我实现过的反调试技术及对应的反反调式技术。

BeingDebugged判断法


1.在PEB结构中,有BeingDebugged成员,当程序处于调式状态时,该成员的值会变为1,否则为0.根据这个特性可以判断程序是否处于调式状态,fs寄存器偏移0x18处指向TEB结构,TEB结构偏移0x30处指向PEB结构,PEB结构偏移0x2处即为BeingDebugged成员,TEB和PEB结构如下。
[Asm] 纯文本查看 复制代码
typedef struct _NT_TEB
{
    ........
    PVOID ThreadLocalStoragePointer;    // 2Ch
    PPEB Peb;                           // 30h  <--------
    ULONG LastErrorValue;               // 34h
    ULONG CountOfOwnedCriticalSections; // 38h
    PVOID CsrClientThread;              // 3Ch
    PVOID Win32ThreadInfo;              // 40h
    ULONG Win32ClientInfo[0x1F];        // 44h
    PVOID WOW32Reserved;                // C0h
    ULONG CurrentLocale;                // C4h
    ULONG FpSoftwareStatusRegister;     // C8h
    ........
}
typedef struct _PEB
{
    UCHAR InheritedAddressSpace;                     // 00h
    UCHAR ReadImageFileExecOptions;                  // 01h
    UCHAR BeingDebugged;                             // 02h <-----------
    UCHAR Spare;                                     // 03h
    PVOID Mutant;                                    // 04h
    PVOID ImageBaseAddress;                          // 08h
    PPEB_LDR_DATA Ldr;                               // 0Ch <-------------
    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;  // 10h
    PVOID SubSystemData;                             // 14h
    PVOID ProcessHeap;                               // 18h
   ................
}

2.实现反调试技术,我使用了裸函数,一般的函数,IDE都会使用编译器和链接器进行处理,使其附加上一些汇编代码,而对裸函数不会进行任何处理,在VC中编写一个空的裸函数,发现调式时,按f11,直接步过该函数。
意味着C编译器对裸函数将生成不含函数框架的纯汇编代码,内部的汇编代码要自己实现。
2345截图20200502154956.png

2345截图20200502155139.png
3.根据第一步的分析,可以写出如下反调试代码:
[C++] 纯文本查看 复制代码
int __declspec(naked) Getdebugging()
{
_asm{
push ebp
//申请堆空间
mov ebp, esp
sub esp, 0x40
//保护现场
push edi
push esi
push ebx
//缓冲区初始化
lea edi, dword ptr ds : [ebp - 0x40]
mov ecx, 0x10
mov eax, 0xCCCCCCCC
rep stos

//关键操作
mov eax, dword ptr fs : [0x18]
mov eax, dword ptr ds : [eax+0x30]
movzx eax, byte ptr ds : [eax + 0x2]

//恢复现场
pop ebx
pop esi
pop edi

//降低堆栈空间
mov esp, ebp
pop ebp

ret
}
	
}
int main()
{

	if (Getdebugging())
	{
		cout<<"debugging"<<endl;
		exit(0);
	}
		
	return 0;
}

运行结果:
2345截图20200502160939.png
2.解决方案
1.寄存器修改法
当程序运行完movzx eax, byte ptr ds : [eax + 0x2]时,修改寄存器的值或者运行完Getdebugging函数后,双击修改ZF标志位的值
2345截图20200502164050.png
2.内存补丁法
找到movzx eax, byte ptr ds : [eax + 0x2]地址,使用WriteProcessMemory函数NOP掉,或者NOP掉关键跳。
[C++] 纯文本查看 复制代码
    cin>>pid;

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

    BYTE buff[] = { 0x90,0x90,0x90,0x90,0x90 };
    if (WriteProcessMemory(hProcess, (LPVOID)ProModAddr, buff, len, NULL))
        MessageBox(NULL, TEXT("success!"), TEXT("标题"), MB_OKCANCEL);
    else
        MessageBox(NULL, TEXT("fail!"), TEXT("标题"), MB_OKCANCEL);
 

扫描Ldr成员内存法


1.在调式进程时,内存中会出现一些特殊的标记,也就是未使用的堆内存全部填充着0xFEEEFEEE,这一特征可以用来判断程序是否处于调式状态。在PEB结构中,可以看到Ldr成员,该成员指向了一个在堆内存中的结构,通过该成员可以对堆内存进行扫描,进而来判断进程是否被调式。
2345截图20200502144908.png
2.那么根据分析,可以写出如下的反调试代码,在获取Ldr成员的值(地址值)后,通过地址叠加的方式扫描内存并读取内存,判断堆内存中是否含有0xFEEEFEEE,含有则退出。
[C++] 纯文本查看 复制代码
DWORD __declspec(naked) GetLdr()
{
	_asm{
		push ebp
			//申请堆空间
			mov ebp, esp
			sub esp, 0x40

			push edi
			push esi
			push ebx

			lea edi, dword ptr ds : [ebp - 0x40]
			mov ecx, 0x10
			mov eax, 0xCCCCCCCC
			rep stos

			mov eax, dword ptr fs : [0x18]
			mov eax, dword ptr ds : [eax+0x30]
			mov eax, dword ptr ds : [eax + 0xC]

			pop ebx
			pop esi
			pop edi
			mov esp, ebp
			pop ebp
			ret
	}
		
}

int isDebugging()
{
	DWORD LdrAddr = GetLdr();
	//byte b1 = 0, b2 = 0;
	DWORD b = 0;
	int i = 0;
	int pid = GetCurrentProcessId();
	HANDLE hpro = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	while (i<0x1000)
	{
		ReadProcessMemory(hpro, (LPVOID)LdrAddr, &b, 4, NULL);
		//判断内存中是否含有0xFEEEFEEE
		if (b==0xFEEEFEEE)
			return 1;
		LdrAddr += 4;
		i++;
	}
	return 0;
}
int main()
{
	if (isDebugging())
	{
		cout<<"debugging"<<endl;
		exit(0);
	}
		
	return 0;
}


2.解决方案
1.内存填充法
把内存中,含有0xFEEEFEEE的内容全部填充为NULL,即可,实现的代码如下
[C++] 纯文本查看 复制代码
 cin>>pid;

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

    BYTE buff[] = { 0x90,0x90,0x90,0x90};
    DWORD LdrAddr = GetLdr();
	while (i<0x1000)
	{
		ReadProcessMemory(hpro, (LPVOID)LdrAddr, &b, 4, NULL);
		if (b==0xFEEEFEEE)
		{
		   WriteProcessMemory(hProcess, (LPVOID)LdrAddr, buff, 4,NULL);	
		}
		LdrAddr += 4;
		i++;
	}


2.PE文件修改法
1.有时为了方便破解程序,需要去除程序的ASLR功能,某些程序之所以有ASLR功能,是因为多了IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE标志,去除该标志就能去除ASLR功能。
2345截图20200429102912.png
2.用二进制工具打开程序,在0x136处把40 81 修改成00 81即可去除ASLR功能。
2345截图20200504145811.png
3.用OD运行程序发现循环关键跳,把该跳NOP掉即可,在OD中看到该跳的地址为3B82C4,而代码段的起始地址为3b1000,那么该跳指令在PE中的地址为:3B82C4-3b1000+400=76C4
2345截图20200504145251.png
4.随后在编辑工具中进行NOP。
2345截图20200504145231.png

数据隐藏代码法


1.在每个进程中都存在堆结构,堆中存储了许多有关进程的信息,Windows系统堆结构如下所示。其中有两个字段:Flags和Force Flags,当程序处于非调式状态时,两个字段的值分别为2和0.根据这个特征可以写出反调试代码
[Asm] 纯文本查看 复制代码
'_HEAP' : [ 0x588, {
    'Entry' : [ 0x0, ['_HEAP_ENTRY']],
    'Signature' : [ 0x8, ['unsigned long']],
    'Flags' : [ 0xc, ['unsigned long']],
    'ForceFlags' : [ 0x10, ['unsigned long']],
    'VirtualMemoryThreshold' : [ 0x14, ['unsigned long']],
    'SegmentReserve' : [ 0x18, ['unsigned long']],
    'SegmentCommit' : [ 0x1c, ['unsigned long']],
    'DeCommitFreeBlockThreshold' : [ 0x20, ['unsigned long']],
    'DeCommitTotalFreeThreshold' : [ 0x24, ['unsigned long']],
.............

2.在PEB结构中,可以看到ProcessHeap字段,该结构指向堆结构,那么通过偏移就可以获取这两个字段的值,实现的代码如下:
[Asm] 纯文本查看 复制代码
  '_PEB' : [ 0x210, {
........
    'SubSystemData' : [ 0x14, ['pointer', ['void']]],
    'ProcessHeap' : [ 0x18, ['pointer', ['void']]],
.............

3.实现反调试的代码如下:
[C++] 纯文本查看 复制代码
DWORD __declspec(naked) isDebugging()
{
	_asm{
			push ebp
			mov ebp, esp
			sub esp, 0x40
			push edi
			push esi
			push ebx
			lea edi, dword ptr ds : [ebp - 0x40]
			mov ecx, 0x10
			mov eax, 0xCCCCCCCC
			rep stos
			mov eax, dword ptr fs : [0x18]
			mov eax, dword ptr ds : [eax + 0x30]
			mov eax, dword ptr ds : [eax + 0x18]
			mov ebx,eax
			//Flags值
			mov eax, dword ptr ds : [ebx + 0xC]
			cmp al,0x2
			jne _AL 
			//Force Flags值
			mov eax, dword ptr ds : [ebx + 0x10]
			cmp al, 0x0
			jne _AL
			jmp _BL
			_AL:
			   mov eax,0
			   jmp _AL1
			_BL:
			   mov eax, 1
		   _AL1:
			//恢复现场
			pop ebx
			pop esi
			pop edi

			//降低堆栈空间
			mov esp, ebp
			pop ebp
			ret
	}
}
		int main()
		{
			if (!isDebugging())
			{
				cout << "debugging" << endl;
				exit(0);
			}

			return 0;
		}


4.在VC中直接运行,没有输出debugging,而在VC中调式,则输出debugging,所以可以检测出反调试。当该程序处于调式状态时,在内存中可以看到Flags和Force Flags字段的值分别为:50000062和40000060
2345截图20200505195415.png

2345截图20200505195446.png

2345截图20200505201152.png
5.在不同的Windows版本中,堆结构可能不同,在Windows 7/10系统中,堆结构如下。那么在实现反调试时,需要修改堆结构中的偏移。
[Asm] 纯文本查看 复制代码
  '_HEAP' : [ 0x2a0, {
 ........
    'NumberOfUnCommittedRanges' : [ 0x54, ['unsigned long']],
    'SegmentAllocatorBackTraceIndex' : [ 0x58, ['unsigned short']],
    'Reserved' : [ 0x5a, ['unsigned short']],
    'UCRSegmentList' : [ 0x60, ['_LIST_ENTRY']],
    'Flags' : [ 0x70, ['unsigned long']],
    'ForceFlags' : [ 0x74, ['unsigned long']],

......


6.接着在内存中,可以看到isDebugging对应的shellcode,如果把shellcode赋值给unsigned char数组,结合指针的使用,该数组能起到函数的作用,这样做的好处就是防止被反反调式。实现的代码如下:
2345截图20200516154620.png

[C++] 纯文本查看 复制代码
unsigned char code[]={0x55,0x8B,0xEC,0x83,0xEC,0x40 ,0x57 ,0x56,0x53 ,0x3E ,0x8D ,0x7D ,0xC0 ,0xB9 ,0x10 ,0x00
,0x00 ,0x00 ,0xB8 ,0xCC ,0xCC ,0xCC ,0xCC ,0xF3 ,0xAA ,0x64 ,0xA1 ,0x18 ,0x00 ,0x00 ,0x00 ,0x3E
,0x8B ,0x40 ,0x30 ,0x3E ,0x8B ,0x40 ,0x18 ,0x8B,0xD8 ,0x3E,0x8B ,0x43 ,0x0C ,0x3C,0x02 ,0x75
,0x0A ,0x3E ,0x8B ,0x43 ,0x10 ,0x3C ,0x00 ,0x75,0x02 ,0xEB,0x07 ,0xB8,0x00 ,0x00,0x00 ,0x00
,0xEB ,0x05 ,0xB8 ,0x01 ,0x00 ,0x00,0x00,0x5B ,0x5E,0x5F ,0x8B ,0xE5,0x5D ,0xC3};

int main()
{
    int (*pfun)();

    pfun=(int (*)())&code;  //默认是__cdecl约定

    int x=pfun();

    if (!x)
	{
		cout << "debugging" << endl;
		exit(0);
	}

    return 0;
}

SEH反调试法


1.SEH是Windows系统提供的异常处理机制,当发生异常时,可通过程序中的异常处理函数来进行处理,那么在程序运行时,往程序中加载异常处理函数,如果异常处理函数中有反调式功能代码,那么触发异常就可以起到反调式效果。Windows系统常见的异常如下:
[Asm] 纯文本查看 复制代码
EXCEPTION_ACCESS_VIOLATION  //本文用到的异常
EXCEPTION_STACK_OVERFLOW
EXCEPTION_HEAP_OVERFLOW
EXCEPTION_INT_OVERFLOW
EXCEPTION_SINGLE_STEP
.........

2.程序都是从OEP(ImageBase+AddressOfEntryPoint)开始运行,为了让程序运行反调试代码,修改AddressOfEntryPoint值,运行完反调试检测代码后,修改EIP的值,让程序从原来的 OEP开始运行。
2345截图20200515085416.png
3.在反调试代码中,先添加SEH处理器,触发异常,程序跳转到SEH处理程序,执行反调式功能,添加的代码如下:
[Asm] 纯文本查看 复制代码
0040106C >/$  68 B8104000   push seh_add1.004010B8                   ;  SE 处理程序安装; SE handler installation
00401071  |.  64:FF35 00000>push dword ptr fs:[0]                    ;  添加SEH异常处理器
00401078  |.  64:8925 00000>mov dword ptr fs:[0],esp
0040107F  |.  90            nop
00401080  |.  90            nop
00401081  |.  90            nop
00401082  |.  90            nop
00401083  |.  33C0          xor eax,eax
00401085  |.  C700 01000000 mov dword ptr ds:[eax],0x1
0040108B  |.  90            nop
0040108C  |.  0000          add byte ptr ds:[eax],al
0040108E  |.  6A 00         push 0x0                                 ; /Style = MB_OK|MB_APPLMODAL
00401090  |.  6A 00         push 0x0                                 ; |Title = NULL
00401092  |.  68 20304000   push seh_add1.00403020                   ; |Text = "Debugging"
00401097  |.  6A 00         push 0x0                                 ; |hOwner = NULL
00401099      E8 C2FFFFFF   call <jmp.&USER32.MessageBoxA>
0040109E  |.  64:8F05 00000>pop dword ptr fs:[0]                     ;  删除SEH程序
004010A5  |.  83C4 04       add esp,0x4
004010A8  |.  6A 00         push 0x0                                 ; /ExitCode = 0x0
004010AA      E8 B7FFFFFF   call <jmp.&KERNEL32.ExitProcess>
004010AF   .  C3            retn
004010B0      90            nop
004010B1      90            nop
004010B2      90            nop
004010B3      90            nop
004010B4      90            nop
004010B5      90            nop
004010B6      90            nop
004010B7      90            nop
004010B8  /$  8B7424 0C     mov esi,dword ptr ss:[esp+0xC]           ;  结构异常处理程序; Structured exception handler
004010BC  |.  64:A1 3000000>mov eax,dword ptr fs:[0x30]
004010C2  |.  8078 02 01    cmp byte ptr ds:[eax+0x2],0x1            ;  判断BeingDebugged的值是否为1
004010C6  |.  75 0C         jnz short seh_add1.004010D4              ;  没有处于调式状态时,进行跳转
004010C8  |.  C786 B8000000>mov dword ptr ds:[esi+0xB8],seh_add1.004>;  把EIP修改为40108e,弹出消息提示框
004010D2  |.  EB 0A         jmp short seh_add1.004010DE
004010D4  |>  C786 B8000000>mov dword ptr ds:[esi+0xB8],seh_add1.004>;  把EIP修改为原OEP
004010DE  |>  33C0          xor eax,eax
004010E0  \.  C3            retn[mw_shl_code=asm,true]0040106C >/$  68 B8104000   push seh_add1.004010B8                   ;  SE 处理程序安装; SE handler installation
00401071  |.  64:FF35 00000>push dword ptr fs:[0]                    ;  添加SEH异常处理器
00401078  |.  64:8925 00000>mov dword ptr fs:[0],esp
0040107F  |.  90            nop
00401080  |.  90            nop
00401081  |.  90            nop
00401082  |.  90            nop
00401083  |.  33C0          xor eax,eax
00401085  |.  C700 01000000 mov dword ptr ds:[eax],0x1
0040108B  |.  90            nop
0040108C  |.  0000          add byte ptr ds:[eax],al
0040108E  |.  6A 00         push 0x0                                 ; /Style = MB_OK|MB_APPLMODAL
00401090  |.  6A 00         push 0x0                                 ; |Title = NULL
00401092  |.  68 20304000   push seh_add1.00403020                   ; |Text = "Debugging"
00401097  |.  6A 00         push 0x0                                 ; |hOwner = NULL
00401099      E8 C2FFFFFF   call <jmp.&USER32.MessageBoxA>
0040109E  |.  64:8F05 00000>pop dword ptr fs:[0]                     ;  删除SEH程序
004010A5  |.  83C4 04       add esp,0x4
004010A8  |.  6A 00         push 0x0                                 ; /ExitCode = 0x0
004010AA      E8 B7FFFFFF   call <jmp.&KERNEL32.ExitProcess>
004010AF   .  C3            retn
004010B0      90            nop
004010B1      90            nop
004010B2      90            nop
004010B3      90            nop
004010B4      90            nop
004010B5      90            nop
004010B6      90            nop
004010B7      90            nop
004010B8  /$  8B7424 0C     mov esi,dword ptr ss:[esp+0xC]           ;  结构异常处理程序; Structured exception handler
004010BC  |.  64:A1 3000000>mov eax,dword ptr fs:[0x30]
004010C2  |.  8078 02 01    cmp byte ptr ds:[eax+0x2],0x1            ;  判断BeingDebugged的值是否为1
004010C6  |.  75 0C         jnz short seh_add1.004010D4              ;  没有处于调式状态时,进行跳转
004010C8  |.  C786 B8000000>mov dword ptr ds:[esi+0xB8],seh_add1.004>;  把EIP修改为40108e,弹出消息提示框
004010D2  |.  EB 0A         jmp short seh_add1.004010DE
004010D4  |>  C786 B8000000>mov dword ptr ds:[esi+0xB8],seh_add1.004>;  把EIP修改为原OEP
004010DE  |>  33C0          xor eax,eax
004010E0  \.  C3            retn

4.在内存窗口,可以看到该汇编代码对应的shellcode。通过手动的方式添加反调试代码必然麻烦,那么就需要开发一个工具来实现seh反调试的添加功能.该工具的实现思路是:加载PE文件到内存中->在代码段处查找空白代码区,记录该区的地址-》搜索空白代码区,添加debugging字符串->计算调用函数的汇编代码-》修改shellcode,导出PE文件到磁盘中。
2345截图20200515154720.png
5.工具实现的重要的代码如下,
[C++] 纯文本查看 复制代码
LPVOID CAllToolDlg::PEFileToMemory(LPSTR lpszFile)
{
	FILE *pFile = NULL;

	LPVOID pFileBuffer = NULL;

	errno_t err;
	
	if (!(err = fopen_s(&pFile, lpszFile, "wr+")))
	{
		printf(" 无法打开 EXE 文件! ");
		return NULL;
	}
	//读取文件大小
	fseek(pFile, 0, SEEK_END);
	fileSize = ftell(pFile);
	fseek(pFile, 0, SEEK_SET);
	//分配缓冲区
	pFileBuffer = malloc(fileSize);

	//将文件数据读取到缓冲区
	size_t n = fread(pFileBuffer, fileSize, 1, pFile);
	if (!n)
	{
		printf(" 读取数据失败! ");
		free(pFileBuffer);
		fclose(pFile);
		return NULL;
	}
	//关闭文件
	fclose(pFile);
	return pFileBuffer;
}

//搜索代码段空白代码
DWORD CAllToolDlg::FindEmptyCodeAndModifyShellCode()
{
	//把exe文件加载进内存
	//pFileBuffer = PEFileToMemory(strpath);
	DWORD* DPointer = (DWORD*)pFileBuffer;
	BYTE* BPointer = (BYTE*)pFileBuffer;
	DWORD ImageBase = *(DPointer + 59);
	DWORD AddrEP = *(DPointer + 56);
	//修改OEP的值
	ModifyShellCode(ImageBase + AddrEP, 0x6E);
	//记录空白代码区的起始地址
	DWORD BeginAddr = 0;
	int i = 0;
	//找出空白代码区的起始地址
	for (int j = 0x1000; j < 0x1000/4; j++)
	{
		if (*(DPointer + j) == 0x0)
		{
			i++;
			if (i >= 30)
				break;
		}
		else
		{
			i = 0;
			BeginAddr = j+1;
		}
	}
	//AddressOfEntryPoint修改后的值
	DWORD AfterAddrEP = BeginAddr*4 + 0x1000;

	//修改AddressOfEntryPoint
	*(DPointer + 56) = AfterAddrEP;

	//获取MessageBoxA和EXitProcess的十六进制编码并修改
	DWORD MessageBoxA = AddrMessageBoxA - (0x400000 + AfterAddrEP + 0x2D + 0x5);
	ModifyShellCode(MessageBoxA,0x2E);
	DWORD EXitProcess = AddrEXitProcess  - (0x400000 + AfterAddrEP + 0x3E + 0x5);
	ModifyShellCode(EXitProcess, 0x3F);
	return AfterAddrEP;
}
//搜索数据段空白数据
void CAllToolDlg::FindEmptyDataAndAddData()
{
	
	int i = 0;
	DWORD BeginAddr = 0;
	for (int j = 0x3000; j < 0x1000/4; j++)
	{
		if (*(DPointer + j) == 0x0)
		{
			i++;
			if (i >= 3)
				break;
		}
		else
		{
			i = 0;
			BeginAddr = j + 1;
		}
	}
	//修改内存数据
	for (i = 0; i < strlen(s); i++)
	{
		*(BPointer + i + BeginAddr) = s[i];
	}
	DWORD AddrData = 0x3000 + BeginAddr * 4 + 0x400000;
	ModifyShellCode(AddrData, 0x28);
}
void CAllToolDlg::WriterToPEFileAndDisk(DWORD Addr)
{
	for (int i = 0; i < len; i++)
	{
		*(BPointer + i + Addr) = ShellCode[i];
	}
	//写入磁盘中
	ofstream out("seh_add.exe", ios::out | ios::trunc);
	if (!out.is_open())
	{
		cout << "文件创建失败!" << endl;
		exit(0);
	}
	for (int index = 0; index<fileSize; index++)
	{
		out << *(BPointer + index);
	}
	out.close();
	return;
}

运行效果:
2345截图20200515200115.png
解决方案:
NOP掉异常的代码
把触发异常的代码NOP掉,就能阻止反调试代码的运行,思路为:1.在OD中手动修改后另存;2.使用内存修改的方式,nop掉触发异常的代码,大致代码如下:
[C++] 纯文本查看 复制代码
HANDLE hpro = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
WriteProcessMemory(hpro, (LPVOID)Addr, buff, 4,NULL)

TLS反调试


1.在C++开发中,会经常遇到TLS(线程局部存储)回调函数。该函数有一个非常大的特点就是在线程运行前要先运行该函数,那么只需要在TLS回调函数中,写上反调试功能的代码,就可以有反调试功能,而不需要修改OEP,来执行反调试代码。使用PEView打开某程序,在PE扩展头中,如果TLS Table项中两个字段值都为0, 则该程序没使用TLS回调函数,否则就用了。
2345截图20200516100307.png
2.接着我往程序中添加TLS反调试功能,需要修改和读取、添加的值如下:
[Asm] 纯文本查看 复制代码
读取值:
节表数量:Word(value(0x3C)+0x4+0x02)
PE扩展头的地址:value(0x3C)+0x4+0x14
最后节表位置:addr(PE扩展头)+0xE0+0x28*(节表数量-1)

修改值:
PE扩展头修改的字段及偏移:

TLS表偏移:0xA8
RVA(偏移:0xA8):ReadDword(最后节表+0xC)+0x200+0x1000*(节表数量-1)
Size(偏移:0xAC):->0x18

最后节表结构修改的字段及偏移:
SizeOfRawData(+0x8):+0x200
PointerToRawData(+0xC):+0x200
Characteristics(+0x1C):40->E0

在PE文件末尾添加0x200空白区域,写入的数据如下:
偏移            值
0x0              0x400000+TLS表.Size+TLS表.RVA
0x4               上面的值+0x4
0x8               上面的值+0x4
0xC               上面的值+0x4
0x10               0
0x14               0
0x24              0x400000+TLS表.RVA+0x30  //TLS回调函数地址

3.从上面可以看出,在PE文件末尾添加的是结构体_IMAGE_TLS_DIRECTORY32中的内部字段值,该结构体如下,看出TLS回调函数要加载到PE文件中地址为:TLS表.RVA+0x30的地方。
[Asm] 纯文本查看 复制代码
  typedef struct _IMAGE_TLS_DIRECTORY32 {
    DWORD StartAddressOfRawData;
    DWORD EndAddressOfRawData;
    PDWORD AddressOfIndex;
    PIMAGE_TLS_CALLBACK *AddressOfCallBacks;
    DWORD SizeOfZeroFill;
    DWORD Characteristics;
    } IMAGE_TLS_DIRECTORY32;


4.先在OD的"0x400000+TLS表.RVA+0x30"处添加反调试代码,代码如下:
[Asm] 纯文本查看 复制代码
0040C230    837C24 08 01    cmp dword ptr ss:[esp+0x8],0x1
0040C235    75 28           jnz  0040C25F                   
0040C237    64:A1 30000000  mov eax,dword ptr fs:[0x30]
0040C23D    8078 02 00      cmp byte ptr ds:[eax+0x2],0x0                  ; 判断是否处于调式状态
0040C241    74 1C           je  0040C25F
0040C243    6A 00           push 0x0
0040C245    68 70C24000     push 0x0                       
0040C24A    68 80C24000     push 0040C280                         ; ASCII "Debugging"
0040C24F    6A 00           push 0x0
0040C251    FF15 E8804000   call dword ptr ds:[<&USER32.MessageBoxA>]      ; user32.MessageBoxA
0040C257    6A 01           push 0x1
0040C259    FF15 28804000   call dword ptr ds:[<&KERNEL32.ExitProcess>]    ; kernel32.ExitProcess
0040C25F    C2 0C00         retn 0xC

5.接着把上面代码对应的shellcode,复制粘贴出来添加到PE文件中,接着开发工具来往程序中添加TLS反调试,实现的思路跟SEH反调试差不多,实现的代码(与SEH反调试类似的代码已省略)如下:。
[C++] 纯文本查看 复制代码
//读取字段值
VOID CAllToolDlg::ReadFieldValue()
{
	pFileBuffer = PEFileToMemory(strPath);
	if (!pFileBuffer)
	{
		printf("文件读取失败\n");
		return;
	}

	//判断是否是有效的MZ标志
	if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效的MZ标志\n");
		free(pFileBuffer);
		return;
	}
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	//PE扩展头的地址
	PEOptionalAddress = pDosHeader->e_lfanew+0x4+0x14;

	//判断是否是有效的PE标志
	if (*((PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
	{
		printf("不是有效的PE标志\n");
		free(pFileBuffer);
		return;
	}
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
	//节表数量
	NumberOfSection = pPEHeader->NumberOfSections;
	
	//最后节表位置
	LastSectionAddr = PEOptionalAddress + pPEHeader->SizeOfOptionalHeader + (NumberOfSection - 1) * 0x28;

	DWORD* DPointer = (DWORD*)pFileBuffer;

	//PointerToRawData值
	LastPointerToRawDataV = *(DPointer + LastSectionAddr+0xC);

}
//修改字段值
VOID CAllToolDlg::ModifyMemoryValue()
{

	//修改TLS表中的RVA值
	*(DPointer + PEOptionalAddress + 0xA8) = LastPointerToRawDataV + 0x200 + (NumberOfSection - 1) * 0x28;
	//修改TLS表中的Size值,_IMAGE_TLS_DIRECTORY32结构大小
	*(DPointer + PEOptionalAddress + 0xAC) = 0x18;
	//SizeOfRawData值加0x200
	*(DPointer + LastSectionAddr + 0x8) = *(DPointer + LastSectionAddr + 0x8) + 0x200;
	//PointerToRawData值加0x200
	*(DPointer + LastSectionAddr + 0xC) = LastPointerToRawDataV + 0x200;
	//修改Characteristics值
	*(BPointer + LastSectionAddr + 0x1C) = 0xE0;
}
//给TLS结构体赋值
VOID CAllToolDlg::WriterToNewTls()
{
	DWORD Value = 0x400000 + 0x18 + *(DPointer + PEOptionalAddress + 0xA8);
	*(DPointer + fileSize + 0x200) = Value;
	*(DPointer + fileSize + 0x204) = Value+0x4;
	*(DPointer + fileSize + 0x208) = Value + 0x8;
	*(DPointer + fileSize + 0x20C) = Value + 0xC;
	*(DPointer + fileSize + 0x224) = 0x400000 +  *(DPointer + PEOptionalAddress + 0xA8)+0x30;

}
//往PE文件中写入TLS回调函数,同WriterToPEFileAndDisk

解决方案:
删除TLS回调功能
TLS表中的 RVA处存放的是TLS结构体信息,Size字段记录的是TLS结构体的大小,把这两个字段的值设成0,就能删除TLS回调函数。实现的代码如下:
[Asm] 纯文本查看 复制代码
//修改TLS表中的RVA值
*(DPointer + PEOptionalAddress + 0xA8) = 0;
//修改TLS表中的Size值
*(DPointer + PEOptionalAddress + 0xAC) = 0;
再写入磁盘中,就删除了。












免费评分

参与人数 16威望 +1 吾爱币 +33 热心值 +14 收起 理由
youku2020 + 1 谢谢@Thanks!
wangjieaaa + 1 + 1 我很赞同!
sam喵喵 + 1 谢谢@Thanks!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
天上飞来一只 + 1 热心回复!
生有涯知无涯 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
37566454 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
涛之雨 + 3 + 1 用心讨论,共获提升!
simakuangbiao + 1 + 1 谢谢@Thanks!
星辰物语呀 + 1 + 1 我很赞同!
xtvott + 1 谢谢@Thanks!
Myitmx + 1 + 1 我很赞同!
LjeA + 1 我很赞同!看得非常舒服!
fydcrvip + 1 + 1 我很赞同!
明月相照 + 1 + 1 谢谢@Thanks!
Juch + 1 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

明月相照 发表于 2020-5-16 17:35
谢谢分享,这个总结得好。
头像被屏蔽
fydcrvip 发表于 2020-5-16 16:57
陆想想 发表于 2020-5-16 17:24
给你一拳 发表于 2020-5-16 18:47
我码,谢谢分享
头像被屏蔽
庞晓晓 发表于 2020-5-16 21:27
提示: 作者被禁止或删除 内容自动屏蔽
jiaocoll 发表于 2020-5-16 23:32
大佬给的方法和教程很好,长知识了,可以去尝试一下
雨落惊鸿, 发表于 2020-5-17 07:32
厉害厉害
来看看啊 发表于 2020-5-17 08:26
厉害 厉害 学习了
shallies 发表于 2020-5-17 09:09
好帖,支持分享!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-3-29 00:52

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表