Lua_DoString() in C++ w/ CreateRemoteThread/WriteProcessMemory + Security questions menu

User Tag List

Results 1 to 13 of 13
  1. #1
    reliasn's Avatar Legendary Authenticator enabled
    Reputation
    774
    Join Date
    Jan 2009
    Posts
    136
    Thanks G/R
    24/215
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Lua_DoString() in C++ w/ CreateRemoteThread/WriteProcessMemory + Security questions

    Hello Ownedcore!

    One of the main issues with my bot is that I sadly have to ask for the user to set ALL the keys, such as, Interact with MouseOver, /cast Fishing, mount key, combat keys, etc. In order to avoid this, I decided once and for all to learn about this "function calling", where you simply call WoW function that does all the work for you. In this case, the function I want to call is the Lua_DoString() which, well, can be used to do everything, including interacting with targets, casting spells, etc. After reading a bunch of posts here in Ownedcore, I saw that most people were using the famous Blackmagic to manage memory along with C#. Since I use C++ and I don't know how to use Blackmagic nor I like to use managed libraries (not in this case, as my only purpose is learning), I decided to call this Lua_DoString() only by using CreateRemoteThread and WriteProcessMemory, which is the method greatly explained in this link.

    So after a lot of tries and simulations into smaller projects, I finally managed to get it working. The main C++ code is the one below:
    Code:
    typedef struct {
       DWORD funcptr;
       char command[255];
    } INJDATA;
    
    __declspec( naked ) DWORD codeasm(){
    	__asm{
    		nop
    		push ebx //here we have address of funcptr, but I want the address of command on EAX so...
    		pop eax // remove from stack and pass it to EAX
    		add eax, 4 // add 4 to the address, therefore, now we have the address of char command[255]
    		push 0
    		push eax
    		push eax
    		mov edx, [ebx] //grab the content of EBX which is funcptr and pass to EDX
    		call edx // call Lua_doString()
    		add esp, 0xC //probably just "fixing" the stack pointer
    		nop
    		ret
    	};
    	//return 0;
    }
    
    static void after_codeasm (void) {
    }
    
    void convertToASCII(string letter, char x[255])
    {
    	for(int j = 0; j <= 255; j++){
    		x[j] = 0x0;
    	}
    	for (int i = 0; i < letter.size(); i++)
    	{
    		x[i] = letter.at(i);
        }
    }
    
    void Lua_DoString(string cmd){
    	void* Handle = ::Handle2;
    	DWORD func = reinterpret_cast<unsigned int>(module.modBaseAddr) + Framescript_ExecuteBuffer;
    	DWORD cbCodeSize = ((PBYTE) after_codeasm - (PBYTE) codeasm);
    	cbCodeSize = 200;   // meh, I should actually check the size of codeasm() but whatever...
    	INJDATA mydata;
    	convertToASCII(cmd,mydata.command);
    	mydata.funcptr = func;
    	LPVOID pData = VirtualAllocEx( Handle, NULL, sizeof(func), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
    	LPVOID pLibRemote = VirtualAllocEx( Handle, NULL, cbCodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
    	WriteProcessMemory( Handle, pData, &mydata, sizeof(mydata), NULL);
    	WriteProcessMemory( Handle, pLibRemote, &codeasm, cbCodeSize, NULL);
    	HANDLE hThread = CreateRemoteThread(Handle, NULL, 0,(LPTHREAD_START_ROUTINE) pLibRemote, pData, 0, NULL );
    	if(hThread != 0){
    		WaitForSingleObject(hThread, INFINITE);
    		CloseHandle(hThread);
    		VirtualFreeEx( Handle, pLibRemote, cbCodeSize, MEM_RELEASE );
    		VirtualFreeEx( Handle, pData, sizeof(func), MEM_RELEASE );
    	}
    }
    As you guys can see, it isn't that complicated the way I did it. I basically allocated 2 memory spaces, one to receive my ASM and another one to receive my 2 parameters: one holding the address of where my ASM code was injected and another holding the string converted to ASCII. The parameters and the ASM code are then written with WriteProcessMemory into WoW's thread. After that, I'm good to start the CreateRemoteThread(). This function will then open a new thread on WoW that will run my injected ASM which will then call Lua_DoString(). I copied most of the ASM from Vandra, who made this post here. However, in my case, I had to modify his ASM a little. I noticed with CheatEngine that when you do a simple /script DoEmote("dance") in WoW, the command is converted to ASCII and the register EAX receives the address where the command is. So when I was running the "call eax" from Vandra's code, it would call Lua_DoString() but EAX would be pointing to Lua_DoString(). This is why I had to use another register, EDX, while the EAX register I left with the address that points to my LUA command.

    Anyways, I guess my method is quite complicated afterall and has a bunch of useless codes, but hey, it works so far. Although it's working, I've got a couple questions:
    1 - Do I really need to allocate 2 memory spaces or is there a more intelligent/cleaner way of doing it?
    2 - In the ASM code, there are 3 instructions that I didn't understand at all why they are there:
    Code:
    push 0
    push eax
    push eax
    I didn't try yet with one single push eax, which for me, is enough, because I would probably be lost later on what I should "add" to later to the ESP. So is it really needed to have 3 "push" instructions like that?
    3 - My knowledge about Warden is practically ZERO. The idea I have of it is: don't mess with the addresses that it scans. Since I'm allocating memory and then injecting code, I think I'm running a huge risk of writing something where Warden scans. So I take that, although Injection is quite risky, if I don't mess with the scanned addresses, I should be fine, correct? And for that, I should implement some sort of protection in my bot to avoid memory allocation or code injection in forbidden addresses, correct? Oooor... should I just leave this away and call Lua_DoString() without worrying too much?
    4 - What would be a nice way of checking that my VirtualFreeEx() is actually working? It basically frees the allocated memory... so should I just open WoW, run my fishing bot and monitor WoW's memory in the Task Manager to see if it gets bigger and bigger over time? Or I could just check the return of VirtualFreeEx()?

    Well, that's basically it, guys... As you probably noticed, I'm quite new to ASM, injection and I'm terrible at OOP. And yeah, I'm having a hard ****ing time to learn all of it, but it's been fun so far. Sometimes it's quite frustrating and others I just think it's impossible to do something... but damn, this is what I study at my college and what I want to work with in the future (not that I want to live making bots -_-... which wouldn't be a bad idea either considering the popularity of Honorbuddy haha, but anyways...).

    Finally, feel free to post your comments and criticize whatever I did wrong or whatever could be done in a much simpler way. I'll see if I can answer my own questions right now by testing some stuff, but I'm mainly worried about Warden. I'm ready to replace all my "PostMessage()" in my bot... but I don't know if it's a good idea to just do it right away without having some sort of Warden protection...

    Thanks a lot for your attention! And a special thanks to:
    Vandra - for the ASM
    TOM_RUS - Framescript_ExecuteBuffer offset
    To whoever made CheatEngine <3 Damn, the way it shows what's inside each register is just... amazing!
    And well, all the posts I read from googling "call interact site: ownedcore.com" and "call lua do string site: ownedcore.com"!
    Last edited by reliasn; 02-20-2013 at 08:57 AM.

    Lua_DoString() in C++ w/ CreateRemoteThread/WriteProcessMemory + Security questions
  2. #2
    Jadd's Avatar 🐸 Premium Seller
    Reputation
    1511
    Join Date
    May 2008
    Posts
    2,433
    Thanks G/R
    81/333
    Trade Feedback
    1 (100%)
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    1. Inject the delegate assembly once and free it when the bot unloads - it will be a lot faster when you're cutting out the allocating and freeing with each lua execution (this is more susceptible to warden scans, but can easily be circumvented).
    2. Look up how the call stack works (also calling conventions).
    3. Unless the bot is public, you don't really need to worry about cave scans.
    4. Why would it fail..?
    Last edited by Jadd; 02-20-2013 at 09:16 AM.

  3. #3
    DrakeFish's Avatar Lazy Leecher

    Reputation
    634
    Join Date
    Nov 2008
    Posts
    569
    Thanks G/R
    0/14
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by reliasn View Post
    1 - Do I really need to allocate 2 memory spaces or is there a more intelligent/cleaner way of doing it?
    You are currently storing your function's size in the cbCodeSize variable. If you want to have a single alloc, you could be doing something like the following:
    Code:
    SIZE_T cbCodeSize = ((PBYTE) after_codeasm - (PBYTE) codeasm);
    SIZE_T cbLuaSize = strlen(szLuaCode); // some ANSI C str ptr
    PVOID pAlloc = VirtualAllocEx(Handle, NULL, cbCodeSize+cbLuaSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory( Handle, pAlloc, &codeasm, cbCodeSize, NULL);
    WriteProcessMemory( Handle, pAlloc+(cbCodeSize), &szLuaCode, cbLuaSize, NULL);
    As Jadd pointed out, you may want to allocate your remote stub when the program starts (or the first time the function is called) and then re-use it until you don't need it anymore and free it, but since you are creating (and waiting after) a new thread every time you need to execute this function, I don't think this would bring any performance improvement.

    As for the push instructions, they follow a specific order and count because these are the arguments you pass to the function you are calling. It expects to read them in the right order on the stack and if it doesn't, it will use what was there anyways and this may lead to unwanted results. I believe one of the EAX pushed there is for the actual code and the other is for the "file/scope" name or something like that (if I'm right and it's used you should probably not be pushing a pointer to your lua code there).

    Yes, VirtualFreeEx returns a BOOL value indicating if it was a sucess. VirtualFreeEx function (Windows). When you finish this piece of code, you will also be wanting to check if your allocation and memory writes are succeeding or failing, but I am sure you already know that.

    On a side note, someone correct me if I'm wrong, but I always thought that the Lua engine stack/state was thread unsafe (or at least unsafe by default, I don't think Blizzard needed to implement multi-threading for Lua) and shouldn't be called from another thread than the main one. I have not verified this but I remember having problems like the UI getting tinted or models information getting corrupted (like the model in the character's pane).
    Last edited by DrakeFish; 02-20-2013 at 05:40 PM.

  4. #4
    Frosttall's Avatar Active Member
    Reputation
    64
    Join Date
    Feb 2011
    Posts
    261
    Thanks G/R
    16/3
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by DrakeFish View Post
    On a side note, someone correct me if I'm wrong, but I always thought that the Lua engine stack/state was thread unsafe (or at least unsafe by default, I don't think Blizzard needed to implement multi-threading for Lua) and shouldn't be called from another thread than the main one. I have not verified this but I remember having problems like the UI getting tinted or models information getting corrupted (like the model in the character's pane).
    Well this may be true, but why not simple (if you keep the project private) add detours in Framescript_ExecuteBuffer which check and set an event like 'executingScript' in order to determine if any thread is currently running a luascript and block all other requests until the executeScript-event fired that the execution was finished. Now do a synchronizied access to the event-set in order to give only one thread the access for Framescript_ExecuteBuffer and put the other ones again on hold until they're in the queue.

  5. #5
    DarthTon's Avatar Contributor
    Reputation
    171
    Join Date
    Apr 2010
    Posts
    108
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    BTW, instead of constant thread creation you can create only one sleeping thread and execute your code in it's context. For example - creating an alertable thread + QueueUserAPC function (Windows)

  6. #6
    ~Unknown~'s Avatar Contributor
    Reputation
    193
    Join Date
    Jan 2009
    Posts
    211
    Thanks G/R
    0/5
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by reliasn View Post
    2 - In the ASM code, there are 3 instructions that I didn't understand at all why they are there:
    Code:
    push 0
    push eax
    push eax
    I didn't try yet with one single push eax, which for me, is enough, because I would probably be lost later on what I should "add" to later to the ESP. So is it really needed to have 3 "push" instructions like that?
    The reason the asm is pushed in that order is from what I understand framescript_executebuffer is a cdecl calling convention so arguments are pushed on the stack in reverse order (Jadd hinted at this). The important push is the last one as it is the pointer to your script string. I see where the first two go in decompilation, but not sure of their significance as I wasn't that interested. You will need 3 pushes though for this function as it takes 3 arguments. If you don't weird stuff might happen.


    Originally Posted by DrakeFish View Post
    On a side note, someone correct me if I'm wrong, but I always thought that the Lua engine stack/state was thread unsafe (or at least unsafe by default, I don't think Blizzard needed to implement multi-threading for Lua) and shouldn't be called from another thread than the main one. I have not verified this but I remember having problems like the UI getting tinted or models information getting corrupted (like the model in the character's pane).
    To add to DrakeFish, as far as I know, the lua engine isn't entirely thread safe. I'm not an expert, but after trying different ways of accessing it on different threads, it either produced odd results, or didn't work all together. It sort of surprises me if another thread aside from the main works without any other modifications.


    Originally Posted by Frosttall View Post
    Well this may be true, but why not simple (if you keep the project private) add detours in Framescript_ExecuteBuffer which check and set an event like 'executingScript' in order to determine if any thread is currently running a luascript and block all other requests until the executeScript-event fired that the execution was finished. Now do a synchronizied access to the event-set in order to give only one thread the access for Framescript_ExecuteBuffer and put the other ones again on hold until they're in the queue.
    Frosttall makes a good point here. If you can understand how detours work, you can just add a check for syncing issues.


    In summary to your attempt as a whole: I don't know programming in C++ very well, but I know from injecting a C# dll with the necessary methods, I can just create a method delegate and call the function normally instead of writing asm. If I'm not mistaken, I was certain with proper signatures and function pointers you could do the same with C++ and it is quite easy. You might want to look into this as it seems easier once done and cleaner. Unless of course I'm just making shit up.

  7. #7
    Jadd's Avatar 🐸 Premium Seller
    Reputation
    1511
    Join Date
    May 2008
    Posts
    2,433
    Thanks G/R
    81/333
    Trade Feedback
    1 (100%)
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Lua is thread-safe, the registered CFunctions may not be.

    However, it would be a pretty rare occurrence for it to give you any troubles - one in a million I'd say.

  8. #8
    reliasn's Avatar Legendary Authenticator enabled
    Reputation
    774
    Join Date
    Jan 2009
    Posts
    136
    Thanks G/R
    24/215
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by Jadd View Post
    1. Inject the delegate assembly once and free it when the bot unloads - it will be a lot faster when you're cutting out the allocating and freeing with each lua execution (this is more susceptible to warden scans, but can easily be circumvented).
    Indeed, this idea is much better. I guess I should only beware for not allocating/injecting where Warden scans.

    Originally Posted by Jadd View Post
    2. Look up how the call stack works (also calling conventions).
    Regarding the PUSH instructions, now I have a better idea of why they are there. Thanks to everyone for the hints and explanations. I double-checked the Framescript_ExecuteBuffer pseudocode in IDA and it really has 3 parameters:
    Code:
    int __cdecl FrameScript_ExecuteBuffer(const char *a1, int a2, int a3)
    which, in my opinion, means that I need to have 3 PUSH instructions for them. This makes sense to me although I don't quite understand yet how these parameters are manipulated inside the function. I'm gonna take a look here and see if I have a better idea of what is happening!

    Originally Posted by Jadd View Post
    3. Unless the bot is public, you don't really need to worry about cave scans.
    Well... it's this bot here. Yes, it's public, but not as famous as other bots out there. In this link, you can see that the bot is run more or less 300-500 times per day. It used to be around 1000-1200, but since I stopped playing the game for some time and also slowed down the updates, turned out that not many people use it nowadays. Anyways, should I be worried about cave scans? Before anything, by "cave scan" I understand that Warden is able to check the memory for certain patterns of instructions and if it fits with the one used by my bot, gg, it's detectable. Is my assumption correct? And since you mentioned this, wouldn't be nice to replace my injected ASM with a bunch of NOP instructions before freeing the memory? That way the "pattern" would be in the memory just during the Lua_DoString() execution and that's it. Other than this, I can't think of another way to avoid cave scans...

    Originally Posted by Jadd View Post
    4. Why would it fail..?
    I honestly don't know. Like others mentioned below, simply checking the return of VirtualFreeEx() may be enough to see if everything is fine. Aaand I'm not gonna lie, but I also checked on Task Manager and I noticed the memory was being allocated/freed as the bot ran. WoW's memory stood more or less with the same size hehe

    Originally Posted by DrakeFish View Post
    You are currently storing your function's size in the cbCodeSize variable. If you want to have a single alloc, you could be doing something like the following:
    Code:
    SIZE_T cbCodeSize = ((PBYTE) after_codeasm - (PBYTE) codeasm);
    SIZE_T cbLuaSize = strlen(szLuaCode); // some ANSI C str ptr
    PVOID pAlloc = VirtualAllocEx(Handle, NULL, cbCodeSize+cbLuaSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory( Handle, pAlloc, &codeasm, cbCodeSize, NULL);
    WriteProcessMemory( Handle, pAlloc+(cbCodeSize), &szLuaCode, cbLuaSize, NULL);
    If I do this, I guess I will have to add some extra ASM instructions, but I don't see why it wouldn't work this way. Thanks
    Honestly though, I'm getting more worried about security than efficiency itself. I want to use Lua_DoString() for simple things, casting a Fishing spell, interacting with a Mob/Object, RepopMe(), RetrieveCorpse(), etc. Indeed, writing my stub there and freeing its memory only after the bot closes sounds a great idea, but damn, wouldn't it be risky to leave my ASM in there for a long time even though I call Lua_DoString() in a few situations only? Like I said, I don't know much about Warden, but for me, it seems safer to leave the code like this, creating threads every time I call Lua_DoString() and if possible, replace the ASM I injected with NOP instructions before ending the thread.

    Originally Posted by DrakeFish View Post
    On a side note, someone correct me if I'm wrong, but I always thought that the Lua engine stack/state was thread unsafe (or at least unsafe by default, I don't think Blizzard needed to implement multi-threading for Lua) and shouldn't be called from another thread than the main one. I have not verified this but I remember having problems like the UI getting tinted or models information getting corrupted (like the model in the character's pane).
    So far it's working perfectly for me. No glitches, nothing getting corrupted. I'm able to call every single LUA function, including protected ones. Just out of curiosity, I tested with only one PUSH EAX and add esp, 0x4 and I could still call Lua_DoString(), but I couldn't call the protected functions such as CastSpellByName(), InteractUnit(), etc.

    Originally Posted by Frosttall View Post
    Well this may be true, but why not simple (if you keep the project private) add detours in Framescript_ExecuteBuffer which check and set an event like 'executingScript' in order to determine if any thread is currently running a luascript and block all other requests until the executeScript-event fired that the execution was finished. Now do a synchronizied access to the event-set in order to give only one thread the access for Framescript_ExecuteBuffer and put the other ones again on hold until they're in the queue.
    Finally, regarding the Thread Syncing and Detours... I have no idea how they work so I need to take a look at them before I say something stupid. From what I understood, it seems that I should use them to avoid a bunch of threads calling at the same time Lua_DoString(). But I don't know if I will run into this situation, because I plan, at least for now, to use Lua_DoString() for only specific things. But yeah, I will take a look at this for sure and thanks for the suggestion

    And I guess that's it, guys. Thanks a lot once again for everyone posting here! It really helped me out not only to better understand what I did but also to improve all of it. It was fun to just make a bot simply using WriteProcessMemory and ReadProcessMemory, but injection seems quite hard for me, so I still need to learn and read a lot. Right now, I'm crazy to replace all my PostMessage() stuff in my bot which I use to send specific keys to WoW, but since I don't feel I'm an expert at avoiding getting caught... I guess I'm gonna wait a little

  9. #9
    bisfs's Avatar Private
    Reputation
    1
    Join Date
    Aug 2014
    Posts
    11
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    i tested this code in v5.4.8 client and use the Lua_dostring offet ([WoW] [5.4.8 18291] Release x86 Info Dump Thread)0x4fd26 and something goes wrong.
    i am not sure if the offset is wrong or the createremotethread approach is not a right way in v5.4.8.
    has any one verified the offset or this approach before?
    Last edited by bisfs; 08-12-2014 at 01:50 AM.

  10. #10
    hamburger12's Avatar Contributor CoreCoins Purchaser
    Reputation
    87
    Join Date
    Jan 2010
    Posts
    297
    Thanks G/R
    0/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    If you just wanna execute in mainthread like environment.. you can suspend mainthread and copy tls to your new created thread. Then run your code and continue mainthread ;-)

  11. #11
    bisfs's Avatar Private
    Reputation
    1
    Join Date
    Aug 2014
    Posts
    11
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by hamburger12 View Post
    If you just wanna execute in mainthread like environment.. you can suspend mainthread and copy tls to your new created thread. Then run your code and continue mainthread ;-)
    why need i to suspend mainthread and copy tls?in my new thread created by the win32 api "createremotethread", there is no need for resources which in mainthread.
    what my new thread do is just pushing some parameters to the stack and calling Lua_Dostring fuction.
    is it that the lua_dostring can only be called in mainthread, otherwise it will be wrong?

  12. #12
    streppel's Avatar Active Member
    Reputation
    78
    Join Date
    Mar 2007
    Posts
    196
    Thanks G/R
    0/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    if i remember correctly, lua_dostring was available for non-protected functions (like /dance) from every thread, but only available for protected functions (like moveforward) from the main thread.
    you can either hook/detour the original mainthread (most of the time EndScene (DX9) or Present (DX10/11) is hooked for this, as it is called once per frame (at the end of it) from the main thread.

    this implies that you can execute every function as if the game itself was executing it.
    i'm not that familiar with c++ but i think you should stick to creating a thread once where you hook one of the aforementioned functions and have a queue there for functions you want to execute.
    you should consider injecting a dll that does this and provides an IPC (inter-process-communication) with your bots main application. this could be either named-pipes, sockets or anything else you could think of. (like start a webservice in the context of wow and expose the methods you want to use there)
    but as i already said,i'm more familiar with c# and have a huge framework to use.
    NON-OBJECTIVE HYPOTHETICAL PSEUDO PROCESSOR SPEED.
    https://memegenerator.net/cache/instances/400x/9/10044/10285683.jpg

  13. #13
    bisfs's Avatar Private
    Reputation
    1
    Join Date
    Aug 2014
    Posts
    11
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    thanks a lot

Similar Threads

  1. [Selling] World of Warcraft: Fully upgraded account (with security questions/MoP) £25
    By Monkeyshyne in forum WoW-EU Account Buy Sell Trade
    Replies: 1
    Last Post: 02-10-2013, 09:37 AM
  2. Account Security Question
    By Nasmira in forum World of Warcraft General
    Replies: 2
    Last Post: 03-28-2012, 03:59 PM
  3. forgot security question
    By Shobek in forum World of Warcraft General
    Replies: 3
    Last Post: 09-14-2010, 09:37 PM
  4. [Help Request] Security Questions
    By abuckau907 in forum WoW Memory Editing
    Replies: 4
    Last Post: 08-18-2009, 07:09 AM
All times are GMT -5. The time now is 02:48 PM. Powered by vBulletin® Version 4.2.3
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved. User Alert System provided by Advanced User Tagging (Pro) - vBulletin Mods & Addons Copyright © 2024 DragonByte Technologies Ltd.
Digital Point modules: Sphinx-based search