找回密码
 注册帐号
查看: 4869|回复: 4

[编程其他] 反NP之三——过游戏保护详解

[复制链接]
发表于 2010-5-29 11:17:26 | 显示全部楼层 |阅读模式
本帖最后由 coocoocoo10 于 2010-5-29 11:19 编辑

游戏更新了。
NP这个问题,又被焦点了。
我只是找出来大家分享下。

 随着国内软件安全行业的发展,驱动这一名词逐渐被摘去神秘的光环. 而3721的出现,告诉了人们驱动这东西不仅仅是用于硬件
越来越多的人认识到驱动的巨大作用,当<<MU>>引入了 nProtect 反工具系统后,似乎驱动反工具成了相当理想的选择.
但这一切,只是看起来很美. 随着越来越多的ROOTKIT出现,各大杀毒厂商逐渐的加强了这一方面的监控. 越来越多的各类监控
软件也使得驱动反工具举步维难.
在进入正题之前,首先要明确一点. 你的驱动将是游戏客户端的组成部分, 很多ROOTKIT上可以用的手段你不能使用.
游戏玩家并不是专业人士,他们更相信他们所选择的杀毒软件. 总不能当你的游戏运行时,杀毒软件便提示说 - 这是个ROOTKIT
首先我们抛开驱动的兼容性不谈 - 这也没法谈, 正如你驾驶汽车,你可以保证自己不出错. 但是你能保证其他人都能吗?
说到驱动反工具,你应该立马想到 HOOK SSDT与SSSDT 拦截API防止游戏进程被修改. 可是这真的那么有效吗?
好吧,你想说阻止 OpenProcess,ReadProcessMemory,WriteProcessMemory 这三个API就好? 不 - 相信我,这只能防防菜鸟而已.
即使你不考虑兼容性把 PsLookupProcessByProcessId,ObOpenObjectByPointer,ObOpenObjectByName,KeAttachProcess 等
全部HOOK,真的就能阻止修改了吗?
不,我们来看看下面的代码.
复制内容到剪贴板
代码:
Function GetInfoTable(ATableType:dword):Pointer;
var
mSize: dword;
mPtr: pointer;
St: NTStatus;
begin
Result := nil;
mSize := $4000;
repeat
mPtr := VirtualAlloc(nil, mSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE);
if mPtr = nil then Exit;
St := ZwQuerySystemInformation(ATableType, mPtr, mSize, nil);
if St = STATUS_INFO_LENGTH_MISMATCH then
begin
VirtualFree(mPtr, 0, MEM_RELEASE);
mSize := mSize * 2;
end;
until St <> STATUS_INFO_LENGTH_MISMATCH;
if St = STATUS_SUCCESS
then Result := mPtr
else VirtualFree(mPtr, 0, MEM_RELEASE);
end;
function iOpenProcess(ProcessId:DWORD):DWORD;
var
HandlesInfo: PSYSTEM_HANDLE_INFORMATION_EX;
ClientID:TClientID;
pbi:_PROCESS_BASIC_INFORMATION;
oa:TObjectAttributes;
hProcessCur,hProcessToDup,hProcessToRet:DWORD;
Ret:DWORD;
I:Integer;
begin
SetPrivilege('SE_DEBUG',TRUE);
Result:=0;
FillChar(oa,SizeOf(TObjectAttributes),0);
FillChar(ClientID,SizeOf(TClientID),0);
oa.Length:=SizeOf(TObjectAttributes);
HandlesInfo:=GetInfoTable(SystemHandleInformation);
for I:=0 to HandlesInfo^.NumberOfHandles do
begin
If (HandlesInfo^.Information.ObjectTypeNumber=5) Then //OB_TYPE_PROCESS
ClientID.UniqueProcess:=HandlesInfo^.Information.ProcessId;
If ZwDuplicateObject(hProcessToDup,HandlesInfo^.Information.Handle,GetCurrentProcess,@hProcessCur,PROCESS_ALL_ACCESS,0,$4)=STATUS_SUCCESS then
If ZwQueryInformationProcess(hProcessCur,ProcessBasicInformation,@pbi,Sizeof(_PROCESS_BASIC_INFORMATION),@Ret)=STATUS_SUCCESS then
If (pbi.UniqueProcessId=ProcessId) Then
If ZwDuplicateObject(hProcessToDup,HandlesInfo^.Information.Handle,GetCurrentProcess,@hProcessToRet,PROCESS_ALL_ACCESS,0,$4)=STATUS_SUCCESS then
begin
Result:=hProcessToRet;
Break;
end;
end;
if hProcessCur>0 then ZwClose(hProcessCur);
if hProcessToDup>0 then ZwClose(hProcessToDup);
VirtualFree(HandlesInfo,0,MEM_RELEASE);
SetPrivilege('SE_DEBUG',FALSE);
end;
这是枚举系统中所有已知举柄达到取得进程Handle的函数. 你或许会认为,拦截ZwDuplicateObject,ZwQueryInformationProcess不就解决问题了?
这没错,你是对的.但是你不能这样做,你做的是反工具,不是ROOTKIT, 当你尝试这样做的时候,你会发现你的杀毒软件提示你. 这是ROOTKIT的典型行为
怎么办? 难道你要象ROOTKIT那样关闭掉玩家的杀毒软件? 还是联系各大杀毒软件厂商告诉他们: 麻烦您修改你们的规则?
这仅仅是RING 3的普通运用而已, 千万不要认为做工具的不会驱动. 相反,与游戏开发公司那点可怜的薪水比起来. 工具的利润只会让更多的驱动开发者
加入这一行列. 即使你HOOK接管了这一切函数,不管是inline还是普通的ssdt. 下面的驱动很轻易的就能突破任意的HOOK.
复制内容到剪贴板
代码:
.....................
NTSTATUS NTAPI GetRealAddress(PIMPORT_ENTRY Import)
{
MODULE_INFORMATION mi,idmi;
DWORD        i,j;
DWORD        dwKernelBase;
NTSTATUS        status;
PDWORD        KiServiceTable;
UNICODE_STRING NtdllName;
if (KeGetCurrentIrql()!=PASSIVE_LEVEL) return STATUS_PASSIVE_LEVEL_REQUIRED;
RtlZeroMemory(&mi,sizeof(mi));
if (!NT_SUCCESS(status=MapKernelImage(&mi,&dwKernelBase))) return status;
RtlZeroMemory(&idmi,sizeof(idmi));
RtlInitUnicodeString(&NtdllName, L"\\SystemRoot\\System32\\ntdll.dll");
if (!NT_SUCCESS(status=MapPeImage(&idmi,&NtdllName))) return status;
try {
for (i=0;Import.szName;i++){
Import.dwAddress=0;
switch (Import.dwType) {
case IMPORT_BY_NAME:
if (!(Import.dwAddress=GetProcRva(mi.hModule,Import.szName))) {
#ifdef DEBUG
DbgPrint("GetRealAddress(): Failed to get %s rva!\n",Import.szName);
#endif
}
break;
case IMPORT_BY_RVA:
Import.dwAddress=(DWORD)Import.szName;
break;
case IMPORT_BY_ADDRESS:
Import.dwAddress=(DWORD)Import.szName-dwKernelBase;
break;
case IMPORT_BY_SERVICE_ID:
// do not search this rva if it has been already found
if (!KiServiceTable_RVA) {
if (!(KiServiceTable_RVA=FindKiServiceTable(mi.hModule))) {
#ifdef DEBUG
DbgPrint("GetRealAddress(): Failed to get KiServiceTable RVA!\n");
#endif
break;
}
}
KiServiceTable=(PDWORD)(KiServiceTable_RVA+mi.hModule);
Import.dwAddress=KiServiceTable[(DWORD)Import.szName]-mi.dwImageBase;
break;
case IMPORT_BY_SERVICE_NAME:
if (!KiServiceTable_RVA){
if (!(KiServiceTable_RVA=FindKiServiceTable(mi.hModule)))        break;
}
Import.dwId=GetIdForName(idmi.hModule,Import.szName);
KiServiceTable=(PDWORD)(KiServiceTable_RVA+mi.hModule);
Import.dwAddress=KiServiceTable[Import.dwId]-mi.dwImageBase;
break;
default:
break;
} //Case End
if (Import.dwId==0){
if (!KiServiceTable_RVA)
KiServiceTable_RVA=FindKiServiceTable(mi.hModule);
KiServiceTable=(PDWORD)(KiServiceTable_RVA+mi.hModule);
for (j=0;KiServiceTable[j];j++){if (Import.dwAddress==KiServiceTable[j]-mi.dwImageBase){Import.dwId=j;break;}}
}
Import.dwAddress=dwKernelBase+Import.dwAddress;
}
}except(EXCEPTION_EXECUTE_HANDLER){
return STATUS_ADD_FUNCTION_FAILED;
}
try {
UnmapPeImage(&mi);
UnmapPeImage(&idmi);
}except(EXCEPTION_EXECUTE_HANDLER){
return STATUS_CODE_REBUILDING_FAILED;
}
return STATUS_SUCCESS;
}
...........
恩..这不是完整的代码,这理所当然,不是么?
面对任何HOOK,只需要从NT的内核文件中取出其真实的地址,很轻易的就可以饶过SSDT的HOOK,INLINE HOOK只需要恢复代码即可.
更何况你的驱动肯定会比工具的驱动还晚加载.
即使除开上面这些不谈,你依然要面对你的驱动被PATCH,又或者被个假冒的驱动所替代. 更别说 lpk.dll usp10.dll 了.
这时候你应该会想反驳我,看看 nPROTECT ,安博士 吧. 好的,那么我们来看看下面这段函数
复制内容到剪贴板
代码:
NTSTATUS ReadPhysicalMemory(char *startaddress, UINT_PTR bytestoread, void *output)
{
HANDLE                        physmem;
UNICODE_STRING        physmemString;
OBJECT_ATTRIBUTES attributes;
WCHAR                        physmemName[] = L"\\device\\physicalmemory";
UCHAR*                        memoryview;
NTSTATUS                ntStatus = STATUS_UNSUCCESSFUL;
__try
{
RtlInitUnicodeString( &physmemString, physmemName );
InitializeObjectAttributes( &attributes, &physmemString, OBJ_CASE_INSENSITIVE, NULL, NULL );
ntStatus=ZwOpenSection( &physmem, SECTION_MAP_READ, &attributes );
if (ntStatus==STATUS_SUCCESS)
{
//hey look, it didn't kill it
UINT_PTR length;
PHYSICAL_ADDRESS        viewBase;
UINT_PTR offset;
UINT_PTR toread;
viewBase.QuadPart = (ULONGLONG)(startaddress);
length=0x2000;//pinp->bytestoread; //in case of a overlapping region
toread=bytestoread;
memoryview=NULL;
DbgPrint("ReadPhysicalMemory:viewBase.QuadPart=%x", viewBase.QuadPart);
ntStatus=ZwMapViewOfSection(
physmem,  //sectionhandle
NtCurrentProcess(), //processhandle (should be -1)
&memoryview, //BaseAddress
0L, //ZeroBits
length, //CommitSize
&viewBase, //SectionOffset
&length, //ViewSize
ViewShare,
0,
PAGE_READWRITE);
if (ntStatus==STATUS_SUCCESS)
{
offset=(UINT_PTR)(startaddress)-(UINT_PTR)viewBase.QuadPart;
RtlCopyMemory(output,&memoryview[offset],toread);
ZwUnmapViewOfSection( NtCurrentProcess(), memoryview);
}
else
{
DbgPrint("ReadPhysicalMemory:ntStatus=%x", ntStatus);
}
ZwClose(physmem);
};
}
__except(1)
{
DbgPrint("Error while reading physical memory\n");
}
return ntStatus;
}
直接读取物理内存, 到目前为止,这个方法依然对 nPROTECT 保护的进程有效.
实际上反工具的驱动能拦截的不过是API而已, 你能拦截 mov eax,[xxxxxxx] 吗?
别忘记,你在驱动中采取的手段越多,驱动的兼容性必定越差.
在家中的玩家还好说,可是面对目前主要的玩家多数在网吧上网的情况,你不的不考虑各种网吧管理软件.
这样的情况,不谈兼容性光是你的驱动到底有没有机会被加载还是个问题....
即使是在家中上网的玩家,你难道要告诉使用 Vista 或者 Windows 7 的普通用户: 请关闭你的UAC
好吧,再这样写下去简直没完没了.  综上所述, 驱动反工具, 这只是看起来很美而已.
怎么办?
二. 如何有效的阻止工具
前言中提到,要有效的反工具,必先了解工具如何运作.  在前文中,也描述了当前工具主要的运作模式. 现在工具已不是要求什么三步瞬移,格位刺杀之类的特殊功能了,对于工作室.
他们的需要仅仅是稳定的机器人,如果游戏提供的话,他们常常还需要能够把使用角色上的金钱物品邮寄或者交易给某个账号的功能. 那么制作一个这样的机器人至少需要的是什么?
1. 游戏角色的生命值,魔法值之类的数据
2. 游戏角色的物品数据
3. 游戏角色周围的怪物数据
4. 移动函数
5. 热键函数 [假如客户端接受 SendMessage 模拟键盘这样的消息,这不需要]
6. 选中怪物函数
7. 打开NPC函数
8. 打开仓库函数
9. 交易或邮寄函数
其中的 4-9 可以被一个数据包发送函数所替代,例如
复制内容到剪贴板
代码:
procedure SendPack(buf:PChar;len:DWORD); stdcall;
procedure TOSEND; stdcall;
asm
push    -1
push    SENDPACK_STAK
mov     eax, dword ptr fs:[0]
push    eax
mov     dword ptr fs:[0], esp
sub     esp,$18
push    ebx
push    esi
push    edi
mov     edi, ecx
xor     ebx, ebx
xor     eax, eax
jmp     SENDPACK_JMP
end;
begin
asm
pushad;
mov  ecx, [CALL_BASE];
push len;
push buf;
mov  ecx, [ecx+$20];
call TOSEND;
popad;
end;
end;
procedure SendBuyItem(ItemId,ItemPos,ItemCount:DWORD);
var
//25 00 01 00 00 00 14 00 00 00 00 00 00 00 01 00 00 00 AA 21 00 00 01 00 00 00 01 00 00 00
//25 00 01 00 00 00 ByteCount 00 00 00 00 GroupCount ItemId ItemPos ItemCount
Pack:Array [0..29] of Byte;
begin
FillChar(Pack,SizeOf(Pack),0);
Pack[0]:=$25;
Pack[2]:=$01;
Pack[6]:=$14;
Pack[14]:=$01;
CopyMemory(@Pack[18],@ItemId,4);
CopyMemory(@Pack[22],@ItemPos,4);
CopyMemory(@Pack[26],@ItemCount,4);
SendPack(@Pack[0],30);
end;
归根结底, 要反工具,主要防御的只有两点:
1. 防止外部修改内存
2. 防止外部调用函数
对于第一点,比如修改某个怪物的数据,使得客户端判断该怪物在游戏角色的攻击范围之内.
最佳的解决办法不是去HOOK什么内存读写函数. 而是把判断这些数据的责任交给服务器端.
可如果是引进的游戏呢? 解决办法便是CRC32或者别的什么HASH算法校验这段内存数据.
对于第二点,最简单的办法便是在函数内取得 ESP 判断函数的返回地址. 以上面的那段函数为例.
只要游戏开发商稍微更改一下他的发包函数,判断下call 的来源, 我想这已经会让工具的作者头痛
很久.
发表于 2010-5-30 17:10:13 | 显示全部楼层
来学习一下,呵呵
发表于 2010-5-30 17:52:50 | 显示全部楼层
深奥,现在还看不明白
发表于 2010-6-3 14:32:08 | 显示全部楼层
我说,这论坛上有人几人能看得懂?
 楼主| 发表于 2010-6-3 18:04:45 | 显示全部楼层
我说,这论坛上有人几人能看得懂?
zy8852008 发表于 2010-6-3 14:32


又不是让你们看懂~~
我自己 也看不懂。。。。
只是为了我发的能过NP的工具而做铺垫 而已。
您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

QQ|Archiver|手机版|小黑屋|依人网络官方网站 ( 陕ICP备19025998号-1 )

GMT+8, 2024-4-27 20:02 , Processed in 0.051178 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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