Иконка ресурса

[INC] Memory Extended 3.1

Rostu

Добрая душа
Сообщения
986
Реакции
622
  • Автор ресурса
  • #21
Фишка конечно прикольная с созданием энтити взамен malloc, уже хотел было взять на вооружение,
только вот она ставит временные ограничения на момент использования этой функции.

Эх, если бы что-нибудь такое же простое придумать.
Слышал есть такие энтити, которые сохраняются даже при смене карты.

PS. В принципе, как еще более дикий план, можно задействовать World (0), и сразу же восстановить ему память =)
Я долго думал над этим - иного способа я не заметил - эта штучка появляется только на Windows - в виде решения вы обязаны делать CheckInitPEB где будет вызвана функция MemoryEx_InitPEB и это реализовано в MemoryEx.
Вот тут посмотрите - я исправлял человеку

Ни одна энтити при смене карты не сохраняется - все это не работает
Я долго пытался это сделать в одном тике - при запуске сервера - все пути у меня не получились
world entity (0) и так действует в этих манипуляциях - посмотрите логику CheckInitPEB по ссылке ниже🙂

И по этому, нужно делать все манипуляции не в OnPluginStart...
C-подобный:
public void OnPluginStart()
{
    CheckInitPEB();
}
public Action MemoryEx_InitPEB()
{
// any code...
}

Наверное нужно об этом где-то написать...
 
Последнее редактирование:

Dragokas

Добрая душа
Сообщения
229
Реакции
213
Нашел ошибочку в MemoryAlloc.inc, функция VirtualAlloc под винду.

C-подобный:
pFunc = GetImportAddress("kernel32", "HeapAlloc");

HeapAlloc ведь должна браться из таблицы импорта своего модуля, а не kernel32, аналогично как у вас дальше по коду для линукс:

C-подобный:
pFunc = GetImportAddress(NULL_STRING, "malloc");

Попробовал варианты:

C++:
            // case 1
            pFunc = GetImportAddress(NULL_STRING, "HeapAlloc");
            LogError("GetImportAddressWindows(NULL_STRING, HeapAlloc) = %i", pFunc);
           
            // case 2
            Address pBase = GetModuleHandle("0");
            LogError("self base GetModuleHandle(0) = %i", pBase);
           
            // case 3
            ModuleInfo info;
            StringMap list = WindowsGetDllList(false);
            list.GetArray("srcds.exe", info, sizeof(ModuleInfo));
            pBase = info.base;
            LogError("self base (srcds.exe) = %i", pBase);  
           
            pFunc = GetImportAddressWindows(pBase, "HeapAlloc");
            LogError("GetImportAddressWindows(pBase, HeapAlloc) = %i", pFunc);

GetImportAddressWindows(NULL_STRING, HeapAlloc) = 0
self base GetModuleHandle(0) = 0
self base (srcds.exe) = 19464192
GetImportAddressWindows(pBase, HeapAlloc) = 2005786902

Case 1. Похоже, семантика с NULL_STRING для винды в коде не предусмотрена.
Case 2. Вариант из примера 1-го поста через GetModuleHandle("0") почему-то не работает (не стал разбираться). Имя процесса туда тоже не подставишь, т.к. дальше в коде идёт автоподстановка ".dll".
Case 3. Норм. отрабатывает. Но хотелось бы иметь возможность получать адрес базового модуля лаконичней.
 

Rostu

Добрая душа
Сообщения
986
Реакции
622
  • Автор ресурса
  • #23
Нашел ошибочку в MemoryAlloc.inc, функция VirtualAlloc под винду.

C-подобный:
pFunc = GetImportAddress("kernel32", "HeapAlloc");

HeapAlloc ведь должна браться из таблицы импорта своего модуля, а не kernel32, аналогично как у вас дальше по коду для линукс:
Не совсем - я хотел его брать из таблицы экспорта данной библиотеки - ошибся, давно у себя исправил никак на гит не залью. Почему не из импорта модуля? - Не все функции, которые может дать Kernel32 - используется и не все они в импорте будут

Case 2. Вариант из примера 1-го поста через GetModuleHandle("0") почему-то не работает (не стал разбираться). Имя процесса туда тоже не подставишь, т.к. дальше в коде идёт автоподстановка ".dll".
Тут согласен, немного костыльно работает с exe файлами из-за авто-подстановки
надо что-то сделать
Спасибо🙂
 

Dragokas

Добрая душа
Сообщения
229
Реакции
213
Здравствуйте, Rostu!

А вы не планировали добавить поддержку GameData конфигов? Чтобы прям от туда читать исходную инфу для паттернов.
Лично мне сигнатуры удобно хранить как и обычно, в отдельных файлах в gamedata,
но SourceMod терпеть не может gamedata, в котором в параметре "library" стоит что-то нестандартное.

Вот примерно так я вижу:
GameDataEx.inc
C++:
#include <MemoryEx>

methodmap GameDataEx < KeyValues
{
    public GameDataEx(char[] gameconf)
    {
        char sPath[PLATFORM_MAX_PATH];
        BuildPath(Path_SM, sPath, sizeof(sPath), "gamedata/%s.txt", gameconf);
        
        KeyValues kv = new KeyValues("Games");
        if( !kv.ImportFromFile(sPath) ) {
            LogError("Failed to load \"%s.txt\" gamedata as keyvalues file.", gameconf);
            delete kv;
        }
        return view_as<GameDataEx>(kv);
    }
    
    public void GetSourceEngineName(char[] gamename, int iSize)
    {
        static char name[32];
        if( name[0] == 0 ) {
            switch( GetEngineVersion() ) // CoreProviderImpl::GetSourceEngineName()
            {
                case Engine_Original:         name = "original";
                case Engine_Left4Dead:         name = "left4dead";
                case Engine_DarkMessiah:     name = "darkmessiah";
                case Engine_Left4Dead2:        name = "left4dead2";
                case Engine_AlienSwarm:        name =  "alienswarm";
                case Engine_BloodyGoodTime:    name =  "bloodygoodtime";
                case Engine_EYE:            name =  "eye";
                case Engine_Portal2:        name =  "portal2";
                case Engine_CSGO:            name =  "csgo";
                case Engine_CSS:            name =  "css";
                case Engine_HL2DM:            name =  "hl2dm";
                case Engine_DODS:            name =  "dods";
                case Engine_TF2:            name =  "tf2";
                case Engine_NuclearDawn:    name =  "nucleardawn";
                case Engine_SDK2013:        name =  "sdk2013";
                case Engine_Blade:            name =  "blade";
                case Engine_Insurgency:        name =  "insurgency";
                case Engine_Contagion:        name =  "contagion";
                case Engine_BlackMesa:        name =  "bms";
                case Engine_DOI:            name =  "doi";
                default: {
                    LogError("Unknown engine: %i", GetEngineVersion());
                    return;
                }
            }
        }
        FormatEx(gamename, iSize, name);
    }
    
    public bool JumpSection(char[] name, char[] type)
    {
        this.Rewind();
        if( this.JumpToKey("#default", false) )
        {
            if( this.JumpToKey(type, false) )
            {
                if( this.JumpToKey(name, false) )
                {
                    return true;
                }
            }
        }
        
        static char gamename[32];
        if( gamename[0] == 0 )
            this.GetSourceEngineName(gamename, sizeof(gamename));
        
        this.Rewind();
        if( this.JumpToKey(gamename, false) )
        {
            if( this.JumpToKey(type, false) )
            {
                if( this.JumpToKey(name, false) )
                {
                    return true;
                }
            }
        }
        
        LogError("Cannot find signature section '%s'", name);
        return false;
    }
    
    public bool GetLibrary(char[] name, char[] pattern, int size)
    {
        if( this.JumpSection(name, "Signatures") )
        {
            this.GetString("library", pattern, size);
            return pattern[0] != 0;
        }
        LogError("Cannot find library of '%s'", name);
        return false;
    }
    
    public bool GetPattern(char[] name, char[] pattern, int size)
    {
        if( this.JumpSection(name, "Signatures") )
        {
            this.GetString(GetServerOS() == OS_Windows ? "windows" : "linux", pattern, size);
            return pattern[0] != 0;
        }
        LogError("Cannot find pattern of '%s'", name);
        return false;
    }
    
    public int GetPatternBytes(char[] name, int[] pattern, int size)
    {
        char sig[256];
        if( this.GetPattern (name, sig, sizeof(sig)) )
        {
            int cnt, f1, f2, p, n, len = strlen(sig);
            while( p < len && n < size )
            {
                if( f1 && f2) // "\x"
                {
                    cnt = StringToIntEx(sig[p], pattern[n++], 16);
                    p += cnt - 1;
                    f1 = 0;
                    f2 = 0;
                }
                else if( !f1 && sig[p] == '\\' )
                    f1 = 1;
                else if( f1 && sig[p] == 'x' )
                    f2 = 1;
                else {
                    pattern[n++] = sig[p];
                    f1 = 0;
                    f2 = 0;
                }
                ++p;
            }
            return n;
        }
        LogError("Cannot parse pattern bytes of '%s'", name);
        return 0;
    }
    
    public Pointer GetAddress(char[] name)
    {
        char sLibrary[64];
        int pattern[256], iSize;
        this.GetLibrary(name, sLibrary, sizeof(sLibrary));       
        iSize = this.GetPatternBytes(name, pattern, sizeof(pattern));
        
        return g_hMem.lib.FindPattern(sLibrary, pattern, iSize);
    }
    
    public int GetOffset(char[] name)
    {
        if( this.JumpSection(name, "Offsets") )
        {
            char sNum[16];
            int ret;
            this.GetString(GetServerOS() == OS_Windows ? "windows" : "linux", sNum, sizeof(sNum));
            if( sNum[0] != 0 ) {
                if( strlen(sNum) > 2 && strncmp( sNum, "0x", 2, false ) == 0 )
                {
                    ret = StringToInt(sNum[2], 16);
                }
                else {
                    ret = StringToInt(sNum, 10);
                }
                return ret;
            }
            else {
                LogError("Cannot find offset '%s'", name);
            }
        }
        return 0;
    }
}
gamedata/Tier_MemScan.txt
PHP:
"Games"
{
    "left4dead2"
    {
        "Offsets"
        {
            "Detour_Bytes" // mov [esi+1Ch], ecx (3 bytes)
            {
                "windows"        "0x1C4E89"
            }
        }
        
        "Signatures"
        {
            "Tier_Sub1"
            {
                "library"        "tier0"
                "windows"        "\x89\x4E\x1C\x89\x7E\x14\x89\x7E\x10\x8B\x7B\x04\x8D\x94\x8B\x2A\x2A\x00\x00"
                /* 89 4E 1C 89 7E 14 89 7E 10 8B 7B 04 8D 94 8B ? ? 00 00 */
                /* Found by Crash Address "tier0.dll + 0x1991d" */
                /* Has XRef with VirtualQuery (import) call */
                /* Notice: sig points to crash place, not EP */
            }
        }
    }
}
Пример использования:
C++:
#include <MemoryEx>
#include "include/GameDataEx"

public void OnPluginStart()
{
    CheckInitPEB();
}

public Action MemoryEx_InitPEB()
{
    // Открываем GameData. Tier_MemScan - имя txt файла в sourcemod/gamedata/
    GameDataEx gd = new GameDataEx("Tier_MemScan");
    if( gd != null )
    {
        // Получаем адрес функции из GameData (библиотека tier0, имя секции "Tier_Sub1")
        // Прим: Секция "Addresses" не анализируется и не требуется.
        Address Addr_Sub = gd.GetAddress("Tier_Sub1");
        
        // Получаем служебное значение из секции "Offsets".
        // Прим.: допускаются числа в Hex (т.е. "windows" "0xFF")
        int offs = gd.GetOffset("Detour_Bytes");
        
        PrintToServer("addr = %i", Addr_Sub);
        PrintToServer("offs = %i", offs);
        
        delete gd;
    }
}

Да, знаю, что SM использует SMC, но принципиально сделал по-проще пока.
+ в моем варианте, секция "Addresses" не нужна, адрес сразу берется по имени секции из "Signatures". Иначе пришлось бы реализовывать и поддержку "read" / "offset".
+ возможность указывать "Offsets" в формате Hex. А то постоянно приходится пересчитывать.

Собственно issues для SM тоже открыл, вдруг когда-нибудь реализуют(?)

И ещё пожелание, добавить в MemoryEx.inc:
C++:
#if defined _MemoryEx_included
 #endinput
#endif
#define _MemoryEx_included
Чтобы инклуд не включался случайно дважды (вот как в моем примере выше).
 

Dragokas

Добрая душа
Сообщения
229
Реакции
213
Мда, использовать KeyValues для чтения оффсетов вида "0x" бесполезно.
Там какая-то бага с неявным преобразованием строки в число типа float + 1.0 с попыткой преобразования в десятичное.


Интересно, KeyValues это вообще функции чисто SM, или часть движка Source ...
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #26
Правильный ответ -
часть движка Source
SM просто пробрасывает API движка в SP. Потому этот Issue они скорее всего закроют, сославшись, что они ничего не могут сделать.
Именно поэтому, кстати, для чтения геймдаты и прочих хитрых структурок, в SM используется их собственный парсер KeyValues - SMC, он же textparse.inc.
 

Dragokas

Добрая душа
Сообщения
229
Реакции
213
А у вашего inc есть поддержка поиска адреса по именованной метке, т.е. linux "@_FuncName" ?
Так, как это делает GameConfGetAddress.
 

Rostu

Добрая душа
Сообщения
986
Реакции
622
  • Автор ресурса
  • #28
А у вашего inc есть поддержка поиска адреса по именованной метке, т.е. linux "@_FuncName" ?
Так, как это делает GameConfGetAddress.
Можно только через GetProcAddress функции из экспорта
по типу @DevWarning@@YAXPBDZZ
 

Dragokas

Добрая душа
Сообщения
229
Реакции
213
@Rostu, вы случайно не обновляли GetProcAddressLinux?

Пробую получить адрес из экспорта:
C-подобный:
Address pFunc = g_hMem.GetProcAddress("server_srv", "_ZN22ChargerReturnToNavMesh6UpdateEP7Chargerf");
Возвращает 0.
C-подобный:
readelf -Ws ./server_srv.so
...
Symbol table '.symtab' contains 56505 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
...
21385: 00a3cb30   266 FUNC    LOCAL  DEFAULT   11 _ZN22ChargerReturnToNavMesh6UpdateEP7Chargerf
 
Последнее редактирование:

Dragokas

Добрая душа
Сообщения
229
Реакции
213
@Rostu, я отправил пул запрос на GitHub с протестированным кодом, а здесь оставлю подробное описание.

Что добавлено:
1) GameDataEx.inc - методмап для работы с GameData конфигами.
(финальная версия с исправлением всех упомянутых багов, работает через SMC Parser, базовый тип - всё так же KeyValues).
- умеет подгружать GameData конфиги с кастомными названиями модулей.
- допускается формат Hex для смещений (оффсетов).
PHP:
Pointer GetAddress(char[] name) // возвращает адрес функции (при этом, секция "Address" в GameData не требуется).
int GetOffset(char[] name) // возвращает оффсет из секции "Offsets" по его имени.

2) MemSearcher.inc
PHP:
bool IsValidAddress(Address addr, int &numbytes) // отвечает, доступен ли адрес для чтения (определяет это через перечисление всех регионов памяти и проверки прав доступа)
// как вариант, можно было бы переименовать в ! IsBadReadPtr
Потом сможете воспользоваться этими же функциями для реализации самого Mem Searcher, или что там задумывалось.

3) BinaryFile.inc - методмап для работы с бинарными файлами (используется далее в LinuxFunction)
Предоставляет методы с похожими названиями как и при работе с памятью (в идеале, для LinuxFunction.inc переделать под BinaryStream, чтобы не читать постоянно с диска, а замапить сразу весь файл на память).
PHP:
enum FILE_ACCESS
{
    FILE_READ = 1,
    FILE_WRITE = 2,
    FILE_APPEND = 4,
    FILE_OVERWRITE = 8
}
new BinaryFile(char[] path, FILE_ACCESS access) // открытие файла в бинарном режиме с указанным флагом (-ами) доступа
int LoadFromAddress(Address base, NumberType size) // прочитать число (1, 2 или 4 байта) по указаному адресу (из файла)
Address LoadFromAddressEx(Address base, NumberType size) // тоже самое, результат возвращается с типом Address
int StoreToAddress(Address base, int data, NumberType size) // записать число (1, 2 или 4 байта) по указаному адресу (в файл). (не тестировалось)
int ReadString(Address base, char[] sResult, int iMaxLength) // прочитать ANSI строку по адресу (функция - копия вашего BaseMemory.inc)
int ReadUnicodeString(Address base, char[] sResult, int iMaxLength) // прочитать Unicode строку по адресу (функция - копия вашего BaseMemory.inc)
int WriteString(Address base, const char[] sString, bool bNull = true) // записать строку по адресу (не тестировалось)

4) Новые методы для Stocks.inc
PHP:
int LoadFromAddressInt24(Address base) // прочитать 3 байта из памяти по адресу
void StoreToAddressArray(Address base, int[] data, int iSize) // записать в память по адресу содержимое массива
int GetByte(int iNum32, int iByteIndex) // извлечь байт из Dword (int) по номеру байта (справа налево). Счёт от единицы.
void ArrayPushDword(int[] array, int &index, int data) // расщепить dword на байты и записать в байтовый массив, начиная с указанного индекса; index возвращается инкрементированным на 4 (удобно для подготовки пэйлоада с динамическими адресами (для джампов и т.п.)

Изменения:

1) Повтыкал блокировки повторных инклудов
2) Модифицированы LinuxParseMapsFile / WindowsGetDllList. Новый опциональный аргумент bAddressAsKey.
PHP:
    /*
        bAddressAsKey -> true
            StringMap
                key   == базовый адрес модуля
                value == полный путь к модулю
      
        bAddressAsKey -> false - старое поведение
            StringMap
                key   == имя модуля
                value == структура LibraryInfo { base; size }
    */
Получить новую форму StringMap можно через:
PHP:
stock StringMap GetListModulePath()
3) Дописан GetProcAddressLinux.
Сразу говорю, что разбором ELF занимаюсь впервые, так что могут быть ошибки/неточности. Распишу то, как я понял.

Дело в том, что в формате эльфа есть 2 строковые таблицы с символами для экспортируемых функций:
- .dynsym
- .symtab
При этом в режиме исполнения в памяти хранится только первая (.dynsym).
Несмотря на то, что вы извлекаете из динамического сегмента указатель на динамическую секцию, имеющую тип DT_SYMTAB - пусть вас это название не заводит в заблуждение, указатель ведёт именно на .dynsym, которая содержит самый минимальный набор функций. В то же время, .symtab в памяти нет вообще и его можно прочитать только из файла нужного модуля.

У меня в коде из файла читается таблица секций, выбираются типы SHT_STRTAB и SHT_SYMTAB, который собственно и указывает на .symtab.
Дальше таблица читается аналогично, как у вас, и в итоге к целевому адресу функции в файле добавляется адрес базового модуля в памяти, чтобы получить адрес функции в памяти.
Поиск по имени функции не ведётся сразу. Вместо этого читается вся таблица целиком в StringMap, и затем 2 и сл. разы адрес забирается уже из кеша. И да, в угоду простоты, кеш получился смешанным, т.е. сюда сбрасываются имена функций от скана любых других имён модулей, если таковые буду запрошены.

Из минусов, работа с файлом ведётся напрямую (без маппинга). Чтения всей .symtab таблицы (а например, на L4D2 это 56000 символов) у меня занимает почти секунду (на SSD). В перспективе лучше переделать, прочитав файл в память целиком.

4) MemoryAlloc.inc - VirtualAlloc/FreeMemory под Win временно переделал под получение адреса из таблицы импорта srcds.exe, чтобы пока хотя бы так работало.
 
Последнее редактирование:

Dragokas

Добрая душа
Сообщения
229
Реакции
213
Форк v3.1.6
Изменения:
- Добавлен дизассембер длин опкодов (спасибо Ms-rem и The Trick). SIMD и x64 не поддерживаются. Новые свойства g_ASM:
* SizeOfCode - размер инструкции
* SizeOfProc - размер процедуры
* IsRelativeCmd - отвечает, относительный ли прыжок
- Маппинг matchmaking_ds к _srv (для GameDataEx конфигов Linux/Mac)
- Определения PTR, Pointer, nullptr признаны устаревшими, чтобы не путать с указателями на Cи. Используйте ADDR и Address_Null.
- Новые определения: LA_8, LA_16, LA_24, LA_32 - ярлыки для LoadFromAddress соответственно для получения 1,2,3,4-байтовых данных типа int.
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #32
@Dragokas, опубликуйте отдельным ресурсом. Так лучше будет.
 

xstage

🏹
Сообщения
726
Реакции
754
@Rostu тут проблема выхода динамического массива за пределы в файле MemoryEx/Stocks.inc на 89 строке:

1667681256712.png


На Linux почему-то все отрабатывает, на Windows выпадает ошибка
 
Сверху Снизу