First, the ASM that we're going to bother with:
- Code: Select all
00402D50 | 8BC1 | MOV EAX, ECX |
00402D52 | 56 | PUSH ESI |
00402D53 | 8B7424 08 | MOV ESI, DWORD PTR [ESP+8] |
00402D57 | 8D48 78 | LEA ECX, DWORD PTR [EAX+78] | ;<-----breakpointed
00402D5A | 8B40 74 | MOV EAX, DWORD PTR [EAX+74] |
00402D5D | 50 | PUSH EAX |
00402D5E | E8 BD220000 | CALL 00405020 |
00402D63 | 5E | POP ESI |
00402D64 | C2 0400 | RETN 4 |
- The black box is the address. Obviously, it's important that you breakpoint the correct address.
- The red box is the type of breakpoint to apply. I chose hardware (causes no change in RAM, therefore CRC checks don't pick them up... I always use hardware BPs, if possible) and on Execute because this is a line of ASM that will be executed. If you were breakpointing for a change in a certain value--i.e. breakpoint whenever the value at address 0x65EB80 is changed--you'd use On Write, etc.
- The blue box is telling the debugger which function to call before or after the line of ASM is executed. Prolog, Callback, and Epilog are fully documented in the MHS Help File AND in the third reply to this thread, where L. Spiro goes a little bit more into depth.
If you want this breakpoint to be handled by a script function, be sure to choose Script Function from the dropdown, then set a unique Parm number. The Parm number is important as it tells the debugger WHICH script function is going to be handling this particular breakpoint. If you set Parm to 1, you'll need to write a function named On_BP_1(..); if you set Parm to 27, you'll need to write a function named On_BP_27(..). Process-specific breakpoint handlers--i.e. being able to write a function named On_BP_TROSE_EXE_1(..) and have it called only when TRose.exe is the current process--are coming in the 4.0.0.7 release of MHS, according to L. Spiro.
Now that that's explained, how do we go about handling the breakpoint? Simple! Open up your Script Editor and create a new script. Before you do anything, press CTRL+S to save it; after it's saved, click on the File menu and select Add to Scripts (or, alternatively, just press CTRL+D). This will make 'whatever_you_named_it.lss' show up in the Scripts toolwindow and add the script to the System, allowing it to execute and be compiled.
First things first, add a function like so: void On_BP_1(LPVOID lpvAddress, LPPROC_INFO_MHS lpProcInfo). This is the function that will handle the breakpoint. Depending on whether you chose Prolog or Callback, this function will be called either directly before or directly after the line on which you set the breakpoint is executed--in my case, since I chose Prolog, my BP handler is called before the line in question is executed.
Note: as explained above, in the 4.0.0.7 release of MHS, you will be able to use process-specific breakpoint handlers, i.e. void On_BP_TROSE_EXE_1(LPVOID lpvAddress, LPPROC_INFO_MHS lpProcInfo). Even if, for some reason, this doesn't get fully documented in the MHS 4.0.0.7 Help File, it acts exactly the same way as Hotkeys currently act, so look under how to handle a hotkey with a script function in the current help file.
Now that we have a function to handle it, let's do something inside the function, shall we? Well, let's declare our global variables first so that they don't have to be redeclared each time the breakpoint is hit:
- Code: Select all
//##################################
//be extra sure to carefully name global variables
//these variables are global throughout all of MHS scripts
//not just this one. for instance, if i were to name
//these without the bp_ header, i wouldn't be able to have
//a variable named 'shouldinject' anywhere in any other
//script, even as a local variable.
//##################################
//this is the packet for sending chat '123'
BYTE bp_comparepacket[10] = {0x0A, 0x00, 0x83, 0x07, 0x78, 0x01, 0x31, 0x32, 0x33, 0x00};
//this is the packet for sending chat '321'
BYTE bp_injectpacket[10] = {0x0A, 0x00, 0x83, 0x07, 0x78, 0x01, 0x33, 0x32, 0x31, 0x00};
//our packet buffer
BYTE bp_newpacket[255] = {0};
//string to display the outgoing packet
char bp_packet[1024] = {0};
//determines whether we change the outgoing packet or not
bool bp_shouldinject = false;
- Code: Select all
//this is the function that will handle a breakpoint that has a Parm of '1'
void On_BP_1(LPVOID lpvAddress, LPPROC_INFO_MHS lpProcInfo)
{
//lpvAddress (parameter 1) holds a pointer to the address at which the breakpoint was hit
//obviously the user should already know where they set the breakpoint, but this
//allows you to gather that information programmatically. i suppose one could use this
//for multiple breakpoints handled by the same function with a switch..case
//used to determine at which address the breakpoint was hit, but i a) don't know if
//that's possible and b) don't see the reason for doing that
//lpProcInfo is a struct with all kinds of important information and resources
//it allows us access to the process that hit the breakpoint, the thread that hit
//the breakpoint, the context of the thread (registers, flags, etc), etc.
//information on structs of type LPPROC_INFO_MHS can be found at this website:
// http://www.memoryhacking.com/Misc/Tut/Disassembler%20-%20Debugger%20-%20Breakpoints.htm
//ESI holds the pointer to the packet
DWORD bp_ptr = lpProcInfo->pcContext->Esi;
//the first byte of the packet pointed to by 'ptr' is the length
extern BYTE bp_len = {"", bp_ptr};
//optional verbose feedback to tell the user that the breakpoint was hit
//MessageBox(MBS_OK, "Breakpoint!", "You just sent a packet!\n\nBreakpoint hit at 0x%08X\nLen: %i\nPtr: 0x%08X", (DWORD)lpvAddress, len, ptr);
//read the packet out of memory
if (!ReadProcessMemory(lpProcInfo->hProcess, (void *)bp_ptr, &bp_newpacket, bp_len, NULL))
return;
//initialize 'shouldinject' as true
//we'll change 'shouldinject' to false as soon as a difference between
//the packet we just read from memory and our compare packet is seen
bp_shouldinject = true;
//loop over the new packet
for (int i = 0; i < bp_len; i++)
{
//populate the string with the bytes of the packet
SPrintF(&bp_packet[i*3], "%02X ", bp_newpacket[i]);
//if any single byte differs between the two packets
//set boolean variable 'shouldinject' to false
if (bp_comparepacket[i] != bp_newpacket[i]) bp_shouldinject = false;
}
if (bp_shouldinject) SPrintF(&bp_packet, "%s -- PACKET INJECTED!", bp_packet);
//print out the packet string
PrintF("Packet: %s", bp_packet);
//if the two packets were the same
if (bp_shouldinject)
//write the changed packet over the current packet
WriteProcessMemory(lpProcInfo->hProcess, (void *)bp_ptr, bp_injectpacket, sizeof(bp_injectpacket), NULL);
}
The big difference between this script and my previous thread (linked to at the top) is there is no need to loop and poll memory for changes. It's executed each time the game hits the breakpointed operation, therefore it's both event-driven AND we have full access to the context of the thread that hits the breakpoint--we have full access to the game's registers and flags and all of that stuff. We don't have to inject code to write ESI to a static address, read ESI from memory, etc. We simply set DWORD bp_ptr = lpProcInfo->pcContext->Esi;. lpProcInfo is the struct of information that is passed to the breakpoint handler function, ->pcContext is a thread context struct INSIDE lpProcInfo, and ->Esi is a DWORD representing the register ESI INSIDE lpProcInfo->pcContext.
Now we simply read the packet from memory at the address contained in ESI (which is also in 'bp_ptr' now), loop through it and compare it to our compare packet / output it to the console. If 'bp_shouldinject' remains true after the loop, we simply inject our packet by writing it over the current packet in memory.
Voila! Now, again, every time your character chats '123' it will be sent to the server as '321.'
-Shynd