写在译文之前:
—最近在学习游戏安全相关内容,然后我发现,在游戏破解,数据分析,逻辑逆向方面的文章,前辈们写的很多,有许多可以学习的地方了,但是对于反作弊系统的研究,主要还是以英文文献为主。
—所以我在学习时,就在想顺便做一个翻译,将自己学习到的文章都翻译成中文,方便一些英语不好的同学阅读,同时也是对自己知识的一次巩固。
—翻译是在百度翻译的基础上带上我自己的修修补补,如果翻译有错误、不当的地方,还请多多谅解。
原文链接:
译文:
随着时间的推移,反外挂策略也在发生着变化,功能在不断的变化,以图最大化产品的效率。我在一年前曾经在我的博客上完成过一份完整的关于BE的shellcode的总结,今天这篇文章只是在上次所说的shellcode的基础上,看看哪些地方发生了改变。
Blacklisted Timestamps
上一次在我分析BE时,仅仅只有两个编译时产生的时间戳的黑名单,现在看来,他们在这方面增加了很多。
0x5B12C900 (action_x64.dll)
0x5A180C35 (TerSafe.dll, Epic Games)
0xFC9B9325 (?)
0x456CED13 (d3dx9_32.dll)
0x46495AD9 (d3dx9_34.dll)
0x47CDEE2B (d3dx9_32.dll)
0x469FF22E (d3dx9_35.dll)
0x48EC3AD7 (D3DCompiler_40.dll)
0x5A8E6020 (?)
0x55C85371 (d3dx9_32.dll)
0x456CED13 (?)
0x46495AD9 (D3DCompiler_40.dll)
0x47CDEE2B (D3DX9_37.dll)
0x469FF22E (?)
0x48EC3AD7 (?)
0xFC9B9325 (?)
0x5A8E6020 (?)
0x55C85371 (?)
我无法确定其他的时间戳的来源,并且两个0xFCXXXXXX是visual studio复制版编译时产生的哈希。如果谁能够识别其中某个时间戳,请在Twitter上给我留言。
感谢@mottikraus和T0B1标识了一些时间戳。
Module checks
根据主要的分析显示,模块的枚举是BE的一大特性。在我上次分析以后,BE又增加了一个新的模块到模块列表。
void battleye::misc::module_unknown1()
{
if (!GetProcAddress(current_module, "NSPStartup"))
return;
if (optional_header.data_directory[4].size == 0x1B20 ||
optional_header.data_directory[4].size == 0xE70 ||
optional_header.data_directory[4].size == 0x1A38 ||
timestamp >= 0x5C600000 && timestamp < 0x5C700000)
{
report_module_unknown report = {};
report.unknown = 0;
report.report_id = 0x35;
report.val1 = 0x5C0;
report.timestamp = timestamp;
report.image_size = optional_header.size_of_image;
report.entrypoint = optional_header.address_of_entry_point;
report.directory_size = optional_header.data_directory[4].size;
battleye::report(&report, sizeof(report), false);
}
}
这可能是对某些特定的代理dll的检测,因为它会检查重定位表的大小。
Window titles
在先前的分析中,BE通过判断窗口标题识别到了大量的作弊者,而现在,BE不再检测这些窗口的标题。窗口列表的黑名单被下列名单所取代。
Chod's
Satan5
Image names
BE一直因为在使用一些原始的检测方法而臭名昭著,其中,对于映像名的黑名单检测便是之一。映像名黑名单每年都会变得更多,在过去的11个月中,这5个被添加到了这个黑名单。
frAQBc8W.dll
C:\\Windows\\mscorlib.ni.dll
DxtoryMM_x64.dll
Project1.dll
OWClient.dll
值得注意的是,对应的模块名字被检测到之后,并不会导致您会被马上封禁。报告还会包括这个模块的一些能够区分作弊行为与BE服务器冲突的基本信息。
7-zip
7-zip在作弊软件中一直以来都在被使用着,BE试图通过一种糟糕的完整性检查来对抗这种利用,这种糟糕的检查在我的上一篇文章中被提到过。
void module::check_7zip()
{
const auto module_handle = GetModuleHandleA("..\\..\\Plugins\\ZipUtility\\ThirdParty\\7zpp\\dll\\Win64\\7z.dll");
// --- REMOVED ---
// if (module_handle && *(int*)(module_handle + 0x1000) != 0xFF1441C7)
// --- ADDED ---
if (module_handle && *(int*)(module_handle + 0x1008) != 0x83485348)
{
sevenzip_report.unknown_1 = 0;
sevenzip_report.report_id = 0x46;
sevenzip_report.unknown_2 = 0;
sevenzip_report.data1 = *(__int64*)(module_handle + 0x1000;
sevenzip_report.data2 = *(__int64*)(module_handle + 0x1008;
battleye::report(&sevenzip_report, sizeof(sevenzip_report), false);
}
}
看来BE的开发者发现了我上一篇文章让大量的用户通过复制几个符合条件的字节便绕过了这个检查。那他们是怎么改进的呢?位移了8个字节,然后用同样的方法进行检测。):其实可执行的节区是只读的属性,所要做的就是从硬盘加载7-zip,并且检查那些重定位后并相邻的节区,如果其中有任何的不一致,那么这就说明有问题。做一致性检查真的没有那么难。
Network check
TCP表的枚举仍然在BE中被使用着,但是因为我发布的上一次的分析中抨击了他们对于cloud-flare的IP地址的标记后,他们确实取消了对这些IP的检查。他们现在仍然在检查着和xera.ph通讯所需要的端口,但是他们已经增加了一个新的检查用来检测有关进程的通讯是否受到有效保护。(可能是通过hook的方式)
void network::scan_tcp_table
{
memset(local_port_buffer, 0, sizeof(local_port_buffer);
for (iteration_index = 0; iteration_index; < 500 ++iteration_index)
{
// GET NECESSARY SIZE OF TCP TABLE
auto table_size = 0;
GetExtendedTcpTable(0, &table_size, false, AF_INET, TCP_TABLE_OWNER_MODULE_ALL, 0);
// ALLOCATE BUFFER OF PROPER SIZE FOR TCP TABLE
auto allocated_ip_table = (MIB_TCPTABLE_OWNER_MODULE*)malloc(table_size);
if (GetExtendedTcpTable(allocated_ip_table, &table_size, false, AF_INET, TCP_TABLE_OWNER_MODULE_ALL, 0) != NO_ERROR)
goto cleanup;
for (entry_index = 0; entry_index < allocated_ip_table->dwNumEntries; ++entry_index)
{
// --- REMOVED ---
// const auto ip_address_match_1 =
// allocated_ip_table->table[entry_index].dwRemoteAddr == 0x656B1468; // 104.20.107.101
//
// const auto ip_address_match_2 =
// allocated_ip_table->table[entry_index].dwRemoteAddr == 0x656C1468; // 104.20.108.101
// +++ ADDED +++
const auto target_process = OpenProcess(QueryLimitedInformation, 0, ip_table->table[entry_index].dwOwningPid);
const auto protected = target_process == INVALID_HANDLE && GetLastError() == 0x57;
if (!protected)
{
CloseHandle(target_process);
return;
}
const auto port_match =
allocated_ip_table->table[entry_index].dwRemotePort == 20480;
for (port_index = 0;
port_index < 10 &&
allocated_ip_table->table[entry_index].dwLocalPort !=
local_port_buffer[port_index];
++port_index)
{
if (local_port_buffer[port_index])
continue
tcp_table_report.unknown = 0;
tcp_table_report.report_id = 0x48;
tcp_table_report.module_id = 0x5B9;
tcp_table_report.data =
BYTE1(allocated_ip_table->table[entry_index].dwLocalPort) |
(LOBYTE(allocated_ip_table->table[entry_index.dwLocalPort) << 8;
battleye::report(&tcp_table_report, sizeof(tcp_table_report), false);
local_port_buffer[port_index] = allocated_ip_table->table[entry_index].dwLocalPort;
break
}
}
cleanup:
// FREE TABLE AND SLEEP
free(allocated_ip_table);
Sleep(10
}
}
Thanks
- IChooseYou
- abstract