L2Bot icon indicating copy to clipboard operation
L2Bot copied to clipboard

Determining Offsets and their meaning

Open dev-spoiler opened this issue 4 years ago • 5 comments

Hey, just wanted to say thanks for the article. That was quite an interesting reading. Short but intense. My short-term nostalgia periodically hits me as well, and... well.. today is the day. Wanted to try some of your stuff, but not being a reverse engineer by any means I've stuck on the very first steps. Could you clarify some stuff? The test server I've selected is C4. ATM I see (from the comments in the code and github) that you suggest to somehow find out the offsets for functions from engine.dll (e.g. send packet). I was re-reading the article multiple times and haven't found a clue of what are the required steps to achieve this. This is the screen from your article: image Further you set the offsets in the code:

#define DATA_SEND_ADDR 0x3E3B80          // Offset of the "send packet" function in engine.dll
#define DATA_SEND_SOCKET_INFO 0xFD890000 // Offset of the networking structure

Basically I was trying to guess what you've done and came to the next:

  1. First, just for my confidence, can you confirm that "offset" refers to some number that when added to some base address points to some address in virtual memory? If yes, what is this base: 0, some imagebase, or whatever currently is in EBP? (I'm basically concerned if image base should be subtracted from the address)

  2. Are the all offsets I need to find out listed in the code as follows: DATA_RECV_ADDR, DATA_SEND_ADDR, DATA_SEND_SOCKET_INFO, DATA_SEND_MOVEMENT_ADDR ? Is this exhaustive list? Or will there be an offset per every action that triggers a packet send (or receive)? Like I see here a special offset for movement, or at least it gives that impression.

  3. Do I understand correctly that in order to find out the offset I need to find where the beginning of the send packet function lives in engine.dll in virtual memory? In my case from what I see sending message in chat goes through the path of ... -> nwindow -> engine * N -> ws_32. engine module is calling memory inside itself few times, and is one of that addresses what I'm looking for? If yes, how can I know which offset is correct (assuming changing value in code and running the solution in visual studio may not work as I may have other stuff miscofigured)?

  4. Also I've found that from launch to launch the addresses I see in x32dbg differ. E.g. image image Am I doing something wrong? Any clues what it might be? (also I've used the editbin to turn off the ASLR for l2.exe)

  5. The offset that needs to be found, is it the address where the call instruction goes? or where the push ebp instruction goes (atm it seems for me like the preparation for a call) or which one?

  6. In your code snippet you have the DATA_SEND_ADDR = 0x3E3B80. I've reviewed the screens very carefully but couldn't identify where (and how) you've got it from.

Can you clarify that? Thanks Again nice work with article!

dev-spoiler avatar Nov 28 '21 19:11 dev-spoiler

Sorted some of the stuff out. Seems like offset means offset relatively to engine.dll in memory, which exact location may be different on each launch, but GetModuleHandle gets it. Having the offset constant we can accurately locate the desired function inside engine.dll.

dev-spoiler avatar Dec 06 '21 20:12 dev-spoiler

Hey, glad you enjoyed the article! I'm sorry that I'm a bit late, I will try to answer your questions nevertheless :)

  1. First, just for my confidence, can you confirm that "offset" refers to some number that when added to some base address points to some address in virtual memory? If yes, what is this base: 0, some imagebase, or whatever currently is in EBP? (I'm basically concerned if image base should be subtracted from the address)

In this context when I use the term offset I'm actually talking about RVA (Relative Virtual Address) which corresponds to the distance from the base Virtual Address whatever it is. For instance if engine.dll is loaded at 0x50000 in memory and the interesting function is at 0x65432 in memory, then the RVA (offset) would be 0x65432 - 0x50000 = 0x15432. For the second one though (DATA_SEND_SOCKET_INFO), I am actually mentioning a raw constant pointer to some space in memory.

  1. Are the all offsets I need to find out listed in the code as follows: DATA_RECV_ADDR, DATA_SEND_ADDR, DATA_SEND_SOCKET_INFO, DATA_SEND_MOVEMENT_ADDR ? Is this exhaustive list? Or will there be an offset per every action that triggers a packet send (or receive)? Like I see here a special offset for movement, or at least it gives that impression.

I still haven't figured out how movements are exactly handled in the game, so I'm not sure about this specific question, the answer could be yes. However for any other action in the game, when the client tries to do it, it will use the function at DATA_SEND_ADDR and wait for the server response through the function at DATA_RECV_ADDR to make sure the action is valid and can be played (and then animate the character, open the corresponding UI, or whatever). So yes I think these are the only things you need to figure out.

  1. Do I understand correctly that in order to find out the offset I need to find where the beginning of the send packet function lives in engine.dll in virtual memory? In my case from what I see sending message in chat goes through the path of ... -> nwindow -> engine * N -> ws_32. engine module is calling memory inside itself few times, and is one of that addresses what I'm looking for? If yes, how can I know which offset is correct (assuming changing value in code and running the solution in visual studio may not work as I may have other stuff miscofigured)?

Yes that's what you would have to do. I think the easiest is to find your way through the debugger. To know which function is the correct one, check the first screenshot you linked. As you can see in the bottom right corner, there are the values pushed on the stack which in this context corresponds to the arguments given to the function. If you manage to put a breakpoint at the beginning of what you think is the "send" function, you should see the parameters (on the top of the stack) such as:

  • 0 return address (here 0x202d7c4f)
  • DATA_SEND_SOCKET_INFO (here 0xFD890000)
  • packet format cSd which stands for character, string and integer on 4 bytes (iirc)
  • the c information (the packet ID, here 0x49)
  • the S string information, a pointer to Hello (yeah I said Hello in the chat)
  • the d 4 bytes information which here is 0.

If so you can be pretty sure you found the right spot, and then you can start modifying in memory the information (e.g. typing "Hello" in the game, and modifying the string in memory thanks to x64dbg to something else, and confirm that in game you see the modified string). Then I think you would be pretty confident it is the proper function.

  1. Also I've found that from launch to launch the addresses I see in x32dbg differ. E.g.

As far as I remember, ASLR in Windows is per module, which means you have to disable ASLR on the engine.dll itself if you don't want it to move. It's easier when you begin but if you think in term of RVA, it's not that complicated. x32dbg can actually handle those pretty well and keep your breakpoints at the same relative location from an execution to the other.

  1. The offset that needs to be found, is it the address where the call instruction goes? or where the push ebp instruction goes (atm it seems for me like the preparation for a call) or which one?

I think the address is the one for the push because the call might change the context too much and then retrieving arguments would be too annoying.

  1. In your code snippet you have the DATA_SEND_ADDR = 0x3E3B80. I've reviewed the screens very carefully but couldn't identify where (and how) you've got it from.

If you put a breakpoint on ws2.send you can be pretty confident the caller is one function from engine.dll. It can be confirmed thanks to the call stack (check the return addresses). Then you can just navigate there in a disassembler or in x32dbg itself, and find the beginning of this function, and put a breakpoint there. Probably at some point you will see unencrypted data and that will help finding the right spot :)

Hope it helps! :)

xarkes avatar Dec 08 '21 20:12 xarkes

Hey.

I think I've managed to make that working. At least with respect to the bot lib. I was able to target npc and log incoming packets internally from the lib. Anyway there is some issue with IPC (it spams "Well that's sucks..." in the log). However I'm considering dotnet as a platform for a controller app and hopefully I'll resolve the issue as well having your code as an archetype. I think it will produce much more questions from my side, but at least I'll be more confident on what I'm doing =) Anyway, thanks for answering.

dev-spoiler avatar Dec 09 '21 18:12 dev-spoiler

This error message comes from the IPC socket: https://github.com/xarkes/L2Bot/blob/c8bbef4fe17f6689dd02d7068352ac2067db6f9e/L2Bot/IPCSocket.cpp#L147-L153 Basically my code isn't perfect and does not handle this edge case. It is possible that the buffer is already full after a single read and that you are trying to parse a packet which is split because of my buffer limit. As you can see there is some commented code there in an attempt to handle this case, basically we should refill the buffer with what's in the pipe and start over the parsing :) Or it could mean you are reading garbage and the size you are reading is wrong. And then you would have to try to understand what's going on between the injected DLL and the bot GUI :P Anyways your best shot would be to debug it in visual studio :) Have fun!

xarkes avatar Dec 11 '21 09:12 xarkes

Currently, I'm basically trying to establish communication through dotnet app, having my fingers crossed that dotnet is smart enough to figure out the packet size or whatever the atomic pipe communication unit is called. Hopefully I won't face the same issue or at least dotnet might mitigate it somehow. I try to start servers in the app using NamedPipeServerStream classes, but in dotnet creating pipe funcs signatures are a bit different to what I see in c++ (in your gui app), also lack of experience in IPC made me a bit stuck. ATM I think my problem is that in dotnet I need to wait for client connection which is basically a blocking call and I somehow need to inject the lib in parallel (which is actually a client to be connecting), which might lead me from Task based classes to thread based what I've basically wanted to avoid. But anyway I'll think I'll get over it eventually.

dev-spoiler avatar Dec 11 '21 11:12 dev-spoiler