void *(*old_getClassPassive)(void *instance);
void *getClassPassive(void *instance) {
LOGD("Return getClassPassive");
return old_getClassPassive(instance);
}
HOOK_LIB("libil2cpp.so", "0x1546698", getClassPassive, old_getClassPassive);
auto BattleUserServantDataClass = LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"));
BNM::HOOK(BattleUserServantDataClass.GetMethodByName(OBFUSCATE_BNM("getClassPassive")), getClassPassive, old_getClassPassive);
void LogMemory(uintptr_t address, int numInstructions)
{
int bytesToRead = numInstructions * 4;
std::vector<uintptr_t> byteVec = {};
LOGD("========== START ==========");
LOGD("");
LOGD("> getClassPassive <");
for (int i = 0; i <= bytesToRead; i++)
{
if (i > 0 && (i % 4) == 0)
{
char bytes[128];
sprintf(bytes, "0x%01x\t0x%01x\t0x%01x\t0x%01x", *(char*)byteVec[0], *(char*)byteVec[1], *(char*)byteVec[2], *(char*)byteVec[3]);
LOGD("Address: %p | Bytes: %s", (void*)(address-4), bytes);
byteVec.clear();
}
if (i == 8)
{
LOGD("");
LOGD("> GetSelfSkillArray < ");
}
byteVec.push_back(address);
address++;
}
LOGD("");
LOGD("=========== END ===========");
}
LOGD("BEFORE");
LogMemory(getclassPassiveAddr, 8);
install_hook_getClassPassive((void*)getclassPassiveAddr);
LOGD("AFTER");
LogMemory(getclassPassiveAddr, 8);
adrp x17, #0x682f000
add x17, x17, #0x8bc
br x17
adrp x17, #0x682f000
add x17, x17, #0x8bc
br x17
Tried this with frida and they're only using 7 bytes, don't know if it's working thoJust had a look in IDA at this function, it looks like its too small, check the spoiler for the screenshot
getClassPassive() is at 0x1546698, but its only 8 bytes (00 D4 40 F9 C0 03 5F D6]), the next function starts right after it and is GetSelfSkillArray() at 0x15466A0.
Hooking usually requires 8 bytes ALTEAST, it can also require more, which would mean getClassPassive is to small to fit the hook.
I logged the bytes starting from getClassPassive(), using a very quickly made function and using Dobby Hook:
Then logging the bytes before and after the hook:C++:void LogMemory(uintptr_t address, int numInstructions) { int bytesToRead = numInstructions * 4; std::vector<uintptr_t> byteVec = {}; LOGD("========== START =========="); LOGD(""); LOGD("> getClassPassive <"); for (int i = 0; i <= bytesToRead; i++) { if (i > 0 && (i % 4) == 0) { char bytes[128]; sprintf(bytes, "0x%01x\t0x%01x\t0x%01x\t0x%01x", *(char*)byteVec[0], *(char*)byteVec[1], *(char*)byteVec[2], *(char*)byteVec[3]); LOGD("Address: %p | Bytes: %s", (void*)(address-4), bytes); byteVec.clear(); } if (i == 8) { LOGD(""); LOGD("> GetSelfSkillArray < "); } byteVec.push_back(address); address++; } LOGD(""); LOGD("=========== END ==========="); }
The first time it logs the same bytes shown in IDA:C++:LOGD("BEFORE"); LogMemory(getclassPassiveAddr, 8); install_hook_getClassPassive((void*)getclassPassiveAddr); LOGD("AFTER"); LogMemory(getclassPassiveAddr, 8);
After the hook is done, check the bytes getClassPassive() and the first instructions bytes for the GetSelfSkillArray(), they are differentFGO_TEST: > getClassPassive <
FGO_TEST: Address: 0x7666c9d698 | Bytes: 0x00 0xD4 0x40 0xf9
FGO_TEST: Address: 0x7666c9d69c | Bytes: 0xC0 0x03 0x5f 0xd6
FGO_TEST:
FGO_TEST: > GetSelfSkillArray <
FGO_TEST: Address: 0x7666c9d6a0 | Bytes: 0xf4 0x4f 0xbe 0xa9
FGO_TEST: Address: 0x7666c9d6a4 | Bytes: 0xfd 0x7b 0x1 0xa9
FGO_TEST: Address: 0x7666c9d6a8 | Bytes: 0xfd 0x43 0x0 0x91
FGO_TEST: Address: 0x7666c9d6ac | Bytes: 0xf3 0x3 0x0 0xaa
FGO_TEST: Address: 0x7666c9d6b0 | Bytes: 0xbd 0xfe 0xff 0x97
FGO_TEST: Address: 0x7666c9d6b4 | Bytes: 0x68 0x2 0x40 0xf9
Dobby is writing 12 bytes to getClassPassive() but getClassPassive only has 8 bytes or space, so its bleeding into the next functionFGO_TEST: > getClassPassive <
FGO_TEST: Address: 0x7666c9d698 | Bytes: 0x71 0x41 0x3 0xf0
FGO_TEST: Address: 0x7666c9d69c | Bytes: 0x31 0xf2 0x22 0x91
FGO_TEST:
FGO_TEST: > GetSelfSkillArray <
FGO_TEST: Address: 0x7666c9d6a0 | Bytes: 0x20 0x2 0x1f 0xd6
FGO_TEST: Address: 0x7666c9d6a4 | Bytes: 0xfd 0x7b 0x1 0xa9
FGO_TEST: Address: 0x7666c9d6a8 | Bytes: 0xfd 0x43 0x0 0x91
FGO_TEST: Address: 0x7666c9d6ac | Bytes: 0xf3 0x3 0x0 0xaa
FGO_TEST: Address: 0x7666c9d6b0 | Bytes: 0xbd 0xfe 0xff 0x97
FGO_TEST: Address: 0x7666c9d6b4 | Bytes: 0x68 0x2 0x40 0xf9
Dobby is trying to write these 12 bytes [71 41 03 f0 31 f2 22 91 20 02 1f d6] which in arm64 assembly is:
This will cause a crash in the affected functions since getClassPassive now look like:Code:adrp x17, #0x682f000 add x17, x17, #0x8bc br x17
and the first instruction of GetSelfSkillArray now has the actual jump instruction to what dobby intended to be your hook code:Code:adrp x17, #0x682f000 add x17, x17, #0x8bc
Code:br x17
Yeah, I would imagine its hooking framework dependent based on their implementation?,I wonder if frida detects that this function length is 8 bytes and use hooking method which require less bytes
adrp x17, #0x682f000
add x17, x17, #0x8bc
Bx x17
Frida writes:
Code:
[50 d5 83 90 00 02 1f d6]
adrp x16, #0xffffffff07aa8000
br x16
Why not use the field offset of the function instead of hooking the function itselfThank you legends for finding what the issue is! I don't suppose this will be the last 8 byte function that we'll encounter, so what are some methods to circumvent this problem? Any frameworks/libs that we know of that can have the option to squeeze the hook within 8 bytes?
Not that I know of personally, Frida appears to work as @mIsmanXP said above, although frida is amazing for testing to actually make a packaged mod apk its not the best, although it is possible.Any frameworks/libs that we know of that can have the option to squeeze the hook within 8 bytes?
monoArray<int> *customArray = nullptr;
void initCustomArrays() {
customArray = monoArray<int>::Create(3);
customArray ->Update(0, 100);
customArray ->Update(1, 100);
customArray ->Update(2, 100);
}
monoArray<int> *(*old_someMethodToReturnAnArray)(void *instance);
monoArray<int> *someMethodToReturnAnArray(void *instance) {
if (someCondition) {
return customArray;
}
return old_someMethodToReturnAnArray(instance);
}
BNM::Field<monoArray<int>*> BattleUserServantData_classPassive__Field{};
int (*old_getBattleSvtId)(void *instance);
int getBattleSvtId(void *instance) {
bool isMyServant;
// Some code around here to determine which are the characters/servants I'm looking for
if (isMyServant) {
monoArray<int> *classPassive = *BattleUserServantData_classPassive__Field[instance].GetPointer();
for (int i = 0; i < classPassive->GetCapacity(); i++){
LOGD("The Servants's classPassive: i: %d, v: %d", i, *classPassive->At(i));
}
monoArray<int> *myOwnArray = BNM::LoadClass(BNM::LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"))).NewArray<int>(3);
for (int i = 0; i < myOwnArray->GetCapacity(); i++) {
myOwnArray->Update(i, 2188000);
}
BattleUserServantData_classPassive__Field[instance].Set(myOwnArray);
monoArray<int> *classPassiveVerify = *BattleUserServantData_classPassive__Field[instance].GetPointer();
for (int i = 0; i < classPassiveVerify->GetCapacity(); i++){
LOGD("The Servants's classPassive UPDATED: i: %d, v: %d", i, *classPassiveVerify->At(i));
}
}
return old_getBattleSvtId(instance);
}
void OnLoaded_Hooks() {
auto BattleUserServantDataClass = LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"));
BattleUserServantData_classPassive__Field = BattleUserServantDataClass.GetFieldByName(OBFUSCATE_BNM("classPassive"));
}
Could you provide crash log when you crash in battle maybe it's causing null reference somewhereI haven't had much luck in successfully creating a new monoArray and replacing the old one with the new one within an instance.
I have no problem in replacing/updating individual elements of the native int array, however the size of the array is predetermined by who the servant/character is.
So if I wanted to add a few more passives then I'll need a bigger array.
I've had luck in using BNM's method of creating a new array, and have a function that suppose to return the monoArray to return the array that I've made. e.g.
C++:monoArray<int> *customArray = nullptr; void initCustomArrays() { customArray = monoArray<int>::Create(3); customArray ->Update(0, 100); customArray ->Update(1, 100); customArray ->Update(2, 100); } monoArray<int> *(*old_someMethodToReturnAnArray)(void *instance); monoArray<int> *someMethodToReturnAnArray(void *instance) { if (someCondition) { return customArray; } return old_someMethodToReturnAnArray(instance); }
The code above works fine and I haven't had any crashes related to it.
However, if I were to try something like this in the BattleUserServantData:
C++:BNM::Field<monoArray<int>*> BattleUserServantData_classPassive__Field{}; int (*old_getBattleSvtId)(void *instance); int getBattleSvtId(void *instance) { bool isMyServant; // Some code around here to determine which are the characters/servants I'm looking for if (isMyServant) { monoArray<int> *classPassive = *BattleUserServantData_classPassive__Field[instance].GetPointer(); for (int i = 0; i < classPassive->GetCapacity(); i++){ LOGD("The Servants's classPassive: i: %d, v: %d", i, *classPassive->At(i)); } monoArray<int> *myOwnArray = BNM::LoadClass(BNM::LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"))).NewArray<int>(3); for (int i = 0; i < myOwnArray->GetCapacity(); i++) { myOwnArray->Update(i, 2188000); } BattleUserServantData_classPassive__Field[instance].Set(myOwnArray); monoArray<int> *classPassiveVerify = *BattleUserServantData_classPassive__Field[instance].GetPointer(); for (int i = 0; i < classPassiveVerify->GetCapacity(); i++){ LOGD("The Servants's classPassive UPDATED: i: %d, v: %d", i, *classPassiveVerify->At(i)); } } return old_getBattleSvtId(instance); } void OnLoaded_Hooks() { auto BattleUserServantDataClass = LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData")); BattleUserServantData_classPassive__Field = BattleUserServantDataClass.GetFieldByName(OBFUSCATE_BNM("classPassive")); }
Through classPassive, I'm able to see what was there before, and through classPassiveVerify what is there after I've updated the field.
However the game would crash before fully loading in the battle.
I'm currently assuming it is something to do with Unity's garbage collection that is causing it to crash perhaps?
Ok just had a look, here is the code I came up with:I haven't had much luck in successfully creating a new monoArray and replacing the old one with the new one within an instance.
I have no problem in replacing/updating individual elements of the native int array, however the size of the array is predetermined by who the servant/character is.
So if I wanted to add a few more passives then I'll need a bigger array.
I've had luck in using BNM's method of creating a new array, and have a function that suppose to return the monoArray to return the array that I've made. e.g.
The code above works fine and I haven't had any crashes related to it.C++:monoArray<int> *customArray = nullptr; void initCustomArrays() { customArray = monoArray<int>::Create(3); customArray ->Update(0, 100); customArray ->Update(1, 100); customArray ->Update(2, 100); } monoArray<int> *(*old_someMethodToReturnAnArray)(void *instance); monoArray<int> *someMethodToReturnAnArray(void *instance) { if (someCondition) { return customArray; } return old_someMethodToReturnAnArray(instance); }
However, if I were to try something like this in the BattleUserServantData:
C++:BNM::Field<monoArray<int>*> BattleUserServantData_classPassive__Field{}; int (*old_getBattleSvtId)(void *instance); int getBattleSvtId(void *instance) { bool isMyServant; // Some code around here to determine which are the characters/servants I'm looking for if (isMyServant) { monoArray<int> *classPassive = *BattleUserServantData_classPassive__Field[instance].GetPointer(); for (int i = 0; i < classPassive->GetCapacity(); i++){ LOGD("The Servants's classPassive: i: %d, v: %d", i, *classPassive->At(i)); } monoArray<int> *myOwnArray = BNM::LoadClass(BNM::LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"))).NewArray<int>(3); for (int i = 0; i < myOwnArray->GetCapacity(); i++) { myOwnArray->Update(i, 2188000); } BattleUserServantData_classPassive__Field[instance].Set(myOwnArray); monoArray<int> *classPassiveVerify = *BattleUserServantData_classPassive__Field[instance].GetPointer(); for (int i = 0; i < classPassiveVerify->GetCapacity(); i++){ LOGD("The Servants's classPassive UPDATED: i: %d, v: %d", i, *classPassiveVerify->At(i)); } } return old_getBattleSvtId(instance); } void OnLoaded_Hooks() { auto BattleUserServantDataClass = LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData")); BattleUserServantData_classPassive__Field = BattleUserServantDataClass.GetFieldByName(OBFUSCATE_BNM("classPassive")); }
Through classPassive, I'm able to see what was there before, and through classPassiveVerify what is there after I've updated the field.
However the game would crash before fully loading in the battle.
I'm currently assuming it is something to do with Unity's garbage collection that is causing it to crash perhaps?
install_hook_name(getBattleSvtId, int, BattleUserServantData_o* pThis)
{
bool isMyServant = true; // force it because I dont have the logic
// Some code around here to determine which are the characters/servants I'm looking for
if (isMyServant) {
monoArray<int> *passives = (monoArray<int>*)pThis->fields.classPassive;
for(int i = 0; i < passives->GetCapacity(); i++) {
if (*passives->At(i) == 2188000) {
LOGD("Char already has passive added!");
return orig_getBattleSvtId(pThis);
}
}
for(int i = 0; i < passives->GetCapacity(); i++)
LOGD("[BEFORE] The Servants's classPassive: i: %d, v: %d", i, *passives->At(i));
std::vector<int> newPassives = {};
for(int i = 0; i < passives->GetCapacity(); i++)
newPassives.push_back(passives->At(i));
newPassives.push_back(2188000);
pThis->fields.classPassive = (System_Int32_array*)monoArray<int>().Create(newPassives);
monoArray<int> *passivesVerify = (monoArray<int>*)pThis->fields.classPassive;
for(int i = 0; i < passivesVerify->GetCapacity(); i++)
LOGD("[AFTER] The Servants's classPassive: i: %d, v: %d", i, *passivesVerify->At(i));
}
return orig_getBattleSvtId(pThis);
}
void* instance
, void* is just a raw address, il2cppdumper actually generates an il2cpp.h which can be massively useful, although I dont see anyone ever make use of it. including that header from FGO il2cppdumper you have access to all the games classes/fields etc as structs hence I changed it to BattleUserServantData_o* pThis
which then allows me to do this:monoArray<int> *passives = (monoArray<int>*)pThis->fields.classPassive;
pThis->fields.classPassive
is of type struct System_Int32_array *
hence the cast to BNM's type for easy use.FGO_TEST: [BEFORE] The Servants's classPassive: i: 0, v: 34550
FGO_TEST: [BEFORE] The Servants's classPassive: i: 1, v: 88350
FGO_TEST: [AFTER] The Servants's classPassive: i: 0, v: 34550
FGO_TEST: [AFTER] The Servants's classPassive: i: 1, v: 88350
FGO_TEST: [AFTER] The Servants's classPassive: i: 2, v: 2188000
FGO_TEST: Char already has passive added!
FGO_TEST: [AFTER] The Servants's classPassive: i: 0, v: 2188000
FGO_TEST: Char already has passive added!
--------- beginning of crash
FGO_TEST: Char already has passive added!
and the backtrace for the crash doesnt mention my ELF binary at all, so it makes me wonder if the crash is potentially related to the last last log for the single passive being added to what I assume is the enemy is then confusing the game state later on in il2cpp.so since the enemy maybe isnt supposed to have this passive? or any passives at all? No idea since I never actually played FGO. Anyway here is the crashlog:backtrace:
#00 pc 0000000000cbc91c libil2cpp.so
#01 pc 0000000000cbc850 libil2cpp.so
#02 pc 0000000000cbc4b8 libil2cpp.so
#03 pc 0000000000cbc6f8 libil2cpp.so
#04 pc 0000000000cbc4b8 libil2cpp.so
#05 pc 0000000000cbc75c libil2cpp.so
#06 pc 0000000000cbc4b8 libil2cpp.so
#07 pc 0000000000cbc6f8 libil2cpp.so
#08 pc 0000000000cbc4b8 libil2cpp.so
#09 pc 0000000000cbc75c libil2cpp.so
#10 pc 0000000000cbc4b8 libil2cpp.so
#11 pc 0000000000cbc6f8 libil2cpp.so
#12 pc 0000000000cbc4b8 libil2cpp.so
#13 pc 0000000000cbc75c libil2cpp.so
#14 pc 0000000000cbc4b8 libil2cpp.so
#15 pc 0000000000cbcf48 libil2cpp.so
#16 pc 00000000002a0258 libunity.so
#17 pc 00000000002adf00 libunity.so
#18 pc 00000000002ad194 libunity.so
#19 pc 00000000002adaf4 libunity.so
#20 pc 00000000002a15b4 libunity.so
#21 pc 00000000002a15e8 libunity.so
#22 pc 00000000002a182c libunity.so
#23 pc 00000000003bf37c libunity.so
#24 pc 00000000003d51c4 libunity.so
#25 pc 00000000000448fc base.odex
We use cookies to personalize content and ads, to provide social media features and to analyse our traffic. We also share necessary information with our advertising and analytics partners to optimize your experience on our site.
Learn more about cookies
We use cookies to personalize content and ads, to provide social media features and to analyse our traffic. We also share necessary information with our advertising and analytics partners to optimize your experience on our site.
Learn more about cookies