反调试技术	2
发现OD的处理	2
1.	窗口类名、窗口名	3
2.	检测调试器进程	4
3.	父进程是否是Explorer	5
4.	RDTSC/ GetTickCount时间敏感程序段	6
5.	StartupInfo结构	7
6.	BeingDebugged	8
7.	PEB.NtGlobalFlag , Heap.HeapFlags, Heap.ForceFlags	9
8.	DebugPort: CheckRemoteDebuggerPresent()/NtQueryInformationProcess()	12
9.	SetUnhandledExceptionFilter/ Debugger Interrupts	14
10.	Trap Flag单步标志异常	16
11.	SeDebugPrivilege 进程权限	16
12.	DebugObject: NtQueryObject()犀利	17
13.	OllyDbg:Guard Pages	20
14.	Software Breakpoint Detection	22
15.	Hardware Breakpoints Detection	24
16.	PatchingDetection CodeChecksumCalculation补丁检测,代码检验和	25
17.	block input封锁键盘、鼠标输入	26
18.	EnableWindow禁用窗口	27
19.	ThreadHideFromDebugger	27
20.	Disabling Breakpoints禁用硬件断点	29
21.	OllyDbg:OutputDebugString() Format String Bug	30
22.	TLS Callbacks	30
反反调试技术	35反调试技术 VC版
写意互联网,关注搜索引擎技术,涉猎搜索引擎优化、软件破解、PHP网站建设、Wordpress应用等 
声明:这篇文章是本人学习的总结,理论部分参考了《脱壳的艺术》、《加密与解密》以及本人从网络上收集的资料,在此向原作者致敬。本人的贡献在于根据个人理解对各种反调试技术进行了汇总和高度归纳,并提供了本人创作的各种反调试实例及源代码。本人于09年9月份开始学习软件逆向工程的相关知识,在学习过程中得到大量网友的热心帮助,在此向各位致以诚挚谢意。希望本人的这些工作能够对各位有所帮助,浅陋之处,莫要见笑。各种形式的转载都必须保留作者信息及本声明。
由于本人入门较晚、能力有限,部分方法尚未实现,望高手不吝赐教。实现了的方法大都附有实例程序。
很多方法对于修改版的OD已经失效,请用原版OD进行测试。
发现OD的处理
一、如何获取OD窗口的句柄
1.已经获取了窗口类名或标题:FindWindow函数
2.没有获取窗口类名或标题:GetForeGroundWindow返回前台窗口,这里就是OD的窗口句柄了。注意这种方法更为重要,因为大多数情况下不会知道OD的窗口类名。
invoke  IsDebuggerPresent.if     eaxinvoke  GetForegroundWindow   ;获得的是OD的窗口句柄invoke  SendMessage,eax,WM_CLOSE,NULL,NULL.endif
二、获取OD窗口句柄后的处理
(1)向窗口发送WM_CLOSE消息
void CDetectODDlg::OnWndcls() 
{// TODO: Add your control notification handler code hereHWND hWnd;if(hWnd=::FindWindow("OllyDbg",NULL)){MessageBox("发现OD");::SendMessage(hWnd,WM_CLOSE,NULL,NULL);		}else{MessageBox("没发现OD");}	
}
(2)使OD窗口不可用
HWND hd_od=FindWindow("ollydbg",NULL);
SetWindowLong(hd_od,GWL_STYLE,WS_DISABLED);
(3)终止相关进程,根据窗口句柄获取进程ID,根据进程ID获取进程句柄,HWND hWnd;HANDLE hProc;DWORD pId;if(hWnd=::FindWindow("OllyDbg",NULL))     //获取窗口句柄{MessageBox("发现OD");GetWindowThreadProcessId(hWnd,&pId);  //获取进程IDhProc=OpenProcess(PROCESS_TERMINATE,TRUE,pId); //获取进程句柄TerminateProcess(hProc,200);  //终止进程CloseHandle(hProc);}else{MessageBox("没发现OD");}	
(2)程序自身直接退出1.窗口类名、窗口名
(1)FindWindow
(2)EnumWindow函数调用后,系统枚举所有顶级窗口,为每个窗口调用一次回调函数。在回调函数中用GetWindowText得到窗口标题,用strstr等函数查找有无Ollydbg字符串。StrStr(大小写敏感,对应的StrStrI大小写不敏感)函数返回str2第一次出现在str1中的位置,如果没有找到,返回NULL。
(3)GetForeGroundWindow返回前台窗口(用户当前工作的窗口)。当程序被调试时,调用这个函数将获得Ollydbg的窗口句柄,这样就可以向其发送WM_CLOSE消息将其关闭了。
(1)FindWindow
void CDetectODDlg::OnWndcls() 
{// TODO: Add your control notification handler code hereHWND hWnd;if(hWnd=::FindWindow("OllyDbg",NULL)){MessageBox("发现OD");::SendMessage(hWnd,WM_CLOSE,NULL,NULL);		}else{MessageBox("没发现OD");}	
}(2)EnumWindow 
包含头文件:#include "Shlwapi.h"
BOOL CALLBACK EnumWindowsProc(HWND hwnd,      // handle to parent windowLPARAM lParam   // application-defined value)
{char ch[100];CString str="Ollydbg";if(IsWindowVisible(hwnd)){::GetWindowText(hwnd,ch,100);//AfxMessageBox(ch);if(::StrStrI(ch,str)){AfxMessageBox("发现OD");return FALSE;}}	return TRUE;
}void CDetectODDlg::OnEnumwindow() 
{// TODO: Add your control notification handler code hereEnumWindows(EnumWindowsProc,NULL);
AfxMessageBox("枚举窗口结束,未提示发现OD,则没有OD");
}
2.检测调试器进程
枚举进程列表,看是否有调试器进程(OLLYDBG.EXE,windbg.exe等)。
利用kernel32!ReadProcessMemory()读取进程内存,然后寻找调试器相关的字符串(如”OLLYDBG”)以防止逆向分析人员修改调试器的可执行文件名。
需要头文件:#include "tlhelp32.h"
void CDetectODDlg::OnEnumProcess() 
{// TODO: Add your control notification handler code hereHANDLE hwnd;PROCESSENTRY32 tp32;  //结构体CString str="OLLYDBG.EXE";BOOL bFindOD=FALSE;hwnd=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);if(INVALID_HANDLE_VALUE!=hwnd) {Process32First(hwnd,&tp32);do{if(0==lstrcmp(str,tp32.szExeFile)){AfxMessageBox("发现OD");bFindOD=TRUE;break;}}while(Process32Next(hwnd,&tp32));if(!bFindOD)AfxMessageBox("没有OD");}CloseHandle(hwnd);
}  
3.父进程是否是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,则目标进程很可能被调试
对策:Olly Advanced提供的方法是让Process32Next()总是返回fail,使进程枚举失效,PID检查将会被跳过。这些是通过补丁 kernel32!Process32NextW()的入口代码(将EAX值设为0然后直接返回)实现的。
(1)通过桌面类和名称获得Explorer的PID 源码见附件DWORD ExplorerID;::GetWindowThreadProcessId(::FindWindow("Progman",NULL),&ExplorerID); 
(2)通过进程列表快照获得Explorer的PID 源码见附件
void CDetectODDlg::OnExplorer() 
{// TODO: Add your control notification handler code hereHANDLE hwnd;PROCESSENTRY32 tp32;  //结构体CString str="Explorer.EXE";DWORD ExplorerID;DWORD SelfID;DWORD SelfParentID;SelfID=GetCurrentProcessId();::GetWindowThreadProcessId(::FindWindow("Progman",NULL),&ExplorerID);hwnd=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);if(INVALID_HANDLE_VALUE!=hwnd) {Process32First(hwnd,&tp32);do{if(0==lstrcmp(str,tp32.szExeFile)){//	ExplorerID=tp32.th32ProcessID;//	AfxMessageBox("aaa");}if(SelfID==tp32.th32ProcessID){SelfParentID=tp32.th32ParentProcessID;}}while(Process32Next(hwnd,&tp32));str.Format("本进程:%d 父进程:%d Explorer进程: %d ",SelfID,SelfParentID,ExplorerID);MessageBox(str);if(ExplorerID==SelfParentID){AfxMessageBox("没有OD");}else{AfxMessageBox("发现OD");}}CloseHandle(hwnd);
}4.RDTSC/ GetTickCount时间敏感程序段
当进程被调试时,调试器事件处理代码、步过指令等将占用CPU循环。如果相邻指令之间所花费的时间如果大大超出常规,就意味着进程很可能是在被调试。
(1)RDTSC
将计算机启动以来的CPU运行周期数放到EDX:EAX里面,EDX是高位,EAX是低位。
如果CR4的TSD(time stamp disabled)置位,则rdtsc在ring3下运行会导致异常(特权指令),所以进入ring0,把这个标记置上,然后Hook OD的WaitForDebugEvent,拦截异常事件,当异常代码为特权指令时,把异常处的opcode读出检查,如果是rdtsc,把eip加2,SetThreadContext,edx:eax的返回由你了。
(2)GetTickCount 源码见附件
void CDetectODDlg::OnGetTickCount() 
{// TODO: Add your control notification handler code hereDWORD dTime1;DWORD dTime2;dTime1=GetTickCount();GetCurrentProcessId();GetCurrentProcessId();GetCurrentProcessId();GetCurrentProcessId();dTime2=GetTickCount();if(dTime2-dTime1>100){AfxMessageBox("发现OD");}else{AfxMessageBox("没有OD");}
}             
5.StartupInfo结构
原理:Windows操作系统中的explorer.exe创建进程的时候会把STARTUPINFO结构中的值设为0,而非explorer.exe创建进程的时候会忽略这个结构中的值,也就是结构中的值不为0,所以可以利用这个来判断OD是否在调试程序.
*******************************************************************************
结构体
typedef struct _STARTUPINFO
{DWORD cb;            0000 PSTR lpReserved;        0004PSTR lpDesktop;         0008PSTR lpTitle;            000DDWORD dwX;           0010DWORD dwY;           0014DWORD dwXSize;        0018DWORD dwYSize;        001DDWORD dwXCountChars;  0020DWORD dwYCountChars;  0024DWORD dwFillAttribute;   0028DWORD dwFlags;         002DWORD wShowWindow;    0030WORD cbReserved2;       0034PBYTE lpReserved2;       0038HANDLE hStdInput;       003DHANDLE hStdOutput;      0040HANDLE hStdError;       0044
} STARTUPINFO, *LPSTARTUPINFO;
void CDetectODDlg::OnGetStartupInfo() 
{// TODO: Add your control notification handler code hereSTARTUPINFO info;GetStartupInfo(&info);if(info.dwX!=0 || info.dwY!=0 || info.dwXCountChars!=0 || info.dwYCountChars!=0|| info.dwFillAttribute!=0 || info.dwXSize!=0 || info.dwYSize!=0){AfxMessageBox("发现OD");}else{AfxMessageBox("没有OD");}}6.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"可以补丁这个标志
void CDetectODDlg::OnIsdebuggerpresent() 
{// TODO: Add your control notification handler code hereif(IsDebuggerPresent()){MessageBox("发现OD");}	else{MessageBox("没有OD");}
}
7.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\Windows Nt\CurrentVersion\Image File Execution Options
assume  fs:nothingmov     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:nothingmov     ebx,fs:[30h]     ;ebx指向PEBmov     eax,[ebx+18h]   ;PEB.ProcessHeapcmp	     dword ptr [eax+0ch],2    ;PEB.ProcessHeap.Flagsjne	     debugger_foundcmp	dword ptr [eax+10h],0         ;PEB.ProcessHeap.ForceFlagsjne	debugger_found
这些标志位都是因为BeingDebugged引起的。系统创建进程的时候设置BeingDebugged=TRUE,后来NtGlobalFlag根据这个标记设置FLG_VALIDATE_PARAMETERS等标记。在为进程创建堆时,又由于NtGlobalFlag的作用,堆的Flags被设置了一些标记,这个Flags随即被填充到ProcessHeap的Flags和ForceFlags中,同时堆中被填充了很多BAADF00D之类的东西(HeapMagic,也可用来检测调试)。
一次性解决这些状态见加密解密P413
//**********************************************
typedef ULONG NTSTATUS;
typedef ULONG PPEB;
typedef ULONG KAFFINITY;
typedef ULONG KPRIORITY;typedef struct _PROCESS_BASIC_INFORMATION { // Information Class 0
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
KAFFINITY AffinityMask;
KPRIORITY BasePriority;
ULONG UniqueProcessId;
ULONG InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;typedef enum _PROCESSINFOCLASS {
ProcessBasicInformation, // 0 Y N
ProcessQuotaLimits, // 1 Y Y
ProcessIoCounters, // 2 Y N
ProcessVmCounters, // 3 Y N
ProcessTimes, // 4 Y N
ProcessBasePriority, // 5 N Y
ProcessRaisePriority, // 6 N Y
ProcessDebugPort, // 7 Y Y
ProcessExceptionPort, // 8 N Y
ProcessAccessToken, // 9 N Y
ProcessLdtInformation, // 10 Y Y
ProcessLdtSize, // 11 N Y
ProcessDefaultHardErrorMode, // 12 Y Y
ProcessIoPortHandlers, // 13 N Y
ProcessPooledUsageAndLimits, // 14 Y N
ProcessWorkingSetWatch, // 15 Y Y
ProcessUserModeIOPL, // 16 N Y
ProcessEnableAlignmentFaultFixup, // 17 N Y
ProcessPriorityClass, // 18 N Y
ProcessWx86Information, // 19 Y N
ProcessHandleCount, // 20 Y N
ProcessAffinityMask, // 21 N Y
ProcessPriorityBoost, // 22 Y Y
ProcessDeviceMap,// 23 Y Y
ProcessSessionInformation, // 24 Y Y
ProcessForegroundInformation, // 25 N Y
ProcessWow64Information // 26 Y N
} PROCESSINFOCLASS;typedef NTSTATUS (_stdcall *ZwQueryInformationProcess)(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
); //定义函数指针
void CDetectODDlg::OnPebflags() 
{// TODO: Add your control notification handler code here//定义函数指针变量ZwQueryInformationProcess MyZwQueryInformationProcess;HANDLE hProcess = NULL;PROCESS_BASIC_INFORMATION pbi = {0};ULONG peb = 0;        ULONG cnt = 0;ULONG PebBase = 0;ULONG AddrBase;BOOL bFoundOD=FALSE;WORD flag;DWORD dwFlag;DWORD bytesrw;	DWORD ProcessId=GetCurrentProcessId();hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ProcessId);	if (hProcess != NULL) {//函数指针变量赋值MyZwQueryInformationProcess=(ZwQueryInformationProcess)GetProcAddress(LoadLibrary("ntdll.dll"),"ZwQueryInformationProcess");//函数指针变量调用if (MyZwQueryInformationProcess( hProcess,ProcessBasicInformation,&pbi,sizeof(PROCESS_BASIC_INFORMATION),&cnt) == 0){PebBase = (ULONG)pbi.PebBaseAddress;  //获取PEB地址AddrBase=PebBase;if (ReadProcessMemory(hProcess,(LPCVOID)(PebBase+0x68),&flag,2,&bytesrw) && bytesrw==2)  //读内存地址{ //PEB.NtGlobalFlag				if(0x70==flag){bFoundOD=TRUE;}}if (ReadProcessMemory(hProcess,(LPCVOID)(PebBase+0x18),&dwFlag,4,&bytesrw) && bytesrw==4){AddrBase=dwFlag;}if (ReadProcessMemory(hProcess,(LPCVOID)(AddrBase+0x0c),&flag,2,&bytesrw) && bytesrw==2){//PEB.ProcessHeap.Flagsif(2!=flag){					bFoundOD=TRUE;}}if (ReadProcessMemory(hProcess,(LPCVOID)(AddrBase+0x10),&flag,2,&bytesrw) && bytesrw==2){//PEB.ProcessHeap.ForceFlagsif(0!=flag){bFoundOD=TRUE;}}if(bFoundOD==FALSE){AfxMessageBox("没有OD");}else{AfxMessageBox("发现OD");}}CloseHandle(hProcess);}
}
8.DebugPort: CheckRemoteDebuggerPresent()/NtQueryInformationProcess()
Kernel32!CheckRemoteDebuggerPresent()是用于确定是否有调试器被附加到进程。
BOOL CheckRemoteDebuggerPresent(HANDLE	hProcess,PBOOL  	pbDebuggerPresent
)
Kernel32!CheckRemoteDebuggerPresent()接受2个参数,第1个参数是进程句柄,第2个参数是一个指向boolean变量的指针,如果进程被调试,该变量将包含TRUE返回值。
这个API内部调用了ntdll!NtQueryInformationProcess(),由它完成检测工作。
typedef BOOL (WINAPI *CHECK_REMOTE_DEBUGGER_PRESENT)(HANDLE, PBOOL); //定义函数指针void CDetectODDlg::OnCheckremotedebuggerpresent() 
{// TODO: Add your control notification handler code hereHANDLE      hProcess;HINSTANCE   hModule;    BOOL        bDebuggerPresent = FALSE;CHECK_REMOTE_DEBUGGER_PRESENT CheckRemoteDebuggerPresent; //建立函数指针变量hModule = GetModuleHandleA("Kernel32");      //地址要从模块中动态获得CheckRemoteDebuggerPresent = (CHECK_REMOTE_DEBUGGER_PRESENT)GetProcAddress(hModule, "CheckRemoteDebuggerPresent");      //获取地址hProcess = GetCurrentProcess();CheckRemoteDebuggerPresent(hProcess,&bDebuggerPresent);  //调用if(bDebuggerPresent==TRUE){AfxMessageBox("发现OD");}else{AfxMessageBox("没有OD");}
}
ntdll!NtQueryInformationProcess()有5个参数。
为了检测调试器的存在,需要将ProcessInformationclass参数设为ProcessDebugPort(7)。
NtQueryInformationProcess()检索内核结构EPROCESS5的DebugPort成员,这个成员是系统用来与调试器通信的端口句柄。非0的DebugPort成员意味着进程正在被用户模式的调试器调试。如果是这样的话,ProcessInformation 将被置为0xFFFFFFFF ,否则ProcessInformation 将被置为0。
ZwQueryInformationProcess(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
//********************************************************
typedef NTSTATUS (_stdcall *ZW_QUERY_INFORMATION_PROCESS)(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass, //该参数也需要上面声明的数据结构
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
); //定义函数指针void CDetectODDlg::OnZwqueryinfomationprocess() 
{// TODO: Add your control notification handler code hereHANDLE      hProcess;HINSTANCE   hModule;DWORD       dwResult;ZW_QUERY_INFORMATION_PROCESS MyFunc;hModule = GetModuleHandle("ntdll.dll");MyFunc=(ZW_QUERY_INFORMATION_PROCESS)GetProcAddress(hModule,"ZwQueryInformationProcess");hProcess = GetCurrentProcess();MyFunc(hProcess,ProcessDebugPort,&dwResult,4,NULL);if(dwResult!=0){AfxMessageBox("发现OD");}else{AfxMessageBox("没有OD");}
}
9.SetUnhandledExceptionFilter/ Debugger Interrupts
调试器中步过INT3和INT1指令的时候,由于调试器通常会处理这些调试中断,所以设置的异常处理例程默认情况下不会被调用,Debugger Interrupts就利用了这个事实。这样我们可以在异常处理例程中设置标志,通过INT指令后如果这些标志没有被设置则意味着进程正在被调试。另外,kernel32!DebugBreak()内部是调用了INT3来实现的,有些壳也会使用这个API。注意测试时,在异常处理里取消选中INT3 breaks 和 Singal-step break
安全地址的获取是关键
//********************************************************
static DWORD lpOldHandler;
typedef LPTOP_LEVEL_EXCEPTION_FILTER (_stdcall  *pSetUnhandledExceptionFilter)(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter );
pSetUnhandledExceptionFilter lpSetUnhandledExceptionFilter;LONG WINAPI TopUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo
)
{_asm pushadAfxMessageBox("回调函数");lpSetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER )lpOldHandler);ExceptionInfo->ContextRecord->Eip=NewEip;//转移到安全位置_asm popadreturn EXCEPTION_CONTINUE_EXECUTION;
}void CDetectODDlg::OnSetUnhandledExceptionFilter() 
{bool isDebugged=0;// TODO: Add your control notification handler code herelpSetUnhandledExceptionFilter = (pSetUnhandledExceptionFilter)GetProcAddress(LoadLibrary(("kernel32.dll")),"SetUnhandledExceptionFilter"); lpOldHandler=(DWORD)lpSetUnhandledExceptionFilter(TopUnhandledExceptionFilter);_asm{  //获取这个安全地址call me     //方式一,需要NewEip加上一个偏移值
me:pop NewEip  //方式一结束mov NewEip,offset safe //方式二,更简单int 3  //触发异常}	AfxMessageBox("检测到OD");isDebugged=1;_asm{
safe:	}if(1==isDebugged){}else{AfxMessageBox("没有OD");}	
}
//********************************************************
由于调试中断而导致执行停止时,在OllyDbg中识别出异常处理例程(通过视图->SEH链)并下断点,然后Shift+F9将调试中断/异常传递给异常处理例程,最终异常处理例程中的断点会断下来,这时就可以跟踪了。
另一个方法是允许调试中断自动地传递给异常处理例程。在OllyDbg中可以通过 选项-> 调试选项 -> 异常 -> 忽略下列异常 选项卡中钩选"INT3中断"和"单步中断"复选框来完成设置。
10.Trap Flag单步标志异常
TF=1的时候,会触发单步异常。该方法属于异常处理,不过比较特殊:未修改的OD无论是F9还是F8都不能处理异常,有插件的OD在F9时能正确处理,F8时不能正确处理。void CDetectODDlg::OnTrapFlag() 
{try{_asm{					pushfd					 //触发单步异常or      dword ptr [esp],100h   ;TF=1popfd}AfxMessageBox("检测到OD");}catch(...){AfxMessageBox("没有OD");	}
}
11.SeDebugPrivilege 进程权限 
默认情况下进程没有SeDebugPrivilege权限,调试时,会从调试器继承这个权限,可以通过打开CSRSS.EXE进程间接地使用SeDebugPrivilege确定进程是否被调试。注意默认情况下这一权限仅仅授予了Administrators组的成员。可以使用ntdll!CsrGetProcessId() API获取CSRSS.EXE的PID,也可以通过枚举进程来得到CSRSS.EXE的PID。
实例测试中,OD载入后,第一次不能正确检测,第二次可以,不知为何。void CDetectODDlg::OnSeDebugPrivilege() 
{// TODO: Add your control notification handler code hereHANDLE hProcessSnap;HANDLE hProcess;PROCESSENTRY32 tp32;  //结构体CString str="csrss.exe";hProcessSnap=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);if(INVALID_HANDLE_VALUE!=hProcessSnap) {		Process32First(hProcessSnap,&tp32);do{if(0==lstrcmpi(str,tp32.szExeFile)){hProcess=OpenProcess(PROCESS_QUERY_INFORMATION,NULL,tp32.th32ProcessID);if(NULL!=hProcess){AfxMessageBox("发现OD");					}else{AfxMessageBox("没有OD");}CloseHandle(hProcess);}		}while(Process32Next(hProcessSnap,&tp32));			}CloseHandle(hProcessSnap);
}
12.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值。
#ifndef STATUS_INFO_LENGTH_MISMATCH
#define STATUS_INFO_LENGTH_MISMATCH	((UINT32)0xC0000004L)
#endiftypedef enum _POOL_TYPE {NonPagedPool,PagedPool,NonPagedPoolMustSucceed,DontUseThisType,NonPagedPoolCacheAligned,PagedPoolCacheAligned,NonPagedPoolCacheAlignedMustS
} POOL_TYPE;typedef struct _UNICODE_STRING {USHORT Length;USHORT MaximumLength;PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef const UNICODE_STRING *PCUNICODE_STRING;typedef enum _OBJECT_INFORMATION_CLASS
{ObjectBasicInformation,			// Result is OBJECT_BASIC_INFORMATION structureObjectNameInformation,			// Result is OBJECT_NAME_INFORMATION structureObjectTypeInformation,			// Result is OBJECT_TYPE_INFORMATION structureObjectAllTypesInformation,			// Result is OBJECT_ALL_INFORMATION structureObjectDataInformation			// Result is OBJECT_DATA_INFORMATION structure} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;typedef struct _OBJECT_TYPE_INFORMATION {UNICODE_STRING TypeName; ULONG TotalNumberOfHandles; ULONG TotalNumberOfObjects; WCHAR Unused1[8]; ULONG HighWaterNumberOfHandles; ULONG HighWaterNumberOfObjects; WCHAR Unused2[8]; ACCESS_MASK InvalidAttributes; GENERIC_MAPPING GenericMapping; ACCESS_MASK ValidAttributes; BOOLEAN SecurityRequired; BOOLEAN MaintainHandleCount; USHORT MaintainTypeList; POOL_TYPE PoolType; ULONG DefaultPagedPoolCharge; ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;typedef struct _OBJECT_ALL_INFORMATION {ULONG NumberOfObjectsTypes; OBJECT_TYPE_INFORMATION ObjectTypeInformation[1];
} OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;typedef struct _OBJECT_ALL_TYPES_INFORMATION {ULONG NumberOfTypes;OBJECT_TYPE_INFORMATION TypeInformation[1];
} OBJECT_ALL_TYPES_INFORMATION, *POBJECT_ALL_TYPES_INFORMATION;typedef UINT32 (__stdcall  *ZwQueryObject_t) ( 		  IN HANDLE ObjectHandle, IN OBJECT_INFORMATION_CLASS ObjectInformationClass, OUT PVOID ObjectInformation, IN ULONG Length, OUT PULONG ResultLength );void CDetectODDlg::OnNTQueryObject() 
{// TODO: Add your control notification handler code here// 调试器必须正在调试才能检测到,仅打开OD是检测不到的HMODULE hNtDLL;DWORD dwSize;UINT i;UCHAR  KeyType=0;OBJECT_ALL_TYPES_INFORMATION *Types;OBJECT_TYPE_INFORMATION	*t;ZwQueryObject_t ZwQueryObject;hNtDLL = GetModuleHandle("ntdll.dll");if(hNtDLL){ZwQueryObject = (ZwQueryObject_t)GetProcAddress(hNtDLL, "ZwQueryObject");UINT32 iResult = ZwQueryObject(NULL, ObjectAllTypesInformation, NULL, NULL, &dwSize);if(iResult==STATUS_INFO_LENGTH_MISMATCH){Types = (OBJECT_ALL_TYPES_INFORMATION*)VirtualAlloc(NULL,dwSize,MEM_COMMIT,PAGE_READWRITE);if (Types == NULL) 	return;if (iResult=ZwQueryObject(NULL,ObjectAllTypesInformation, Types, dwSize, &dwSize)) return;	for (t=Types->TypeInformation,i=0;i<Types->NumberOfTypes;i++){   if ( !_wcsicmp(t->TypeName.Buffer,L"DebugObject")) //比较两个是否相等,这个L很特殊,本地的意思{   if(t->TotalNumberOfHandles > 0 || t->TotalNumberOfObjects > 0){AfxMessageBox("发现OD");VirtualFree (Types,0,MEM_RELEASE);return;}break; // Found Anyways}t=(OBJECT_TYPE_INFORMATION *)((char *)t->TypeName.Buffer+((t->TypeName.MaximumLength+3)&~3));}}AfxMessageBox("没有OD!");VirtualFree (Types,0,MEM_RELEASE);}
}
13.OllyDbg: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即可。
实例:
//需要用到在UnhandledExceptionHandler 里定义的一些结构
//********************************************************
static bool isDebugged=1;
LONG WINAPI TopUnhandledExceptionFilter2(struct _EXCEPTION_POINTERS *ExceptionInfo
)
{_asm pushadAfxMessageBox("回调函数");lpSetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER )lpOldHandler);ExceptionInfo->ContextRecord->Eip=NewEip;isDebugged=0;_asm popadreturn EXCEPTION_CONTINUE_EXECUTION;
}void CDetectODDlg::OnGuardPages() 
{// TODO: Add your control notification handler code hereULONG dwOldType;DWORD dwPageSize;LPVOID lpvBase;               // 获取内存的基地址SYSTEM_INFO sSysInfo;         // 系统信息GetSystemInfo(&sSysInfo);     // 获取系统信息dwPageSize=sSysInfo.dwPageSize;		//系统内存页大小lpSetUnhandledExceptionFilter = (pSetUnhandledExceptionFilter)GetProcAddress(LoadLibrary(("kernel32.dll")),"SetUnhandledExceptionFilter"); lpOldHandler=(DWORD)lpSetUnhandledExceptionFilter(TopUnhandledExceptionFilter2);// 分配内存lpvBase = VirtualAlloc(NULL,dwPageSize,MEM_COMMIT,PAGE_READWRITE);if (lpvBase==NULL)	AfxMessageBox("内存分配失败");_asm{mov   NewEip,offset safe //方式二,更简单mov   eax,lpvBasepush  eaxmov   byte ptr [eax],0C3H //写一个 RETN 到保留内存,以便下面的调用}if(0==::VirtualProtect(lpvBase,dwPageSize,PAGE_EXECUTE_READ | PAGE_GUARD,&dwOldType)){AfxMessageBox("执行失败");	}_asm{pop   ecxcall  ecx   //调用时压栈
safe:pop	  ecx    //堆栈平衡,弹出调用时的压栈}	if(1==isDebugged){AfxMessageBox("发现OD");}else{AfxMessageBox("没有OD");}VirtualFree(lpvBase,dwPageSize,MEM_DECOMMIT);
}
14.Software Breakpoint Detection
软件断点是通过修改目标地址代码为0xCC(INT3/Breakpoint Interrupt)来设置的断点。通过在受保护的代码段和(或)API函数中扫描字节0xCC来识别软件断点。这里以普通断点和函数断点分别举例。
(1)实例一   普通断点
注意:在被保护的代码区域下INT3断点进行测试
BOOL DetectBreakpoints()
{BOOL bFoundOD;bFoundOD=FALSE;__asm{jmp     CodeEnd     CodeStart:   mov     eax,ecx  ;被保护的程序段noppush    eaxpush    ecxpop     ecxpop     eaxCodeEnd:     cld               ;检测代码开始mov     edi,offset CodeStartmov     edx,offset CodeStartmov     ecx,offset CodeEndsub     ecx,edxmov     al,0CCHrepne   scasbjnz      ODNotFoundmov bFoundOD,1ODNotFound:				}return bFoundOD;
}
void CDetectODDlg::OnDectectBreakpoints() 
{// TODO: Add your control notification handler code hereHANDLE hProcess;hProcess=::GetCurrentProcess();CString str="利用我定位";if(DetectBreakpoints()){AfxMessageBox("发现OD");}else{AfxMessageBox("没有OD");}	
}
(2)实例二 函数断点bp
利用GetProcAddress函数获取API的地址
注意:检测时,BP MessageBoxA
BOOL DetectFuncBreakpoints()
{BOOL bFoundOD;bFoundOD=FALSE;DWORD dwAddr;dwAddr=(DWORD)::GetProcAddress(LoadLibrary("user32.dll"),"MessageBoxA");__asm{cld               ;检测代码开始mov    edi,dwAddr  ;起始地址mov    ecx,100   ;100bytes  ;检测100个字节mov    al,0CCHrepne   scasbjnz     ODNotFoundmov bFoundOD,1ODNotFound:				}return bFoundOD;
}
void CDetectODDlg::OnDectectFuncBreakpoints() 
{// TODO: Add your control notification handler code hereCString str="利用我定位";if(DetectFuncBreakpoints()){AfxMessageBox("发现OD");}else{AfxMessageBox("没有OD");}	
}
15.Hardware Breakpoints Detection
硬件断点是通过设置名为Dr0到Dr7的调试寄存器来实现的。Dr0-Dr3包含至多4个断点的地址,Dr6是个标志,它指示哪个断点被触发了,Dr7包含了控制4个硬件断点诸如启用/禁用或者中断于读/写的标志。
由于调试寄存器无法在Ring3下访问,硬件断点的检测需要执行一小段代码。可以利用含有调试寄存器值的CONTEXT结构,该结构可以通过传递给异常处理例程的ContextRecord参数来访问。
//********************************************************
static bool isDebuggedHBP=0;
LONG WINAPI TopUnhandledExceptionFilterHBP(struct _EXCEPTION_POINTERS *ExceptionInfo
)
{_asm pushadAfxMessageBox("回调函数被调用");ExceptionInfo->ContextRecord->Eip=NewEip;if(0!=ExceptionInfo->ContextRecord->Dr0||0!=ExceptionInfo->ContextRecord->Dr1||0!=ExceptionInfo->ContextRecord->Dr2||0!=ExceptionInfo->ContextRecord->Dr3)isDebuggedHBP=1;  //检测有无硬件断点ExceptionInfo->ContextRecord->Dr0=0; //禁用硬件断点,置0ExceptionInfo->ContextRecord->Dr1=0;ExceptionInfo->ContextRecord->Dr2=0;ExceptionInfo->ContextRecord->Dr3=0;ExceptionInfo->ContextRecord->Dr6=0;ExceptionInfo->ContextRecord->Dr7=0;ExceptionInfo->ContextRecord->Eip=NewEip; //转移到安全位置_asm popadreturn EXCEPTION_CONTINUE_EXECUTION;
}void CDetectODDlg::OnHardwarebreakpoint() 
{// TODO: Add your control notification handler code herelpSetUnhandledExceptionFilter = (pSetUnhandledExceptionFilter)GetProcAddress(LoadLibrary(("kernel32.dll")),"SetUnhandledExceptionFilter"); lpOldHandler=(DWORD)lpSetUnhandledExceptionFilter(TopUnhandledExceptionFilterHBP);_asm{mov   NewEip,offset safe //方式二,更简单int   3mov   isDebuggedHBP,1 //调试时可能也不会触发异常去检测硬件断点
safe:}	if(1==isDebuggedHBP){AfxMessageBox("发现OD");}else{AfxMessageBox("没有OD");}
}
//********************************************************
16.PatchingDetection CodeChecksumCalculation补丁检测,代码检验和
补丁检测技术能识别壳的代码是否被修改,也能识别是否设置了软件断点。补丁检测是通过代码校验来实现的,校验计算包括从简单到复杂的校验和/哈希算法。
实例:改动被保护代码的话,CHECKSUM需要修改,通过OD等找出该值
注意:在被保护代码段下F2断点或修改字节来测试
/*********************************************************/
BOOL CheckSum()
{BOOL bFoundOD;bFoundOD=FALSE;DWORD CHECK_SUM=5555; //正确校验值DWORD dwAddr;dwAddr=(DWORD)CheckSum;__asm{  ;检测代码开始mov     esi,dwAddrmov     ecx,100xor     eax,eaxchecksum_loop:movzx 	ebx,byte ptr [esi]add 	eax,ebxrol 	eax,1inc 	esiloop 	checksum_loopcmp 	eax,CHECK_SUM		jz      ODNotFoundmov     bFoundOD,1ODNotFound:				}return bFoundOD;
}
void CDetectODDlg::OnChecksum() 
{// TODO: Add your control notification handler code here	if(CheckSum()){AfxMessageBox("发现OD");}else{AfxMessageBox("没有OD");}	
}
17.block input封锁键盘、鼠标输入
user32!BlockInput() API 阻断键盘和鼠标的输入。
典型的场景可能是逆向分析人员在GetProcAddress()内下断,然后运行脱壳代码直到被断下。但是跳过一段垃圾代码之后壳调用BlockInput()。当GetProcAddress()断点断下来后,逆向分析人员会突然困惑地发现无法控制调试器了,不知究竟发生了什么。
示例:源码看附件
BlockInput()参数fBlockIt,true,键盘和鼠标事件被阻断;false,键盘和鼠标事件解除阻断:
; Block input
push 			TRUE
call 			[BlockInput];...Unpacking code...;Unblock input
push 			FALSE
call 			[BlockInput]void CDetectODDlg::OnBlockInput() 
{   // #include "Winable.h"// TODO: Add your control notification handler code hereCString str="利用我定位";DWORD dwNoUse;DWORD dwNoUse2;::BlockInput(TRUE);dwNoUse=2;dwNoUse2=3;dwNoUse=dwNoUse2;::BlockInput(FALSE);	
}
对策
(1)最简单的方法就是补丁 BlockInput()使它直接返回。
(2)同时按CTRL+ALT+DELETE键手工解除阻断。
18.EnableWindow禁用窗口
与BlockInput异曲同工,也是禁用窗口然后再解禁 
在资源管理器里直接双击运行的话,会使当前的资源管理器窗口被禁用。
在OD里面的话,就会使OD窗口被禁用。   MFC里对OD貌似无效void CDetectODDlg::OnEnableWindow() 
{// TODO: Add your control notification handler code hereCString str="利用我定位";CWnd *wnd;wnd=GetForegroundWindow();wnd->EnableWindow(FALSE);DWORD dwNoUse;DWORD dwNoUse2;dwNoUse=2;dwNoUse2=3;dwNoUse=dwNoUse2;wnd->EnableWindow(TRUE);
}t
19.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将不再深入内核仅仅执行一个简单的返回。
/*********************************************************/
typedef enum _THREADINFOCLASS {
ThreadBasicInformation, // 0 Y N
ThreadTimes, // 1 Y N
ThreadPriority, // 2 N Y
ThreadBasePriority, // 3 N Y
ThreadAffinityMask, // 4 N Y
ThreadImpersonationToken, // 5 N Y
ThreadDescriptorTableEntry, // 6 Y N
ThreadEnableAlignmentFaultFixup, // 7 N Y
ThreadEventPair, // 8 N Y
ThreadQuerySetWin32StartAddress, // 9 Y Y
ThreadZeroTlsCell, // 10 N Y
ThreadPerformanceCount, // 11 Y N
ThreadAmILastThread, // 12 Y N
ThreadIdealProcessor, // 13 N Y
ThreadPriorityBoost, // 14 Y Y
ThreadSetTlsArrayAddress, // 15 N Y
ThreadIsIoPending, // 16 Y N
ThreadHideFromDebugger // 17 N Y
} THREAD_INFO_CLASS;typedef NTSTATUS (NTAPI *ZwSetInformationThread)(
IN  HANDLE 						ThreadHandle,
IN  THREAD_INFO_CLASS			ThreadInformaitonClass,
IN  PVOID 						ThreadInformation,
IN  ULONG 						ThreadInformationLength
);void CDetectODDlg::OnZwSetInformationThread() 
{// TODO: Add your control notification handler code hereCString str="利用我定位";HANDLE hwnd;HMODULE hModule;hwnd=GetCurrentThread();hModule=LoadLibrary("ntdll.dll");ZwSetInformationThread myFunc;myFunc=(ZwSetInformationThread)GetProcAddress(hModule,"ZwSetInformationThread");myFunc(hwnd,ThreadHideFromDebugger,NULL,NULL);
}
/*********************************************************/	20.Disabling Breakpoints禁用硬件断点
;执行过后,OD查看硬件断点还存在,但实际已经不起作用了
;利用CONTEXT结构,该结构利用异常处理获得,异常处理完后会自动写回见 Hardware Breakpoints Detection
21.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()入口使之直接返回
void CDetectODDlg::OnOutputDebugString() 
{// TODO: Add your control notification handler code here::OutputDebugString("%s%s%s");
} 
22.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    STRUCStartAddressOfRawData  DWORD  ?   ; 内存起始地址,用于初始化新线程的TLSEndAddressOfRawData   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
include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib.data?
dwTLS_Index dd  ?OPTION    DOTNAME
;; 定义一个TLS节          
.tls  SEGMENT                        
TLS_Start LABEL  DWORDdd    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_used IMAGE_TLS_DIRECTORY <TLS_Start, TLS_End, dwTLS_Index, TLS_CallBackStart, 0, ?>.code
;***************************************************************
;; TLS的回调函数
TlsCallBack0 proc Dllhandle:LPVOID,dwReason:DWORD,lpvReserved:LPVOID  mov     eax,dwReason ;判断dwReason发生的条件cmp     eax,DLL_PROCESS_ATTACH  ; 在进行加载时被调用jnz     ExitTlsCallBack0invoke  FindWindow,addr szClassName,NULL  ;通过类名进行检测.if     eax     ;找到invoke    SendMessage,eax,WM_CLOSE,NULL,NULL.endifinvoke  MessageBox,NULL,addr szInTls,addr szTitle,MB_OKmov     dword ptr[TLS_Start],0  xor     eax,eaxinc     eax
ExitTlsCallBack0: ret
TlsCallBack0   ENDP
;****************************************************************
Start:invoke   MessageBox,NULL,addr szInNormal,addr szTitle,MB_OKinvoke   ExitProcess, 1end  Start
VC++ 6.0
VC里的TLS回调,总是有一些问题,基本如下:
1、VC6不支持。
2、VS2005的Debug版正常,Release版不正常。
3、VS2005的Release版正常,Debug版不正常。
VC6不支持的原因是VC6带的TLSSUP.OBJ有问题,它已定义了回调表的第一项,并且为0,0意味着回调表的结束,因此我们加的函数都不会被调用。[INDENT]对于第2个问题,我没遇到,倒是遇到了第3个问题。对这个问题进行了一下研究,发现问题所在:在Link过程中节.CRT$XLA和.CRT$XLB合并时,应该是按字母顺序无间隙合并,但在DEBUG版的输出中实事并非如此,顺序没错,但却产生了很大的间隙,间隙填0,相当于在我们的回调表前加0若干个0,又是回调表提前结束,这也许是BUG。针对第二种情况,我没有遇到,不知道是否是这个原因,如果是,则我想应是LINK的BUG。针对上述问题,本来我想可以使用VS2008的tlssup.obj,但是它与VC6的不兼容,改起来比较麻烦,后来我突然想到,也许我们可以自己创建一个tlssup.obj,基于这个思路,写了自己的tlssup,目前测试结果显示,它可以兼容VC6,VS2005,VS2008。(1)建立一个控制台工程
(2)创建tlssup.c文件,代码如下
(3)将该文件加入工程
(4)英文版:右键点击该tlssup.c文件,选择Setting->C/C++->Gategory->Precomliled Headers->Not using precompiled headers。中文版:右键点击该tlssup.c文件->设置->C/C++->预编译的头文件->不使用预补偿页眉->确定
// tlssup.c文件代码:
#include <windows.h>
#include <winnt.h>
int _tls_index=0;
#pragma data_seg(".tls")
int _tls_start=0;
#pragma data_seg(".tls$ZZZ")
int _tls_end=0;
#pragma data_seg(".CRT$XLA")
int __xl_a=0;
#pragma data_seg(".CRT$XLZ")
int __xl_z=0;
#pragma data_seg(".rdata$T")
extern PIMAGE_TLS_CALLBACK my_tls_callbacktbl[];
IMAGE_TLS_DIRECTORY32 _tls_used={(DWORD)&_tls_start,(DWORD)&_tls_end,(DWORD)&_tls_index,(DWORD)my_tls_callbacktbl,0,0};然后,我们在其它CPP文件中定义my_tls_callbacktbl如下即可:
extern "C" PIMAGE_TLS_CALLBACK my_tls_callbacktbl[] = {my_tls_callback1,0}; //可以有多个回调,但一定要在最后加一个空项,否则很可能出错。当然下面一行也不能少:
#pragma comment(linker, "/INCLUDE:__tls_used")// 工程cpp文件代码:
// TLS_CallBack_test.cpp : Defines the entry point for the console application.
//#include <windows.h>
#include <winnt.h>
//下面这行告诉链接器在PE文件中要创建TLS目录
#pragma comment(linker, "/INCLUDE:__tls_used")
void NTAPI my_tls_callback1(PVOID h, DWORD reason, PVOID pv)
{
//仅在进程初始化创建主线程时执行的代码
if( reason == DLL_PROCESS_ATTACH ){MessageBox(NULL,"hi,this is tls callback","title",MB_OK);
}
return;
}
#pragma data_seg(".CRT$XLB")
extern "C" PIMAGE_TLS_CALLBACK my_tls_callbacktbl[] = {my_tls_callback1,0};
#pragma data_seg()
int main(void)
{
MessageBox(NULL,"hi,this is main()","title",MB_OK);
return 0;
}
MFC里
(1)tlssup.c文件 同样设置
(2)代码
#pragma comment(linker, "/INCLUDE:__tls_used")
/*这是PIMAGE_TLS_CALLBACK()函数的原型,其中第一个和第三个参数保留,第二个参数决定函数在那种情况下*/
void NTAPI my_tls_callback1(PVOID h, DWORD reason, PVOID pv)
{
if( reason == DLL_PROCESS_ATTACH ){MessageBox(NULL,"hi,this is tls callback","title",MB_OK);
}
return;
}
#pragma data_seg(".CRT$XLB")
extern "C" PIMAGE_TLS_CALLBACK my_tls_callbacktbl[] = {my_tls_callback1,0};
#pragma data_seg()反反调试技术
本人脱壳逆向的水平不高,这里仅说一下本人的一点体会:
对于初学者来说主要是利用StrongOD等各种插件,这些插件能够躲过上面所说的很多检测。有了一定基础以后就可以根据各种反调试方法的弱点寻求反反调试的途径了。曾经写过一篇关于ANTI-OD的原理和应对方法的文章,也可以用于增强自己的OD,各位可以看一下:OD被Anti的原因分析及应对之道:
http://www.ucooper.com/od-anti-reasons.html各种反调试技术原理与实例 汇编版
http://www.ucooper.com/anti-debug-methods-asm.html欢迎莅临本人空间:http://ucooper.com
写意互联网,关注搜索引擎技术,涉猎搜索引擎优化、软件破解、PHP网站建设、Wordpress应用等。失误之处,敬请指教。
参考文献:《脱壳的艺术》、《加密与解密》、看雪论坛、其它资料

 

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. .net批量下载图片

    问题:我想实现的是一个一个的下载。比如我有一个文件列表。通过checkbox选择。通过单击下载按钮下载选中文件。百度到都是用打包形式实现批量下载。这是我自己写的代码,但是点击下载后只能下载一个文件。单步调试,循环只执行一次。不知道是什么原因。后台代码string Name;st…...

    2024/4/28 12:23:59
  2. Linux下Xdebug的编译安装及使用方法

    http://zhugebin.sinaapp.com/linux/make_xdebug/ Xdebug是一款开源的PHP跟踪调试器,它以PHP模块的形式加载和被使用。Xdebug可以以日志的形式记录PHP代码执行的每一个环节以及 消耗的时间,支持通过配置LOG的级别来选择记录信息的深度(比如是否记录方法调用的参数类型和值)…...

    2024/4/27 22:56:24
  3. python 单步调试

    命令行启动目标程序,加上-m参数,这样调用myscript.py的话断点就是程序的执行第一行之前python -m pdb myscript.py参考文件http://pythonconquerstheuniverse.wordpress.com/category/python-debugger/翻译不是一一对应Debug功能对于developer是非常重要的,python提供了相应…...

    2024/4/24 7:29:44
  4. PHP 性能分析(三): 性能调优实战

    在本系列的 第一篇 中,我们介绍了 XHProf 。而在 第二篇 中,我们深入研究了 XHGui UI, 现在最后一篇,让我们把 XHProf /XHGui 的知识用到工作中!性能调优 不用运行的代码才是绝好的代码。其他只是好的代码。所以,性能调优时,最好的选择是首先确保运行尽可能少的代码。Op…...

    2024/4/26 8:05:55
  5. PHP 性能分析第三篇: 性能调优实战

    性能调优 不用运行的代码才是绝好的代码。其他只是好的代码。所以,性能调优时,最好的选择是首先确保运行尽可能少的代码。 OpCode 缓存 首先,最快且最简单的选择是启用 OpCode 缓存。OpCode 缓存的更多信息可以在 这里 找到。在上图,我们看到启用 Zend OpCache 后发生的情况…...

    2024/4/27 3:51:08
  6. Ubuntu 10.04环境下载&编译Android-2.2.1 (froyo) 源代码 [转]

    应一个在电信做android开发的哥们要求,帮忙把Goole未在sdk中打包的API(即:mms彩信功能),打包到自己编译的sdk里面。偶用了差 不多5天+5夜的时间,虽然没有成功。但对于Ubuntu 10.04环境下载&编译Android-2.2.1 (froyo)源代码这个过程是相当熟悉啦,来来回回,在虚拟…...

    2024/4/27 20:42:20
  7. OneAPM大讲堂 | 提高JavaScript性能的30个技巧

    文章系国内领先的 ITOM 管理平台供应商 OneAPM 编译呈现。 您是网站管理员还是网页开发人员?想创建超快速的网站吗? 今天我们来看看 JavaScript,这项神奇而又复杂的技术。它使网站内容更加丰富,但常常出现的运行性能问题又降低了用户的体验。事实已经证明,最佳的终端用户体…...

    2024/4/19 8:52:47
  8. 推荐10款非常优秀的 HTML5 开发工具[转]

    http://www.cnblogs.com/lhb25/archive/2011/10/09/10-online-tools-to-simplify-html5-coding.htmlHTML5 发展如火如荼,随着各大浏览器对 HTML5 技术支持的不断完善以及 HTML5 技术的不断成熟,未来 HTML5 必将改变我们创建 Web 应用程序的方式。今天这篇文章向大家推荐10款优…...

    2024/4/27 3:51:22
  9. 10款非常优秀的 HTML5 开发工具

     HTML5 发展如火如荼,随着各大浏览器对 HTML5 技术支持的不断完善以及 HTML5 技术的不断成熟,未来 HTML5 必将改变我们创建 Web 应用程序的方式。今天这篇文章向大家推荐10款优秀的HTML5开发工具,帮助你更高效的编写 HTML5 应用。InitializrInitializr 是制作 HTML5 网站…...

    2024/4/24 14:45:32
  10. html5资源

    HTML50.开发框架和开发工具汇总 0.1.推荐25款很棒的 HTML5 开发框架和开发工具【上篇】 0.2.HTML5 开发者需要了解的技巧和工具汇总 0.3.你真的精通 CSS 了?来挑战一下 CSS 选择器测验吧 1.不同浏览器对CSS3和html5的支持 2.推荐10款非常优秀的HTML5 开发工具 Initializr →访…...

    2024/4/25 21:46:55
  11. Https下字体文件无法加载的解决方案

    一开始以为是权限问题,但是更改后无效 在nginx.conf文件中加入如下配置 然后重启nginx即可生效,如需别的格式 自行添加 location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico|eot|otf|ttf|woff|woff2)$ {expires 30d;access_log off;}...

    2024/4/26 18:20:39
  12. 高性能MySQL集群详解(二)

    一.通过Keepalived搭建MySQL双主模式的高可用集群系统 1.MySQL Replication介绍: MySQL Replication是MySQL自身提供的一个主从复制功能,其实也就是一台MySQL服务器(称为Slave)从另一台MySQL服务器(称为Master)上复制日志,然后解析日志并应用到自身的过程。MySQL Rep…...

    2024/4/24 22:48:10
  13. 史上最详细的WordPress安装教程(四):安装mysql 5.7

    安装mysql添加源rpm -Uvh http://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm#或wget http://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpmrpm -ivh mysql57-community-release-el7-9.noarch.rpm​安装yum -y install mysql-community-s…...

    2024/4/27 6:48:08
  14. centos7 mysql The server quit without updating PID file(错误解决)

    1 问题[root@localhost mysql]# /etc/rc.d/init.d/mysql statusMySQL is not running, but lock file (/var/lock/subsys/mysql[FAILED][root@localhost mysql]# /etc/rc.d/init.d/mysql startStarting MySQL...The server quit without updating PID file (/usr/local/mysql/d…...

    2024/4/26 18:45:02
  15. 如何在docker中建立mysql的主从同步

    关于主从同步的背景: 最近在工作上遇到一个任务:将主服务器上的mysql数据库和从服务器的mysql数据库实现主从同步。即当主库发生数据的变更时,从库也能做出同样的变更。 1.环境:Linux, docker; 2.mysql运行在docker容器中; 找到mysql的配置文件 MySQL默认读取的配置文件是“…...

    2024/4/25 16:18:30
  16. docker 部署高可用 HAProxy Mysql 双主方案

    主从模式: mysql主从架构中其实就一个主在工作,而从就相当于一个备份机器,从通过日志监测的方式来备份主库上的数据而保证主库的数据安全。 主主模式: 主主复制,就是在mysql主从架构上让mysql主实现监测从的日志变化,从而实现两台机器相互同步。 多主多从模式: MMM…...

    2024/4/24 7:45:35
  17. 单节点k8s的一个小例子 webapp+mysql

    安装kubernetes 准备一台centos7 1) 关闭firewalld 和 selinux systemctl stop firewalld systemctl disable firewalld setenforce 0 2)安装etcd和kubernetes yum install -y etcd kubernetes 3)修改配置文件 vi /etc/sysconfig/docker 将--selinux-enabled 改为 …...

    2024/4/19 15:33:01
  18. phpMyAdmin解决Can 't connect to local MySQL server through socket '/tmp/mysql.sock '(2) ";

    真是受够了,百度问题出来的解决方案各种抄袭。 首先明确解决方法两个 (1)要么ln -s /tmp/mysql.sock /var/lib/mysql/mysql.sock (2)要么在etc/my.cnf里修改 结果我两个都不行,第一个方法还是报错,第二个方法mysqld启动不起来。 (如果你跟我一样那么很可能是phpMyAdmin配置错…...

    2024/4/25 22:11:27
  19. MySQL主从介绍

    MySQL主从介绍•MySQL主从又叫做Replication、AB复制。简单讲就是A和B两台机器做主从后,在A上写数据,另外一台B也会跟着写数据,两者数据实时同步的•MySQL主从是基于binlog的,主上须开启binlog才能进行主从。复制的基本过程如下:1)、Slave上面的IO_thread连接上Master,并…...

    2024/4/28 1:23:11
  20. mysql报错的“Table 'mysql.servers' doesn't exist”的解决方法

    原文:http://www.weiruoyu.cn/?p=571 刷新时提示错误mysql> flush privileges;ERROR 1146 (42S02): Table mysql.servers doesnt exist解决方法一,自己建立这张表。use mysql;CREATE TABLE `servers` (`Server_name` char(64) NOT NULL,`Host` char(64) NOT NULL,`Db` ch…...

    2024/4/19 15:32:58

最新文章

  1. 百度 测试|测试开发 面试真题|面经 汇总

    百度测开 开发测试工程师 提前批一二三面面经 事业群&#xff1a;MEG base&#xff1a;北京 一面&#xff1a;2023.8.12 时长&#xff1a;50min 自我介绍 个人项目&#xff0c;我的项目是围绕着学校课程的项目来的&#xff0c;面试官就让我介绍这门课讲了些什么 &#xff…...

    2024/4/28 19:29:41
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 鸿蒙OS开发实例:【应用事件打点】

    简介 传统的日志系统里汇聚了整个设备上所有程序运行的过程流水日志&#xff0c;难以识别其中的关键信息。因此&#xff0c;应用开发者需要一种数据打点机制&#xff0c;用来评估如访问数、日活、用户操作习惯以及影响用户使用的关键因素等关键信息。 HiAppEvent是在系统层面…...

    2024/4/27 19:15:54
  4. 设计之魅:高质量面向对象设计的秘密

    设计模式是在软件设计中用于解决常见问题的经过验证的解决方案。设计模式并不是代码或库&#xff0c;而是一种解决问题的思考方式。在使用设计模式时&#xff0c;需要考虑一些基本的设计原则&#xff0c;这些原则有助于构建灵活、可维护和可扩展的软件系统。以下是一些常见的设…...

    2024/4/28 16:51:51
  5. 对于布局的见解

    position: absolute;元素的宽度变为content的宽度,这是与position:relative(100%)不同的地方,若要呈现为100%, 有两个方法: 1.直接设置 width:100% 2.设置left:0px right:0px; 布局参看http://www.zhangxinxu.com/study/201010/mini-blog-no-width.html 采用无宽度布局…...

    2024/4/27 21:39:12
  6. 不同浏览器中网页内容高度取值

    原文地址:http://www.alisdn.com/wordpress/?p=1700关于浏览器的clientHeight、offsetHeight和scrollHeight在IE、FireFox、Netscape等不同的浏览器里,对于document.body 的 clientHeight、offsetHeight 和scrollHeight 有着不同的含义,比较容易搞混,现整理一下相关的内容…...

    2024/4/28 5:01:39
  7. 在网页中使用SVG技术

    在网页中使用SVG技术分享:0可缩放矢量图形(SVG)是矢量图形家族的一部分。相比其他光栅图形(JPEG、GIF 和 PNG),SVG 图形具有更多的优势。本文将探讨 SVG 图形的基本概念和在 HTML5 中的使用。学习绘制、过滤器、渐变、文本和将 SVG XML 添加到网页。 简介 可缩放矢量图形…...

    2024/4/28 18:58:36
  8. 瀑布流布局实现的三种方式

    瀑布流布局的特点? 答:首先,是图片的宽度固定,图片的长度不一样。其次,是当页面滚动时 ,会再次加载数据,动态的渲染在页面上。瀑布流布局的原理? 首先,获取图片的固定的宽度W,网页body的clientWidth 其次,获取在你的版面中显示多少列图片。cols = clientHeight / W;…...

    2024/4/28 11:18:00
  9. 移动web图片高度自适应的解决方案

    由于图片的加载是在dom加载完成之后进行的,于是,在手机端浏览网页时,经常会看到页面刚打开时很多内容叠在一起,当图片加载完成后,页面会由于图片加载完成出现明显的抖动针对这个问题,有以下几种解决方案媒体查询+px rem vm padding 媒体查询+px@media screenand(max - wi…...

    2024/4/26 23:06:04
  10. 网页布局之》》》》弹性盒子》》》

    网页弹性盒子》》》》box-flex 属性 按比例占据父元素的尺寸。1,(纵向)水平方向的弹性布局:先给其父元素设置样式为弹性盒子display:box; #father{ display:-webkit-box; display: box;}》》》》》》》子元素(div或盒子)默认是纵向挨着排列的,。》》》》》》定义两个可…...

    2024/4/28 14:32:02
  11. 在xcode中格式化代码

    1. 安装homebrew/usr/bin/ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"2. 安装uncrustifybrew install uncrustify3. 下载配置好的workflow包http://by-igotit.com/wordpress/wp-content/uploads/2012/03/Uncrustify-Objective-C.workflow.tar.gz4. 解…...

    2024/4/28 15:12:03
  12. css content

    before after demo 1 添加描述信息div1:after{conent:"天假额外的文字"}2 也可以显示元素的某些属性<a class="div1" href="http://www.baidu.com"></a>div1:after{ content:attr(href)}注意:当使用attr()获取标签属性名的时候,千…...

    2024/4/28 2:33:44
  13. web PC分页功能实现。动态加载数据列表并分页

    1.最终效果图展示:2.代码截图:3.代码文字:<!DOCTYPE HTML> <HTML><head> <meta charset="utf-8"> <meta name="keywords" content="分页教程"> <meta name="description" content="CSDN平…...

    2024/4/19 15:33:19
  14. 周末作业

    ```<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Polp360首页</title> <link rel="stylesheet" type="text/css" href="css/reset.css">…...

    2024/4/27 23:09:05
  15. 提高JavaScript性能的30个技巧

    今天我们来看看 JavaScript,这项神奇而又复杂的技术。它使网站内容更加丰富,但常常出现的运行性能问题又降低了用户的体验。事实已经证明,最佳的终端用户体验能提升网站的转换率、Google搜索排名以及访问者的满意度。高性能的JavaScript意味着会给您和您的公司带来更多更好的…...

    2024/4/28 1:20:12
  16. PHP 性能分析第三篇: 性能调优实战

    注意:本文是我们的 PHP 性能分析系列的第三篇,点此阅读 PHP 性能分析第一篇: XHProf & XHGui 介绍 ,或 PHP 性能分析第二篇: 深入研究 XHGui 。 在本系列的 第一篇 中,我们介绍了 XHProf 。而在 第二篇 中,我们深入研究了 XHGui UI, 现在最后一篇,让我们把 XHProf …...

    2024/4/24 7:29:46
  17. 读《软星七年》有感

    看完这篇好友推荐的文章,一口气细读下来,心中时而豪气激昂,时而酸痛万分,真是感慨良多,只知道现在心中有说不完的话。。。 "...8月3日,北京举行《仙四》首发签售活动,张毅君如约到场。活动中,一位玩家表示愿意出钱捐助上海软星,支持《仙剑》的研发,被婉拒后,他…...

    2024/4/28 19:11:29
  18. Docker命令行

    Docker命令行(sudo)Docker容器命令行1.查看Docker服务是否正常2.运行容器(docker run -i -t 镜像名称 命令代码)3.使用容器4.查看当前系统中存在的容器:5.容器命名6.启动已停止的容器7.进行容器内部命令行8.创建守护式容器(长期运行的容器)9.查看容器日志10.查看容器内进…...

    2024/4/27 21:14:28
  19. 利用qemu写mips汇编程序控制malta显示器

    hello world不会写,先玩玩malta虚拟器上的跑马灯,就是qemu起来后ctrl+atl+5切换出来那个,安装debian mips版本后会有个"LINUX ON MALTA"在那一直循环。参考之前的文章,windows和linux下都有qemu,gnu的交叉编译工具也都有,所以两个平台应该都能跑。本文参考了:…...

    2024/4/28 10:13:55
  20. Redis 常见面试题整理

    1 什么是 Redis?简述它的优缺点?Redis 的全称是:Remote Dictionary.Server,本质上是一个 Key-Value 类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘上进行保存。因为是纯内存操作,Redis 的性能非常出…...

    2024/4/27 23:06:58
  21. Redis 常见面试题

    今天跟大家分享下Redis 常见面试题的知识。 1 什么是 Redis?简述它的优缺点? Redis 的全称是:Remote Dictionary.Server,本质上是一个 Key-Value 类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘上进行…...

    2024/4/27 3:51:09
  22. OneAPM大讲堂 | 提高JavaScript性能的30个技巧

    文章系国内领先的 ITOM 管理平台供应商 OneAPM 编译呈现。 您是网站管理员还是网页开发人员?想创建超快速的网站吗? 今天我们来看看 JavaScript,这项神奇而又复杂的技术。它使网站内容更加丰富,但常常出现的运行性能问题又降低了用户的体验。事实已经证明,最佳的终端用户体…...

    2024/4/28 1:29:09
  23. 面试之Redis

    什么是 Redis?简述它的优缺点? Redis 的全称是:Remote Dictionary.Server,本质上是一个 Key-Value 类型的内存数据库,很像 memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘上进行保存。 因为是纯内存操作,Redis 的性能非常…...

    2024/4/28 1:41:30
  24. 读完这46道Redis面试题之后,你就会觉得自己的Redis白学了

    前言今天跟大家分享下Redis 常见面试题的知识,总共46道,希望大家能够喜欢。1 什么是 Redis?简述它的优缺点?Redis 的全称是:Remote Dictionary.Server,本质上是一个 Key-Value 类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作…...

    2024/4/25 1:31:28
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57