Вступление
Доброго времени суток всем. Не так давно я решил заняться собственным сервером Team Fortress 2, и для этого мне понадобилось написание плагинов с помощью SourcePawn. Я начал изучать этот язык относительно недавно и в целом он мне нравится, за исключением некоторых моментов, о которых я бы хотел поговорить сегодня, а также обсудить решения для них.
Если вы не хотите читать философские рассуждения на тему ограниченности языка, то я рекомендую пропустить данный раздел и сразу перейти к следующему.
Один из этих моментов - ограничение в использовании адресного пространства процесса srcds. Увы, но мы можем работать только с библиотеками server и engine. Ну, этого, по идее, должно хватить, так как основная работа движка происходит именно в этих модулях. Однако, не поймите меня неправильно, но я не люблю чувствовать себя ограниченным при работе с памятью, хех. Тем не менее, с этим всё же можно смириться, думал я первое время.
Однако то, что мне действительно чертовски не понравилось, так это отсутствие возможности "смешивания" памяти плагина и сервера. То есть если я захочу внедрить указатель на какой-либо участок памяти своего плагина в любую библиотеку процесса или сам процесс srcds - я буду вынужден воздержаться, поскольку во-первых, получать нативные адреса собственных переменных или функций плагина невозможно, а во-вторых, работать я могу только с, опять же, server или engine. Даже если бы у меня была возможность получения адресов с помощью "#emit", то получал бы я отнюдь не адреса относительно начала памяти процесса (нуля), а относительно специальной памяти плагина. Более того, в самом плагине нельзя выделить память процесса.
Опять же, не поймите меня неправильно! Я не критикую язык SourcePawn с намеком на то, что язык нуждается в доработке. SourcePawn - не тот язык, который предназначен для сложных манипуляций с памятью. Он предназначен для написания обычных плагинов, которые будут менять существующий функционал на сервере или добавлять новый. В общем, неплохой язык для написания сервер-сайд модификаций. Однако те моменты, которые я перечислил выше, лично мне очень не нравятся, и поэтому здесь я бы хотел предложить варианты решения нескольких проблем для серверов на платформе Windows, с которыми я столкнулся при разработке собственных плагинов.
Я практически уверен в том, что кто-то скажет, что для реализации подобного функционала можно воспользоваться native функциями, и я буду вынужден согласиться. С помощью native можно реализовать всё, чем я был недоволен до этого, однако, я предпочитаю не таскать за своими проектами огромное количество модульных зависимостей, а native функции заставят вас это делать. Поэтому все решения проблем я буду воспроизводить с использованием чистого SourcePawn кода, который можно будет аккуратно завернуть в include файл.
Начнем же.
Что мы имеем?
Чтобы найти какой-то паттерн кода с целью произвести его патчинг, нам дают возможность использовать специальную вещь - gamedata. Это файл, в котором вы указываете паттерн для поиска, модуль, операционную систему и оффсет. Достаточно полезная вещь.
Для работы с чистой памятью процесса (не плагина) мы имеем две основные функций - StoreToAddress() и LoadFromAddress(). Данные функции дают возможность обращаться к памяти по указанному адресу, но так как полноценной информации о адресах у нас нет (мы можем получать адреса только поиском с помощью gamedata), то мы остаемся слепы в этом вопросе.
Еще одна самая главная особенность чистого языка - функция SDKCall(), которая была разработана для вызовов функций классов, как ни странно.
Вот и всё. Эти вещи позволят нам сделать SourcePawn намного мощнее в плане работы с памятью.
Получение версии Windows
Для начала начнем с самого простого - попробуем получить версию нашей операционной системы. Как это сделать, не прибегая к native? Очень просто! Следите за руками.
В ОС Windows есть особое место в памяти, адрес которого статичен еще со времен Windows 2000. Это место представляет собой структуру KUSER_SHARED_DATA, которая как раз-таки и содержит необходимую для нас информацию. Адрес данной структуры - 0x7FFE0000. Давайте на основе имеющейся информации напишем функцию для получения версии ОС.
Как видите - ничего сложного, за исключением не самой лучшей читабельности кода, но это можно исправить с помощью "#define".
Получение PEB
Process Environment Block, пожалуй, одна из самых полезных и нужных для нас вещей. С его помощью можно получить доступ к списку загруженных модулей, а также адрес самого процесса srcds. Есть только одно "но" - его невозможно получить с помощью LoadFromAddress(), так как его адрес меняется от запуска к запуску сервера. Более того, получить его можно только прибегнув к x86 ассемблеру. Вот как это выглядит на языке C++:
Выглядит просто, не так ли? Увы, но выполнить подобный трюк на SourcePawn невозможно. Ну а если сильно захотеть? Давайте попробуем.
Прежде всего необходимо создать файл в папке gamedata, назовем его "any.tutor.memory":
Увы, но решение без использования данного файла я не нашел. Зачем это необходимо я разъясню позднее.
Далее реализуем необходимый функционал. Для начала напишем вот такой код:
Данные дефайны можно опустить, но дабы увеличить читабельность кода я буду их использовать.
Затем напишем функции чтения данных по адресу:
Мы написали обертки для LoadFromAddress(), которые позволят читать типы данных, которые нам необходимы. Без этих функций наш код стал бы достаточно большим и неудобным в чтении.
После этих функций создадим еще две:
Функция Transpose() нужна для изменения значения указателя, не прибегая к приведению типов. Функция Dereference() аналогична Transpose(), но после изменения адреса она еще и произведет разыменования указателя. Опять же - всё для повышенной читабельности кода.
А теперь реализуем действительно необходимый функционал:
WriteData() нужна для записи массива байт по указанному адресу. Не обращайте внимание на то, что передается "int[]", функция работает с этим аргументом, как с "byte[]". GetModuleSize() функция получает виртуальный размер модуля, загруженного в адресное пространство. В аргумент pAddr нужно передавать результат GetModuleHandle()/LoadLibrary(), но об этом позднее. FindPattern() нужна для поиска паттерна в памяти без использования gamedata.
Теперь напишем достаточно специфичную функцию:
Пока что я не буду заострять на этой функции внимание, просто скажу, что без ее помощи у нас ничего не получится.
А теперь напишем кое-что действительно впечатляющее:
Впечатляет, не правда ли? Возможно, кто-то даже назовет всё это извращением и безумием, но тем не менее давайте разберем всё по порядку.
Сначала функция проверяет, был ли уже найдена PEB, и возвращает его адрес, если была. Затем происходит получение адреса модуля server. Мы используем здесь небольшой трюк, основываясь на архитектуре PE файлов, а именно правилу, что первые два байта exe или dll равны константе "MZ". При старте поиска ядро SourceMod сразу же натыкается на эти два байта и возвращает адрес начала модуля. После всего этого вызывается функция CreateMemoryForSDKCall(). Ее цель - создание уникального паттерна в неиспользуемой области адресного пространства модуля server, который будет найден функцией PrepSDKCall_SetSignature() и FindPattern(). Затем мы строим SDK вызов, который на самом деле является не SDK, а нашей будущей функцией, написанной на x86 ассемблере. PrepSDKCall_SetSignature() просто находит наш уникальный паттерн, в который мы в будущем сможем записать любой код. Затем мы с помощью FindPattern() находим это же самое место и изменяем его, внедряя туда наш ассемблерный код. После всего этого мы производим вызов SDKCall() функции и получаем наш PEB адрес. Хитро, не правда ли?
Теперь, имея на руках PEB, можно делать очень много интересных вещей. Давайте попробуем получить адрес srcds, например.
Переменная ImageBaseAddress, которая входит в состав структуры PEB и располагается по смещению 8, содержит адрес процесса, который загрузил библиотеку. Функция просто получает это число, ничего сложного. Точно так же работает функция GetModuleHandle(), если в ее аргументе указать 0.
Заключение
Туториал получился достаточно большой, как по мне, поэтому я считаю, что пока что достаточно. Мы рассмотрели реализацию получения версии Windows и возможность выполнения собственного x86 ассемблерного кода. Как видите, это возможно. Если пользователям понравится подобная тематика, то я продолжу написание туториалов, в которых рассмотрю еще более интересные вещи. Благодарю за чтение.
Доброго времени суток всем. Не так давно я решил заняться собственным сервером Team Fortress 2, и для этого мне понадобилось написание плагинов с помощью SourcePawn. Я начал изучать этот язык относительно недавно и в целом он мне нравится, за исключением некоторых моментов, о которых я бы хотел поговорить сегодня, а также обсудить решения для них.
Если вы не хотите читать философские рассуждения на тему ограниченности языка, то я рекомендую пропустить данный раздел и сразу перейти к следующему.
Один из этих моментов - ограничение в использовании адресного пространства процесса srcds. Увы, но мы можем работать только с библиотеками server и engine. Ну, этого, по идее, должно хватить, так как основная работа движка происходит именно в этих модулях. Однако, не поймите меня неправильно, но я не люблю чувствовать себя ограниченным при работе с памятью, хех. Тем не менее, с этим всё же можно смириться, думал я первое время.
Однако то, что мне действительно чертовски не понравилось, так это отсутствие возможности "смешивания" памяти плагина и сервера. То есть если я захочу внедрить указатель на какой-либо участок памяти своего плагина в любую библиотеку процесса или сам процесс srcds - я буду вынужден воздержаться, поскольку во-первых, получать нативные адреса собственных переменных или функций плагина невозможно, а во-вторых, работать я могу только с, опять же, server или engine. Даже если бы у меня была возможность получения адресов с помощью "#emit", то получал бы я отнюдь не адреса относительно начала памяти процесса (нуля), а относительно специальной памяти плагина. Более того, в самом плагине нельзя выделить память процесса.
Опять же, не поймите меня неправильно! Я не критикую язык SourcePawn с намеком на то, что язык нуждается в доработке. SourcePawn - не тот язык, который предназначен для сложных манипуляций с памятью. Он предназначен для написания обычных плагинов, которые будут менять существующий функционал на сервере или добавлять новый. В общем, неплохой язык для написания сервер-сайд модификаций. Однако те моменты, которые я перечислил выше, лично мне очень не нравятся, и поэтому здесь я бы хотел предложить варианты решения нескольких проблем для серверов на платформе Windows, с которыми я столкнулся при разработке собственных плагинов.
Я практически уверен в том, что кто-то скажет, что для реализации подобного функционала можно воспользоваться native функциями, и я буду вынужден согласиться. С помощью native можно реализовать всё, чем я был недоволен до этого, однако, я предпочитаю не таскать за своими проектами огромное количество модульных зависимостей, а native функции заставят вас это делать. Поэтому все решения проблем я буду воспроизводить с использованием чистого SourcePawn кода, который можно будет аккуратно завернуть в include файл.
Начнем же.
Что мы имеем?
Чтобы найти какой-то паттерн кода с целью произвести его патчинг, нам дают возможность использовать специальную вещь - gamedata. Это файл, в котором вы указываете паттерн для поиска, модуль, операционную систему и оффсет. Достаточно полезная вещь.
Для работы с чистой памятью процесса (не плагина) мы имеем две основные функций - StoreToAddress() и LoadFromAddress(). Данные функции дают возможность обращаться к памяти по указанному адресу, но так как полноценной информации о адресах у нас нет (мы можем получать адреса только поиском с помощью gamedata), то мы остаемся слепы в этом вопросе.
Еще одна самая главная особенность чистого языка - функция SDKCall(), которая была разработана для вызовов функций классов, как ни странно.
Вот и всё. Эти вещи позволят нам сделать SourcePawn намного мощнее в плане работы с памятью.
Получение версии Windows
Для начала начнем с самого простого - попробуем получить версию нашей операционной системы. Как это сделать, не прибегая к native? Очень просто! Следите за руками.
В ОС Windows есть особое место в памяти, адрес которого статичен еще со времен Windows 2000. Это место представляет собой структуру KUSER_SHARED_DATA, которая как раз-таки и содержит необходимую для нас информацию. Адрес данной структуры - 0x7FFE0000. Давайте на основе имеющейся информации напишем функцию для получения версии ОС.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | stock void GetWindowsVersion( int & iMajorVer, int & iMinorVer) { 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 iMajor, iMinor; GetWindowsVersion(iMajor, iMinor); PrintToServer( "Windows Version : Major=%d, Minor=%d" , iMajor, iMinor); } |
Как видите - ничего сложного, за исключением не самой лучшей читабельности кода, но это можно исправить с помощью "#define".
Получение PEB
Process Environment Block, пожалуй, одна из самых полезных и нужных для нас вещей. С его помощью можно получить доступ к списку загруженных модулей, а также адрес самого процесса srcds. Есть только одно "но" - его невозможно получить с помощью LoadFromAddress(), так как его адрес меняется от запуска к запуску сервера. Более того, получить его можно только прибегнув к x86 ассемблеру. Вот как это выглядит на языке C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 | void * GetPEB() { __asm { mov eax, dword ptr fs:[30]; } } |
Выглядит просто, не так ли? Увы, но выполнить подобный трюк на SourcePawn невозможно. Ну а если сильно захотеть? Давайте попробуем.
Прежде всего необходимо создать файл в папке gamedata, назовем его "any.tutor.memory":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | "Games" { "#default" { "Addresses" { "server" { "windows" { "signature" "Find_Server" } } } "Signatures" { "Find_Server" { "library" "server" "windows" "\x4D\x5A" } } } } |
Увы, но решение без использования данного файла я не нашел. Зачем это необходимо я разъясню позднее.
Далее реализуем необходимый функционал. Для начала напишем вот такой код:
1 2 3 4 5 6 7 8 9 | #define Pointer Address #define nullptr Address_Null #define int(%1) view_as<int>(%1) #define ptr(%1) view_as<Pointer>(%1) |
Данные дефайны можно опустить, но дабы увеличить читабельность кода я буду их использовать.
Затем напишем функции чтения данных по адресу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | stock int ReadByte(Pointer pAddr) { if (pAddr == nullptr) { return -1; } return LoadFromAddress(pAddr, NumberType_Int8); } stock int ReadWord(Pointer pAddr) { if (pAddr == nullptr) { return -1; } return LoadFromAddress(pAddr, NumberType_Int16); } stock int ReadInt(Pointer pAddr) { if (pAddr == nullptr) { return -1; } return LoadFromAddress(pAddr, NumberType_Int32); } |
Мы написали обертки для LoadFromAddress(), которые позволят читать типы данных, которые нам необходимы. Без этих функций наш код стал бы достаточно большим и неудобным в чтении.
После этих функций создадим еще две:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | stock Pointer Transpose(Pointer pAddr, int iOffset) { return ptr( int (pAddr) + iOffset); } stock int Dereference(Pointer pAddr, int iOffset = 0) { if (pAddr == nullptr) { return -1; } return ReadInt(Transpose(pAddr, iOffset)); } |
Функция Transpose() нужна для изменения значения указателя, не прибегая к приведению типов. Функция Dereference() аналогична Transpose(), но после изменения адреса она еще и произведет разыменования указателя. Опять же - всё для повышенной читабельности кода.
А теперь реализуем действительно необходимый функционал:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | stock Pointer WriteData(Pointer pAddr, int [] data, int iSize) { if (pAddr == nullptr) { return nullptr; } for ( int i = 0; i < iSize; i++) { StoreToAddress(pAddr, data[i], NumberType_Int8); pAddr++; } pAddr++; return pAddr; } stock Pointer FindPattern(Pointer pStart, int iSize, int [] pattern, int iPatternSize, int iOffset) { for ( int i = 0; i < iSize; i++) { if (ReadByte(pStart) == pattern[0]) { bool bFound = true ; for ( int j = 1; j < iPatternSize; j++) { 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(pAddr, 0x3C); // NT headers offset int iRes = Dereference(pAddr, iOffset + 0x50); // nt->OptionalHeader.SizeOfImage return iRes; } else { return -1; } } stock int GetModuleSize(Pointer pAddr) { if (pAddr == nullptr) { return 0; } if (ReadWord(pAddr) == 0x5A4D) // MZ { int iOffset = Dereference(pAddr, 0x3C); // NT headers offset int iRes = Dereference(pAddr, iOffset + 0x50); // nt->OptionalHeader.SizeOfImage return iRes; } else { return -1; } } |
WriteData() нужна для записи массива байт по указанному адресу. Не обращайте внимание на то, что передается "int[]", функция работает с этим аргументом, как с "byte[]". GetModuleSize() функция получает виртуальный размер модуля, загруженного в адресное пространство. В аргумент pAddr нужно передавать результат GetModuleHandle()/LoadLibrary(), но об этом позднее. FindPattern() нужна для поиска паттерна в памяти без использования gamedata.
Теперь напишем достаточно специфичную функцию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 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 (b != 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 = 0; i < 4; i++) { StoreToAddress(pAddr, 0xDEADBEEF, NumberType_Int32); pAddr = Transpose(pAddr, 4); } return true ; } |
Пока что я не буду заострять на этой функции внимание, просто скажу, что без ее помощи у нас ничего не получится.
А теперь напишем кое-что действительно впечатляющее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | Pointer g_pPEB = nullptr; int g_GetPEBAsmCode[] = { 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, // mov eax, dword ptr fs:[30] 0xC3 // ret }; int g_DeadBeef[] = { 0xEF, 0xBE, 0xAD, 0xDE, 0xEF, 0xBE, 0xAD, 0xDE, 0xEF, 0xBE, 0xAD, 0xDE, 0xEF, 0xBE, 0xAD, 0xDE }; /* 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 (h == 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_PlainOldData, SDKPass_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(pServerBase, iServerSize, g_DeadBeef, 16, 0); if (p == nullptr) { return nullptr; } /* Create little function that will help us to get process' PEB */ WriteData(p, g_GetPEBAsmCode, 7); g_pPEB = SDKCall(hGetPEB); /* We don't need this SDK call anymore */ delete hGetPEB; return g_pPEB; } |
Впечатляет, не правда ли? Возможно, кто-то даже назовет всё это извращением и безумием, но тем не менее давайте разберем всё по порядку.
Сначала функция проверяет, был ли уже найдена PEB, и возвращает его адрес, если была. Затем происходит получение адреса модуля server. Мы используем здесь небольшой трюк, основываясь на архитектуре PE файлов, а именно правилу, что первые два байта exe или dll равны константе "MZ". При старте поиска ядро SourceMod сразу же натыкается на эти два байта и возвращает адрес начала модуля. После всего этого вызывается функция CreateMemoryForSDKCall(). Ее цель - создание уникального паттерна в неиспользуемой области адресного пространства модуля server, который будет найден функцией PrepSDKCall_SetSignature() и FindPattern(). Затем мы строим SDK вызов, который на самом деле является не SDK, а нашей будущей функцией, написанной на x86 ассемблере. PrepSDKCall_SetSignature() просто находит наш уникальный паттерн, в который мы в будущем сможем записать любой код. Затем мы с помощью FindPattern() находим это же самое место и изменяем его, внедряя туда наш ассемблерный код. После всего этого мы производим вызов SDKCall() функции и получаем наш PEB адрес. Хитро, не правда ли?
Теперь, имея на руках PEB, можно делать очень много интересных вещей. Давайте попробуем получить адрес srcds, например.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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()); } |
Переменная ImageBaseAddress, которая входит в состав структуры PEB и располагается по смещению 8, содержит адрес процесса, который загрузил библиотеку. Функция просто получает это число, ничего сложного. Точно так же работает функция GetModuleHandle(), если в ее аргументе указать 0.
Заключение
Туториал получился достаточно большой, как по мне, поэтому я считаю, что пока что достаточно. Мы рассмотрели реализацию получения версии Windows и возможность выполнения собственного x86 ассемблерного кода. Как видите, это возможно. Если пользователям понравится подобная тематика, то я продолжу написание туториалов, в которых рассмотрю еще более интересные вещи. Благодарю за чтение.
Серьёзно. Даже более чем.
ОтветитьУдалитьУ меня есть кое-какие вопросы, если еще не отошел от этого языка я бы хотел связаться с тобой.
Познавательно!
ОтветитьУдалитьSeminole Hard Rock Hotel Casino - MapyRO
ОтветитьУдалитьFind Seminole Hard Rock Hotel Casino, Fort 상주 출장샵 Lauderdale in picturesque Florida. 청주 출장안마 Seminole Hard Rock Hotel & 포천 출장마사지 Casino, Fort Lauderdale, is 남원 출장샵 a hotel on the 충청북도 출장마사지 lake