Intel CPU相关指令:
LOCK
这是一个指令前缀,在所对应的指令操作期间使此指令的目标操作数指定的存储区域锁定,以得到保护。
XADD
先交换两个操作数的值,再进行算术加法操作。多处理器安全,在80486及以上CPU中支持。
CMPXCHG
比较交换指令,第一操作数先和AL/AX/EAX比较,如果相等ZF置1,第二操作数赋给第一操作数,否则ZF清0,第一操作数赋给AL/AX/EAX。
多处理器安全,在80486及以上CPU中支持。
CMPXCHG8B
同上,64位比较交换指令,第二操作数隐含为EDX:EAX,比较EDX:EAX与64位的目标,如果相等则ECX:EBX送往目标且ZF置1,否则目标送EDX:EAX且ZF清0。
windows互锁API列表:
InterlockedCompareExchange/InterlockedCompareExchange64
InterlockedCompareExchangeAcquire/InterlockedCompareExchangeAcquire64
InterlockedCompareExchangePointer
InterlockedCompareExchangeRelease/InterlockedCompareExchangeRelease64
InterlockedDecrement/InterlockedDecrement64
InterlockedDecrementAcquire
InterlockedDecrementRelease
InterlockedExchange/InterlockedExchange64
InterlockedExchangeAcquire64
InterlockedExchangeAdd/InterlockedExchangeAdd64
InterlockedExchangePointer
InterlockedIncrement/InterlockedIncrement64
InterlockedIncrementAcquire
InterlockedIncrementRelease
- 多处理器安全就是当某值被某处理器修改后,其他处理器应当知晓,而不再使用CPU缓存内的旧数据。
- 本文不讨论WinNT3.51/Win95以及更早的操作系统(它们是为80386设计的),以及安装在80386上的Win98(Win98安装时自动判断是否支持XADD指令)。
看一个简单的函数,它的作用是将lpAddend加1,并返回之。
LONG InterlockedIncrement( LPLONG lpAddend )
{
MOV ecx, lpAddend
MOV eax, 1
LOCK XADD dword ptr [ecx], eax
INC eax
RET 4
}
看一个复杂的函数,它的作用是将lValue赋值给*plTarget,并返回*plTarget的原先值。
LONG InterlockedExchange( LPLONG plTarget, LONG lValue )
{
MOV ecx, plTarget
MOV edx, lValue
MOV eax, dword ptr [ecx]
L: LOCK CMPXCHG dword ptr [ecx], edx
JNE L
RET 8
}
看,不得不动用了一个循环指令。类似的还有InterlockedCompareExchange(pdestination,exchage,comperand)函数,如果Destination等于Comperand,将Exchange赋值给Destination,否则什么也不干,返回值为Destination的原先值。
循环技巧是很有用的,Jeffrey Richter给出了一个功能代码,如果值大于0,则加1,代码如下:
do {
LONG lStartValue = *plTarget;
LONG lNewValue = lStartValue + ((lStartValue > 0) ? 1 : 0);
} while( InterlockedCompareExchange(plTarget,lNewValue,lStartValue) != lStartValue );
一个未知的问题是,为什么在有的操作系统中没看到LOCK。未来的研究是线程/进程锁。
Windows貌似没有提供相关函数来处理hook代码时的多核同步问题,搜索相关帖子,找到一个通过获取用于线程调度自旋锁方式来达到这个目标;还有一种,是插dpc,让其他cpu空耗,这段期间完成代码替换。
感觉都不好用00!想起前一段时间看软件调试时提到了可以冻结其他cpu的函数KiSendFreeze(),能从多核模式变成单核模式。但不能逆向操作-不能再从单核模式恢复多核模式,也不好00!
既然这些主防在内核上下了这么多控制点,她们是怎么处理多核同步问题的。跟踪逆向了一下,
.text:00010DFC sub_10DFC proc near ; CODE XREF: sub_10E24+BE p
.text:00010DFC ; sub_10E24+FB p ...
.text:00010DFC
.text:00010DFC var_4 = dword ptr -4
.text:00010DFC arg_0 = dword ptr 8
.text:00010DFC
.text:00010DFC mov edi, edi
.text:00010DFE push ebp
.text:00010DFF mov ebp, esp
.text:00010E01 push ecx
.text:00010E02 cli
.text:00010E03 push eax
.text:00010E04 mov eax, cr0
.text:00010E07 mov [ebp+var_4], eax
.text:00010E0A and eax, 0FFFEFFFFh
.text:00010E0F mov cr0, eax
.text:00010E12 pop eax
.text:00010E13 mov eax, [ebp+arg_0]
.text:00010E16 mov ecx, [ebp+var_4]
.text:00010E19 mov [eax], ecx
.text:00010E1B leave
.text:00010E1C retn 4
.text:00010E1C sub_10DFC endp
.text:00010F1F call sub_10DFC
.text:00010F24 mov ebx, dword_13464[ebx]
.text:00010F2A sub edx, ebx
.text:00010F2C sub edx, 4
.text:00010F2F xchg edx, [ebx]
.text:00010F31 push eax
.text:00010F32 mov eax, [ebp+var_20]
.text:00010F35 mov cr0, eax
.text:00010F38 pop eax
.text:00010F39 sti
无非也是关中断,开启内存可写,然后一句xchg edx, [ebx]搞定。她们HOOK的位置都是callxxxx中的xxxx这四个字节,一句xchg指令就可以搞定。由此来通过巧妙的选择hook位置及hook字节数绕过多核同步问题。
//还有一种搞法,可以hook任意字节数,在有本书上有介绍
#include "ntddk.h"
#include "gainexclusivity.h"
ULONG NumberOfRaisedCPU;
ULONG AllCPURaised;
PKDPC g_basePKDPC;
/****************************************************************************
*
* FUNCTION:VOID RaiseCPUIrqlAndWait()
*
* PURPOSE: The callback routine when a DPC is to be removed
*
****************************************************************************/
VOID RaiseCPUIrqlAndWait(
IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
InterlockedIncrement(&NumberOfRaisedCPU);
while (!InterlockedCompareExchange(&AllCPURaised, 1, 1))
{
__asm nop;
}
InterlockedDecrement(&NumberOfRaisedCPU);
}
/****************************************************************************
*
* FUNCTION:NTSTATUS ReleaseExclusivity(PKDPC pKdpc)
*
* PURPOSE: Release resources creates by GainExlusivity()
*
****************************************************************************/
void ReleaseExclusivity()
{
InterlockedIncrement(&AllCPURaised);
while (InterlockedCompareExchange(&NumberOfRaisedCPU, 0, 0))
{
__asm nop;
}
if (NULL != g_basePKDPC)
{
ExFreePool((PVOID)g_basePKDPC);
g_basePKDPC = NULL;
}
return;
}
/****************************************************************************
*
* FUNCTION:PKDPC GainExlusivity(VOID)
*
* PURPOSE: Call this function, before you want to modify EPROCESS or...
*
****************************************************************************/
BOOLEAN GainExlusivity(VOID)
{
NTSTATUS ntStatus;
ULONG u_currentCPU;
CCHAR i;
PKDPC pKdpc, temp_pkdpc;
if (DISPATCH_LEVEL != KeGetCurrentIrql())
{
return FALSE;
}
InterlockedAnd(&NumberOfRaisedCPU, 0);
InterlockedAnd(&AllCPURaised, 0);
temp_pkdpc = (PKDPC)ExAllocatePool(NonPagedPool,
KeNumberProcessors * sizeof(KDPC));
if (NULL == temp_pkdpc)
{
return NULL;
}
g_basePKDPC = temp_pkdpc;
u_currentCPU = KeGetCurrentProcessorNumber();
for (i = 0; i < KeNumberProcessors; i++, *temp_pkdpc++)
{
if (i != u_currentCPU)
{
KeInitializeDpc(temp_pkdpc, RaiseCPUIrqlAndWait, NULL);
KeSetTargetProcessorDpc(temp_pkdpc, i);
KeInsertQueueDpc(temp_pkdpc, NULL, NULL);
}
}
while (KeNumberProcessors - 1
!= InterlockedCompareExchange(&NumberOfRaisedCPU,
KeNumberProcessors - 1,
KeNumberProcessors - 1)
)
{
__asm nop;
}
return TRUE;
}
#define CLEAR_WP() \
__asm cli \
__asm mov eax,cr0 \
__asm and eax,not 000010000h \
__asm mov cr0,eax
#define SET_WP() \
__asm mov eax,cr0 \
__asm or eax,000010000h \
__asm mov cr0,eax \
__asm sti
void __stdcall SetWp()
{
SET_WP();
ReleaseExclusivity();
}
void __stdcall ClearWp()
{
GainExlusivity();
CLEAR_WP();
}
//在替换前后 调用SetWp() 和 ClearWp()就可