Open pacioc193 opened 2 years ago
It depends of the game you're wanting to bypass it. In this repository, you are not really bypassing XignCode, you are avoiding it to be added to the game (so the heartbeat is not send anymore, causing a disconnection in the following 10 minutes if you don't replicate the behaviour of XignCode). How to find the jmp is rather easy, start the game with your debugger (I use Cheat Engine), then step over until the process stops (since you are attaching a debugger, XignCode will detect it and stop your game), once found, go in the call and continue the same process, you can also use a tool like Ghidra which will help you to do that quicker thanks to the C decompilation.
If you really want to bypass XignCode, you have (at least, for NosTale), two options:
The first one will require some more advanced reverse engineer skills, while the second only require to understand how your game works. An example with NosTale is described in this issue: https://github.com/ApourtArtt/NtHkBypass/issues/1#issuecomment-1186504005
I'm searching to reproduce it but is impossible to start application with debugger. Themida will stop the process. At start the application load x3.xem and this will cut the possibility to do anything else...
Done, i've found the loading of dll xigncode, now executable bypass it. Now the issue is heartbeat... Do you have more information about it?
Sorry for the late response - I was planning to share my thoughts but I prefered to actually experiment and try to simulate the heartbeat. My client is currently running and so far, no disconnection due to heatbeat, so I guess it works.
I opened my game client (NostaleClientX.exe, in my case) in Ghidra and looked for when the client opens x3.xem. I saw that it was starting a function named (LPCSTR)0x01 - as you can see on the screenshot above.
My first idea was to make a DLL proxy between the client and x3.xem (code later), so I am able to run my code through my DLL before x3.xem is actually used.
The final result I get is:
kernel32.LoadLibraryA_0(pCVar1);
then
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
start();
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
fprintf(stdout, "This condition is triggered");
fflush(stdout);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
auto func = kernel32.GetProcAddress_0(DAT_0087610c,(LPCSTR)0x1);
(*func)();
then, my first exported function (Note: it is REALLY important to know that the first exported function will be callable through the name (LPCSTR)0x1, the second by (LPCSTR)0x2, etc.) is:
extern "C" __declspec(dllexport) void __declspec(naked) FirstExportedFunc()
{
__asm JMP OrigOrdinal1;
}
And from there, you understand that the unconditional jmp is there to call the original first exported function from the original x3.xem.
Here is the final code:
#include <Windows.h>
#include <stdio.h>
#include <thread>
static bool started{ false };
FARPROC OrigOrdinal1 = NULL;
FARPROC OrigOrdinal2 = NULL;
static void start()
{
if (started)
return;
started = true;
AllocConsole();
freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
const HMODULE h_lib_module = LoadLibraryA("XIGNCODE\\orig_x3.xem");
if (h_lib_module == nullptr)
{
fprintf(stdout, "ERROR!!!!!!!!");
return;
}
OrigOrdinal1 = GetProcAddress(h_lib_module, (LPCSTR)0x1);
if (OrigOrdinal1 == NULL)
fprintf(stdout, "ERROR Orig1");
OrigOrdinal2 = GetProcAddress(h_lib_module, (LPCSTR)0x2);
if (OrigOrdinal1 == NULL)
fprintf(stdout, "ERROR Orig2");
std::thread* th = new std::thread([]()
{
while (true)
{
fprintf(stdout, "From Thread\n");
Sleep(5000);
}
});
}
extern "C" __declspec(dllexport) void __declspec(naked) BLABLABLA()
{
__asm JMP OrigOrdinal1;
}
extern "C" __declspec(dllexport) void __declspec(naked) X()
{
__asm JMP OrigOrdinal2;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
start();
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
fprintf(stdout, "DLL_PROCESS_ATTACH\n");
fflush(stdout);
case DLL_THREAD_ATTACH:
fprintf(stdout, "DLL_THREAD_ATTACH\n");
fflush(stdout);
case DLL_THREAD_DETACH:
fprintf(stdout, "DLL_THREAD_DETACH\n");
fflush(stdout);
case DLL_PROCESS_DETACH:
fprintf(stdout, "DLL_PROCESS_DETACH\n");
fflush(stdout);
break;
}
return TRUE;
}
And... Voilà!
Obviously, the code itself does not do anything, and is not really clean, but you can just go on with this snippet.
First of all thanks you very much for the detail explanation of how you create the bypass. It’s a very good approach of reverse.
I will perform some test this weekend with the suggested info.
From my fast understand :
Is this correct?
1) You are correct, but it is not really an "empty dll", it is your loader! 2) You are correct 3) I am not sure I understood this point
I already released some bot for this game but I never include a loader. Always read and write memory using a special software in Java. It was never detected. Now I want to find new variable for this boy and I need to run cheat engine… I don’t need to inject, I need to run cheat engine. I will try by the way
Hi,
just some update. My binary that load x3.xem use an hash check to be sure no injection happen to the main software. I won’t be able to create a proxy server between dll and game. I’m working on heartbeat . I find that software use 3 channels socket where receive some message ( 1 from login server, 1 from game server and 1 seems from xigncode ) After some minute in game it check the queue of the xigncode and disconnect the client.
I want try to bypass the 3rd queue or send empty package from my bot software. Do you think is possible?
I've a game that run xigncode 3 as software protection. I need to bypass it. Could you explain me how to find the correct jmp to replace?
Grazie