好友
阅读权限30
听众
最后登录1970-1-1
|
前言
随着软件逆向工程技术的快速发展,针对各种软件的分析破解技术层出不穷,动态调试技术也在不断的发展中,那么在这种环境下,就非常需要反调试技术了,反调试成了一种防止破解的一种手段,很好得使用反调试,可以保护软件。接下来,给大家分享一下我实现过的反调试技术及对应的反反调式技术。
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编译器对裸函数将生成不含函数框架的纯汇编代码,内部的汇编代码要自己实现。
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;
}
运行结果:
2.解决方案
1.寄存器修改法
当程序运行完movzx eax, byte ptr ds : [eax + 0x2]时,修改寄存器的值或者运行完Getdebugging函数后,双击修改ZF标志位的值
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成员,该成员指向了一个在堆内存中的结构,通过该成员可以对堆内存进行扫描,进而来判断进程是否被调式。
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功能。
2.用二进制工具打开程序,在0x136处把40 81 修改成00 81即可去除ASLR功能。
3.用OD运行程序发现循环关键跳,把该跳NOP掉即可,在OD中看到该跳的地址为3B82C4,而代码段的起始地址为3b1000,那么该跳指令在PE中的地址为:3B82C4-3b1000+400=76C4
4.随后在编辑工具中进行NOP。
数据隐藏代码法
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
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数组,结合指针的使用,该数组能起到函数的作用,这样做的好处就是防止被反反调式。实现的代码如下:
[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开始运行。
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文件到磁盘中。
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;
}
运行效果:
解决方案:
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回调函数,否则就用了。
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;
再写入磁盘中,就删除了。
|
免费评分
-
查看全部评分
|