mirror of
https://github.com/HIllya51/LunaHook.git
synced 2024-12-25 12:44:13 +08:00
401 lines
14 KiB
C++
401 lines
14 KiB
C++
#include"Circus2.h"
|
||
#include"embed_util.h"
|
||
namespace{
|
||
bool filter(void* data, size_t* len, HookParam* hp){
|
||
if (strstr((char*)data,"@i")||strstr((char*)data,"@y"))return false;
|
||
//{てんきゅう/天穹}
|
||
if(strstr((char*)data,"\x81\x6f")&&strstr((char*)data,"\x81\x5e")&&strstr((char*)data,"\x81\x70")){
|
||
StringFilter((char*)data, len, "\x81\x70", 2);
|
||
StringFilterBetween((char*)data,len, "\x81\x6f", 2, "\x81\x5e", 2);
|
||
}
|
||
return true;
|
||
};
|
||
}
|
||
/**
|
||
* jichi 6/5/2014: Sample function from DC3 at 0x4201d0
|
||
* 004201ce cc int3
|
||
* 004201cf cc int3
|
||
* 004201d0 /$ 8b4c24 08 mov ecx,dword ptr ss:[esp+0x8]
|
||
* 004201d4 |. 8a01 mov al,byte ptr ds:[ecx]
|
||
* 004201d6 |. 84c0 test al,al
|
||
* 004201d8 |. 74 1c je short dc3.004201f6
|
||
* 004201da |. 8b5424 04 mov edx,dword ptr ss:[esp+0x4]
|
||
* 004201de |. 8bff mov edi,edi
|
||
* 004201e0 |> 3c 24 /cmp al,0x24
|
||
* 004201e2 |. 75 05 |jnz short dc3.004201e9
|
||
* 004201e4 |. 83c1 02 |add ecx,0x2
|
||
* 004201e7 |. eb 04 |jmp short dc3.004201ed
|
||
* 004201e9 |> 8802 |mov byte ptr ds:[edx],al
|
||
* 004201eb |. 42 |inc edx
|
||
* 004201ec |. 41 |inc ecx
|
||
* 004201ed |> 8a01 |mov al,byte ptr ds:[ecx]
|
||
* 004201ef |. 84c0 |test al,al
|
||
* 004201f1 |.^75 ed \jnz short dc3.004201e0
|
||
* 004201f3 |. 8802 mov byte ptr ds:[edx],al
|
||
* 004201f5 |. c3 retn
|
||
* 004201f6 |> 8b4424 04 mov eax,dword ptr ss:[esp+0x4]
|
||
* 004201fa |. c600 00 mov byte ptr ds:[eax],0x0
|
||
* 004201fd \. c3 retn
|
||
*/
|
||
bool InsertCircusHook2() // jichi 10/2/2013: Change return type to bool
|
||
{
|
||
for (DWORD i = processStartAddress + 0x1000; i < processStopAddress -4; i++)
|
||
if ((*(DWORD *)i & 0xffffff) == 0x75243c) { // cmp al, 24; je
|
||
if (DWORD j = SafeFindEnclosingAlignedFunction(i, 0x80)) {
|
||
HookParam hp;
|
||
hp.address = j;
|
||
hp.offset=get_stack(2);
|
||
//hp.filter_fun = CharNewLineFilter; // \n\s* is used to remove new line
|
||
hp.type = USING_STRING;
|
||
//GROWL_DWORD(hp.address); // jichi 6/5/2014: 0x4201d0 for DC3
|
||
|
||
//RegisterEngineType(ENGINE_CIRCUS);
|
||
return NewHook(hp, "Circus");
|
||
}
|
||
break;
|
||
}
|
||
//ConsoleOutput("Unknown CIRCUS engine.");
|
||
ConsoleOutput("CIRCUS: failed");
|
||
return false;
|
||
}
|
||
namespace{
|
||
bool c2(){
|
||
//D.C.III Dream Days~ダ・カーポIII~ドリームデイズ
|
||
auto entry=Util::FindImportEntry(processStartAddress,(DWORD)GetGlyphOutlineA);
|
||
DWORD funcaddr=0;
|
||
if(entry==0)return false;
|
||
for (auto addr : Util::SearchMemory(&entry, 4, PAGE_EXECUTE, processStartAddress, processStopAddress) ) {
|
||
DWORD _=0xCCCCCCCC;
|
||
funcaddr=reverseFindBytes((BYTE*)&_,4,addr-0x1000,addr);
|
||
//funcaddr=MemDbg::findEnclosingAlignedFunction(addr,0x1000);ConsoleOutput("%p",funcaddr);
|
||
}
|
||
if(funcaddr==0)return false;
|
||
funcaddr+=4;
|
||
HookParam hp;
|
||
hp.address = funcaddr;
|
||
hp.offset=get_stack(2);
|
||
hp.type = USING_STRING;//|EMBED_ABLE|EMBED_BEFORE_SIMPLE|EMBED_AFTER_NEW|EMBED_DYNA_SJIS;
|
||
//hp.hook_font=F_GetGlyphOutlineA;
|
||
//it will split a long to many lines
|
||
hp.filter_fun=filter;
|
||
|
||
return NewHook(hp, "Circus2");
|
||
}
|
||
}
|
||
|
||
namespace { // unnamed
|
||
|
||
// Skip leading tags such as @K and @c5
|
||
template <typename strT>
|
||
strT ltrim(strT s)
|
||
{
|
||
if (s && *s == '@')
|
||
while ((signed char)*++s > 0);
|
||
return s;
|
||
}
|
||
|
||
namespace ScenarioHook {
|
||
namespace Private {
|
||
|
||
DWORD nameReturnAddress_,
|
||
scenarioReturnAddress_;
|
||
|
||
/**
|
||
* Sample game: DC3, function: 0x4201d0
|
||
*
|
||
* IDA: sub_4201D0 proc near
|
||
* - arg_0 = dword ptr 4
|
||
* - arg_4 = dword ptr 8
|
||
*
|
||
* Observations:
|
||
* - arg1: LPVOID, pointed to unknown object
|
||
* - arg2: LPCSTR, the actual text
|
||
*
|
||
* Example runtime stack:
|
||
* 0012F15C 0040C208 RETURN to .0040C208 from .00420460
|
||
* 0012F160 0012F7CC ; jichi: unknown stck
|
||
* 0012F164 0012F174 ; jichi: text
|
||
* 0012F168 0012F6CC
|
||
* 0012F16C 0012F7CC
|
||
* 0012F170 0012F7CC
|
||
*/
|
||
void hookafter(hook_stack*s,void* data, size_t len){
|
||
|
||
auto newData =std::string((char*)data,len);
|
||
LPCSTR text = (LPCSTR)s->stack[2], // arg2
|
||
trimmedText = ltrim(text);
|
||
if (trimmedText != text)
|
||
newData.insert(0,std::string(text, trimmedText - text));
|
||
auto ss=new char[newData.size()+1];
|
||
strcpy(ss,newData.c_str());
|
||
s->stack[2] =(ULONG)ss; // reset arg2
|
||
|
||
}
|
||
bool hookBefore(hook_stack*s,void* data, size_t* len,uintptr_t*role)
|
||
{
|
||
|
||
LPCSTR text = (LPCSTR)s->stack[2], // arg2
|
||
trimmedText = ltrim(text);
|
||
if (!trimmedText || !*trimmedText)
|
||
return false;
|
||
auto retaddr = s->stack[0]; // retaddr
|
||
* role = retaddr == scenarioReturnAddress_ ? Engine::ScenarioRole :
|
||
retaddr == nameReturnAddress_ ? Engine::NameRole :
|
||
Engine::OtherRole;
|
||
//s->ebx? Engine::OtherRole : // other threads ebx is not zero
|
||
//// 004201e4 |. 83c1 02 |add ecx,0x2
|
||
//// 004201e7 |. eb 04 |jmp short dc3.004201ed
|
||
//*(BYTE *)(retaddr + 3) == 0xe9 // old name
|
||
//? Engine::NameRole : // retaddr+3 is jmp
|
||
//Engine::ScenarioRole;
|
||
|
||
std::string oldData = trimmedText;
|
||
strcpy((char*)data,oldData.c_str());
|
||
*len=oldData.size();
|
||
return true;
|
||
}
|
||
|
||
// Alternatively, using the following pattern bytes also works:
|
||
//
|
||
// 3c24750583c102eb0488024241
|
||
//
|
||
// 004201e0 |> 3c 24 /cmp al,0x24
|
||
// 004201e2 |. 75 05 |jnz short dc3.004201e9
|
||
// 004201e4 |. 83c1 02 |add ecx,0x2
|
||
// 004201e7 |. eb 04 |jmp short dc3.004201ed
|
||
// 004201e9 |> 8802 |mov byte ptr ds:[edx],al
|
||
// 004201eb |. 42 |inc edx
|
||
// 004201ec |. 41 |inc ecx
|
||
ULONG findFunctionAddress(ULONG startAddress, ULONG stopAddress) // find the function to hook
|
||
{
|
||
//return 0x4201d0; // DC3 function address
|
||
for (ULONG i = startAddress + 0x1000; i < stopAddress -4; i++)
|
||
// * 004201e0 |> 3c 24 /cmp al,0x24
|
||
// * 004201e2 |. 75 05 |jnz short dc3.004201e9
|
||
if ((*(ULONG *)i & 0xffffff) == 0x75243c) { // cmp al, 24; je
|
||
enum { range = 0x80 }; // the range is small, since it is a small function
|
||
if (ULONG addr = MemDbg::findEnclosingAlignedFunction(i, range))
|
||
return addr;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
} // namespace Private
|
||
|
||
/**
|
||
* jichi 6/5/2014: Sample function from DC3 at 0x4201d0
|
||
*
|
||
* Sample game: DC3PP
|
||
* 0042CE1E 68 E0F0B700 PUSH .00B7F0E0
|
||
* 0042CE23 A3 0C824800 MOV DWORD PTR DS:[0x48820C],EAX
|
||
* 0042CE28 E8 A352FFFF CALL .004220D0 ; jichi: name thread
|
||
* 0042CE2D C705 08024D00 01>MOV DWORD PTR DS:[0x4D0208],0x1
|
||
* 0042CE37 EB 52 JMP SHORT .0042CE8B
|
||
* 0042CE39 392D 08024D00 CMP DWORD PTR DS:[0x4D0208],EBP
|
||
* 0042CE3F 74 08 JE SHORT .0042CE49
|
||
* 0042CE41 392D 205BB900 CMP DWORD PTR DS:[0xB95B20],EBP
|
||
* 0042CE47 74 07 JE SHORT .0042CE50
|
||
* 0042CE49 C605 E0F0B700 00 MOV BYTE PTR DS:[0xB7F0E0],0x0
|
||
* 0042CE50 8D5424 40 LEA EDX,DWORD PTR SS:[ESP+0x40]
|
||
* 0042CE54 52 PUSH EDX
|
||
* 0042CE55 68 30B5BA00 PUSH .00BAB530
|
||
* 0042CE5A 892D 08024D00 MOV DWORD PTR DS:[0x4D0208],EBP
|
||
* 0042CE60 E8 6B52FFFF CALL .004220D0 ; jichi: scenario thread
|
||
* 0042CE65 C705 A0814800 FF>MOV DWORD PTR DS:[0x4881A0],-0x1
|
||
* 0042CE6F 892D 2C824800 MOV DWORD PTR DS:[0x48822C],EBP
|
||
*
|
||
* Sample game: 水夏弐律
|
||
*
|
||
* 004201ce cc int3
|
||
* 004201cf cc int3
|
||
* 004201d0 /$ 8b4c24 08 mov ecx,dword ptr ss:[esp+0x8]
|
||
* 004201d4 |. 8a01 mov al,byte ptr ds:[ecx]
|
||
* 004201d6 |. 84c0 test al,al
|
||
* 004201d8 |. 74 1c je short dc3.004201f6
|
||
* 004201da |. 8b5424 04 mov edx,dword ptr ss:[esp+0x4]
|
||
* 004201de |. 8bff mov edi,edi
|
||
* 004201e0 |> 3c 24 /cmp al,0x24
|
||
* 004201e2 |. 75 05 |jnz short dc3.004201e9
|
||
* 004201e4 |. 83c1 02 |add ecx,0x2
|
||
* 004201e7 |. eb 04 |jmp short dc3.004201ed
|
||
* 004201e9 |> 8802 |mov byte ptr ds:[edx],al
|
||
* 004201eb |. 42 |inc edx
|
||
* 004201ec |. 41 |inc ecx
|
||
* 004201ed |> 8a01 |mov al,byte ptr ds:[ecx]
|
||
* 004201ef |. 84c0 |test al,al
|
||
* 004201f1 |.^75 ed \jnz short dc3.004201e0
|
||
* 004201f3 |. 8802 mov byte ptr ds:[edx],al
|
||
* 004201f5 |. c3 retn
|
||
* 004201f6 |> 8b4424 04 mov eax,dword ptr ss:[esp+0x4]
|
||
* 004201fa |. c600 00 mov byte ptr ds:[eax],0x0
|
||
* 004201fd \. c3 retn
|
||
*
|
||
* Sample registers:
|
||
* EAX 0012F998
|
||
* ECX 000000DB
|
||
* EDX 00000059
|
||
* EBX 00000000 ; ebx is zero for name/scenario thread
|
||
* ESP 0012F96C
|
||
* EBP 00000003
|
||
* ESI 00000025
|
||
* EDI 000000DB
|
||
* EIP 022C0000
|
||
*
|
||
* EAX 0012F174
|
||
* ECX 0012F7CC
|
||
* EDX FDFBF80C
|
||
* EBX 0012F6CC
|
||
* ESP 0012F15C
|
||
* EBP 0012F5CC
|
||
* ESI 800000DB
|
||
* EDI 00000001
|
||
* EIP 00420460 .00420460
|
||
*
|
||
* EAX 0012F174
|
||
* ECX 0012F7CC
|
||
* EDX FDFBF7DF
|
||
* EBX 0012F6CC
|
||
* ESP 0012F15C
|
||
* EBP 0012F5CC
|
||
* ESI 00000108
|
||
* EDI 00000001
|
||
* EIP 00420460 .00420460
|
||
*
|
||
* 0042DC5D 52 PUSH EDX
|
||
* 0042DC5E 68 E038AC00 PUSH .00AC38E0 ; ASCII "Ami"
|
||
* 0042DC63 E8 F827FFFF CALL .00420460 ; jichi: name thread
|
||
* 0042DC68 83C4 08 ADD ESP,0x8
|
||
* 0042DC6B E9 48000000 JMP .0042DCB8
|
||
* 0042DC70 83FD 58 CMP EBP,0x58
|
||
* 0042DC73 74 07 JE SHORT .0042DC7C
|
||
* 0042DC75 C605 E038AC00 00 MOV BYTE PTR DS:[0xAC38E0],0x0
|
||
* 0042DC7C 8D4424 20 LEA EAX,DWORD PTR SS:[ESP+0x20]
|
||
* 0042DC80 50 PUSH EAX
|
||
* 0042DC81 68 0808AF00 PUSH .00AF0808
|
||
* 0042DC86 E8 D527FFFF CALL .00420460 ; jichi: scenario thread
|
||
* 0042DC8B 83C4 08 ADD ESP,0x8
|
||
* 0042DC8E 33C0 XOR EAX,EAX
|
||
* 0042DC90 C705 D0DF4700 FF>MOV DWORD PTR DS:[0x47DFD0],-0x1
|
||
* 0042DC9A A3 0CE04700 MOV DWORD PTR DS:[0x47E00C],EAX
|
||
* 0042DC9F A3 940EB200 MOV DWORD PTR DS:[0xB20E94],EAX
|
||
* 0042DCA4 A3 2C65AC00 MOV DWORD PTR DS:[0xAC652C],EAX
|
||
* 0042DCA9 C705 50F9AC00 59>MOV DWORD PTR DS:[0xACF950],0x59
|
||
* 0042DCB3 A3 3C70AE00 MOV DWORD PTR DS:[0xAE703C],EAX
|
||
*/
|
||
bool attach(ULONG startAddress, ULONG stopAddress)
|
||
{
|
||
ULONG addr = Private::findFunctionAddress(startAddress, stopAddress);
|
||
if (!addr)
|
||
return false;
|
||
// Find the nearest two callers (distance within 100)
|
||
ULONG lastCall = 0;
|
||
auto fun = [&lastCall](ULONG call) -> bool {
|
||
// scenario: 0x42b78c
|
||
// name: 0x42b754
|
||
if (call - lastCall < 100) {
|
||
Private::scenarioReturnAddress_ = call + 5;
|
||
Private::nameReturnAddress_ = lastCall + 5;
|
||
return false; // found target
|
||
}
|
||
lastCall = call;
|
||
return true; // replace all functions
|
||
};
|
||
MemDbg::iterNearCallAddress(fun, addr, startAddress, stopAddress);
|
||
if (!Private::scenarioReturnAddress_ && lastCall) {
|
||
Private::scenarioReturnAddress_ = lastCall + 5;
|
||
}
|
||
HookParam hp;
|
||
hp.address=addr;
|
||
hp.filter_fun=filter;
|
||
hp.hook_before=Private::hookBefore;
|
||
hp.hook_after=Private::hookafter;
|
||
hp.hook_font=F_GetGlyphOutlineA;
|
||
hp.type=USING_STRING|EMBED_ABLE|NO_CONTEXT|EMBED_DYNA_SJIS;
|
||
|
||
|
||
return NewHook(hp,"EmbedCircus");
|
||
}
|
||
|
||
} // namespace ScenarioHook
|
||
|
||
} // unnamed namespace
|
||
bool InsertCircusHook3()
|
||
{
|
||
/*
|
||
* Sample games:
|
||
* https://vndb.org/v20218
|
||
*/
|
||
const BYTE bytes[] = {
|
||
0xCC, // int 3
|
||
0x81, 0xEC, XX4, // sub esp,000004E0 << hook here
|
||
0xA1, XX4, // mov eax,[DSIF.EXE+AD288]
|
||
0x33, 0xC4, // xor eax,esp
|
||
0x89, 0x84, 0x24, XX4, // mov [esp+000004DC],eax
|
||
0x8B, 0x84, 0x24, XX4, // mov eax,[esp+000004E4]
|
||
0x53, // push ebx
|
||
0x55, // push ebp
|
||
0x56, // push esi
|
||
0x8B, 0xB4, 0x24, XX4 // mov esi,[esp+000004F4]
|
||
};
|
||
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 + 1;
|
||
hp.offset=get_reg(regs::esi);
|
||
hp.split = get_reg(regs::ecx);
|
||
hp.type = USING_STRING | USING_SPLIT;
|
||
return NewHook(hp, "Circus3");
|
||
}
|
||
|
||
bool CircusFilter(LPVOID data, size_t *size, HookParam *)
|
||
{
|
||
auto text = reinterpret_cast<LPSTR>(data);
|
||
auto len = reinterpret_cast<size_t *>(size);
|
||
|
||
//ConsoleOutput("debug:Circus: -%.*s-", *len, text);
|
||
if (*len <= 1 || cpp_strnstr(text, "\\", *len) || (text[0] == '&' && text[1] == 'n'))
|
||
return false;
|
||
|
||
CharReplacer(text, len, '\n', ' ');
|
||
|
||
return true;
|
||
}
|
||
|
||
bool InsertCircusHook4()
|
||
{
|
||
/*
|
||
* Sample games:
|
||
* https://vndb.org/r46909
|
||
*/
|
||
const BYTE bytes[] = {
|
||
0x83, 0xF8, 0xFF, // cmp eax,-01 << hook here
|
||
0x0F, 0x84, XX4, // je DST.exe+1BCF0
|
||
0x8B, 0x0D, XX4 // mov ecx,[DST.exe+A41F0]
|
||
};
|
||
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::edx);
|
||
hp.split =get_stack(4); //arg4
|
||
hp.padding = 0x40;
|
||
hp.type = USING_STRING | USING_SPLIT;
|
||
hp.filter_fun = CircusFilter;
|
||
|
||
return NewHook(hp, "Circus4");
|
||
}
|
||
bool Circus2::attach_function() {
|
||
bool ch2=InsertCircusHook2();
|
||
bool _1= ch2||c2();
|
||
bool _2=ch2|| InsertCircusHook3() || InsertCircusHook4();
|
||
bool embed=ScenarioHook::attach(processStartAddress,processStopAddress);
|
||
return _1||embed||_2;
|
||
}
|