-
-
Save scizzydo/06cf226cd92e5250aae36ddd2eddf21e to your computer and use it in GitHub Desktop.
crc bypass used in d2r (testing)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Windows.h> | |
#include <cstdint> | |
#include <TlHelp32.h> | |
#include <stdio.h> | |
#include <functional> | |
#include <unordered_map> | |
#include <iostream> | |
#include <sstream> | |
#include "memory.h" | |
#include "assembler.h" | |
#include "disassembler.h" | |
#define SEC_NO_CHANGE 0x00400000 | |
void EnumRegions(DWORD pid, const char* mod, std::function<void(HANDLE, uint8_t*, size_t)> func) { | |
HANDLE hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); | |
if (INVALID_HANDLE_VALUE == hModuleSnap) { | |
printf("EnumRegions CreateToolhelp32Snapshot error [%d] \r\n", GetLastError()); | |
return; | |
} | |
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, pid); | |
if (!hProcess || hProcess == INVALID_HANDLE_VALUE) { | |
printf("EnumRegions OpenProcess error [%d] \r\n", GetLastError()); | |
goto cleanup; | |
} | |
MODULEENTRY32 me32; | |
me32.dwSize = sizeof(MODULEENTRY32); | |
if (Module32First(hModuleSnap, &me32)) { | |
do { | |
if (!mod || !strcmp(me32.szModule, mod)) { | |
func(hProcess, me32.modBaseAddr, me32.modBaseSize); | |
goto cleanup; | |
} | |
} while (Module32Next(hModuleSnap, &me32)); | |
} | |
else { | |
printf("EnumRegions Module32First error [%d] \r\n", GetLastError()); | |
} | |
cleanup: | |
if (hProcess) | |
CloseHandle(hProcess); | |
if (hModuleSnap) | |
CloseHandle(hModuleSnap); | |
} | |
std::string replace_all(std::string str, const std::string& from, const std::string& to) { | |
size_t start_pos = 0; | |
while ((start_pos = str.find(from, start_pos)) != std::string::npos) { | |
str.replace(start_pos, from.length(), to); | |
start_pos += to.length(); | |
} | |
return str; | |
} | |
std::string detour = R"( push rax | |
movabs rax, _cave | |
call rax | |
pop rax)"; | |
// removed to just test .text only for now since .rdata requires another region too | |
/*std::string crc = R"( push rcx | |
movabs rcx, _text_start | |
cmp rdi, rcx | |
jl test_rdata | |
movabs rcx, _text_end | |
cmp rdi, rcx | |
jg test_rdata | |
jmp swap_crc | |
test_rdata: | |
movabs rcx, _rdata_start | |
cmp rdi, rcx | |
jl cleanup | |
movabs rcx, _rdata_end | |
cmp rdi, rcx | |
jg cleanup | |
swap_crc: | |
movabs rcx, _base | |
sub rdi, rcx | |
movabs rcx, _copy_base | |
add rdi, rcx | |
cleanup: | |
pop rcx | |
normal_crc: | |
)";*/ | |
std::string crc = R"( push rcx | |
movabs rcx, _text_start | |
cmp rdi, rcx | |
jl cleanup | |
movabs rcx, _text_end | |
cmp rdi, rcx | |
jg cleanup | |
swap_crc: | |
movabs rcx, _base | |
sub rdi, rcx | |
movabs rcx, _copy_base | |
add rdi, rcx | |
cleanup: | |
pop rcx | |
normal_crc: | |
)"; | |
size_t cave_size = 0; | |
size_t region_size = 0; | |
uint64_t text_start = 0; | |
uint64_t text_end = 0; | |
uint64_t rdata_start = 0; | |
uint64_t rdata_end = 0; | |
uint64_t region_base = 0; | |
uint64_t copy_base = 0; | |
uint64_t cave = 0; | |
static bool sym_resolver(const char* symbol, uint64_t* value) { | |
if (!strcmp(symbol, "_cave")) { | |
*value = cave; | |
return true; | |
} | |
if (!strcmp(symbol, "_text_start")) { | |
*value = text_start; | |
return true; | |
} if (!strcmp(symbol, "_text_end")) { | |
*value = text_end; | |
return true; | |
} | |
if (!strcmp(symbol, "_rdata_start")) { | |
*value = rdata_start; | |
return true; | |
} | |
if (!strcmp(symbol, "_rdata_end")) { | |
*value = rdata_end; | |
return true; | |
} | |
if (!strcmp(symbol, "_base")) { | |
*value = region_base; | |
return true; | |
} | |
if (!strcmp(symbol, "_copy_base")) { | |
*value = copy_base; | |
return true; | |
} | |
return false; | |
} | |
struct RegionData { | |
uint32_t start; | |
uint32_t size; | |
}; | |
uint8_t* scan_internal(const char* pattern, const char* mask, uint8_t* begin, size_t size) { | |
auto pattern_length = strlen(mask); | |
for (int i = 0; i < size; ++i) { | |
bool found = true; | |
for (auto j = 0u; j < pattern_length; ++j) { | |
if (mask[j] != '?' && pattern[j] != *(int8_t*)((int8_t*)begin + i + j)) { | |
found = false; | |
break; | |
} | |
} | |
if (found) { | |
return begin + i; | |
} | |
} | |
return nullptr; | |
} | |
uint8_t* scan(const char* pattern, const char* mask, uint8_t* begin, uint8_t* end) { | |
uint8_t* match = nullptr; | |
MEMORY_BASIC_INFORMATION mbi{}; | |
for (auto curr = begin; curr < end; curr += mbi.RegionSize) { | |
if (!VirtualQuery(curr, &mbi, sizeof(mbi))) | |
continue; | |
if (mbi.State != MEM_COMMIT) | |
continue; | |
if (mbi.Protect == PAGE_NOACCESS) | |
continue; | |
auto temp_end = curr + mbi.RegionSize; | |
if (curr + mbi.RegionSize > end) { | |
auto diff = temp_end - end; | |
mbi.RegionSize -= diff; | |
} | |
match = scan_internal(pattern, mask, curr, mbi.RegionSize); | |
if (match) | |
break; | |
} | |
return match; | |
} | |
uint8_t* scan_external(const char* pattern, const char* mask, uint8_t* begin, uint8_t* end, HANDLE hProc) { | |
uint8_t* match = nullptr; | |
SIZE_T bytes_read; | |
MEMORY_BASIC_INFORMATION mbi; | |
for (auto curr = begin; curr < end; curr += mbi.RegionSize) { | |
if (!VirtualQueryEx(hProc, curr, &mbi, sizeof(mbi))) | |
continue; | |
if (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) | |
continue; | |
if (mbi.State != MEM_COMMIT) | |
continue; | |
auto buffer = std::make_unique<uint8_t[]>(mbi.RegionSize); | |
ReadProcessMemory(hProc, curr, buffer.get(), mbi.RegionSize, &bytes_read); | |
auto addr = scan_internal(pattern, mask, buffer.get(), bytes_read); | |
if (addr) { | |
match = curr + ((uint8_t*)addr - buffer.get()); | |
break; | |
} | |
} | |
return match; | |
} | |
uint8_t* scan_process(const char* pattern, const char* mask, uint8_t* begin, HANDLE hProc) { | |
return scan_external(pattern, mask, begin, (uint8_t*)0x800000000000, hProc); | |
} | |
int main(int argc, char** argv) { | |
if (argc != 2) { | |
printf("Invalid usage!\r\nExample: crc_bypass.exe <pid>\r\n"); | |
return EXIT_FAILURE; | |
} | |
if (!initialize()) { | |
printf("Failed to initialize the Nt memory functions\r\n"); | |
return EXIT_FAILURE; | |
} | |
DWORD pid = atoi(argv[1]); | |
printf("Attempting to bypass %d\r\n", pid); | |
EnumRegions(pid, nullptr, [](HANDLE hproc, uint8_t* base, size_t size) { | |
MEMORY_BASIC_INFORMATION mbi = { 0 }; | |
if (!VirtualQueryEx(hproc, base, &mbi, sizeof(MEMORY_BASIC_INFORMATION))) { | |
printf("Failed to VirtualQueryEx [%d] \r\n", GetLastError()); | |
return; | |
} | |
region_base = (uintptr_t)mbi.BaseAddress; | |
region_size = mbi.RegionSize; | |
RegionData text = { 0 }; | |
if (!ReadProcessMemory(hproc, base + 0x25c, &text, sizeof(RegionData), nullptr)) { | |
printf("Failed to ReadProcessMemory .text region [%d] \r\n", GetLastError()); | |
return; | |
} | |
text_start = (region_base + text.start); | |
text_end = text_start + text.size; | |
RegionData data = { 0 }; | |
if (!ReadProcessMemory(hproc, base + 0x284, &data, sizeof(RegionData), nullptr)) { | |
printf("Failed to ReadProcessMemory .rdata start offset [%d] \r\n", GetLastError()); | |
return; | |
} | |
rdata_start = (region_base + data.start); | |
rdata_end = rdata_start + data.size; | |
auto region_end = region_base + region_size - 1; | |
printf("%-10s %p...%p (%llu bytes)\n", | |
".text", | |
(PVOID)text_start, | |
(PVOID)text_end, | |
(text_end - text_start)); | |
printf("%-10s %p...%p (%llu bytes)\n", | |
".rdata", | |
(PVOID)rdata_start, | |
(PVOID)rdata_end, | |
(rdata_end - rdata_start)); | |
printf("%-10s %p...%p (%llu bytes)\n", | |
"region", | |
(PVOID)region_base, | |
(PVOID)region_end, | |
region_size); | |
}); | |
if (text_start && text_end) { | |
auto hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); | |
if (!hProc) { | |
printf("Failed to open process [%d] \r\n", GetLastError()); | |
return EXIT_FAILURE; | |
} | |
auto copy_buffer = std::make_unique<uint8_t[]>(region_size); | |
if (!copy_buffer) { | |
printf("Failed to allocate copy buffer [%d] \r\n", GetLastError()); | |
CloseHandle(hProc); | |
return EXIT_FAILURE; | |
} | |
if (!ReadProcessMemory(hProc, (PVOID)region_base, copy_buffer.get(), region_size, NULL)) { | |
printf("Failed to read memory to local buffer [%d] \r\n", GetLastError()); | |
CloseHandle(hProc); | |
return EXIT_FAILURE; | |
} | |
NtSuspendProcess(hProc); | |
HANDLE section = NULL; | |
LARGE_INTEGER section_max = { 0 }; | |
section_max.QuadPart = region_size; | |
auto status = NtCreateSection(§ion, SECTION_ALL_ACCESS, nullptr, §ion_max, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL); /*| 0x00400000*/ | |
if (!NT_SUCCEEDED(status)) { | |
printf("Failed to NtCreateSection [0x%lX] \r\n", status); | |
NtResumeProcess(hProc); | |
CloseHandle(hProc); | |
return EXIT_FAILURE; | |
} | |
status = NtUnmapViewOfSection(hProc, (PVOID)region_base); | |
if (!NT_SUCCEEDED(status)) { | |
printf("Failed to UnmapViewOfSection [0x%lX] \r\n", status); | |
NtResumeProcess(hProc); | |
NtClose(section); | |
CloseHandle(hProc); | |
return EXIT_FAILURE; | |
} | |
PVOID our_base = nullptr; | |
PVOID view_base = (PVOID)region_base; | |
LARGE_INTEGER section_offset = { 0 }; | |
SIZE_T view_size = NULL; | |
status = NtMapViewOfSection(section, GetCurrentProcess(), &our_base, NULL, region_size, §ion_offset, &view_size, SECTION_INHERIT::ViewUnmap, NULL, PAGE_READWRITE); | |
if (!NT_SUCCEEDED(status)) { | |
printf("Failed to Map view of section in our process [0x%lX]\r\n", status); | |
NtClose(section); | |
CloseHandle(hProc); | |
return EXIT_FAILURE; | |
} | |
memcpy(our_base, copy_buffer.get(), region_size); | |
printf("Created a local view of section at 0x%p\r\n", our_base); | |
auto copy_bufferEx = VirtualAllocEx(hProc, nullptr, region_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); | |
if (!copy_bufferEx) { | |
printf("Failed to VirtualAllocEx [%d] \r\n", GetLastError()); | |
NtClose(section); | |
CloseHandle(hProc); | |
return EXIT_FAILURE; | |
} | |
copy_base = (uintptr_t)copy_bufferEx; | |
if (!WriteProcessMemory(hProc, copy_bufferEx, copy_buffer.get(), view_size, nullptr)) { | |
printf("Failed to WriteProcessMemory to bufferEx [%d] \r\n", GetLastError()); | |
VirtualFreeEx(hProc, copy_bufferEx, 0, MEM_RELEASE); | |
NtClose(section); | |
CloseHandle(hProc); | |
return EXIT_FAILURE; | |
} | |
DWORD old_protect; | |
VirtualProtectEx(hProc, copy_bufferEx, region_size, PAGE_EXECUTE_READ, &old_protect); | |
disassembler disass; | |
auto detourCRC = [&](uintptr_t location) { | |
cave_size = 0; | |
std::string compare_register = "rdi"; | |
std::stringstream original; | |
std::unordered_map<std::string, bool> registers = { | |
{"rax", false}, | |
{"rcx", false}, | |
{"rdx", false}, | |
{"rbx", false}, | |
{"rsi", false}, | |
{"rdi", false}, | |
{"rbp", false}, | |
{"rsp", false} | |
}; | |
uint32_t instruction_length = 0; | |
if (!disass.disassemble((const uint8_t*)location, 88, location, 0, [&](csh handle, cs_insn* insn, size_t count)->bool { | |
cs_regs regs_read, regs_write; | |
uint8_t read_count, write_count, i; | |
for (auto j = 0u; j < count; ++j) { | |
cave_size += insn[j].size; | |
instruction_length += insn[j].size; | |
if (!strcmp(insn[j].mnemonic, "jb")) { | |
original << "\t" << insn[j].mnemonic << " normal_crc" << std::endl; | |
break; | |
} | |
if (cs_regs_access(handle, &insn[j], | |
regs_read, &read_count, | |
regs_write, &write_count) == 0) { | |
if (read_count > 0) { | |
bool is_crc = strcmp(insn[j].mnemonic, "crc32") == 0; | |
for (i = 0; i < read_count; i++) { | |
auto reg = cs_reg_name(handle, regs_read[i]); | |
if (is_crc && i == 1) | |
compare_register.assign(reg); | |
registers[reg] = true; | |
} | |
} | |
if (write_count > 0) { | |
for (i = 0; i < write_count; i++) { | |
auto reg = cs_reg_name(handle, regs_write[i]); | |
registers[reg] = true; | |
} | |
} | |
} | |
} | |
return true; | |
})) { | |
std::cout << "cs_diasm() failed with error = " << disass.err() << std::endl; | |
return false; | |
} | |
assembler ass; | |
ass.add_sym_resolver(sym_resolver); | |
std::ostringstream temp_crc; | |
if (compare_register != "rdi") | |
temp_crc << replace_all(crc, "rdi", compare_register); | |
else | |
temp_crc << crc; | |
temp_crc << original.str(); | |
temp_crc << "\tret"; | |
std::vector<uint8_t> crc_cave_bytes = ass.assemble(temp_crc.str(), 0); | |
if (crc_cave_bytes.empty()) { | |
std::cout << "ks_asm() failed with error = " << ass.err() << std::endl; | |
return false; | |
} | |
auto pCave = VirtualAllocEx(hProc, nullptr, crc_cave_bytes.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); | |
if (!pCave) { | |
printf("Failed to VirtualAllocEx for pCave [%d] \r\n", GetLastError()); | |
return false; | |
} | |
cave = (uintptr_t)pCave; | |
bool replace = true; | |
std::string temp_reg; | |
for (auto& reg : registers) { | |
if (!reg.second) { | |
temp_reg = reg.first; | |
if (reg.first == "rax") { | |
replace = false; | |
break; | |
} | |
} | |
} | |
std::string temp_detour; | |
if (replace) | |
temp_detour = replace_all(detour, "rax", temp_reg); | |
else | |
temp_detour = detour; | |
auto detour_bytes = ass.assemble(temp_detour, 0); | |
if (detour_bytes.empty()) { | |
std::cout << "ks_asm() failed with error = " << ass.err() << std::endl; | |
return false; | |
} | |
for (auto i = detour_bytes.size(); i < instruction_length; ++i) { | |
// Pad the detour to instruction length with nops | |
detour_bytes.push_back(0x90); | |
} | |
memcpy((PVOID)location, &detour_bytes[0], detour_bytes.size()); | |
if (!WriteProcessMemory(hProc, pCave, &crc_cave_bytes[0], crc_cave_bytes.size(), nullptr)) { | |
printf("Failed to WriteProcessMemory pCave [%d] \r\n", GetLastError()); | |
VirtualFreeEx(hProc, pCave, 0, MEM_RELEASE); | |
return false; | |
} | |
DWORD old; | |
VirtualProtectEx(hProc, pCave, crc_cave_bytes.size(), PAGE_EXECUTE_READ, &old); | |
printf("Bypassed CRC at %p \r\n", (PVOID)location); | |
return true; | |
}; | |
auto current = (uint8_t*)our_base; | |
auto end = current + region_size - 1; | |
current = scan("\xF2\x42\x0F\x38\xF1", "x?xxx", current, end); | |
do { | |
if (current) { | |
printf("Found CRC check at %p\r\n", (PVOID)current); | |
detourCRC((uintptr_t)current); | |
current = scan("\xF2\x42\x0F\x38\xF1", "x?xxx", current + 5, end); | |
} | |
} while (current); | |
status = NtMapViewOfSection(section, hProc, &view_base, NULL, region_size, NULL, &view_size, SECTION_INHERIT::ViewUnmap, SEC_NO_CHANGE, PAGE_EXECUTE_READ); | |
if (!NT_SUCCEEDED(status)) { | |
printf("Failed to Map view of section [0x%lX] \r\n", status); | |
NtClose(section); | |
CloseHandle(hProc); | |
return EXIT_FAILURE; | |
} | |
status = NtUnmapViewOfSection(GetCurrentProcess(), our_base); | |
if (!NT_SUCCEEDED(status)) { | |
printf("Failed to NtUnmapViewOfSection for our section [0x%lX] \r\n", status); | |
VirtualFreeEx(hProc, copy_bufferEx, 0, MEM_RELEASE); | |
NtClose(section); | |
CloseHandle(hProc); | |
return EXIT_FAILURE; | |
} | |
NtClose(section); | |
printf("Successfully bypassed CRC checks\r\nPress enter to resume process\r\n"); | |
system("pause"); | |
NtResumeProcess(hProc); | |
CloseHandle(hProc); | |
} | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment