Raised This Month: $32 Target: $400
 8% 

[ANY] Work with memory in SourcePawn


Post New Thread Reply   
 
Thread Tools Display Modes
Author Message
2010kohtep
Junior Member
Join Date: Aug 2017
Old 08-23-2017 , 13:34   [ANY] Work with memory in SourcePawn
Reply With Quote #1

Introduction

Hello, everyone. Not so long ago I decied to make my own Team Fortress 2 server, and it took me to write SourcePawn plugins. I started study this language relatively recent and generally I like it, except for some things that I would like to talk about, and also discuss solutions for it.

If you don't want to read philosophical reasoning about language limitations, I recommend you to skip this section and get straight to the next one.

One of those things - process address space access restriction. Unfortunately, but we can work only with server and engine libraries. Well, normally, this should be enough, because main server engine work occurring it is in these libraries. However, don't take me wrong, but I don't like fell myself limited when working with memory, heh. Nevertheless, you could deal with this... I thought so first time.

But what I really didn't like, is inability to "mix" memories of plugin and server. In other words, if I want to insert pointer on any memory region of my plugin in any library of process or the srcds itself - I'll be forced to abstain, because first of all, I can't get native pointers of variables or functions of my plugin, and secondly, I can interract with, again, server and engine libraries. Even if I had ability to get variables addresses via "#emit", I would get address relative special plugin memory, but relative process memory (nullptr). Moreover, you can't allocate memory in srcds process.

Again, don't take me wrong! I'm not criticizing SourcePawn language with the suggestion that language is necessary to be improvements. SourcePawn is not the language, which is designed for such complex memory manipulations. It is designed to create simple plugins, which will change or add server functionality. In general, it's good language to create server-side modifications. However, personally I don't like these restrictions I have enumerated above, and therefore I would like to propose some solutions for servers on Windows platform.

I'm pretty sure someone says that such functionality can be implemented with native functions, and I'll be forced to agreed. With the help of native you can implement anything, however, I prefer to not use external libraries which create dependence of plugin from SourceMod modules. Therefore, I will reproduce all my solutions with the pure SourcePawn code, which can be easily used as include file.

Let's begin.

What do we have?

We can use special thing called "gamedata" to find code pattern and then patch it. Basically, it's file, which consists code pattern, module name, operating system, and offset. It's a quite useful thing.

To work with native process memory (not plugin) we have two main functions - StoreToAddress() and LoadFromAddress(). These functions allow us to access memory to the desired address, but since we don't have full information about memory regions (we can receive addresses only via gamedata search), we are blind.

Another main feature of SourcePawn language - SDKCall() function, which was developed for calling native class methods (such as CBaseEntity).

That's all. These things are going to allow us to make SourcePawn much more powerful in terms of working with memory.

Getting Windows version

First, we start with the simplest - let's try to get the version of our operating system. How we can do this without using native? Very simple! Watch my hands.

In Windows, there is a special place in the memory, which address is static since the days of Windows 2000. This place is a KUSER_SHARED_DATA structure, which contains the information that is necessary for us. The address of this structure is 0x7FFE0000. Let's use the available information to write a function for obtaining the OS version.

PHP Code:
stock void GetWindowsVersion(intiMajorVerintiMinorVer)
{
    
Address pUserSharedData view_as<Address>(0x7FFE0000);
    
    
/* ((KUSER_SHARED_DATA*)0x7FFE0000)->NtMajorVersion */
    
iMajorVer LoadFromAddress(view_as<Address>(view_as<int>(pUserSharedData) + 0x26C), NumberType_Int32);
    
/* ((KUSER_SHARED_DATA*)0x7FFE0000)->NtMinorVersion */
    
iMinorVer LoadFromAddress(view_as<Address>(view_as<int>(pUserSharedData) + 0x270), NumberType_Int32);
}

// ...

public void OnPluginStart()
{
    
int iMajoriMinor;
    
GetWindowsVersion(iMajoriMinor);
    
PrintToServer("Windows Version : Major=%d, Minor=%d"iMajoriMinor);

As you can see, nothing is complicated, except for the not the best readability of the code, but we can fix this with the help of "#define".

Getting PEB

Process Environment Block, perhaps, is one of the most useful and necessary things for us. Using it, we can access the list of loaded modules, as well as the address of the srcds process itself. There is only one "but" - it can not be obtained with LoadFromAddress(), since it's address changes from start to start of the program. Moreover, we can get it only by using the x86 assembler. Here's how it looks in C++:

PHP Code:

void
GetPEB()
{
    
__asm
    
{
        
mov eaxdword ptr fs:[30];
    }

Looks simple, isn't it? Unfortunately, it's impossible to perform such trick on pure SourcePawn. But what if you really want to? Let's try.

First of all, we need to create a file in the gamedata folder, called "any.tutor.memory.txt":

PHP Code:
"Games"
{
    
"#default"
    
{
        
"Addresses"
        
{
            
"server"
            
{
                
"windows"
                
{
                    
"signature" "Find_Server"
                
}    
            }        
        }
        
        
"Signatures"
        
{
            
"Find_Server"
            
{
                
"library"    "server"
                "windows"    "\x4D\x5A"
            
}            
        }
    }

Sadly, I didn't find a solution without using this file. I will explain later why we need this.

Let's implement the necessary functional. To begin with, we write the code:

PHP Code:
#define Pointer Address
#define nullptr Address_Null

#define int(%1) view_as<int>(%1)
#define ptr(%1) view_as<Pointer>(%1) 
These defines can be omitted, but in order to increase the readability of the code, I will use them.

Then write the functions of reading the data at the address:

PHP Code:
stock int ReadByte(Pointer pAddr)
{
    if(
pAddr == nullptr)
    {
        return -
1;
    }
    
    return 
LoadFromAddress(pAddrNumberType_Int8);
}

stock int ReadWord(Pointer pAddr)
{
    if(
pAddr == nullptr)
    {
        return -
1;
    }
    
    return 
LoadFromAddress(pAddrNumberType_Int16);
}

stock int ReadInt(Pointer pAddr)
{
    if(
pAddr == nullptr)
    {
        return -
1;
    }
    
    return 
LoadFromAddress(pAddrNumberType_Int32);

We have written wrappers for LoadFromAddress() that will allow us to read the types of data that we need. Without these functions, our code would become large enough and inconvenient to read.

After these functions, create two more:

PHP Code:

stock Pointer Transpose
(Pointer pAddrint iOffset)
{
    return 
ptr(int(pAddr) + iOffset);
}

stock int Dereference(Pointer pAddrint iOffset 0)
{
    if(
pAddr == nullptr)
    {
        return -
1;
    }
    
    return 
ReadInt(Transpose(pAddriOffset));

Transpose() function is needed to change the value of the pointer, without type casting. The Dereference() function is similar to Transpose(), but after changing the address, it also produces the pointer dereferences. Again - everything is for increased readability of the code.

And now we are going to implement the really necessary functionality:

PHP Code:
stock Pointer WriteData(Pointer pAddrint[] dataint iSize)
{
    if(
pAddr == nullptr)
    {
        return 
nullptr;
    }
    
    for(
int i 0iSizei++)
    {
        
StoreToAddress(pAddrdata[i], NumberType_Int8);
        
        
pAddr++;
    }
    
    
pAddr++;
    
    return 
pAddr;
}

stock Pointer FindPattern(Pointer pStartint iSizeint[] patternint iPatternSizeint iOffset)
{
    for(
int i 0iSizei++)
    {
        if(
ReadByte(pStart) == pattern[0])
        {
            
bool bFound true;
            
            for(
int j 1iPatternSizej++)
            {
                
int iDestByte ReadByte(ptr(int(pStart) + j));
                
int iSrcByte pattern[j];

                if(
iSrcByte != 0xFF)
                {
                    if(
iDestByte != iSrcByte)
                    {
                        
bFound false;
                        break;
                    }
                }
            }
            
            if(
bFound)
            {
                return 
ptr(int(pStart) + iOffset); 
            }
        }
        
        
pStart++;
    }

    return 
nullptr;
}

stock int GetModuleSize(Pointer pAddr)
{
    if(
pAddr == nullptr)
    {
        return 
0;
    }
    
    if(
ReadWord(pAddr) == 0x5A4D// MZ
    
{
        
int iOffset Dereference(pAddr0x3C); // NT headers offset
        
int iRes Dereference(pAddriOffset 0x50); // nt->OptionalHeader.SizeOfImage
        
return iRes;
    }
    else
    {
        return -
1;
    }

WriteData() is needed to write an array of bytes to the specified address. Do not pay attention to the fact that "int[]" is passed, the function works with this argument, as with "byte[]". GetModuleSize() function gets the virtual size of the module loaded into the address space. In the pAddr argument, we need to pass the result of GetModuleHandle()/LoadLibrary() functions, but we are going to talk about this next time. FindPattern() is needed to find the pattern in memory without using gamedata.

Now let's write a fairly specific function:

PHP Code:
bool CreateMemoryForSDKCall(Pointer pAddr)
{
    if(
pAddr == nullptr)
    {
        return 
false;
    }
    
    static 
Pointer pZeroMem nullptr;
    
    if(
pZeroMem != nullptr)
    {
        return 
true;
    }
    
    
pAddr ptr(int(pAddr) + GetModuleSize(pAddr) - 1);
    
    
/* I could use "true", but compiler blames this line with "redundant test" */
    
while(pAddr)
    {
        
int b ReadByte(pAddr);
        
        if(
!= 0x00)
        {
            break;
        }
        
        
pAddr--;
    }
    
    
/* Align for safe code injection */
    
pZeroMem ptr(int(pAddr) + 0x10 0xFFFFFFF0);
    
    
/* Add unique signature for PrepSDKCall_SetSignature() call */
    
Addr pZeroMem;
    for(
int i 04i++)
    {
        
StoreToAddress(pAddr0xDEADBEEFNumberType_Int32);
        
pAddr Transpose(pAddr4);
    }
    
    return 
true;

So far I will not focus on this function, I'll just say that without this, things are not going to work.

And now we will write something really impressive:

PHP Code:
Pointer g_pPEB nullptr;

int g_GetPEBAsmCode[] = 
{
    
0x640xA10x300x000x000x00// mov eax, dword ptr fs:[30]
    
0xC3                                // ret
};

int g_DeadBeef[] =
{
    
0xEF0xBE0xAD0xDE,
    
0xEF0xBE0xAD0xDE,
    
0xEF0xBE0xAD0xDE,
    
0xEF0xBE0xAD0xDE
};

/* Get Process Environment Block pointer */
stock Pointer NtCurrentPeb()
{    
    
/* Don't do these crazy stuffs again */
    
if(g_pPEB != nullptr)
    {
        return 
g_pPEB;
    }
    
    
Handle h LoadGameConfigFile("any.tutor.memory");
    if(
== null)
    {
        return 
nullptr;
    }

    
Pointer pServerBase GameConfGetAddress(h"server");
    
    
delete h;    
    
    if(
pServerBase == nullptr)
    {
        return 
nullptr;
    }
    
    if(
CreateMemoryForSDKCall(pServerBase) == false)
    {
        return 
nullptr;
    }

    
/* Init handle variable for call */
    
Handle hGetPEB INVALID_HANDLE;
    
    
/* SDKCall_Static means don't use "this" argument */
    
StartPrepSDKCall(SDKCall_Static);
    
    
/* Create SDK call by finding our memory pointer from CreateMemoryForSDKCall() */
    
PrepSDKCall_SetSignature(SDKLibrary_Server"\xEF\xBE\xAD\xDE\xEF\xBE\xAD\xDE\xEF\xBE\xAD\xDE\xEF\xBE\xAD\xDE"16);
    
PrepSDKCall_SetReturnInfo(SDKType_PlainOldDataSDKPass_Plain);
    
hGetPEB EndPrepSDKCall();
    
    
/* Failed to create SDKCall handle. Probably special signature wasn't found */
    
if(hGetPEB == INVALID_HANDLE)
    {
        return 
nullptr;
    }
    
    
int iServerSize GetModuleSize(pServerBase);
    
Pointer p FindPattern(pServerBaseiServerSizeg_DeadBeef160);
    
    if(
== nullptr)
    {
        return 
nullptr;
    }
    
    
/* Create little function that will help us to get process' PEB */
    
WriteData(pg_GetPEBAsmCode7);
    
    
g_pPEB SDKCall(hGetPEB);
    
    
/* We don't need this SDK call anymore */
    
delete hGetPEB;
    
    return 
g_pPEB;

Looks interesting, isn't it? Perhaps someone will even call it perversion and insanity, but nevertheless, let's take it all in order.

First, the function checks whether the PEB has already been found, and returns its address if it was. Then the address of the server module is obtained. We use a small trick here, based on the architecture of PE files, namely the rule that the first two bytes of exe or dll are equal to the "MZ" constant. When we start the search, the SourceMod core immediately stumbles upon these two bytes and returns the address of the beginning of the module. After all this, the CreateMemoryForSDKCall() function is called. Its goal is to find a unique pattern in the unused area of ​​the address space of the server module, which will be found by the function PrepSDKCall_SetSignature() and FindPattern() later. Then we build an SDK call, which is not really an SDK, but our future function, written in x86 assembler. PrepSDKCall_SetSignature() simply finds our unique pattern, in which we will be able to write any code in the future. Then we use FindPattern() to find the same place and change it, implementing our assembler code there. After all this, we call the SDKCall() function and get our PEB address. Cunningly, is not it?

Now, with PEB on hand, we can do so many interesting things. Let's try to get the srcds address, for example.

PHP Code:
stock Pointer GetSRCDSPtr()
{
    
/* Impossible to get module handle without PEB */
    
if(NtCurrentPeb() == nullptr)
    {
        return 
nullptr;
    }
    
    return 
ptr(Dereference(NtCurrentPeb(), 8)); // g_pPEB->ImageBaseAddress
}

// ...

public void OnPluginStart()
{
    
PrintToServer("srcds.exe : Addr=0x%X"GetSRCDSPtr());

The variable ImageBaseAddress, which is part of the PEB structure is located at offset 8 and contains the address of the process that loaded the library. GetSRCDSPtr() function just extract this number, nothing complicated. By the way, GetModuleHandle() function works just similar in Windows OS if its argument is equal to 0.

Conclusion

The article turned out to be quite big as for me, so I think that is enough for now. We examined the implementation of obtaining a version of Windows and the ability to execute its own x86 assembler code. As you can see, this is quite possible. If you like this topic, then I will continue writing these kind of tutorials, in which I will consider even more interesting things. Thank you for reading.

Last edited by 2010kohtep; 08-24-2017 at 18:35.
2010kohtep is offline
ThatKidWhoGames
Veteran Member
Join Date: Jun 2013
Location: IsValidClient()
Old 08-23-2017 , 20:22   Re: [ANY] Work with memory in SourcePawn
Reply With Quote #2

Very nice! Thanks for this!!
ThatKidWhoGames is offline
milutinke
AlliedModders Donor
Join Date: Jun 2012
Location: Serbia
Old 08-24-2017 , 15:26   Re: [ANY] Work with memory in SourcePawn
Reply With Quote #3

Nice, thank you for this.
It is useful.
I personally would like to see more examples
milutinke is offline
Send a message via Skype™ to milutinke
Naydef
Senior Member
Join Date: Dec 2015
Location: Doom Town, Nevada
Old 09-05-2017 , 14:45   Re: [ANY] Work with memory in SourcePawn
Reply With Quote #4

Looks well, but I'm not sure if you can call functions in buffer for data. Does SDKCall() already call VirtualProtect() with execute flag? What about Linux? Probably we can call kernel with int 80h. This makes me think you can exploit/hack servers/OS only with embedding shellcode in plugin.
__________________
My plugins:
*None for now*


Steam:
naydef
Naydef is offline
Fyren
FyrenFyrenFyrenFyrenFyren FyrenFyrenFyrenFyrenFyren FyrenFyrenFyrenFyrenFyren FyrenFyrenFyrenFyrenFyren
Join Date: Feb 2106
Old 09-05-2017 , 16:35   Re: [ANY] Work with memory in SourcePawn
Reply With Quote #5

SDKCall definitely isn't going to set memory executable, but at least some of a plugin's memory is necessarily going to be. Perhaps it all is, but I didn't check the VM code. I assume the example in the first post works, which would indicate it is (whether intentional or not).
Fyren is offline
2010kohtep
Junior Member
Join Date: Aug 2017
Old 10-18-2017 , 08:22   Re: [ANY] Work with memory in SourcePawn
Reply With Quote #6

Quote:
Originally Posted by Naydef View Post
Looks well, but I'm not sure if you can call functions in buffer for data. Does SDKCall() already call VirtualProtect() with execute flag?
If you look at the SourceMod source code, namely the CallWrapper::Execute method, then you can see that VirtualProtect() is not called. There's just the pointer to the code that will be called. No manipulation of the code memory occurs.

Quote:
Originally Posted by Naydef View Post
What about Linux? Probably we can call kernel with int 80h.
Yes, in Linux, you can use int 80h to call the kernel, but I did not reproduce it myself, because my server uses Windows. In theory this is very much possible.

Quote:
Originally Posted by Naydef View Post
This makes me think you can exploit/hack servers/OS only with embedding shellcode in plugin.
Yes, this will allow you to write a plugin that will have access to the WinAPI/NativeAPI/LinuxKernel functions, with all the consequences, of course, but I think you will not have trouble checking out source code of the plugin before installing it on the server. But if you are developing your own plugin, then this will give you a number of possibilities, as for me.

Last edited by 2010kohtep; 10-19-2017 at 18:04.
2010kohtep is offline
Bacardi
Veteran Member
Join Date: Jan 2010
Location: mom's basement
Old 09-23-2023 , 15:34   Re: [ANY] Work with memory in SourcePawn
Reply With Quote #7

Thanks, on Windows to get that server.dll address what I looked.
__________________
Do not Private Message @me
Bacardi is offline
Reply


Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -4. The time now is 22:55.


Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Theme made by Freecode