總結:
1. FindWindow。比如 FindWindowA("OLLYDBG", NULL);
2. EnumWindow函數調用后,系統枚舉所有頂級窗口,為每個窗口調用一次回調函數。在回調函數中用 GetWindowText得到窗口標題,進行檢測。
3. GetForeGroundWindow返回前台窗口(用戶當前工作的窗口)。當程序被調試時,調用這個函數將獲得Ollydbg的窗口句柄,再用GetWindowTextA檢測。
4. 枚舉進程列表,看是否有調試器進程(OLLYDBG.EXE,windbg.exe等)
5. 父進程是否是Explorer。通常進程的父進程是explorer.exe(雙擊執行的情況下),否則可能程序被調試。
6. RDTSC/ GetTickCount時間敏感程序段。當進程被調試時,調試器事件處理代碼、步過指令等將占用CPU循環當進程被調試時,調試器事件處理代碼、步過指令等將占用CPU循環。
7. StartupInfo結構檢測。Windows操作系統中的explorer.exe創建進程的時候會把STARTUPINFO結構中的值設為0,而非explorer.exe創建進程的時候會忽略這個結構中的值,也就是結構中的值不為0,所以可以利用這個來判斷OD是否在調試程序。
8. BeingDebugged。kernel32!IsDebuggerPresent() API檢測進程環境塊(PEB)中的BeingDebugged標志檢查這個標志以確定進程是否正在被用戶模式的調試器調試。
9. PEB.NtGlobalFlag,Heap.HeapFlags, Heap.ForceFlags。通常程序沒有被調試時,PEB另一個成員NtGlobalFlag(偏移0x68)值為0,如果進程被調試通常值為0x70(代表下述標志被設置)由於NtGlobalFlag標志的設置,堆也會打開幾個標志,這個變化可以在ntdll!RtlCreateHeap()里觀測到。正常情況下系統為進程創建第一個堆時會將Flags和ForceFlags分別設為2(HEAP_GROWABLE)和0 。當進程被調試時,這兩個標志通常被設為50000062(取決於NtGlobalFlag)和0x40000060(等於Flags AND 0x6001007D)。
10.EPROCESS的DebugPort成員: CheckRemoteDebuggerPresent() /NtQueryInformationProcess()。Kernel32!CheckRemoteDebuggerPresent() 是用於確定是否有調試器被附加到進程,內部調用了ntdll!NtQueryInformationProcess()檢索內核結構EPROCESS的DebugPort成員。
11.SetUnhandledExceptionFilter/Debugger Interrupts調試器中步過INT3和INT1指令的時候,由於調試器通常會處理這些調試中斷,所以設置的異常處理例程默認情況下不會被調用,這樣我們可以在異常處理例程中設置標志,通過INT指令后如果這些標志沒有被設置則意味着進程正在被調試。
12.Trap Flag單步標志異常。TF=1的時候,會觸發單步異常,在異常中設定檢測,正常程序可進入,未修改OD調試程序不能進入此異常,從而檢測調試。
13.SeDebugPrivilege進程權限.默認情況下進程沒有SeDebugPrivilege權限,調試時,會從調試器繼承這個權限。
14.DebugObject:NtQueryObject()內核對象檢測。
15.OllyDbg:Guard Pages.OllyDbg允許設置一個內存訪問/寫入斷點,這種類型的斷點是通過頁面保護來實現的, 頁面保護是通過PAGE_GUARD頁面保護修改符來設置的,如果訪問的內存地址是受保護頁面的一部分,將會產生一個 STATUS_GUARD_PAGE_VIOLATION(0x80000001)異常。如果進程被OllyDbg調試並且受保護的頁面被訪問,將不會拋 出異常,訪問將會被當作內存斷點來處理,從而檢測到。
16.Software Breakpoint.通過修改目標地址代碼為0xCC(INT3/BreakpointInterrupt)來設置的斷點。通過在受保護的代碼段和(或)API函數中掃描字節0xCC來識別軟件斷點。
17.HardwareBreakpoints.通過傳遞給異常處理例程的ContextRecord參數來訪問, 含有調試寄存器值的CONTEXT結構,判斷Dr0-Dr3是否設置了值,來判斷調試。
18.PatchingDetectionCodeChecksumCalculation補丁檢測,代碼檢驗和.能識別殼的代碼是否被修改,或軟件斷點。
19.block input封鎖鍵盤、鼠標輸入。
20.EnableWindow禁用窗口。
21.ThreadHideFromDebugger. ntdll!NtSetInformationThread()用來設置一個線程的相關信息。把ThreadInformationClass參數設為ThreadHideFromDebugger(11H) 可以禁止線可以禁止線程產生調試事件。
22.DisablingBreakpoints禁用硬件斷點。利用CONTEXT結構,該結構利用異常處理獲得,異常處理完后會自動寫回。
23.OllyDbg:OutputDebugString()Format String Bug。OutputDebugString函數用於向調試器發送一個格式化的串,Ollydbg會在底端顯示相應的信息。OllyDbg存在格式化字符串 溢出漏洞,非常嚴重,輕則崩潰,重則執行任意代碼。這個漏洞是由於Ollydbg對傳遞給kernel32!OutputDebugString()的字符串參數過濾不嚴導致的,它只對參數進行那個長度檢查,只接受255個字節,但沒對參數進行檢查,所以導致緩沖區溢出溢出。
24.TLS Callbacks。使用Thread Local Storage (TLS)回調函數可以實現在實際的入口點之前執行反調試的代碼,這也是OD載入程序就退出的原因所在。
25.CreateFile檢測。win32程序對vxd程序通信時,是通過調用DeviceIoControl函數進入vxd,此函數的一個參數就是由createfile獲得的設備句柄。這樣,同樣生成或打開文件的調用也能夠打開一個到vxd的通道。要用createfile打開一個vxd,而不是一個通常的文件,在文件名的地方必須使用特殊形式。
1.FindWindow
比如 FindWindowA("OLLYDBG", NULL);
szClassName db 'ollydbg',0
invoke FindWindow,addr szClassName,NULL ;通過類名進行檢測
.if eax ;找到
jmp debugger_found
.endif
2.EnumWindow
系統枚舉所有頂級窗口,為每個窗口調用一次回調函數。在回調函數中用 GetWindowText得到窗口標題,進行檢測。
.386
.modelflat,stdcall
optioncasemap:none
includewindows.inc
includeuser32.inc
includelibuser32.lib
includekernel32.inc
includelibkernel32.lib
include Shlwapi.inc
includelib Shlwapi.lib ;strstr
.const
szTitle db 'ollydbg',0
szCaption db '結果',0
szFindOD db '發現目標窗口',0
szText db '枚舉已結束,沒提示發現目標,則沒有找到目標窗口',0
.code
;定義回調函數
_CloseWnd procuses ebx edi esi,_hWnd,_lParam
LOCAL @szBuffer[1024]:BYTE ;接收窗口標題
invoke IsWindowVisible,_hWnd
.if eax ;是否是可見的窗口
invoke GetWindowText,_hWnd,addr@szBuffer,sizeof @szBuffer
invoke StrStrI,addr@szBuffer,offset szTitle ;查找標題中有無字符串,不帶I的大小寫敏感
.if eax
invoke MessageBox,NULL,addr szFindOD,addrszCaption,MB_OK
invoke PostMessage,_hWnd,WM_CLOSE,0,0 ;關閉目標
.endif
.endif
mov eax,TRUE ;返回true 時,EnumWindows繼續枚舉下一個窗口,false退出枚舉.
ret
_CloseWnd endp
start:
invoke EnumWindows,addr _CloseWnd,NULL
;EnumWindows調用,系統枚舉所有頂級窗口,為每個窗口調用一次回調函數
invoke MessageBox,NULL,addr szText,addrszCaption,MB_OK
invoke ExitProcess,NULL
end start
3.GetForeGroundWindow返回前台窗口
GetForeGroundWindow返回前台窗口(用戶當前工作的窗口)。當程序被調試時,調用這個函數將獲得Ollydbg的窗口句柄,這樣就可以向其發送WM_CLOSE消息將其關閉了。
invoke IsDebuggerPresent
.if eax
invoke GetForegroundWindow ;獲得的是OD的窗口句柄
invoke SendMessage,eax,WM_CLOSE,NULL,NULL
.endif
獲取OD窗口句柄后的處理
(1)向窗口發送WM_CLOSE消息
invoke FindWindow,addr szClassName,NULL ;通過類名進行檢測
.if eax ;找到
mov hWinOD,eax
invoke MessageBox,NULL,offset szFound,offset szCaption,MB_OK invoke SendMessage,hWinOD,WM_CLOSE,NULL,NULL
.endif
(2)終止相關進程,根據窗口句柄獲取進程ID,根據進程ID獲取進程句柄,
_GetODProcID proc
LOCAL @hWinOD ;窗口句柄
LOCAL @hProcessOD ;進程句柄
LOCAL @idProcessOD ;進程ID
invoke FindWindow,addr szClassName,NULL ;通過類名進行檢測
.if eax ;找到
mov @hWinOD,eax ;窗口句柄
invoke GetWindowThreadProcessId,@hWinOD,addr @idProcessOD
;獲取進程ID在@idProcessOD里
invoke OpenProcess,PROCESS_TERMINATE,TRUE,@idProcessOD
;獲取進程句柄在返回值里
.if eax ;獲取句柄成功
mov @hProcessOD,eax
invoke TerminateProcess,@hProcessOD,200 ;利用句柄終止進程
invoke CloseHandle,@hProcessOD ;關閉進程句柄
invoke MessageBox,NULL,addr szClose,addr szMerry,MB_OK
.else ;獲取句柄失敗,多因權限問題
invoke MessageBox,NULL,addr szFail,addr szCaption,MB_OK
.endif .
.endif
ret
_GetODProcIDendp
4. 枚舉進程列表,看是否有調試器進程
枚舉進程列表,看是否有調試器進程(OLLYDBG.EXE,windbg.exe等)。
利用kernel32!ReadProcessMemory()讀取進程內存,然后尋找調試器相關的字符串(如”OLLYDBG”)以防止逆向分析人員修改調試器的可執行文件名。
.386
.model flat, stdcall
option casemap :none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.const
stSysProc db 'OLLYDBG.EXE',0
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
_GetProcList proc
LOCAL @stProcessEntry:PROCESSENTRY32
LOCAL @hSnapShot
invoke CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,NULL
mov @hSnapShot,eax
mov @stProcessEntry.dwSize,sizeof @stProcessEntry
invoke Process32First,@hSnapShot,addr @stProcessEntry
.while eax
invokelstrcmp,addr @stProcessEntry.szExeFile,addr stSysProc
.if eax == 0 ;為0,說明進程名相同
push 20
invoke MessageBox,NULL,addrszFound,addr szCaption,MB_OK
.endif
invokeProcess32Next,@hSnapShot,addr @stProcessEntry
.endw
pop eax
.if eax != 20
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
.endif
ret
_GetProcListendp
start:
invoke _GetProcList
invoke ExitProcess,NULL
end start
5. 父進程是否是Explorer
原理:通常進程的父進程是explorer.exe(雙擊執行的情況下),否則可能程序被調試。
下面是實現這種檢查的一種方法:
1.通過TEB(TEB.ClientId)或者使用GetCurrentProcessId()來檢索當前進程的PID
2.用Process32First/Next()得到所有進程的列表,注意explorer.exe的PID(通過PROCESSENTRY32.szExeFile)和通過PROCESSENTRY32.th32ParentProcessID獲得的當前進程的父進程PID。Explorer進程ID也可以通過桌面窗口類和名稱獲得。
3.如果父進程的PID不是explorer.exe,cmd.exe,Services.exe的PID,則目標進程很可能被調試
對策:OllyAdvanced提供的方法是讓Process32Next()總是返回fail,使進程枚舉失效,PID檢查將會被跳過。這些是通過補丁kernel32!Process32NextW()的入口代碼(將EAX值設為0然后直接返回)實現的。
(1)通過桌面類和名稱獲得Explorer的PID 源碼見附件
.data?
szDesktopClass db 'Progman',0 ;桌面的窗口類
szDesktopWindow db 'ProgramManager',0 ;桌面的窗口名稱
dwProcessID dd ? ;保存進程ID
dwThreadID dd ? ;保存線程ID
.code
invoke FindWindow,addr szDesktopClass,addrszDesktopWindow ;獲取桌面窗口句柄
invoke GetWindowThreadProcessId,eax,offsetdwProcessID ;獲取EXPLORER進程ID
mov dwThreadID,eax ;線程ID
(2)通過進程列表快照獲得Explorer的PID 源碼見附件
szExplorer db 'EXPLORER.EXE',0
dwParentID dd ?
dwExplorerID dd ?
_ProcTest proc
local @stProcess:PROCESSENTRY32 ;每一個進程的信息
local @hSnapShot ;快照句柄
pushad
invoke GetCurrentProcessId
mov ebx,eax ;當前進程ID
invoke RtlZeroMemory,addr @stProcess,sizeof @stProcess ; 0初始化進程信息結構
mov @stProcess.dwSize,sizeof@stProcess ;手工填寫結構大小
invoke CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,0;獲取進程列表快照
mov @hSnapShot,eax ;快照句柄
invoke Process32First,@hSnapShot,addr @stProcess ;第一個進程
.while eax
.if ebx ==@stProcess.th32ProcessID ;是當前進程嗎?
mov eax,@stProcess.th32ParentProcessID ;是,則保存父進程ID
mov dwParentID,eax
.endif
invoke lstrcmp,addr @stProcess.szExeFile,addrszExplorer ;Explorer進程ID
.if eax == 0 ;為0,說明進程名相同
mov eax,@stProcess.th32ProcessID
mov dwExplorerID,eax
.endif
invoke Process32Next,@hSnapShot,addr @stProcess ;下一個進程
.endw
invoke CloseHandle,@hSnapShot ;關閉快照
mov ebx,dwParentID
.if ebx == dwExplorerID ;父進程ID與EXPLORER進程ID比較 invoke MessageBox,NULL,offset szNotFound,offset szCaption,MB_OK
.else
invoke MessageBox,NULL,offset szFound,offsetszCaption,MB_OK
.endif
popad
ret
_ProcTest endp
6.RDTSC/ GetTickCount時間敏感程序段
當進程被調試時,調試器事件處理代碼、步過指令等將占用CPU循環。如果相鄰指令之間所花費的時間如果大大超出常規,就意味着進程很可能是在被調試。
(1)RDTSC
將計算機啟動以來的CPU運行周期數放到EDX:EAX里面,EDX是高位,EAX是低位。
如果CR4的TSD(timestamp disabled)置位,則rdtsc在ring3下運行會導致異常(特權指令),所以進入ring0,把這個標記置上,然后Hook OD的WaitForDebugEvent,攔截異常事件,當異常代碼為特權指令時,把異常處的opcode讀出檢查,如果是rdtsc,把eip加2,SetThreadContext,edx:eax的返回由你了。
(2)GetTickCount 源碼見附件
invoke GetTickCount ;第一次調用
mov ebx,eax ;結果保存在ebx里
mov ecx,10 ;延時開始
mov edx,6 ;單步走,放慢速度
mov ecx,10 ;延時結束
invoke GetTickCount ;第二次調用
sub eax,ebx ;計算差值
.if eax > 1000 ;假定大於1000ms,就說明有調試器
jmp debugger_found
.endif
7.StartupInfo結構檢測
原理:Windows操作系統中的explorer.exe創建進程的時候會把STARTUPINFO結構中的值設為0,而非explorer.exe創建進程的時候會忽略這個結構中的值,也就是結構中的值不為0,所以可以利用這個來判斷OD是否在調試程序.
if (Info.dwX<>0) or(Info.dwY<>0) or (Info.dwXCountChars<>0) or(Info.dwYCountChars<>0) or (Info.dwFillAttribute<>0) or (Info.dwXSize<>0) or(Info.dwYSize<>0) then “有調試器”
*******************************************************************************
結構體
typedef struct _STARTUPINFO
{
DWORD cb; 0000
PSTR lpReserved; 0004
PSTR lpDesktop; 0008
PSTR lpTitle; 000D
DWORD dwX; 0010
DWORD dwY; 0014
DWORD dwXSize; 0018
DWORD dwYSize; 001D
DWORD dwXCountChars; 0020
DWORDdwYCountChars; 0024
DWORDdwFillAttribute; 0028
DWORD dwFlags; 002D
WORD wShowWindow; 0030
WORD cbReserved2; 0034
PBYTE lpReserved2; 0038
HANDLE hStdInput; 003D
HANDLE hStdOutput; 0040
HANDLE hStdError; 0044
} STARTUPINFO, *LPSTARTUPINFO;
_ProcTest proc
LOCAL @stStartupInfo:STARTUPINFO
pushad
invoke GetStartupInfo,addr @stStartupInfo
cmp @stStartupInfo.dwX,0
jnz foundDebugger
cmp @stStartupInfo.dwY,0
jnz foundDebugger
cmp @stStartupInfo.dwXCountChars,0
jnz foundDebugger
cmp @stStartupInfo.dwYCountChars,0
jnz foundDebugger
cmp @stStartupInfo.dwFillAttribute,0
jnz foundDebugger
cmp @stStartupInfo.dwXSize,0
jnz foundDebugger
cmp @stStartupInfo.dwYSize,0
jnz foundDebugger
noDebugger: “無調試器”
jmp TestOver
foundDebugger: “有調試器”
TestOver:
popad
ret
_ProcTest endp
8. BeingDebugged
kernel32!IsDebuggerPresent() API檢測進程環境塊(PEB)中的BeingDebugged標志檢查這個標志以確定進程是否正在被用戶模式的調試器調試。
每個進程都有PEB結構,一般通過TEB間接得到PEB地址
Fs:[0]指向當前線程的TEB結構,偏移為0處是線程信息塊結構TIB
TIB偏移18H處是self字段,是TIB的反身指針,指向TIB(也是PEB)首地址
TEB偏移30H處是指向PEB結構的指針
PEB偏移2H處,就是BeingDebugged字段,Uchar類型
(1) 調用IsDebuggerPresent函數,間接讀BeingDebugged字段
(2) 利用地址直接讀BeingDebugged字段
對策:
(1) 數據窗口中Ctrl+G fs:[30] 查看PEB數據,將PEB.BeingDebugged標志置0
(2) Ollyscript命令"dbh"可以補丁這個標志
.386
.modelflat,stdcall
optioncasemap:none
include windows.inc
include user32.inc
include kernel32.inc
includelibuser32.lib
includelibkernel32.lib
.const
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
start:
;調用函數IsDebuggerPresent
invoke IsDebuggerPresent
.if eax
invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK
.else
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
.endif
;直接去讀字段
assume fs:nothing
mov eax,fs:[30h]
movzx eax,byte ptr [eax+2]
.if eax
invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK
.else
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
.endif
invoke ExitProcess,NULL
end start
9. PEB.NtGlobalFlag, Heap.HeapFlags, Heap.ForceFlags
(1)通常程序沒有被調試時,PEB另一個成員NtGlobalFlag(偏移0x68)值為0,如果進程被調試通常值為0x70(代表下述標志被設置):
FLG_HEAP_ENABLE_TAIL_CHECK(0X10)
FLG_HEAP_ENABLE_FREE_CHECK(0X20)
FLG_HEAP_VALIDATE_PARAMETERS(0X40)
這些標志是在ntdll!LdrpInitializeExecutionOptions()里設置的。請注意PEB.NtGlobalFlag的默認值可以通過gflags.exe工具或者在注冊表以下位置創建條目來修改:
HKLM\Software\Microsoft\WindowsNt\CurrentVersion\Image File Execution Options
assume fs:nothing
mov eax,fs:[30h]
mov eax,[eax+68h]
and eax,70h
(2)由於NtGlobalFlag標志的設置,堆也會打開幾個標志,這個變化可以在ntdll!RtlCreateHeap()里觀測到。正常情況下系統為進程創建第一個堆時會將Flags和ForceFlags分別設為2(HEAP_GROWABLE)和0 。當進程被調試時,這兩個標志通常被設為50000062(取決於NtGlobalFlag)和0x40000060(等於Flags AND 0x6001007D)。
assume fs:nothing
mov ebx,fs:[30h] ;ebx指向PEB
mov eax,[ebx+18h] ;PEB.ProcessHeap
cmp dword ptr [eax+0ch],2 ;PEB.ProcessHeap.Flags
jne debugger_found
cmp dword ptr [eax+10h],0 ;PEB.ProcessHeap.ForceFlags
jne debugger_found
這些標志位都是因為BeingDebugged引起的。系統創建進程的時候設置BeingDebugged=TRUE,后來NtGlobalFlag根據這個標記設置FLG_VALIDATE_PARAMETERS等標記。在為進程創建堆時,又由於NtGlobalFlag的作用,堆的Flags被設置了一些標記,這個Flags隨即被填充到ProcessHeap的Flags和ForceFlags中,同時堆中被填充了很多BAADF00D之類的東西(HeapMagic,也可用來檢測調試)。
一次性解決這些狀態見加密解密P413
.386
.model flat,stdcall
optioncasemap:none
include windows.inc
include user32.inc
include kernel32.inc
includelibuser32.lib
includelibkernel32.lib
.const
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
start:
assume fs:nothing
mov ebx,fs:[30h] ;ebx指向PEB
;PEB.NtGlobalFlag
mov eax,[ebx+68h]
cmp eax,70h
je debugger_found
;PEB.ProcessHeap
mov eax,[ebx+18h]
;PEB.ProcessHeap.Flags
cmp dwordptr [eax+0ch],2
jne debugger_found
;PEB.ProcessHeap.ForceFlags
cmp dword ptr [eax+10h],0
jne debugger_found
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
jmp exit
debugger_found:invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK
exit: invoke ExitProcess,NULL
end start
10.EPROCESS的DebugPort成員
Kernel32!CheckRemoteDebuggerPresent()是用於確定是否有調試器被附加到進程。
BOOL CheckRemoteDebuggerPresent(
HANDLE hProcess,
PBOOL pbDebuggerPresent
)
Kernel32!CheckRemoteDebuggerPresent()接受2個參數,第1個參數是進程句柄,第2個參數是一個指向boolean變量的指針,如果進程被調試,該變量將包含TRUE返回值。
這個API內部調用了ntdll!NtQueryInformationProcess(),由它完成檢測工作。
.386
.modelflat,stdcall
optioncasemap:none
include windows.inc
include user32.inc
include kernel32.inc
includelibuser32.lib
includelibkernel32.lib
.data?
dwResult dd ?
.const
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
start:
invoke GetCurrentProcessId
invoke OpenProcess,PROCESS_ALL_ACCESS,NULL,eax
invoke CheckRemoteDebuggerPresent,eax,addr dwResult
cmp dword ptr dwResult,0
jne debugger_found
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
jmp exit
debugger_found:invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK exit: invoke ExitProcess,NULL
end start
ntdll!NtQueryInformationProcess()有5個參數。
為了檢測調試器的存在,需要將ProcessInformationclass參數設為ProcessDebugPort(7)。
NtQueryInformationProcess()檢索內核結構EPROCESS5的DebugPort成員,這個成員是系統用來與調試器通信的端口句柄。非0的DebugPort成員意味着進程正在被用戶模式的調試器調試。如果是這樣的話,ProcessInformation將被置為0xFFFFFFFF,否則ProcessInformation將被置為0。
ZwQueryInformationProcess(
IN HANDLEProcessHandle,
INPROCESSINFOCLASS ProcessInformationClass,
OUT PVOIDProcessInformation,
IN ULONGProcessInformationLength,
OUT PULONGReturnLength OPTIONAL
);
.386
.modelflat,stdcall
optioncasemap:none
include windows.inc
include user32.inc
includelibuser32.lib
include kernel32.inc
includelibkernel32.lib
include ntdll.inc ;這兩個
includelib ntdll.lib
.data?
dwResult dd ?
.const
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
start:
invoke GetCurrentProcessId
invoke OpenProcess,PROCESS_ALL_ACCESS,NULL,eax
invoke ZwQueryInformationProcess,eax,7,offsetdwResult,4,NULL
cmp dwResult,0
jne debugger_found
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
jmp exit
debugger_found:invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK
exit: invoke ExitProcess,NULL
end start
11.SetUnhandledExceptionFilter/ Debugger Interrupts
調試器中步過INT3和INT1指令的時候,由於調試器通常會處理這些調試中斷,所以設置的異常處理例程默認情況下不會被調用,Debugger Interrupts就利用了這個事實。這樣我們可以在異常處理例程中設置標志,通過INT指令后如果這些標志沒有被設置則意味着進程正在被調試。另外,kernel32!DebugBreak()內部是調用了INT3來實現的,有些殼也會使用這個API。注意測試時,在異常處理里取消選中INT3 breaks 和 Singal-stepbreak
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
lpOldHandler dd ?
.const
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
; ExceptionHandler 異常處理程序
_Handler proc _lpExceptionPoint
pushad
mov esi,_lpExceptionPoint
assume esi:ptr EXCEPTION_POINTERS
mov edi,[esi].ContextRecord
assume edi:ptr CONTEXT
mov [edi].regEax,0FFFFFFFFH ;設置EAX
mov [edi].regEip,offset SafePlace
assume esi:nothing,edi:nothing
popad
mov eax,EXCEPTION_CONTINUE_EXECUTION
ret
_Handler endp
start:
invoke SetUnhandledExceptionFilter,addr_Handler
mov lpOldHandler,eax
xor eax,eax ;清零eax
int 3 ;產生異常,然后_Handler被調用
SafePlace:
test eax,eax
je debugger_found
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
jmp exit
debugger_found: invoke MessageBox,NULL,addr szFound,addr szCaption,MB_OK exit: invoke SetUnhandledExceptionFilter,lpOldHandler ;取消異常處理函數
invoke ExitProcess,NULL
end start
由於調試中斷而導致執行停止時,在OllyDbg中識別出異常處理例程(通過視圖->SEH鏈)並下斷點,然后Shift+F9將調試中斷/異常傳遞給異常處理例程,最終異常處理例程中的斷點會斷下來,這時就可以跟蹤了。
另一個方法是允許調試中斷自動地傳遞給異常處理例程。在OllyDbg中可以通過 選項-> 調試選項 -> 異常 -> 忽略下列異常 選項卡中鈎選"INT3中斷"和"單步中斷"復選框來完成設置。
12.單步標志異常
TF=1的時候,會觸發單步異常。該方法屬於異常處理,不過比較特殊:未修改的OD無論是F9還是F8都不能處理異常,有插件的OD在F9時能正確處理,F8時不能正確處理。
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
lpOldHandler dd ?
szCaption db '檢測結果',0
szFound db '程序未收到異常,說明有調試器',0
szNotFound db '程序處理了異常而到達安全位置,沒有調試器',0
.code
_Handler proc _lpExceptionPoint
pushad
mov esi,_lpExceptionPoint
assume esi:ptr EXCEPTION_POINTERS
mov edi,[esi].ContextRecord
assume edi:ptr CONTEXT
mov [edi].regEip,offset SafePlace
assume esi:nothing,edi:nothing
popad
mov eax,EXCEPTION_CONTINUE_EXECUTION
ret
_Handler endp
start:
invoke SetUnhandledExceptionFilter,addr _Handler
mov lpOldHandler,eax
pushfd ;push eflags
or dword ptr [esp],100h ;TF=1
popfd
nop
jmp die
SafePlace:
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
jmp exit
die: invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK
exit: invoke SetUnhandledExceptionFilter,lpOldHandler ;取消異常處理函數
invoke ExitProcess,NULL
end start
13.SeDebugPrivilege 進程權限
默認情況下進程沒有SeDebugPrivilege權限,調試時,會從調試器繼承這個權限,可以通過打開CSRSS.EXE進程間接地使用SeDebugPrivilege確定進程是否被調試。注意默認情況下這一權限僅僅授予了Administrators組的成員。可以使用ntdll!CsrGetProcessId() API獲取CSRSS.EXE的PID,也可以通過枚舉進程來得到CSRSS.EXE的PID。
實例測試中,OD載入后,第一次不能正確檢測,第二次可以,不知為何。
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
include kernel32.inc
include ntdll.inc
includelib user32.lib
includelib kernel32.lib
includelib ntdll.lib
.const
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
start:
invoke CsrGetProcessId ;ntdll!CsrGetProcessId獲取CSRSS.EXE的PID
invoke OpenProcess,PROCESS_QUERY_INFORMATION,NULL,eax
test eax,eax
jnz debugger_found
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
jmp exit
debugger_found:invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK exit: invoke ExitProcess,NULL
end start
14.DebugObject:NtQueryObject()
除了識別進程是否被調試之外,其他的調試器檢測技術牽涉到檢查系統當中是否有調試器正在運行。逆向論壇中討論的一個有趣的方法就是檢查DebugObject類型內核對象的數量。這種方法之所以有效是因為每當一個應用程序被調試的時候,將會為調試對話在內核中創建一
個DebugObject類型的對象。
DebugObject的數量可以通過ntdll!NtQueryObject()檢索所有對象類型的信息而獲得。NtQueryObject接受5個參數,為了查詢所有的對象類型,ObjectHandle參數被設為NULL,ObjectInformationClass參數設為ObjectAllTypeInformation(3):
NTSTATUS NTAPI NtQueryObject(
IN HANDLE ObjectHandle,
IN OBJECT_INFORMATION_CLASS ObjectInformationClass,
OUT PVOID ObjectInformation,
IN ULONG Length,
OUT PULONG ResultLength
)
這個API返回一個OBJECT_ALL_INFORMATION結構,其中NumberOfObjectsTypes成員為所有的對象類型在ObjectTypeInformation數組中的計數:
typedef struct _OBJECT_ALL_INFORMATION{
ULONG NumberOfObjectsTypes;
OBJECT_TYPE_INFORMATION ObjectTypeInformation[1];
}
檢測例程將遍歷擁有如下結構的ObjectTypeInformation數組:
typedef struct _OBJECT_TYPE_INFORMATION{
[00] UNICODE_STRING TypeName;
[08] ULONG TotalNumberofHandles;
[0C] ULONG TotalNumberofObjects;
...more fields...
}
TypeName成員與UNICODE字符串"DebugObject"比較,然后檢查TotalNumberofObjects 或 TotalNumberofHandles 是否為非0值。
15.Guard Pages
這個檢查是針對OllyDbg的,因為它和OllyDbg的內存訪問/寫入斷點特性相關。
除了硬件斷點和軟件斷點外,OllyDbg允許設置一個內存訪問/寫入斷點,這種類型的斷點是通過頁面保護來實現的。簡單地說,頁面保護提供了當應用程序的某塊內存被訪問時獲得通知這樣一個途徑。
頁面保護是通過PAGE_GUARD頁面保護修改符來設置的,如果訪問的內存地址是受保護頁面的一部分,將會產生一個STATUS_GUARD_PAGE_VIOLATION(0x80000001)異常。如果進程被OllyDbg調試並且受保護的頁面被訪問,將不會拋出異常,訪問將會被當作內存斷點來處理,而殼正好利用了這一點。
示例
下面的示例代碼中,將會分配一段內存,並將待執行的代碼保存在分配的內存中,然后啟用頁面的PAGE_GUARD屬性。接着初始化標設符EAX為0,然后通過執行內存中的代碼來引發STATUS_GUARD_PAGE_VIOLATION異常。如果代碼在OllyDbg中被調試,因為異常處理例程不會被調用所以標設符將不會改變。
對策
由於頁面保護引發一個異常,逆向分析人員可以故意引發一個異常,這樣異常處理例程將會
被調用。在示例中,逆向分析人員可以用INT3指令替換掉RETN指令,一旦INT3指令被執行,Shift+F9強制調試器執行異常處理代碼。這樣當異常處理例程調用后,EAX將被設為正確的值,然后RETN指令將會被執行。
如果異常處理例程里檢查異常是否真地是STATUS_GUARD_PAGE_VIOLATION,逆向分析人員可以在異常處理例程中下斷點然后修改傳入的ExceptionRecord參數,具體來說就是ExceptionCode, 手工將ExceptionCode設為STATUS_GUARD_PAGE_VIOLATION即可。
實例:
.386
.modelflat,stdcall
optioncasemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
lpOldHandler dd ?
dwOldType dd ?
.const
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
_Handler proc _lpExceptionPoint
pushad
mov esi,_lpExceptionPoint
assume esi:ptr EXCEPTION_POINTERS
mov edi,[esi].ContextRecord
assume edi:ptr CONTEXT
mov [edi].regEax,0FFFFFFFFH ;檢測標志
mov [edi].regEip,offset SafePlace
assume esi:nothing,edi:nothing
popad
mov eax,EXCEPTION_CONTINUE_EXECUTION
ret
_Handler endp
start:
invoke SetUnhandledExceptionFilter,addr_Handler
mov lpOldHandler,eax
invoke VirtualAlloc,NULL,1000H,MEM_COMMIT,PAGE_READWRITE ;分配內存
push eax
mov byte ptr [eax],0C3H ;寫一個 RETN到保留內存,以便下面的調用
invoke VirtualProtect,eax,1000h,PAGE_EXECUTE_READ or PAGE_GUARD,addr dwOldType
xor eax,eax ;檢測標志
pop ecx
call ecx ;執行保留內存代碼,觸發異常
SafePlace:
test eax,eax ;檢測標志
je debugger_found
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
jmp exit
debugger_found: invoke MessageBox,NULL,addr szFound,addr szCaption,MB_OK
exit: invoke VirtualFree,ecx,1000H,MEM_DECOMMIT
invoke SetUnhandledExceptionFilter,lpOldHandler ;取消異常處理函數
invoke ExitProcess,NULL
end start
16.Software Breakpoint.
軟件斷點是通過修改目標地址代碼為0xCC(INT3/BreakpointInterrupt)來設置的斷點。通過在受保護的代碼段和(或)API函數中掃描字節0xCC來識別軟件斷點。這里以普通斷點和函數斷點分別舉例。
(1) 實例一 普通斷點
注意:在被保護的代碼區域下INT3斷點進行測試
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.const
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
start: jmp CodeEnd
CodeStart: mov eax,ecx ;被保護的程序段
nop
push eax
push ecx
pop ecx
pop eax
CodeEnd:
cld ;檢測代碼開始
mov edi,offset CodeStart
mov ecx,offset CodeEnd -offset CodeStart
mov al,0CCH
repne scasb
jz debugger_found
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
jmp exit
debugger_found:invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK
exit: invoke ExitProcess,NULL
end start
(1) 實例二 函數斷點bp
利用GetProcAddress函數獲取API的地址
注意:檢測時,BPMessageBoxA
.386
.modelflat,stdcall
optioncasemap:none
includewindows.inc
includeuser32.inc
includelibuser32.lib
includekernel32.inc
includelibkernel32.lib
.const
szKernelDll db 'user32.dll',0
szAPIMessboxdb 'MessageBoxA',0
szCaption db '結果',0
szFound db '發現API斷點',0
szNotFound db '未發現斷點',0
.code
start:
invoke GetModuleHandle,addr szKernelDll
invoke GetProcAddress,eax,addrszAPIMessbox ;API地址
cld ;檢測代碼開始
mov edi,eax ;API開始位置
mov ecx,100H ;檢測100字節
mov al,0CCH ;CC
repne scasb
jz debugger_found
invoke MessageBox,NULL,addrszNotFound,addr szCaption,MB_OK
jmp exit
debugger_found:invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK
exit: invoke ExitProcess,NULL
end start
17.Hardware Breakpoints.
硬件斷點是通過設置名為Dr0到Dr7的調試寄存器來實現的。Dr0-Dr3包含至多4個斷點的地址,Dr6是個標志,它指示哪個斷點被觸發了,Dr7包含了控制4個硬件斷點諸如啟用/禁用或者中斷於讀/寫的標志。
由於調試寄存器無法在Ring3下訪問,硬件斷點的檢測需要執行一小段代碼。可以利用含有調試寄存器值的CONTEXT結構,該結構可以通過傳遞給異常處理例程的ContextRecord參數來訪問。
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
lpOldHandler dd ?
.const
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
_Handler proc _lpExceptionPoint
pushad
mov esi,_lpExceptionPoint
assume esi:ptr EXCEPTION_POINTERS
mov edi,[esi].ContextRecord
assume edi:ptr CONTEXT
mov [edi].regEip,offset SafePlace
cmp [edi].iDr0,0 ;檢測硬件斷點
jne debugger_found
cmp [edi].iDr1,0
jne debugger_found
cmp [edi].iDr2,0
jne debugger_found
cmp [edi].iDr3,0
jne debugger_found
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
jmp TestOver
debugger_found:invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK
TestOver:assume esi:nothing,edi:nothing
popad
mov eax,EXCEPTION_CONTINUE_EXECUTION
ret
_Handler endp
start:
invoke SetUnhandledExceptionFilter,addr _Handler
mov lpOldHandler,eax
xor eax,eax ;清零eax
mov dword ptr [eax],0 ;產生異常,然后_Handler被調用
SafePlace: invoke SetUnhandledExceptionFilter,lpOldHandler ;取消異常處理函數
invoke ExitProcess,NULL
end start
18.補丁檢測,代碼檢驗和
補丁檢測技術能識別殼的代碼是否被修改,也能識別是否設置了軟件斷點。補丁檢測是通過代碼校驗來實現的,校驗計算包括從簡單到復雜的校驗和/哈希算法。
實例:改動被保護代碼的話,CHECKSUM需要修改,通過OD等找出該值
注意:在被保護代碼段下F2斷點或修改字節來測試
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
CHECKSUM EQU 915Ch ;改動被保護代碼的話,需要修改
.const
szCaption db '檢測結果',0
szFound db '檢測到調試器',0
szNotFound db '沒有調試器',0
.code
start: jmp CodeEnd
CodeStart: mov eax,ecx ;被保護的程序段
nop
push eax
push ecx
pop ecx
pop eax
CodeEnd:
mov esi,CodeStart
mov ecx,CodeEnd - CodeStart
xor eax,eax
checksum_loop:
movzx ebx,byte ptr [esi]
add eax,ebx
rol eax,1
inc esi
loop checksum_loop
cmp eax,CHECKSUM
jne debugger_found
invoke MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK
jmp exit
debugger_found:invoke MessageBox,NULL,addr szFound,addrszCaption,MB_OK
exit: invoke ExitProcess,NULL
end start
19.封鎖鍵盤、鼠標輸入
user32!BlockInput() API 阻斷鍵盤和鼠標的輸入。
典型的場景可能是逆向分析人員在GetProcAddress()內下斷,然后運行脫殼代碼直到被斷下。但是跳過一段垃圾代碼之后殼調用BlockInput()。當GetProcAddress()斷點斷下來后,逆向分析人員會突然困惑地發現無法控制調試器了,不知究竟發生了什么。
示例:源碼看附件
BlockInput()參數fBlockIt,true,鍵盤和鼠標事件被阻斷;false,鍵盤和鼠標事件解除阻斷:
; Block input
push TRUE
call [BlockInput]
;...Unpackingcode...
;Unblock input
push FALSE
call [BlockInput]
對策
(1)最簡單的方法就是補丁 BlockInput()使它直接返回。
(2)同時按CTRL+ALT+DELETE鍵手工解除阻斷。
20.禁用窗口
在資源管理器里直接雙擊運行的話,會使當前的資源管理器窗口被禁用。
在OD里面的話,就會使OD窗口被禁用。
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.const
szCaption db '結果',0
szEnableFalse db '窗口已經禁用',0
szEnableTrue db '窗口已經恢復',0
.code
start:
invoke GetForegroundWindow
mov ebx,eax
invoke EnableWindow,eax,FALSE
invoke MessageBox,NULL,addr szEnableFalse,addrszCaption,MB_OK
nop
invoke EnableWindow,ebx,TRUE
invoke MessageBox,NULL,addr szEnableTrue,addrszCaption,MB_OK
nop
invoke ExitProcess,NULL
end start
21.ThreadHideFromDebugger
ntdll!NtSetInformationThread()用來設置一個線程的相關信息。把ThreadInformationClass參數設為ThreadHideFromDebugger(11H)可以禁止線程產生調試事件。
ntdll!NtSetInformationThread的參數列表如下。ThreadHandle通常設為當前線程的句柄(0xFFFFFFFE):
NTSTATUS NTAPI NtSetInformationThread(
IN HANDLE ThreadHandle,
IN THREAD_INFORMATION_CLASS ThreadInformaitonClass,
IN PVOID ThreadInformation,
IN ULONG ThreadInformationLength
);
ThreadHideFromDebugger內部設置內核結構ETHREAD的HideThreadFromDebugger成員。一旦這個成員設置以后,主要用來向調試器發送事件的內核函數_DbgkpSendApiMessage()將不再被調用。
invoke GetCurrentThread
invoke NtSetInformationThread,eax,11H,NULL,NULL
對策:
(1)在ntdll!NtSetInformationThread()里下斷,斷下來后,操縱EIP防止API調用到達內核
(2)Olly Advanced插件也有補這個API的選項。補過之后一旦ThreadInformaitonClass參數為HideThreadFromDebugger,API將不再深入內核僅僅執行一個簡單的返回。
.386
.modelflat,stdcall
optioncasemap:none
include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib
include ntdll.inc
includelib ntdll.lib
.const
szCaption db '確定以后看看效果',0
szNotice db '匯編代碼會消失哦',0
szResult db '看到效果了嗎?沒有則稍等',0
.code
start:
invoke MessageBox,NULL,addr szNotice,addrszCaption,MB_OK
invoke GetCurrentThread
invoke NtSetInformationThread,eax,11H,NULL,NULL
invoke MessageBox,NULL,addr szResult,addrszCaption,MB_OK
mov eax,ebx ;其它指令
invoke ExitProcess,NULL
end start
22.禁用硬件斷點
;執行過后,OD查看硬件斷點還存在,但實際已經不起作用了
;利用CONTEXT結構,該結構利用異常處理獲得,異常處理完后會自動寫回
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
lpOldHandler dd ?
.code
_Handler proc _lpExceptionPoint
pushad
mov esi,_lpExceptionPoint
assume esi:ptr EXCEPTION_POINTERS
mov edi,[esi].ContextRecord
assume edi:ptr CONTEXT
mov [edi].regEip,offset SafePlace
xor eax,eax
mov [edi].iDr0,eax
mov [edi].iDr1,eax
mov [edi].iDr2,eax
mov [edi].iDr3,eax
mov [edi].iDr6,eax
mov [edi].iDr7,eax
assume esi:nothing,edi:nothing
popad
mov eax,EXCEPTION_CONTINUE_EXECUTION
ret
_Handler endp
start:
invoke SetUnhandledExceptionFilter,addr _Handler
mov lpOldHandler,eax
xor eax,eax ;清零eax
mov dword ptr [eax],0 ;產生異常,然后_Handler被調用
SafePlace: invoke SetUnhandledExceptionFilter,lpOldHandler ;取消異常處理函數
invoke ExitProcess,NULL
end start
23.OllyDbg:OutputDebugString() Format String Bug
OutputDebugString函數用於向調試器發送一個格式化的串,Ollydbg會在底端顯示相應的信息。OllyDbg存在格式化字符串溢出漏洞,非常嚴重,輕則崩潰,重則執行任意代碼。這個漏洞是由於Ollydbg對傳遞給kernel32!OutputDebugString()的字符串參數過濾不嚴導致的,它只對參數進行那個長度檢查,只接受255個字節,但沒對參數進行檢查,所以導致緩沖區溢出。
例如:printf函數:%d,當所有參數壓棧完畢后調用printf函數的時候,printf並不能檢測參數的正確性,只是機械地從棧中取值作為參數,這樣堆棧就被破壞了,棧中信息泄漏。。
示例:下面這個簡單的示例將導致OllyDbg拋出違規訪問異常或不可預期的終止。
szFormatStr db '%s%s',0
push offset szFormatStr
call OutputDebugString
對策:補丁 kernel32!OutputDebugStringA()入口使之直接返回
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
szFormatStr db '%s%s',0
szCaption db '呵呵',0
szNotice db '執行已結束,看到效果了嗎?',0
.code
start:
push offset szFormatStr
call OutputDebugString
invoke MessageBox,NULL,addr szNotice,addrszCaption,MB_OK
invoke ExitProcess,NULL
end start
24.TLS Callbacks
使用Thread Local Storage (TLS)回調函數可以實現在實際的入口點之前執行反調試的代碼,這也是OD載入程序就退出的原因所在。(Anti-OD)
線程本地存儲器可以將數據與執行的特定線程聯系起來,一個進程中的每個線程在訪問同一個線程局部存儲時,訪問到的都是獨立的綁定於該線程的數據塊。動態綁定(運行時)線程特定數據是通過 TLS API(TlsAlloc、TlsGetValue、TlsSetValue 和 TlsFree)的方式支持的。除了現有的 API 實現,Win32 和 Visual C++ 編譯器現在還支持靜態綁定(加載時間)基於線程的數據。當使用_declspec(thread)聲明的TLS變量
時,編譯器把它們放入一個叫.tls的區塊里。當應用程序加載到內存時,系統尋找可執行文件中的.tls區塊,並動態的分配一個足夠大的內存塊,以便存放TLS變量。系統也將一個指向已分配內存的指針放到TLS數組里,這個數組由FS:[2CH]指向。
數據目錄表中第9索引的IMAGE_DIRECTORY_ENTRY_TLS條目的VirtualAddress指向TLS數據,如果非零,這里是一個IMAGE_TLS_DIRECTORY結構,如下:
IMAGE_TLS_DIRECTORY32 STRUC
StartAddressOfRawData DWORD ? ; 內存起始地址,用於初始化新線程的TLS
EndAddressOfRawData DWORD ? ; 內存終止地址
AddressOfIndex DWORD ? ; 運行庫使用該索引來定位線程局部數據
AddressOfCallBacks DWORD ? ; PIMAGE_TLS_CALLBACK函數指針數組的地址
SizeOfZeroFill DWORD ? ; 用0填充TLS變量區域的大小
Characteristics DWORD ? ; 保留,目前為0
IMAGE_TLS_DIRECTORY32 ENDS
AddressOfCallBacks 是線程建立和退出時的回調函數,包括主線程和其它線程。當一個線程創建或銷毀時,在列表中的每一個函數被調用。一般程序沒有回調函數,這個列表是空的。TLS數據初始化和TLS回調函數調用都在入口點之前執行,也就是說TLS是程序最開始運行的地方。程序退出時,TLS回調函數再被執行一次。回調函數:
TLS_CALLBACK proto Dllhandle : LPVOID, Reason : DWORD,Reserved : LPVOID
參數如下:
Dllhandle : 為模塊的句柄
Reason可取以下值:
DLL_PROCESS_ATTACH 1 : 啟動一個新進程被加載
DLL_THREAD_ATTACH 2 : 啟動一個新線程被加載
DLL_THREAD_DETACH 3 : 終止一個新線程被加載
DLL_PROCESS_DETACH 0 : 終止一個新進程被加載
Reserverd:用於保留,設置為0
IMAGE_TLS_DIRECTORY結構中的地址是虛擬地址,而不是RVA。這樣,如果可執行文件不是從基地址裝入,則這些地址會通過基址重定位修正。而且IMAGE_TLS_DIRECTORY本身不在.TLS區塊中,而在.rdata里。
TLS回調可以使用諸如pedump之類的PE文件分析工具來識別。如果可執行文件中存在TLS條目,數據條目將會顯示出來。
Data directory
EXPORT rva:00000000 size:00000000
IMPORT rva:00061000 size:000000E0
:::
TLS rva:000610E0 size:00000018
:::
IAT rva:00000000 size:00000000
DELAY_IMPORT rva:00000000 size:00000000
COM_DESCRPTR rva:00000000 size:00000000
unused rva:00000000 size:00000000
接着顯示TLS條目的實際內容。AddressOfCallBacks成員指向一個以null結尾的回調函數數組。
TLS directory:
StartAddressOfRawData: 00000000
EndAddressOfRawData: 00000000
AddressOfIndex: 004610F8
AddressOfCallBacks: 004610FC
SizeOfZeroFill: 00000000
Characteristics: 00000000
在這個例子中,RVA 0x4610fc指向回調函數指針(0x490f43和0x44654e):
默認情況下OllyDbg載入程序將會暫停在入口點,應該配置一下OllyDbg使其在TLS回調被調用之前中斷在實際的loader。
通過“選項->調試選項->事件->第一次中斷於->系統斷點”來設置中斷於ntdll.dll內的實際loader代碼。這樣設置以后,OllyDbg將會中斷在位於執行TLS回調的ntdll!LdrpRunInitializeRoutines()之前的ntdll!_LdrpInitializeProcess(),這時就可以在回調例程中下斷並跟蹤了。例如,在內存映像的.text代碼段上設置內存訪問斷點,可以斷在TLS回調函數。
.386
.model flat,stdcall
option casemap:none
includewindows.inc
includeuser32.inc
includekernel32.inc
includelibuser32.lib
includelibkernel32.lib
.data?
dwTLS_Indexdd ?
OPTION DOTNAME
;; 定義一個TLS節
.tls SEGMENT
TLS_StartLABEL DWORD
dd 0100h dup ("slt.")
TLS_End LABEL DWORD
.tls ENDS
OPTION NODOTNAME
.data
TLS_CallBackStart dd TlsCallBack0
TLS_CallBackEnd dd 0
szTitle db "Hello TLS",0
szInTls db "我在TLS里",0
szInNormal db "我在正常代碼內",0
szClassName db "ollydbg" ; OD 類名
;這里需要注意的是,必須要將此結構聲明為PUBLIC,用於讓連接器連接到指定的位置,
;其次結構名必須為_tls_uesd這是微軟的一個規定。編譯器引入的位置名稱也如此。
PUBLIC_tls_used
_tls_usedIMAGE_TLS_DIRECTORY <TLS_Start, TLS_End, dwTLS_Index, TLS_CallBackStart, 0,?>
.code
;***************************************************************
;; TLS的回調函數
TlsCallBack0proc Dllhandle:LPVOID,dwReason:DWORD,lpvReserved:LPVOID
mov eax,dwReason ;判斷dwReason發生的條件
cmp eax,DLL_PROCESS_ATTACH ; 在進行加載時被調用
jnz ExitTlsCallBack0
invoke FindWindow,addr szClassName,NULL ;通過類名進行檢測
.if eax ;找到
invoke SendMessage,eax,WM_CLOSE,NULL,NULL
.endif
invoke MessageBox,NULL,addr szInTls,addr szTitle,MB_OK
mov dword ptr[TLS_Start],0
xor eax,eax
inc eax
ExitTlsCallBack0:
ret
TlsCallBack0 ENDP
;****************************************************************
Start:
invoke MessageBox,NULL,addr szInNormal,addr szTitle,MB_OK
invoke ExitProcess, 1
end Start
25.CreateFile檢測
win32程序對vxd程序通信時,是通過調用DeviceIoControl函數進入vxd,此函數的一個參數就是由createfile獲得的設備句柄。這樣,同樣生成或打開文件的調用也能夠打開一個到vxd的通道。要用createfile打開一個vxd,而不是一個通常的文件,在文件名的地方必須使用特殊形式。
function SoftIce9x32: Boolean; //探測Win9x下的SoftIce, 發現為True,否則為False begin {Detect Softice Win9x 32bit} if CreateFile('\\.\SICE', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end; function SoftIce9x16: Boolean; //探測Dos下的SoftIce, 發現為True,否則為False begin {Detect Softice Win9x 16bit} if _lopen(PChar('\\.\SICE'), OF_READWRITE) <> HFILE_ERROR then Result := True else Result := False; end; function SoftIceNT32: Boolean; //探測WinNt下的SoftIce, 發現為True,否則為False begin {Detect Softice WinNt 32bit} if CreateFile('\\.\NTICE', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end; function SoftIceNt16: Boolean; //探測WinNt下的16位的SoftIce, 發現為True,否則為False begin {Detect Softice WinNt 16bit} if _lopen(PChar('\\.\NTICE'), OF_READWRITE) <> HFILE_ERROR then Result := True else Result := False; end; function SIWDEBUG: Boolean; //探測Win9x/Nt下的SoftIce, 發現為True,否則為False begin {Detect Softice SIWDEBUG} if CreateFile('\\.\SIWDEBUG', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end; function SIWVID: Boolean; ////探測Win9x/Nt下的SoftIce, 發現為True,否則為False begin {Detect Softice SIWVID} if CreateFile('\\.\SIWVID', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end; function FileMon: Boolean; //探測File Monitor, 發現為True,否則為False begin {Detect File Monitor} if CreateFile('\\.\FILEMON', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end; function RegMon: Boolean; //探測Reg Monitor 發現為True,否則為False begin {Detect File Monitor} if CreateFile('\\.\REGMON', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end; function Trw: Boolean; //探測Win9x下的TRW, 發現為True,否則為False var Trw, TrwDebug: Boolean; begin {Detect Trw} if CreateFile('\\.\Trw', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Trw := True else Trw := False; if CreateFile('\\.\TRWDEBUG', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then TrwDebug := True else TrwDebug := False; Result := Trw or TrwDebug; end; function IceDump: Boolean; //探測Win9x下的IceDump, 發現為True,否則為False begin {Detect IceDump} if CreateFile('\\.\ICEDUMP', GENERIC_READ or GENERIC_WRITE, LE_SHARE_READ or FILE_SHARE_WRITE, nil, EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then Result := True else Result := False; end; procedure FindSoftIce; //探測Win9x/WinNt 下的 SoftIce begin try //容錯代碼 asm mov ebp, 04243484Bh //Bounds Checker為SoftICE預留的后門 mov ax, 04h int 3 cmp al,4 jnz GotSoftIce end; except end; end; procedure FindSoftIce9x; //探測Win9x 下的 SoftIce begin try asm mov ah, 43h int 68h cmp ax, 0f386h //檢測此處是否被調試器設置0f386h jz GotSoftIce end; except end; end;