Skip to content

Instantly share code, notes, and snippets.

@scizzydo
Last active August 1, 2022 16:10
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save scizzydo/06cf226cd92e5250aae36ddd2eddf21e to your computer and use it in GitHub Desktop.
Save scizzydo/06cf226cd92e5250aae36ddd2eddf21e to your computer and use it in GitHub Desktop.
crc bypass used in d2r (testing)
#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(&section, SECTION_ALL_ACCESS, nullptr, &section_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, &section_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