LunaHook-mirror/LunaHook/engine32/Yuris.cpp

392 lines
14 KiB
C++
Raw Normal View History

2024-02-07 20:59:24 +08:00
#include"Yuris.h"
/********************************************************************************************
YU-RIS hook:
Becomes common recently. I first encounter this game in Whirlpool games.
Problem is name is repeated multiple times.
Step out of function call to TextOuA, just before call to this function,
there should be a piece of code to calculate the length of the name.
This length is 2 for single character name and text,
For a usual name this value is greater than 2.
********************************************************************************************/
//bool InsertWhirlpoolHook() // jichi: 12/27/2014: Renamed to YU-RIS
static bool InsertYuris1Hook()
{
//IthBreak();
DWORD entry = Util::FindCallAndEntryBoth((DWORD)TextOutA, processStopAddress - processStartAddress, processStartAddress, 0xec83);
//GROWL_DWORD(entry);
if (!entry) {
ConsoleOutput("YU-RIS: function entry does not exist");
return false;
}
entry = Util::FindCallAndEntryRel(entry - 4, processStopAddress - processStartAddress, processStartAddress, 0xec83);
//GROWL_DWORD(entry);
if (!entry) {
ConsoleOutput("YU-RIS: function entry does not exist");
return false;
}
entry = Util::FindCallOrJmpRel(entry - 4,processStopAddress - processStartAddress - 0x10000, processStartAddress + 0x10000, false);
DWORD i,
t = 0;
//GROWL_DWORD(entry);
__try { // jichi 12/27/2014
for (i = entry - 4; i > entry - 0x100; i--)
if (::IsBadReadPtr((LPCVOID)i, 4)) { // jichi 12/27/2014: might raise in new YU-RIS, 4 = sizeof(DWORD)
ConsoleOutput("YU-RIS: do not have read permission");
return false;
} else if (*(WORD *)i == 0xc085) {
t = *(WORD *)(i + 2);
if ((t & 0xff) == 0x76) {
t = 4;
break;
} else if ((t & 0xffff) == 0x860f) {
t = 8;
break;
}
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
ConsoleOutput("YU-RIS: illegal access exception");
return false;
}
if (i == entry - 0x100) {
ConsoleOutput("YU-RIS: pattern not exist");
return false;
}
//GROWL_DWORD2(i,t);
HookParam hp;
hp.address = i + t;
hp.offset=get_reg(regs::edi);
hp.split = get_reg(regs::eax);
hp.type = USING_STRING|USING_SPLIT;
ConsoleOutput("INSERT YU-RIS");
//GROWL_DWORD(hp.address);
return NewHook(hp, "YU-RIS");
}
/** jichi 12/27/2014
*
* Sample game: [Whirlpool] [150217] <EFBFBD><EFBFBD><EFBFBD>
* Call site of TextOutA.
* 00441811 90 nop
* 00441812 90 nop
* 00441813 90 nop
* 00441814 8b4424 04 mov eax,dword ptr ss:[esp+0x4]
* 00441818 8b5424 08 mov edx,dword ptr ss:[esp+0x8]
* 0044181c 8b4c24 0c mov ecx,dword ptr ss:[esp+0xc]
* 00441820 57 push edi
* 00441821 56 push esi
* 00441822 55 push ebp
* 00441823 53 push ebx
* 00441824 83ec 50 sub esp,0x50
* 00441827 8bf9 mov edi,ecx
* 00441829 897c24 1c mov dword ptr ss:[esp+0x1c],edi
* 0044182d 8bda mov ebx,edx
* 0044182f 8be8 mov ebp,eax
* 00441831 8b349d 603f7b00 mov esi,dword ptr ds:[ebx*4+0x7b3f60]
* 00441838 807c24 74 01 cmp byte ptr ss:[esp+0x74],0x1
* 0044183d b9 00000000 mov ecx,0x0
* 00441842 0f94c1 sete cl
* 00441845 8d041b lea eax,dword ptr ds:[ebx+ebx]
* 00441848 03c3 add eax,ebx
* 0044184a 0fafc1 imul eax,ecx
* 0044184d 03c3 add eax,ebx
* 0044184f 894424 0c mov dword ptr ss:[esp+0xc],eax
* 00441853 897424 10 mov dword ptr ss:[esp+0x10],esi
* 00441857 8bc3 mov eax,ebx
* 00441859 8bd7 mov edx,edi
* 0044185b 0fbe4c24 70 movsx ecx,byte ptr ss:[esp+0x70]
* 00441860 e8 0c030000 call .00441b71
* 00441865 0fbec8 movsx ecx,al
* 00441868 83f9 ff cmp ecx,-0x1
* 0044186b 0f84 db020000 je .00441b4c
* 00441871 8bce mov ecx,esi
* 00441873 0fafc9 imul ecx,ecx
* 00441876 a1 64365d00 mov eax,dword ptr ds:[0x5d3664]
* 0044187b 8bf9 mov edi,ecx
* 0044187d c1ff 02 sar edi,0x2
* 00441880 c1ef 1d shr edi,0x1d
* 00441883 03f9 add edi,ecx
* 00441885 c1ff 03 sar edi,0x3
* 00441888 68 ff000000 push 0xff
* 0044188d 57 push edi
* 0044188e ff3485 70b48300 push dword ptr ds:[eax*4+0x83b470]
* 00441895 ff15 a4355d00 call dword ptr ds:[0x5d35a4] ; .00401c88
* 0044189b 83c4 0c add esp,0xc
* 0044189e 8b0d 64365d00 mov ecx,dword ptr ds:[0x5d3664]
* 004418a4 ff348d b4b48300 push dword ptr ds:[ecx*4+0x83b4b4]
* 004418ab ff348d d4b48300 push dword ptr ds:[ecx*4+0x83b4d4]
* 004418b2 ff15 54e05800 call dword ptr ds:[0x58e054] ; gdi32.selectobject
* 004418b8 a3 b0b48300 mov dword ptr ds:[0x83b4b0],eax
* 004418bd 8b0d 64365d00 mov ecx,dword ptr ds:[0x5d3664]
* 004418c3 ff348d 30b48300 push dword ptr ds:[ecx*4+0x83b430]
* 004418ca ff348d d4b48300 push dword ptr ds:[ecx*4+0x83b4d4]
* 004418d1 ff15 54e05800 call dword ptr ds:[0x58e054] ; gdi32.selectobject
* 004418d7 a3 2cb48300 mov dword ptr ds:[0x83b42c],eax
* 004418dc 8b3d 64365d00 mov edi,dword ptr ds:[0x5d3664]
* 004418e2 33c9 xor ecx,ecx
* 004418e4 880cbd f5b48300 mov byte ptr ds:[edi*4+0x83b4f5],cl
* 004418eb 880cbd f6b48300 mov byte ptr ds:[edi*4+0x83b4f6],cl
* 004418f2 0fb64d 00 movzx ecx,byte ptr ss:[ebp]
* 004418f6 0fb689 a0645b00 movzx ecx,byte ptr ds:[ecx+0x5b64a0]
* 004418fd 41 inc ecx
* 004418fe 0fbec9 movsx ecx,cl
* 00441901 51 push ecx
* 00441902 55 push ebp
* 00441903 33c9 xor ecx,ecx
* 00441905 51 push ecx
* 00441906 51 push ecx
* 00441907 ff34bd d4b48300 push dword ptr ds:[edi*4+0x83b4d4]
* 0044190e ff15 74e05800 call dword ptr ds:[0x58e074] ; gdi32.textouta, jichi: TextOutA here
* 00441914 0fb67d 00 movzx edi,byte ptr ss:[ebp]
* 00441918 0fb68f a0645b00 movzx ecx,byte ptr ds:[edi+0x5b64a0]
* 0044191f 41 inc ecx
* 00441920 0fbef9 movsx edi,cl
* 00441923 8b0d 64365d00 mov ecx,dword ptr ds:[0x5d3664]
* 00441929 03c9 add ecx,ecx
* 0044192b 8d8c09 f4b48300 lea ecx,dword ptr ds:[ecx+ecx+0x83b4f4]
*
* Runtime stack: The first dword after arguments on the stack seems to be good split value.
*/
static bool InsertYuris2Hook()
{
ULONG addr = MemDbg::findCallAddress((ULONG)::TextOutA, processStartAddress, processStopAddress);
if (!addr) {
ConsoleOutput("YU-RIS2: failed");
return false;
}
// BOOL TextOut(
// _In_ HDC hdc,
// _In_ int nXStart,
// _In_ int nYStart,
// _In_ LPCTSTR lpString,
// _In_ int cchString
// );
HookParam hp;
hp.address = addr;
hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; // disable context that will cause thread split
hp.offset = get_stack(3);
hp.split = get_stack(5);
ConsoleOutput("INSERT YU-RIS 2");
return NewHook(hp, "YU-RIS2");
}
bool InsertYuris4Hook()
{
/*
* Sample games:
* https://vndb.org/v6540
*/
bool found = false;
const BYTE pattern[] = {
0x52, // 52 push edx
0x68, 0x00, 0x42, 0x5C, 0x00, // 68 00425C00 push euphoria.exe+1C4200
0xFF, 0x15, 0x90, 0x44, 0x7E, 0x00, // FF 15 90447E00 call dword ptr [euphoria.exe+3E4490]
0x83, 0xC4, 0x0C, // 83 C4 0C add esp,0C
0xEB, 0x5F, // EB 5F jmp euphoria.exe+4F4C5
0xFF, 0x35, 0xA4, 0x19, 0x66, 0x00, // FF 35 A4196600 push [euphoria.exe+2619A4]
0x52 // 52 push edx
};
enum { addr_offset = 12 }; // distance to the beginning of the function, which is 0x83, 0xC4, 0x0C (add esp,0C)
for (auto addr : Util::SearchMemory(pattern, sizeof(pattern), PAGE_EXECUTE, processStartAddress, processStopAddress))
{
HookParam hp;
hp.address = addr+addr_offset;
hp.offset=get_reg(regs::edx);
hp.type = USING_STRING ;
ConsoleOutput("INSERT YU-RIS 4");
found|=NewHook(hp, "YU-RIS4");
}
if (!found) ConsoleOutput("YU-RIS 4: pattern not found");
return found;
}
bool InsertYuris5Hook()
{
/*
* Sample games:
* https://vndb.org/v4037
*/
const BYTE bytes[] = {
0x33, 0xD2, // xor edx,edx
0x88, 0x14, 0x0F, // mov [edi+ecx],dl
0xA1, XX4, // mov eax,[exe+2DE630]
0x8B, 0x78, 0x3C, // mov edi,[eax+3C]
0x8B, 0x58, 0x5C, // mov ebx,[eax+5C]
0x88, 0x14, 0x3B // mov [ebx+edi],dl
};
enum { addr_offset = 0 }; // distance to the beginning of the function, which is 0x55 (push ebp)
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr)
return false;
HookParam hp;
hp.address = addr + addr_offset;
hp.offset=get_reg(regs::ecx);
hp.type = USING_STRING | NO_CONTEXT;
ConsoleOutput("INSERT YU-RIS 5");
return NewHook(hp, "YU-RIS5");
}
static bool Yuris6Filter(LPVOID data, size_t *size, HookParam *)
{
auto text = reinterpret_cast<LPSTR>(data);
auto len = reinterpret_cast<size_t *>(size);
static std::string prevText;
if (prevText.length()==*len && prevText.find(text, 0, *len) != std::string::npos) // Check if the string is present in the previous one
return false;
prevText.assign(text, *len);
// ruby <手水舎/ちょうずや>
if (cpp_strnstr(text, "\x81\x83", *len)) { // \x81\x83 -> ''
StringFilterBetween(text, len, "\x81\x5E", 2, "\x81\x84", 2); // \x81\x5E -> '' , \x81\x84 -> ''
StringFilter(text, len, "\x81\x83", 2); // \x81\x83 -> ''
}
// ruby ≪美桜/姉さん≫
else if (cpp_strnstr(text, "\x81\xE1", *len)) { // \x81\xE1 -> '≪'
StringFilterBetween(text, len, "\x81\x5E", 2, "\x81\xE2", 2); // \x81\x5E -> '' , \x81\xE2 -> '≫'
StringFilter(text, len, "\x81\xE1", 2); // \x81\xE1 -> '≪'
}
CharReplacer(text, len, '=', '-');
StringCharReplacer(text, len, "\xEF\xF0", 2, ' ');
StringFilter(text, len, "\xEF\xF2", 2);
StringFilter(text, len, "\xEF\xF5", 2);
StringFilter(text, len, "\x81\x98", 2);
return true;
}
bool InsertYuris6Hook()
{
/*
* work with Windows 11
* Sample games:
* https://vndb.org/v40058
* https://vndb.org/v42883
* https://vndb.org/v44092
* https://vndb.org/v21171
* https://vndb.org/r46910
*/
const BYTE bytes[] = {
0xE9, XX4, // jmp oshitona01.exe+1B629 << hook here
0xBF, XX4, // mov edi,oshitona01.exe+24EEA0
0x8A, 0x17, // mov dl,[edi]
0x47, // inc edi
0x88, 0x16, // mov [esi],dl
0x46, // inc esi
0x84, 0xD2 // test dl,dl
};
enum { addr_offset = 0 };
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr)
return false;
HookParam hp;
hp.address = addr;
hp.offset=get_reg(regs::eax);
hp.index = 0x38;
hp.filter_fun = Yuris6Filter;
hp.type = USING_STRING | NO_CONTEXT | DATA_INDIRECT;
ConsoleOutput("INSERT YU-RIS 6");
return NewHook(hp, "YU-RIS6");
}
bool yuris7(){
//猫忍えくすはーとSPIN!
//夏空あすてりずむ
const BYTE bytes[] = {
0x57,0x56,0x55,0x53,0x83,0xec,0x10,
0x8b,0x5c,0x24,0x24,
0x8b,0x15,XX4,
0x8b,0x0c,0x9a,
0xc6,0x41,0x01,0x03,
0x8b,0xc3,
0xe8
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (!addr) return false;
HookParam hp;
hp.address = addr;
hp.offset=get_reg(regs::edx);
hp.type = USING_STRING;
hp.filter_fun=[](void* data, size_t* len, HookParam* hp){
static std::unordered_set<std::string>filtername;
auto text=std::string((char*)data,*len);
if(*len!=2)return false;
// if(text.find("\x81\x45")!=text.npos)return false;
// if(text.find("item")!=text.npos)return false;
// if(text==std::string("\x81\x48\x81\x48\x81\x48"))return false;
if(all_ascii((char*)data,*len))return false;
// if(filtername.find(text)!=filtername.end())return false;
// std::regex pattern("\x81\x79([^\x81\x7a]+)\x81\x7a");
// std::smatch match;
// if(std::regex_search(text, match, pattern)) {
// filtername.insert(match[1]);
// }
return true;
};
return NewHook(hp,"yuris7");
}
bool yuris8(){
//けもの道☆ガーリッシュスクエア LOVE+PLUS
//https://vndb.org/v36773
//codepage 950
const BYTE bytes[] = {
0x8b,XX,
0x8b,0x94,0x24,XX,0,0,0,
0x8b,0x8c,0x24,XX,0,0,0,
0xe8,XX4,
0xeb,XX,
0x8b,XX,
0x8b,0x94,0x24,XX,0,0,0,
0x8b,0x8c,0x24,XX,0,0,0,
0xe8,XX4,
};
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
if (!addr) return false;
HookParam hp;
hp.address = addr+sizeof(bytes)-5;
hp.type = USING_STRING;
hp.offset=get_reg(regs::ecx);
hp.filter_fun=[](void* data, size_t* len, HookParam* hp){
auto text=std::string((char*)data,*len);
if(std::all_of(text.begin(),text.end(),[](char c){return c=='1'||c=='2'||c=='E';}))return false;
return true;
};
return NewHook(hp,"yuris8");
}
bool InsertYurisHook()
{
bool ok = InsertYuris1Hook();
ok = InsertYuris2Hook() || ok;
ok = InsertYuris4Hook() || ok;
ok = InsertYuris5Hook() || ok;
ok = InsertYuris6Hook() || ok;
ok=yuris7()||ok;
ok=yuris8()||ok;
return ok;
}
bool Yuris::attach_function() {
return InsertYurisHook();
}