有趣的是,由于某种原因没有使用剪贴板监视的完整实现。剪贴板检查对于通过复制其他作业中的元素来捕捉学生在考试中剽窃是绝对显而易见的。
if (Clipboard.ContainsText())
{
return Encoding.UTF8.GetBytes(Clipboard.GetText());
}
else
{
return Encoding.UTF8.GetBytes("no text");
}我们将如何绕过这个监视系统?根据他们的网站,任何因任何原因无法使用相应软件的学生,无论是不兼容还是软件问题,都可以在“严格监督”下参加考试,这使得懒惰绕过超级容易:从您的机器中删除其依赖项并且它将无法运行 :) 部署依赖于ClickOnce,除了安装程序(例如,Chrome 使用它)之外很少使用它,因此您可以毫无问题地删除它,或重命名它以供以后恢复。
但是告诉你这将是一个非常无聊的结论,所以我们决定编写一个完整的原生绕过,本质上是一个 x86 用户模式 rootkit,来隐藏你在考试中使用的任何厚颜无耻的网站。你可能会问,我们为什么要这样做?有人需要为这个软件的绝对灾难负责,教育部需要透明地说明他们在做什么以及为什么需要在个人机器上安装这个软件才能参加。这种缺乏信任对情况无济于事,因为丹麦只有不到 0.1% 的学生被认为作弊,而且只有 56% 的作弊学生使用互联网来这样做。这很可能不会随着监视系统而改变,因为无论哪种方式,这少数人都会抓住机会。
最后,保护您的监控系统免受绕过与反作弊领域相同的猫捉老鼠的困境,只要教育部在他们的公告中足够烦人,我们就会继续玩这个游戏。我们非常有信心,他们永远无法与我们的技术能力相提并论,因为我们习惯于定期剖析内核反作弊软件,早于该监控软件数光年。
屏幕截图由Graphics.CopyFromScreen.NET 函数管理,它本质上是一个位块传输包装器,内部调用 gdi32!BitBlt。正如我们在视频游戏中为防止反作弊软件截屏所做的那样,我们可以在执行截屏之前挂钩 BitBlt 并隐藏任何不需要的信息。
BOOL __stdcall ayyxam::hooks::bit_blt(HDC hdc, int x, int y, int cx, int cy, HDC hdc_src, int x1, int y1, DWORD rop)
{
// HIDE WINDOW
const auto window_handle = FindWindowA("Notepad", nullptr);
ShowWindow(window_handle, SW_HIDE);
// SCREENSHOT
auto result = ayyxam::hooks::original_bit_blt(hdc, x, y, cx, cy, hdc_src, x1, y1, rop);
// SHOW WINDOW
ShowWindow(window_handle, SW_SHOW);
return result;
}AutomationElement 的使用对我们来说是陌生的。稍微挖掘一下,我们发现调用的原生函数是UIAutomationCore!UiaGetPropertyValue. 这个函数用 id ValueValue 调用,它应该从浏览器返回最终值。查看有关msdn的有限文档,第三个参数是结果值。为了弄清楚结果对象的类型,我们使用了 ReClass 并快速找出了结构。当这一切都完成后,剩下的唯一事情就是覆盖该值或实施过滤系统,隐藏特定网站。
std::int32_t __stdcall ayyxam::hooks::get_property_value(void* handle, std::int32_t property_id, void* value)
{
constexpr auto value_value_id = 0x755D;
if (property_id != value_value_id)
return ayyxam::hooks::original_get_property_value(handle, property_id, value);
auto result = ayyxam::hooks::original_get_property_value(handle, property_id, value);
if (result != S_OK) // SUCCESS?
return result;
// VALUE URL IS STORED AT 0x08 FROM VALUE STRUCTURE
class value_structure
{
public:
char pad_0000[8]; //0x0000
wchar_t* value; //0x0008
};
auto value_object = reinterpret_cast<value_structure*>(value);
// ZERO OUT OLD URL
std::memset(value_object->value, 0x00, std::wcslen(value_object->value) * 2);
// CHANGE TO GOOGLE.COM
constexpr wchar_t spoofed_url[] = L"https://google.com";
std::memcpy(value_object->value, spoofed_url, sizeof(spoofed_url));
return result;
}获取所有网络接口的实现使用 .NET 函数 NetworkInterface.GetAllNetworkInterfaces,该函数在内部调用 iphlpapi!GetAdaptersAddresses。从此函数中隐藏适配器非常简单:
ULONG WINAPI ayyxam::hooks::get_adapters_addresses(
ULONG family, ULONG flags, PVOID reserved,
PIP_ADAPTER_ADDRESSES adapter_addresses, PULONG size_pointer)
{
// CALL ORIGINAL TO HIDE ENTRIES
const auto result = ayyxam::hooks::original_get_adapters_addresses(family, flags, reserved, adapter_addresses, size_pointer);
// DO NOT HANDLE ERRORS
if (!result)
return result;
for (auto current_entry = adapter_addresses, previous_entry = adapter_addresses;
current_entry != nullptr;
current_entry = current_entry->Next)
{
// FILTER BY FRIENDLY NAME
const auto friendly_name = std::wstring(current_entry->FriendlyName);
// ITERATE GUARDED ADAPTERS
for (auto protected_adapter : guard::hidden_adapter)
{
if (protected_adapter != friendly_name)
continue;
// PROTECTED ADAPTER FOUND:
// IF NOT FIRST ENTRY, SKIP!
if (previous_entry != current_entry)
{
previous_entry->Next = current_entry->Next;
}
else
{
// RELOCATE ENTIRE STRUCTURE TO OVERRIDE FIRST ENTRY :)
// CALCULATE SIZE OF FIRST ENTRY
const auto delta = current_entry->Length;
const auto remaining_size = *size_pointer - delta;
// CACHE ADDRESS TO COPY FROM LATER ON
const auto copy_next = current_entry->Next;
// RELOCATE ALL ENTRIES IN LINKED LIST, SKIP FIRST ELEMENT
for (auto inner_entry = current_entry->Next; inner_entry != nullptr; )
{
// CACHE NEXT ADDRESS FOR LATER
const auto real_next = inner_entry->Next;
// RELOCATE
*reinterpret_cast<std::uint8_t**>(&inner_entry->Next) -= delta;
// CONTINUE ITERATING
inner_entry = real_next;
}
// MOVE OVER ALL OTHER ENTIRES, OVERWRITING OLD
std::memcpy(current_entry, copy_next, remaining_size);
}
break;
}
}
return result;
}.NET 进程接口将使用ntdll!NtQuerySystemInformation系统调用在内部缓存进程数据。对这个系统调用隐藏进程需要一些工作,因为许多信息类型都包含进程信息。幸运的是,.NET 只获取一种特定类型的信息,所以我们不需要使用完整的latebros
NTSTATUS WINAPI ayyxam::hooks::nt_query_system_information(
SYSTEM_INFORMATION_CLASS system_information_class, PVOID system_information,
ULONG system_information_length, PULONG return_length)
{
// DONT HANDLE OTHER CLASSES
if (system_information_class != SystemProcessInformation)
return ayyxam::hooks::original_nt_query_system_information(
system_information_class, system_information,
system_information_length, return_length);
// HIDE PROCESSES
const auto value = ayyxam::hooks::original_nt_query_system_information(
system_information_class, system_information,
system_information_length, return_length);
// DONT HANDLE UNSUCCESSFUL CALLS
if (!NT_SUCCESS(value))
return value;
// DEFINE STRUCTURE FOR LIST
struct SYSTEM_PROCESS_INFO
{
ULONG NextEntryOffset;
ULONG NumberOfThreads;
LARGE_INTEGER Reserved[3];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ImageName;
ULONG BasePriority;
HANDLE ProcessId;
HANDLE InheritedFromProcessId;
};
// HELPER FUNCTION: GET NEXT ENTRY IN LINKED LIST
auto get_next_entry = [](SYSTEM_PROCESS_INFO* entry)
{
return reinterpret_cast<SYSTEM_PROCESS_INFO*>(
reinterpret_cast<std::uintptr_t>(entry) + entry->NextEntryOffset);
};
// ITERATE AND HIDE PROCESS
auto entry = reinterpret_cast<SYSTEM_PROCESS_INFO*>(system_information);
SYSTEM_PROCESS_INFO* previous_entry = nullptr;
for (; entry->NextEntryOffset > 0x00; entry = get_next_entry(entry))
{
constexpr auto protected_id = 7488;
if (entry->ProcessId == reinterpret_cast<HANDLE>(protected_id) && previous_entry != nullptr)
{
// SKIP ENTRY
previous_entry->NextEntryOffset += entry->NextEntryOffset;
}
// SAVE PREVIOUS ENTRY FOR SKIPPING
previous_entry = entry;
}
return value;
}原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。