added string offsets to hook codes and searches, fixed stack alignment bug, fixed inaccurate documentation on x64 registers, fixed bug with search signature cache, imrpvoed hook search string detection

This commit is contained in:
Akash Mozumdar 2019-06-09 00:48:30 -04:00
parent 7964623ec8
commit 5e27de842b
13 changed files with 90 additions and 75 deletions

View File

@ -108,11 +108,14 @@ namespace
{
auto info = *(HookFoundNotif*)buffer;
auto& OnHookFound = processRecordsByIds->at(processId).OnHookFound;
OnHookFound(info.hp, processId, info.text);
std::wstring wide = info.text;
if (wide.size() > STRING) OnHookFound(info.hp, processId, info.text);
info.hp.type = USING_STRING;
if (auto converted = Util::StringToWideString((char*)info.text, Host::defaultCodepage)) if (converted->size() > 12) OnHookFound(info.hp, processId, converted.value());
if (auto converted = Util::StringToWideString((char*)info.text, Host::defaultCodepage))
if (converted->size() > STRING) OnHookFound(info.hp, processId, converted.value());
info.hp.codepage = CP_UTF8;
if (auto converted = Util::StringToWideString((char*)info.text, CP_UTF8)) if (converted->size() > 12) OnHookFound(info.hp, processId, converted.value());
if (auto converted = Util::StringToWideString((char*)info.text, CP_UTF8))
if (converted->size() > STRING) OnHookFound(info.hp, processId, converted.value());
}
break;
case HOST_NOTIFICATION_RMVHOOK:

View File

@ -41,15 +41,6 @@ namespace
RCode.erase(0, match[0].length());
}
// [*deref_offset]
if (RCode[0] == L'0') RCode.erase(0, 1); // Legacy
if (std::regex_search(RCode, match, std::wregex(L"^\\*(-?[[:xdigit:]]+)")))
{
hp.type |= DATA_INDIRECT;
hp.index = std::stoi(match[1], nullptr, 16);
RCode.erase(0, match[0].length());
}
// @addr
if (!std::regex_match(RCode, match, std::wregex(L"@([[:xdigit:]]+)"))) return {};
hp.address = std::stoull(match[1], nullptr, 16);
@ -132,6 +123,13 @@ namespace
HCode.erase(0, match[0].length());
}
// [padding+]
if (std::regex_search(HCode, match, std::wregex(L"^([[:xdigit:]]+)\\+")))
{
hp.padding = std::stoull(match[1], nullptr, 16);
HCode.erase(0, match[0].length());
}
// data_offset
if (!std::regex_search(HCode, match, std::wregex(L"^-?[[:xdigit:]]+"))) return {};
hp.offset = std::stoi(match[0], nullptr, 16);
@ -206,8 +204,6 @@ namespace
RCode << std::uppercase << std::hex;
if (hp.type & DATA_INDIRECT) RCode << "*" << HexString(hp.index);
RCode << "@" << hp.address;
return RCode.str();
@ -239,6 +235,8 @@ namespace
HCode << std::uppercase << std::hex;
if (hp.padding) HCode << hp.padding << "+";
if (hp.offset < 0) hp.offset += 4;
if (hp.split < 0) hp.split += 4;
@ -338,7 +336,7 @@ namespace Util
assert(StringToWideString(u8"こんにちは").value() == L"こんにちは"),
assert(ParseCode(L"/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")),
assert(ParseCode(L"HB4@0")),
assert(ParseCode(L"/RS*10@44")),
assert(ParseCode(L"/RS65001#@44")),
assert(!ParseCode(L"HQ@4")),
assert(!ParseCode(L"/RW@44")),
assert(!ParseCode(L"/HWG@33"))

View File

@ -33,6 +33,7 @@ extern const char* SEARCH_DURATION;
extern const char* PATTERN_OFFSET;
extern const char* MIN_ADDRESS;
extern const char* MAX_ADDRESS;
extern const char* STRING_OFFSET;
extern const char* HOOK_SEARCH_FILTER;
extern const char* START_HOOK_SEARCH;
extern const char* SAVE_SEARCH_RESULTS;
@ -353,6 +354,7 @@ void MainWindow::FindHooks()
for (auto[value, label] : Array<std::tuple<uintptr_t&, const char*>>{
{ sp.minAddress = 0, MIN_ADDRESS },
{ sp.maxAddress = -1ULL, MAX_ADDRESS },
{ sp.padding = 0, STRING_OFFSET }
})
{
auto input = new QLineEdit(QString::number(value, 16), this);

View File

@ -4,7 +4,7 @@
// 8/23/2013 jichi
// Branch: ITH/common.h, rev 128
enum Misc { MESSAGE_SIZE = 500, PIPE_BUFFER_SIZE = 2000, SHIFT_JIS = 932, MAX_MODULE_SIZE = 120, HOOK_NAME_SIZE = 30, FIXED_SPLIT_VALUE = 0x10001 };
enum Misc { STRING = 12, MESSAGE_SIZE = 500, PIPE_BUFFER_SIZE = 2000, SHIFT_JIS = 932, MAX_MODULE_SIZE = 120, HOOK_NAME_SIZE = 30, FIXED_SPLIT_VALUE = 0x10001 };
enum HostCommandType { HOST_COMMAND_NEW_HOOK, HOST_COMMAND_REMOVE_HOOK, HOST_COMMAND_FIND_HOOK, HOST_COMMAND_MODIFY_HOOK, HOST_COMMAND_HIJACK_PROCESS, HOST_COMMAND_DETACH };

View File

@ -40,6 +40,7 @@ struct HookParam
DWORD type; // flags
UINT codepage; // text encoding
short length_offset; // index of the string length
uintptr_t padding; // padding
DWORD user_value; // 7/20/2014: jichi additional parameters for PSP games
void(*text_fun)(DWORD stack, HookParam* hp, BYTE obsoleteAlwaysZero, DWORD* data, DWORD* split, DWORD* len);
@ -64,7 +65,7 @@ struct SearchParam
int length, // length of pattern
offset, // offset from start of pattern to add hook
searchTime; // ms
uintptr_t minAddress, maxAddress;
uintptr_t padding, minAddress, maxAddress;
};
struct InsertHookCmd // From host

View File

@ -23,19 +23,21 @@ const char* CODE_INFODUMP = u8R"(Search for text
S[codepage#]text
OR
Enter read code
R{S|Q|V}[null_length<][codepage#][*deref_offset]@addr
R{S|Q|V}[null_length<][codepage#]@addr
OR
Enter hook code
H{A|B|W|S|Q|V}[null_length<][N][codepage#]data_offset[*deref_offset1][:split_offset[*deref_offset2]]@addr[:module[:func]]
H{A|B|W|S|Q|V}[null_length<][N][codepage#][padding+]data_offset[*deref_offset][:split_offset[*deref_offset]]@addr[:module[:func]]
All numbers except codepage/null_length in hexadecimal
Default codepage is 932 (Shift-JIS) but this can be changed in settings
A/B: codepage char little/big endian
W: UTF-16 char
S/Q/V: codepage/UTF-16/UTF-8 string
N: don't use context
null_length: length of null terminator used for string
padding: length of padding data before string (C struct { int64_t size; char string[500]; } needs padding = 8)
Negatives for data_offset/split_offset refer to registers
-4 for EAX, -8 for ECX, -C for EDX, -10 for EBX, -14 for ESP, -18 for EBP, -1C for ESI, -20 for EDI
-4 for RAX, -C for RBX, -14 for RCX, -1C for RDX, and so on for RSP, RBP, RSI, RDI, R8-R15
-C for RAX, -14 for RBX, -1C for RCX, -24 for RDX, and so on for RSP, RBP, RSI, RDI, R8-R15
* means dereference pointer+deref_offset)";
const char* SAVE_SETTINGS = u8"Save settings";
const char* EXTEN_WINDOW_INSTRUCTIONS = u8R"(Drag and drop extension (.dll) files here from your computer to add them
@ -53,6 +55,7 @@ const char* SEARCH_DURATION = u8"Search duration (ms)";
const char* PATTERN_OFFSET = u8"Offset from pattern start";
const char* MIN_ADDRESS = u8"Minimum address (hex)";
const char* MAX_ADDRESS = u8"Maximum address (hex)";
const char* STRING_OFFSET = u8"String offset (hex)";
const char* HOOK_SEARCH_FILTER = u8"Results must match this regex";
const char* START_HOOK_SEARCH = u8"Start hook search";
const char* SAVE_SEARCH_RESULTS = u8"Save search results";

View File

@ -11,22 +11,32 @@ extern WinMutex viewMutex;
namespace
{
SearchParam current;
constexpr int CACHE_SIZE = 500'000;
struct HookRecord
{
HookRecord() : address(0) {}
~HookRecord() { if (address) NotifyHookFound(address, offset, text); }
uint64_t address;
int offset;
wchar_t text[200];
~HookRecord()
{
if (!address) return;
HookParam hp = {};
hp.offset = offset;
hp.type = USING_UNICODE | USING_STRING;
hp.address = address;
hp.padding = current.padding;
NotifyHookFound(hp, (wchar_t*)text);
}
uint64_t address = 0;
int offset = 0;
char text[500] = {};
};
std::unique_ptr<HookRecord[]> records;
long recordsAvailable;
uint64_t addressCharCache[CACHE_SIZE] = {};
uint64_t signatureCache[CACHE_SIZE] = {};
long sumCache[CACHE_SIZE] = {};
#ifndef _WIN64
BYTE trampoline[32] =
BYTE trampoline[] =
{
0x9c, // pushfd
0x60, // pushad
@ -43,7 +53,7 @@ namespace
};
constexpr int addr_offset = 3, send_offset = 13, original_offset = 25, registers = 8;
#else
BYTE trampoline[128] = {
BYTE trampoline[] = {
0x9c, // push rflags
0x50, // push rax
0x53, // push rbx
@ -69,7 +79,10 @@ namespace
0x48, 0x8d, 0x8c, 0x24, 0xa8, 0x00, 0x00, 0x00, // lea rcx,[rsp+0xa8]
0x48, 0xba, 0,0,0,0,0,0,0,0, // mov rcx,@addr
0x48, 0xb8, 0,0,0,0,0,0,0,0, // mov rax,@Send
0x48, 0x89, 0xe3, // mov rbx,rsp
0x48, 0x83, 0xe4, 0xf0, // and rsp,0xfffffffffffffff0 ; align stack
0xff, 0xd0, // call rax
0x48, 0x89, 0xdc, // mov rsp,rbx
0xc5, 0xfa, 0x6f, 0x6c, 0x24, 0x10, // vmovdqu xmm5,XMMWORD PTR[rsp + 0x10]
0xc5, 0xfa, 0x6f, 0x24, 0x24, // vmovdqu xmm4,XMMWORD PTR[rsp]
0x48, 0x83, 0xc4, 0x20, // add rsp,0x20
@ -93,11 +106,11 @@ namespace
0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [0] ; relative to next instruction (i.e. jmp @original)
0,0,0,0,0,0,0,0 // @original
};
constexpr int addr_offset = 50, send_offset = 60, original_offset = 116, registers = 16;
constexpr int addr_offset = 50, send_offset = 60, original_offset = 126, registers = 16;
#endif
}
void Send(wchar_t** stack, uintptr_t address)
void Send(char** stack, uintptr_t address)
{
// it is unsafe to call ANY external functions from this, as they may have been hooked (if called the hook would call this function making an infinite loop)
// the exceptions are compiler intrinsics like _InterlockedDecrement
@ -105,16 +118,17 @@ void Send(wchar_t** stack, uintptr_t address)
for (int i = -registers; i < 6; ++i)
{
int length = 0, sum = 0;
__try { for (wchar_t* str = stack[i]; str[length] && length < 200; ++length) sum += str[length]; }
char* str = stack[i] + current.padding;
__try { for (; (str[length] || str[length + 1]) && length < 500; length += 2) sum += str[length] + str[length + 1]; }
__except (EXCEPTION_EXECUTE_HANDLER) {}
if (length > 7 && length < 199)
if (length > STRING && length < 499)
{
__try
{
// many duplicate results with same address and second character will be found: filter them out
uint64_t addressAndChar = (((uint64_t)stack[i][1]) << 48) | address;
if (addressCharCache[addressAndChar % CACHE_SIZE] == addressAndChar) continue;
addressCharCache[addressAndChar % CACHE_SIZE] = addressAndChar;
// many duplicate results with same address, offset, and third/fourth character will be found: filter them out
uint64_t signature = ((uint64_t)i << 56) | ((uint64_t)(str[2] + str[3]) << 48) | address;
if (signatureCache[signature % CACHE_SIZE] == signature) continue;
signatureCache[signature % CACHE_SIZE] = signature;
// if there are huge amount of strings that are the same, it's probably garbage: filter them out
// can't store all the strings, so use sum as heuristic instead
if (_InterlockedIncrement(sumCache + (sum % CACHE_SIZE)) > 25) continue;
@ -127,8 +141,8 @@ void Send(wchar_t** stack, uintptr_t address)
if (n > 0)
{
records[n].address = address;
records[n].offset = i * sizeof(wchar_t*);
for (int j = 0; j < length; ++j) records[n].text[j] = stack[i][j];
records[n].offset = i * sizeof(char*);
for (int j = 0; j < length; ++j) records[n].text[j] = str[j];
records[n].text[length] = 0;
}
}
@ -145,12 +159,11 @@ void SearchForHooks(SearchParam sp)
static std::mutex m;
std::scoped_lock lock(m);
try
{
records = std::make_unique<HookRecord[]>(recordsAvailable = CACHE_SIZE);
}
try { records = std::make_unique<HookRecord[]>(recordsAvailable = CACHE_SIZE); }
catch (std::bad_alloc&) { return ConsoleOutput("Textractor: SearchForHooks ERROR (out of memory)"); }
current = sp;
uintptr_t moduleStartAddress = (uintptr_t)GetModuleHandleW(ITH_DLL);
uintptr_t moduleStopAddress = moduleStartAddress;
MEMORY_BASIC_INFORMATION info;
@ -162,12 +175,9 @@ void SearchForHooks(SearchParam sp)
moduleStopAddress -= info.RegionSize;
ConsoleOutput(STARTING_SEARCH);
std::vector<uint64_t> addresses = Util::SearchMemory(sp.pattern, sp.length);
std::vector<uint64_t> addresses = Util::SearchMemory(sp.pattern, sp.length, PAGE_EXECUTE, sp.minAddress, sp.maxAddress);
for (auto& addr : addresses) addr += sp.offset;
addresses.erase(std::remove_if(addresses.begin(), addresses.end(), [&](uint64_t addr)
{
return (addr > moduleStartAddress && addr < moduleStopAddress) || addr > sp.maxAddress || addr < sp.minAddress;
}), addresses.end());
addresses.erase(std::remove_if(addresses.begin(), addresses.end(), [&](uint64_t addr) { return addr > moduleStartAddress && addr < moduleStopAddress; }), addresses.end());
*(void**)(trampoline + send_offset) = Send;
auto trampolines = (decltype(trampoline)*)VirtualAlloc(NULL, sizeof(trampoline) * addresses.size(), MEM_COMMIT, PAGE_READWRITE);
VirtualProtect(trampolines, addresses.size() * sizeof(trampoline), PAGE_EXECUTE_READWRITE, DUMMY);
@ -189,7 +199,7 @@ void SearchForHooks(SearchParam sp)
for (auto addr : addresses) MH_RemoveHook((void*)addr);
records.reset();
VirtualFree(trampolines, 0, MEM_RELEASE);
for (int i = 0; i < CACHE_SIZE; ++i) addressCharCache[i] = sumCache[i] = 0;
for (int i = 0; i < CACHE_SIZE; ++i) signatureCache[i] = sumCache[i] = 0;
ConsoleOutput(HOOK_SEARCH_FINISHED, CACHE_SIZE - recordsAvailable);
}).detach();
}

View File

@ -102,12 +102,8 @@ void ConsoleOutput(LPCSTR text, ...)
WriteFile(hookPipe, &buffer, sizeof(buffer), DUMMY, nullptr);
}
void NotifyHookFound(uint64_t addr, int offset, wchar_t* text)
void NotifyHookFound(HookParam hp, wchar_t* text)
{
HookParam hp = {};
hp.offset = offset;
hp.type = USING_UNICODE | USING_STRING;
hp.address = addr;
HookFoundNotif buffer(hp, text);
WriteFile(hookPipe, &buffer, sizeof(buffer), DUMMY, nullptr);
}

View File

@ -9,7 +9,7 @@
void TextOutput(ThreadParam tp, BYTE* text, int len);
void ConsoleOutput(LPCSTR text, ...);
void NotifyHookFound(uint64_t addr, int offset, wchar_t* text);
void NotifyHookFound(HookParam hp, wchar_t* text);
void NotifyHookRemove(uint64_t addr, LPCSTR name);
void NewHook(HookParam hp, LPCSTR name, DWORD flag = HOOK_ENGINE);
void RemoveHook(uint64_t addr, int maxOffset = 9);

View File

@ -63,7 +63,10 @@ namespace { // unnamed
0x48, 0x8d, 0x94, 0x24, 0xa8, 0x00, 0x00, 0x00, // lea rdx,[rsp+0xa8]
0x48, 0xb9, 0,0,0,0,0,0,0,0, // mov rcx,@this
0x48, 0xb8, 0,0,0,0,0,0,0,0, // mov rax,@TextHook::Send
0x48, 0x89, 0xe3, // mov rbx,rsp
0x48, 0x83, 0xe4, 0xf0, // and rsp,0xfffffffffffffff0 ; align stack
0xff, 0xd0, // call rax
0x48, 0x89, 0xdc, // mov rsp,rbx
0xc5, 0xfa, 0x6f, 0x6c, 0x24, 0x10, // vmovdqu xmm5,XMMWORD PTR[rsp + 0x10]
0xc5, 0xfa, 0x6f, 0x24, 0x24, // vmovdqu xmm4,XMMWORD PTR[rsp]
0x48, 0x83, 0xc4, 0x20, // add rsp,0x20
@ -87,7 +90,7 @@ namespace { // unnamed
0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [0] ; relative to next instruction (i.e. jmp @original)
0,0,0,0,0,0,0,0 // @original
};
int this_offset = 50, send_offset = 60, original_offset = 116;
int this_offset = 50, send_offset = 60, original_offset = 126;
#endif
bool trigger = false;
@ -142,6 +145,7 @@ void TextHook::Send(uintptr_t dwDataBase)
if (hp.type & SPLIT_INDIRECT) dwSplit = *(DWORD *)(dwSplit + hp.split_index);
}
if (hp.type & DATA_INDIRECT) dwDataIn = *(DWORD *)(dwDataIn + hp.index);
dwDataIn += hp.padding;
dwCount = GetLength(dwDataBase, dwDataIn);
}
@ -173,6 +177,7 @@ void TextHook::Send(uintptr_t dwDataBase)
}
if (hp.type & DATA_INDIRECT) data = *(uintptr_t*)(data + hp.index);
data += hp.padding;
count = GetLength(dwDataBase, data);
if (count == 0) return;
if (count > TEXT_BUFFER_SIZE) count = TEXT_BUFFER_SIZE;
@ -225,18 +230,15 @@ bool TextHook::InsertHookCode()
return MH_EnableHook(location) == MH_OK;
}
DWORD WINAPI TextHook::Reader(LPVOID hookPtr)
void TextHook::Read()
{
TextHook* This = (TextHook*)hookPtr;
BYTE buffer[TEXT_BUFFER_SIZE] = {};
int changeCount = 0, dataLen = 1;
__try
{
uint64_t currentAddress = This->address;
while (WaitForSingleObject(This->readerEvent, 500) == WAIT_TIMEOUT)
while (WaitForSingleObject(readerEvent, 500) == WAIT_TIMEOUT)
{
if (This->hp.type & DATA_INDIRECT) currentAddress = *(uintptr_t*)This->address + This->hp.index;
if (memcmp(buffer, (void*)currentAddress, dataLen) == 0)
if (memcmp(buffer, location, dataLen) == 0)
{
changeCount = 0;
continue;
@ -244,26 +246,25 @@ DWORD WINAPI TextHook::Reader(LPVOID hookPtr)
if (++changeCount > 10)
{
ConsoleOutput(GARBAGE_MEMORY);
This->Clear();
Clear();
break;
}
dataLen = min(This->HookStrlen((BYTE*)currentAddress), TEXT_BUFFER_SIZE);
memcpy(buffer, (void*)currentAddress, dataLen);
TextOutput({ GetCurrentProcessId(), This->address, 0, 0 }, buffer, dataLen);
dataLen = min(HookStrlen((BYTE*)location), TEXT_BUFFER_SIZE);
memcpy(buffer, location, dataLen);
TextOutput({ GetCurrentProcessId(), address, 0, 0 }, buffer, dataLen);
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
ConsoleOutput(READ_ERROR);
This->Clear();
Clear();
}
return 0;
}
bool TextHook::InsertReadCode()
{
readerThread = CreateThread(nullptr, 0, Reader, this, 0, nullptr);
readerThread = CreateThread(nullptr, 0, [](void* This) { ((TextHook*)This)->Read(); return 0UL; }, this, 0, nullptr);
readerEvent = CreateEventW(nullptr, FALSE, FALSE, NULL);
return true;
}

View File

@ -29,7 +29,7 @@ public:
void Clear();
private:
static DWORD WINAPI Reader(LPVOID hookPtr);
void Read();
bool InsertHookCode();
bool InsertReadCode();
void Send(uintptr_t dwDatabase);
@ -40,7 +40,7 @@ private:
HANDLE readerThread, readerEvent;
bool err;
BYTE trampoline[130];
BYTE trampoline[x64 ? 140 : 40];
};

View File

@ -301,7 +301,7 @@ bool SearchResourceString(LPCWSTR str)
return false;
}
std::vector<uint64_t> SearchMemory(const void* bytes, short length, DWORD protect)
std::vector<uint64_t> SearchMemory(const void* bytes, short length, DWORD protect, uintptr_t minAddr, uintptr_t maxAddr)
{
SYSTEM_INFO systemInfo;
GetNativeSystemInfo(&systemInfo);
@ -316,15 +316,16 @@ std::vector<uint64_t> SearchMemory(const void* bytes, short length, DWORD protec
}
else
{
if (info.Protect >= protect && !(info.Protect & PAGE_GUARD)) validMemory.push_back({ (uint64_t)info.BaseAddress, info.RegionSize });
if ((uint64_t)info.BaseAddress + info.RegionSize >= minAddr && info.Protect >= protect && !(info.Protect & PAGE_GUARD))
validMemory.push_back({ (uint64_t)info.BaseAddress, info.RegionSize });
probe += info.RegionSize;
}
}
std::vector<uint64_t> ret;
for (auto memory : validMemory)
for (uint64_t addr = memory.first; true;)
if (addr = SafeSearchMemory(addr, memory.first + memory.second, (const BYTE*)bytes, length))
for (uint64_t addr = max(memory.first, minAddr); true;)
if (addr < maxAddr && (addr = SafeSearchMemory(addr, memory.first + memory.second, (const BYTE*)bytes, length)))
ret.push_back(addr++);
else break;

View File

@ -22,7 +22,7 @@ bool CheckFile(LPCWSTR name);
bool SearchResourceString(LPCWSTR str);
std::vector<uint64_t> SearchMemory(const void* bytes, short length, DWORD protect = PAGE_EXECUTE);
std::vector<uint64_t> SearchMemory(const void* bytes, short length, DWORD protect = PAGE_EXECUTE, uintptr_t minAddr = 0, uintptr_t maxAddr = -1ULL);
} // namespace Util