In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-12 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article introduces the relevant knowledge of "how to realize anti-cheating in the game". In the operation of actual cases, many people will encounter such a dilemma. Next, let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
Process and thread protection
Register the obg callback and remove the handle to prevent the game from being opened:
NTSTATUS InstallCallBacks () {NTSTATUS NtHandleCallback = STATUS_UNSUCCESSFUL; NTSTATUS NtThreadCallback = STATUS_UNSUCCESSFUL; OB_OPERATION_REGISTRATION OBOperationRegistration [2]; OB_CALLBACK_REGISTRATION OBOCallbackRegistration; REG_CONTEXT regContext; UNICODE_STRING usAltitude; memset (& OBOperationRegistration, 0, sizeof (OB_OPERATION_REGISTRATION)); memset (& OBOCallbackRegistration, 0, sizeof (OB_CALLBACK_REGISTRATION)); memset (& ampregContext, 0, sizeof (REG_CONTEXT)) RegContext.ulIndex = 1; regContext.Version = 120; RtlInitUnicodeString (& usAltitude, L "1000"); if ((USHORT) ObGetFilterVersion () = = OB_FLT_REGISTRATION_VERSION) {OBOperationRegistration [1] .ObjectType = PsProcessType; OBOperationRegistration [1] .Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE; OBOperationRegistration [1] .PreOperation = MyHandleProcessCallbacks OBOperationRegistration [1] .PostOperation = HandleAfterCreat; OBOperationRegistration [0] .ObjectType = PsThreadType; OBOperationRegistration [0] .Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE; OBOperationRegistration [0] .PreOperation = MyHandleThreadCallbacks; OBOperationRegistration [0] .PostOperation = HandleAfterCreat; OBOCallbackRegistration.Version = OB_FLT_REGISTRATION_VERSION OBOCallbackRegistration.OperationRegistrationCount = 2; OBOCallbackRegistration.RegistrationContext = & ampregContext; OBOCallbackRegistration.OperationRegistration = OBOperationRegistration; NtHandleCallback = ObRegisterCallbacks (& OBOCallbackRegistration, & g_CallbacksHandle) / / Register The CallBack if (! NT_SUCCESS (NtHandleCallback)) {if (g_CallbacksHandle) {ObUnRegisterCallbacks (g_CallbacksHandle); g_CallbacksHandle = NULL } / / DebugPrint ("[DebugMessage] Failed to install ObRegisterCallbacks: 0xX.\ n", NtHandleCallback); return STATUS_UNSUCCESSFUL;}} return STATUS_SUCCESS;}
The callback code is as follows:
OB_PREOP_CALLBACK_STATUS MyHandleProcessCallbacks (PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation) {if (g_StarterPid = = (DWORD64)-1) return OB_PREOP_SUCCESS; PEPROCESS OpenedProcess = (PEPROCESS) OperationInformation- > Object, CurrentProcess = PsGetCurrentProcess (); ULONG ulProcessId = (ULONG) PsGetProcessId (OpenedProcess); ULONG myProcessId = (ULONG) PsGetProcessId (CurrentProcess) If ((ulProcessId = = (ULONG) g_FlagProcessPid | | ulProcessId = = (ULONG) g_StarterPid) & & myProcessId! = ulProcessId) {if (OperationInformation- > Operation = = OB_OPERATION_HANDLE_CREATE) {if ((OperationInformation- > Parameters- > CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_OPERATION) = = PROCESS_VM_OPERATION) {/ / Modify the address space of the process Such as by calling the user-mode WriteProcessMemory and VirtualProtectEx routines. OperationInformation- > Parameters- > CreateHandleInformation.DesiredAccess & = ~ PROCESS_VM_OPERATION;} if ((OperationInformation- > Parameters- > CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_READ) = = PROCESS_VM_READ) {/ / Read to the address space of the process, such as by calling the user-mode ReadProcessMemory routine. OperationInformation- > Parameters- > CreateHandleInformation.DesiredAccess & = ~ PROCESS_VM_READ;} if ((OperationInformation- > Parameters- > CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_WRITE) = = PROCESS_VM_WRITE) {/ / Write to the address space of the process, such as by calling the user-mode WriteProcessMemory routine. OperationInformation- > Parameters- > CreateHandleInformation.DesiredAccess & = ~ PROCESS_VM_WRITE;} return OB_PREOP_SUCCESS;}
R 3 will not be able to start the process. But it can be opened through crss.exe, system.exe, steam.exe, etc., so there must be a handle to lower the right.
Don't forget this:
/ / Bypass MmVerifyCallbackFunction. PLDR_DATA_TABLE_ENTRY64 ldr = (PLDR_DATA_TABLE_ENTRY64) DriverObject- > DriverSection; ldr- > Flags | = 0x20; handle drop weight
Deprive all threads and processes of game operation rights
VOID StripHandlePermission () {_ try {CheckDebugPort (g_FlagProcessPid); CheckDebugPort ((HANDLE) g_StarterPid); PSYSTEM_HANDLE_INFORMATION_EX HandleInfo = QueryHandleTable (); if (HandleInfo) {for (int I = 0; I)
< HandleInfo->NumberOfHandles ) {/ / 7 is the process attribute if (HandleInfo- > Information [I] .ObjectTypeNumber = = 7 | | HandleInfo- > Information [I] .ObjectTypeNumber = = OB_TYPE_INDEX_PROCESS | | HandleInfo- > Information [I] .ObjectTypeNumbe r = = OB_TYPE_INDEX_THREAD) { If (g_FlagProcessPid = = (HANDLE)-1) break If (HandleInfo- > Information [I] .ProcessId = = (ULONG) g_FlagProcessPid | | HandleInfo- > Information [I] .ProcessId = = 4) continue Bool bCheck = ((HandleInfo- > Information [I] .GrantedAccess & PROCESS_VM_READ) = = PROCESS_VM_READ | (HandleInfo- > Information [I] .GrantedAccess & PROCESS_VM_OPERATION) = = PROCESS_VM_OPERATION | (HandleInfo -> Information [I] .GrantedAccess & PROCESS_VM_WRITE) = = PROCESS_VM_WRITE) PEPROCESS pEprocess = (PEPROCESS) HandleInfo- > Information [I] .Object; if (pEprocess) {HANDLE handle_pid = * (PHANDLE) ((PUCHAR) pEprocess + g_OsData.UniqueProcessId) HANDLE handle_pid2 = * (PHANDLE) ((PUCHAR) pEprocess + g_OsData.InheritedFromUniqueProcessId); if (bCheck & & (handle_pid = = g_FlagProcessPid | | handle_pid2 = = g_FlagProcessPid)) {pEprocess = NULL NTSTATUS status = PsLookupProcessByProcessId ((HANDLE) HandleInfo- > Information [I] .ProcessId, & pEprocess); if (NT_SUCCESS (status)) {/ / DebugPrint ("Full Acess Handle! Pid:% d\ n ", HandleInfo- > Information [I] .ProcessId); PHANDLE_TABLE HandleTable = * (PHANDLE_TABLE*) ((PUCHAR) pEprocess + g_OsData.ObjTable) If (MmIsAddressValid ((void*) HandleTable)) {ExEnumHandleTable (HandleTable, g_isWin7? (DWORD64*) & StripHandleCallback_win7: (DWORD64*) & StripHandleCallback_win10, (PVOID) HandleInfo- > Information [I] .handle, NULL);} ObDereferenceObject (pEprocess) } ExFreePoolWithTag (HandleInfo, POOL_TAG) }} _ except (EXCEPTION_EXECUTE_HANDLER) {return;}}
Callback is as follows, the difference between win10 and win7:
BOOLEAN StripHandleCallback_win10 (IN PHANDLE_TABLE HandleTable, IN PHANDLE_TABLE_ENTRY HandleTableEntry, IN HANDLE Handle, IN PVOID EnumParameter) {BOOLEAN result = FALSE; POBJECT_TYPE ObjectType = NULL; ULONG64 Object = 0; if (g_FlagProcessPid = = (HANDLE)-1) return FALSE; if (ExpIsValidObjectEntry (HandleTableEntry)) {POBJECT_TYPE ObjectType = NULL ULONG64 Object = 0; if (Handle = = (HANDLE) EnumParameter) {HandleTableEntry- > GrantedAccessBits = (SYNCHRONIZE | THREAD_QUERY_LIMITED_INFORMATION); / / DebugPrint ("Fuck Handle: X\ n", Handle); goto _ exit }} else {return FALSE;} _ exit: / / Release implicit locks _ InterlockedExchangeAdd8 ((char*) & HandleTableEntry- > VolatileLowValue, 1); / / Set Unlocked flag to 1 if (HandleTable! = NULL & & HandleTable- > HandleContentionEvent) ExfUnblockPushLock (& HandleTable- > HandleContentionEvent, NULL); return FALSE } BOOLEAN StripHandleCallback_win7 (PHANDLE_TABLE_ENTRY HandleTableEntry, HANDLE Handle, PVOID EnumParameter) {POBJECT_TYPE ObjectType = NULL; ULONG64 Object = 0; if (g_FlagProcessPid = = (HANDLE)-1) return FALSE If (ExpIsValidObjectEntry (HandleTableEntry)) {if (Handle = = (HANDLE) EnumParameter) {HandleTableEntry- > GrantedAccessBits = (SYNCHRONIZE | THREAD_QUERY_LIMITED_INFORMATION); / / DebugPrint ("Fuck Handle: X\ n", Handle); return FALSE }} return FALSE;} Randomized process name
Prevent plug-ins from getting pid through the process name
Void randstring (char* randomString, size_t length) {static char charset [] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; ULONG seed = KeQueryTimeIncrement (); if (randomString) {for (int n = 0; n Buffer! = 0) {RtlZeroMemory (v1-> Buffer, v1-> MaximumLength); RtlCopyMemory (v1-> Buffer, ProcessName, wcslen (ProcessName) * 2) V1-> Length = wcslen (ProcessName) * 2;}} BOOLEAN PathSeAuditProcessCreationInfo (PEPROCESS Process, WCHAR* ProcessName) {PUNICODE_STRING Name; PUNICODE_STRING SelocateName; SeLocateProcessImageName (Process, & SelocateName); ExFreePool (SelocateName); Name = (PUNICODE_STRING) (* (PULONG_PTR) ((ULONG_PTR) Process + g_OsData.SeAuditProcessCreationInfo)); / + 0x468 SeAuditProcessCreationInfo FuckName (Name, ProcessName) Return TRUE;} BOOLEAN PatchImageFileName (PEPROCESS Process, char* cName) {char szNameBuff [15] = {0}; UCHAR* szProcessBuff = NULL; size_t cNamelen = 0; cNamelen = strlen (cName); RtlZeroMemory (szNameBuff, sizeof (szNameBuff)); if (cNamelen > 15) RtlCopyMemory (szNameBuff, cName, sizeof (szNameBuff)); else RtlCopyMemory (szNameBuff, cName, cNamelen) SzProcessBuff = PsGetProcessImageFileName (Process); RtlZeroMemory (szProcessBuff, sizeof (szNameBuff)); RtlCopyMemory (szProcessBuff, szNameBuff, sizeof (szNameBuff)); return TRUE;} void PatchPEB (PEPROCESS Process, WCHAR* ProcessName) {KeAttachProcess ((PEPROCESS) Process); DWORD64 _ peb = * (PDWORD64) ((PUCHAR) Process + g_OsData.peb) DWORD64 peb_ProcessParameters = * (PDWORD64) ((ULONG_PTR) _ peb + g_OsData.peb_ProcessParameters); PUNICODE_STRING peb_ImagePathName = (PUNICODE_STRING) ((ULONG_PTR) peb_ProcessParameters + g_OsData.peb_ImagePathName); PUNICODE_STRING peb_WindowTitle = (PUNICODE_STRING) ((ULONG_PTR) peb_ProcessParameters + g_OsData.peb_WindowTitle) PUNICODE_STRING peb_CommandLine = (PUNICODE_STRING) (ULONG_PTR) peb_ProcessParameters + g_OsData.peb_CommandLine); / / PUNICODE_STRING peb_DllPath = (PUNICODE_STRING) ((ULONG_PTR) peb_ProcessParameters + g_OsData.peb_DllPath); FuckName (peb_ImagePathName, ProcessName); FuckName (peb_WindowTitle, ProcessName); FuckName (peb_CommandLine, ProcessName); KeDetachProcess () } bool Win10ImageNamePoint (PEPROCESS Process, WCHAR* szFullName) {BOOLEAN bRet; PFILE_OBJECT pFileObject; pFileObject = (PFILE_OBJECT) (* (PULONG_PTR) ((ULONG_PTR) Process + g_OsData.ImageFilePointer)); / + 0x448 ImageFilePointer RtlZeroMemory (pFileObject- > FileName.Buffer, pFileObject- > FileName.MaximumLength); RtlCopyMemory (pFileObject- > FileName.Buffer, szFullName, wcslen (szFullName) * 2); pFileObject- > FileName.Length = wcslen (szFullName) * 2 Return true;} BOOLEAN FuckProcessModify (HANDLE pid) {PEPROCESS Process = NULL; NTSTATUS status = PsLookupProcessByProcessId ((HANDLE) pid, & Process); if (! NT_SUCCESS (status)) {return FALSE;} if (CheckProcessTermination (Process)) {return FALSE;} CHAR temp_char [10] = {0x0} Randstring (temp_char, 10-1); WCHAR temp_wchar [50] = {0}; status = RtlStringCbPrintfW (temp_wchar, 50, L "% hs", temp_char); if (NT_SUCCESS (status)) {PatchImageFileName (Process, temp_char); if (g_isWin7 = = false) Win10ImageNamePoint (Process, temp_wchar) PathSeAuditProcessCreationInfo (Process, temp_wchar); PatchPEB (Process, temp_wchar);} ObDereferenceObject (Process); return TRUE;}
Used in createprocessnotifycallbackex
Anti-debugging
Check whether the process is debugged, eprocess- > debugport, find that the debugger directly blue screen, BE is directly related to the game
VOID CheckDebugPort (HANDLE pid) {if (pid = = (HANDLE)-1 | | pid = = (HANDLE) 0) {return;} PEPROCESS process; NTSTATUS status = PsLookupProcessByProcessId (g_FlagProcessPid, & process); if (! NT_SUCCESS (status) | | process = = NULL) return; ObDereferenceObject (process); if (CheckProcessTermination (process)) {return } if (MmIsAddressValid ((PULONG) ((PUCHAR) process + g_OsData.ep_debugport) {ULONG debug_port = * (PULONG) ((PUCHAR) process + g_OsData.ep_debugport); if (debug_port! = 0) {KeBugCheck (2);} reverse callback injection
Invalidate ImageLoadCallback to prevent plug-ins from being injected when the game starts (note that the first version of win7 will be blue)
ULONG64 GetNotifyVarAddress () {if (g_PspNotifyEnableMaskAddr = = 0) {ULONG64 I = 0 * * PULONG64 pAddrOfFnc = 0 * Unicode string fncName;//8B 05? A8 01 7509 F00F BACHAR pattern_PspNotifyEnableMask [] = "\ x8B\ x05\ xCC\ xA8\ X01\ x75\ x09\ xF0\ x0F\ xBA"; NTSTATUS status = UtilScanSection (g_KernelBase, "PAGE", (PCUCHAR) pattern_PspNotifyEnableMask, 0xCC, sizeof (pattern_PspNotifyEnableMask)-1, (PVOID*) & g_PspNotifyEnableMaskAddr); if (! NT_SUCCESS (status)) {/ / DebugPrint ("[DebugMessAge] g_PspNotifyEnableMaskAddr not found!: (\ n"); return 0 } else {/ / g_PspNotifyEnableMaskAddr = g_PspNotifyEnableMaskAddr + 5 * * long OffsetAddr = 0 * memcpy (& OffsetAddr, (UCHAR*) (g_PspNotifyEnableMaskAddr + 2), 4); pAddrOfFnc = (ULONG64*) (OffsetAddr + g_PspNotifyEnableMaskAddr + 0x6); / / DebugPrint ("[DebugMessAge] g_PspNotifyEnableMaskAddr: X\ n", pAddrOfFnc); g_PspNotifyEnableMaskAddr = (ULONG64) pAddrOfFnc;return (ULONG64) groomPspNotifyEnableMaskAddrr;} else {return (ULONG64) groomPspNotifyEnableMaskAddrr;} VOID ChangeNotifyAddress (BOOLEAN enableImage) {ULONG64 varaddress = GetNotifyVarAddress () If (varaddress) {/ / DebugPrint ("[DebugMessage] NotifyVarAddress: X\ n", varaddress); if (MmIsAddressValid ((PVOID) * (ULONG*) (varaddress) {return;} ULONG val = * (ULONG*) (varaddress) / * if (! enableThread) {UNSETBIT (val, 3); UNSETBIT (val, 4);} else {SETBIT (val, 3) SETBIT (val, 4);} * / if (! enableImage) {g_InvalidationLoadImage = true; UNSETBIT (val, 0) } else {g_InvalidationLoadImage = false; SETBIT (val, 0);} * (ULONG*) (varaddress) = val;} else {/ / DebugPrint ("[DebugMessage] Can't find NotifyVarAddress\ n") }}
Specific principle Baidu PspNotifyEnableMask, this is protected by PG, so can not be turned off all the time. It must be opened immediately after startup.
Thread stack walk
Trace back the system process to determine whether it is a plug-in thread
For (ULONG index = 4; index
< 0x30000; index += 4) { PETHREAD ThreadObject; DWORD64 CurrtThreadAddress; if (!NT_SUCCESS(PsLookupThreadByThreadId((HANDLE)index, &ThreadObject))) continue; GetThreadStartAddress(ThreadObject, &CurrtThreadAddress); if (!MmIsAddressValid((PVOID)CurrtThreadAddress)) continue; if (!PsIsSystemThread(ThreadObject) || ThreadObject == KeGetCurrentThread()) { if (PsIsSystemThread(ThreadObject) && ThreadObject != KeGetCurrentThread()) { if (CurrtThreadAddress >* (PULONG) DUCK_ANTI_PASTE) {/ / if this address is in kernel speace, but not a system thread, dump it sends it to the server rpc::CallReportByThreadID (index,RESULT_FAKE_SYSTEMTHREAD) }} DWORD64 kthread_apc_state = * (PDWORD64) ((ULONG_PTR) ThreadObject + g_OsData.thread_apcstate); if (! MmIsAddressValid ((PVOID) kthread_apc_state)) continue; PEPROCESS apc_process = (PEPROCESS) ((ULONG_PTR) kthread_apc_state + g_OsData.kacp_process) If (! MmIsAddressValid ((PVOID) apc_process)) continue; if (apc_process) {HANDLE target_id = (HANDLE) PsGetProcessId (apc_process); / / DebugPrint ("apc_process addr =% p target_id:% p\ n", apc_process, target_id) If (target_id = = g_FlagProcessPid) {/ / DebugPrint ("detect memeory read at thread addr =% p\ n", thrd_id) / / APC attached, mostly plug-in using MmCopyVirtualMemory, that is, the so-called driver read-write kernel rpc::CallReportByThreadID (index,RESULT_APC) }} if (CurrtThreadAddress & & (memcmp ((void*) start_addr, "\ xFF\ xE1", 2) = = 0) & & (CurrtThreadAddress
< g_ntoskrnl_exe_base || CurrtThreadAddress >G_ntoskrnl_exe_base + g_ntoskrnl_exe_len) {/ / jmp rcx rpc::CallReportByThreadID (index,RESULT_JMP_RCX);} ThreadStackWalkStruct stack_walk []; GetThreadRip (ThreadObject,stack_walk) If (CheckRipOutSideSystemMoudles (stack_walk)) {rpc::CallReportByThreadID (index,RESULT_OUTSIDE_MOUDLE);} if (stack_walk- > chect_jmp = = true) {/ / multiple springboard rpc::CallReportByThreadID (index,RESULT_CHECT_JMP) } ObDereferenceObject (ThreadObject); continue;}} Virtual Machine Detection
Detect the existence of a plug-in virtual machine
ULONG rdtsc_diff_vmexit () {auto T1 = _ _ rdtsc (); int r [4]; _ _ cpuid (r, 1); return _ _ rdtsc ()-T1;} bool TimeBaseAttack () {int i; unsigned long long avg = 0; for (I = 0; I
< 10; i++) { avg = avg + rdtsc_diff_vmexit(); sleep(500); } avg = avg / 10; return (avg < 2100 && avg >0);} if (TimeBaseAttackNum > 5 & & CheckKnownHypervistor () = = false) {/ / if a virtual machine is detected but no normal virtual machine flag is found / /.} pool memory detection
The plug-in can be loaded and detected by a vulnerable driver
Void ScanCheatPool () {ULONG len = 4 * 1024 * 1024; auto tmpMemory = ExAllocatePoolWithTag (POOL_TYPE::NonPagedPool, len, POOL_TAG); if (NT_SUCCESS (pfn_NtQuerySystemInformation ((SYSTEM_INFORMATION_CLASS) 0x42, tmpMemory, len, & len)) {auto pBuf = reinterpret_cast (tmpMemory); for (ULONG I = 0; I)
< pBuf->Count; iTunes +) {bool bCehck1 = CheckBlackTagName (pBuf- > AllocatedInfo [I] .TagUlong); / / check the name. 0mVZ SldT rcIC csIC enoN d68x bool bCheck2 = CheckPoolPeHead (tmpMemory); / / check the pe header if (bCehck1 | | bCheck2) {/ / dump to the server rpc::CallReportByPool (tmpMemory,bCehck1,bCheck2);} ExFreePoolWithTag (tmpMemory, POOL_TAG) } process, thread hiding
Copied from a foreigner.
NTSTATUS RemovePspCidTable (PEPROCESS pep, HANDLE pid) {_ try {void * PspCidTable = * ((void * *) MAKEPTR (KernelBase, OFS_PspCidTable)); if (! ExDestroyHandle (PspCidTable, pid, NULL)) {return STATUS_ACCESS_DENIED;} * ((ULONG64 *) MAKEPTR (pep, 0x180)) = 0 / _ EPROCESS- > UniqueProcessId = 0 (avoid CID_HANDLE_DELETION bsod) LIST_ENTRY * ThreadListHead = (LIST_ENTRY *) MAKEPTR (pep, 0x308); / / _ EPROCESS- > ThreadListHead LIST_ENTRY * pLE = ThreadListHead; while ((pLE = pLE- > Flink)! = ThreadListHead) {PETHREAD pet = (PETHREAD) MAKEPTR (pLE,-0x428); / _ ETHREAD- > ThreadListEntry offset HANDLE tid = PsGetThreadId (pet) HANDLE tpid = PsGetThreadProcessId (pet); DbgPrint ("pid:% I64x, tid:% I64x, tpid:% I64x", pid, tid, tpid); if (pid = = tpid) / / just making sure.. {DbgPrint ("Removing thread:% I64x (tid:% I64x)", pet, tid); if (! ExDestroyHandle (PspCidTable, tid, NULL)) {return STATUS_ACCESS_DENIED;} / * ((ULONG64 *) MAKEPTR (pet, 0x3b8 + 0x00)) = 0 / / _ ETHREAD- > Cid.UniqueProcess = 0 * ((ULONG64 *) MAKEPTR (pet, 0x3b8 + 0x08)) = 0; / / _ ETHREAD- > Cid.UniqueThread = 0 (avoid CID_HANDLE_DELETION bsod)}} _ except (EXCEPTION_EXECUTE_HANDLER) {return STATUS_ACCESS_DENIED;} return STATUS_SUCCESS } "how to achieve the game anti-cheating" content is introduced here, thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.