HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2
НОВЫЕ ТОРГОВАЯ НОВОСТИ ЧАТ
loading...
Скрыть
Вернуться   ANTICHAT > ПРОГРАММИРОВАНИЕ > Общие вопросы программирования
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 18.06.2022, 11:42
RedHolms
Постоянный
Регистрация: 04.06.2020
Сообщений: 620
С нами: 3127813

Репутация: 133


По умолчанию

Цитата:

* Вопрос первый: есть ли какие-то различия между new[] и VirtualAlloc в данном случае?
Связанно это с тем, что каждая зона памяти имеет свои "права" (Чтение, запись и исполнение)

Выделяя память через new[] или malloc, CRT выделяет память только с правами на чтение и запись (в теории, это нигде не прописано), а выделяя память через VirtualAlloc мы явно указываем, что мы потом хотим исполнять данный кусок памяти

Память вещь сложная, по мере изучения cs (computer science) и операционных систем разница между ними станет тебе более явной

Цитата:

Вопрос второй: фактически, можно ведь создать что-то вроде своего обработчика через naked?
В теории, это можно сделать, но есть главная проблема - DLL'ки размещаются в памяти динамически (т.е. при каждом запуске в другом месте), а функции вызываются относительным адресом (вроде в x86 есть инструкция для вызова функции по реальному адресу, поищи инфу в инете)

И да, производительность естественно меньше, но разница мизерная, и ради нескольких миллисекунд не думаю, что стоит заморачиваться

Цитата:

И третий вопрос. В случае, если вариант два является более производительным за счёт того, что там не выделяется динамически память, возможно ли сделать это как-то статически, дабы не создавать naked функцию?
Проблема опять же в "правах" зон памяти

Зона статической памяти(там, где хранятся статические переменные) не имеет права на исполнение
 
Ответить с цитированием

  #2  
Старый 18.06.2022, 14:04
D3ad_Parad15e
Новичок
Регистрация: 18.06.2022
Сообщений: 4
С нами: 2057168

Репутация: 1
По умолчанию

Цитата:
Сообщение от RedHolms  

Связанно это с тем, что каждая зона памяти имеет свои "права" (Чтение, запись и исполнение)
Выделяя память через new[] или malloc, CRT выделяет память только с правами на чтение и запись (в теории, это нигде не прописано), а выделяя память через VirtualAlloc мы явно указываем, что мы потом хотим исполнять данный кусок памяти

Память вещь сложная, по мере изучения cs (computer science) и операционных систем разница между ними станет тебе более явной

В теории, это можно сделать, но есть главная проблема - DLL'ки размещаются в памяти динамически (т.е. при каждом запуске в другом месте), а функции вызываются относительным адресом (вроде в x86 есть инструкция для вызова функции по реальному адресу, поищи инфу в инете)

И да, производительность естественно меньше, но разница мизерная, и ради нескольких миллисекунд не думаю, что стоит заморачиваться

Проблема опять же в "правах" зон памяти
Зона статической памяти(там, где хранятся статические переменные) не имеет права на исполнение
Спасибо за ответ. Ещё, как я читал, VirtualAlloc выделяет большой блок памяти (4 кб), и постоянный вызов по сути каждый раз выделяет такой блок памяти. То есть, для того, чтобы не растрачивать память попусту, стоит выделять её один раз, а дальше записывать в память, и делать смещение указателя на размер хука, а после записывать новый хук уже по данному смещению и так далее, верно?
 
Ответить с цитированием

  #3  
Старый 18.06.2022, 14:08
RTD
Постоянный
Регистрация: 18.03.2017
Сообщений: 410
С нами: 4818548

Репутация: 133
По умолчанию

Используй kthook и будет счатье хД

GitHub - kin4stat/kthook

Contribute to kin4stat/kthook development by creating an account on GitHub.

github.com


p.s. @kin4stat обнови документацию для новичков, ктхук в массы!
 
Ответить с цитированием

  #4  
Старый 18.06.2022, 14:09
RedHolms
Постоянный
Регистрация: 04.06.2020
Сообщений: 620
С нами: 3127813

Репутация: 133


По умолчанию

Цитата:
Сообщение от D3ad_Parad15e  

Спасибо за ответ. Ещё, как я читал, VirtualAlloc выделяет большой блок памяти (4 кб), и постоянный вызов по сути каждый раз выделяет такой блок памяти. То есть, для того, чтобы не растрачивать память попусту, стоит выделять её один раз, а дальше записывать в память, и делать смещение указателя на размер хука, а после записывать новый хук уже по данному смещению и так далее, верно?
Тут не уверен, но скорее всего да

Вся память поделена на страницы, размерами по 4 кб, как раз таки эти страницы и имеют права на чтение, запись и исполнение

Очень упрощённо говоря, это наименьшая единица выделения памяти
 
Ответить с цитированием

  #5  
Старый 18.06.2022, 14:57
kin4stat
Флудер
Регистрация: 06.11.2017
Сообщений: 2,759
С нами: 4483143

Репутация: 183


По умолчанию

Цитата:
Сообщение от D3ad_Parad15e  

Вопрос первый: есть ли какие-то различия между new[] и VirtualAlloc в данном случае? Искал информацию в интернете по этому поводу - и понял так, что VirtualAlloc выделяет именно фиксированное число памяти, в то время как new при первом разе выделяет какое-то большое количество памяти, а потом, при каждом новом вызове - хватает из выделенном буфера память, оттого и работает он при каждом вызове быстрее, чем VirtualAlloc. Поправьте, если ошибаюсь. Мне просто интересно, почему используют именно его, а не operator new.
Да, так оно и есть. Но есть одно главное отличие: operator new - стандартная фича плюсов, и она будет работать везде, где поддержан C++. VirtualAlloc же есть только на винде.

Второе отличие - VirtuaAlloc аллоцирует страницу памяти, а не отдает кусок из заранее выделенной памяти. Также operator new юзает под капотом HeapAlloc на винде, а не VirtualAlloc.

Почему для хуков юзают VirtualAlloc: operator new выделяет невыровненный кусок памяти, а также выделяет ее с правами ReadWrite, когда у VirtualAlloc можно выбрать самому сразу, с какой защитой будет выдана страница памяти. Выровненный кусок памяти нужен для скорости. Процессор читает память в кеш кусками по 64 байта. Когда мы используем VirtuaAlloc, процессору нужно лишь подгрузить начало страницы, и он уже будет обладать всеми нужными инструкциями. В случае operator new, ему придется подгрузить невыровненный кусок памяти, который в худшем случае будет содержать всего один байт инструкций, а все остальное будет мусором, что побудит его подгрузить еще 64 байта памяти в кеш линию

Цитата:
Сообщение от D3ad_Parad15e  

опрос второй: фактически, можно ведь создать что-то вроде своего обработчика через naked? То есть, по сути, записать в naked функцию pusha, сделать jmp (или call, но здесь уже понадобится реализация передачи параметров) на функцию со своим кодом, а после вернуться на это же место, выполнить popa, выполнить код тех опкодов, которые были затёрты хуком и продолжить работу функции. Так вот, вопрос такой: будет ли это иметь разницу в производительности?
Разницу в перфе оно будет иметь. Т.к. когда мы создаем хук через аналогичную функцию, мы сохраняем только нужные регистры, и имеем право изменять некоторые регистры, т.к. они являются volatile. Т.е. мы сохраняем на стек 2-3 регистра, и перезатираем 2-3 регистра, не сохраняя их, т.к. до этого вызывающая сторона уже позаботилась об их сохранении, либо не использовала их вообще. Когда мы делаем pusha и popa, мы сохраняем все регистры. Само копирование регистров не сильно ощутимо. Ощутимо количество памяти, которое мы при этом дергаем(стек).

Цитата:
Сообщение от D3ad_Parad15e  

И, как я понимаю, из-за этого уже не получится делать хуки при помощи операторов и лямбд (именно тех лямбд, в которых описан код, который должен выполняться при переходе).
При должной обертке, можно реализовать и такое. Например kthook научился так делать недавно - https://www.blast.hk/threads/101004/post-1063328

Цитата:
Сообщение от D3ad_Parad15e  

В случае, если вариант два является более производительным за счёт того, что там не выделяется динамически память, возможно ли сделать это как-то статически, дабы не создавать naked функцию? К примеру, байтовый массив, в котором будут записаны необходимые опкоды. По факту - как с VirtualAlloc, но память будет выделена не динамически, а, грубо говоря, глобально, так, словно была создана какая-то static переменная? То есть, вызывается функция для хука, и там создаётся эта самая переменная, которая будет жить до конца работы программы
Эту память все еще аллоцирует система при подгрузке приложения в память с HDD. Она точно также через условный VirtualAlloc создает страницы памяти, загружает туда код, размечает секции и прочее.

Также, если мы будем создавать хуки на такой основе - мы будем раздувать размер файла, вместо того чтобы выделять нужное количество памяти когда нам нужно в рантайме.
 
Ответить с цитированием

  #6  
Старый 18.06.2022, 17:58
D3ad_Parad15e
Новичок
Регистрация: 18.06.2022
Сообщений: 4
С нами: 2057168

Репутация: 1
По умолчанию

Цитата:
Сообщение от kin4stat  

Да, так оно и есть. Но есть одно главное отличие: operator new - стандартная фича плюсов, и она будет работать везде, где поддержан C++. VirtualAlloc же есть только на винде.
Второе отличие - VirtuaAlloc аллоцирует страницу памяти, а не отдает кусок из заранее выделенной памяти. Также operator new юзает под капотом HeapAlloc на винде, а не VirtualAlloc.
Почему для хуков юзают VirtualAlloc: operator new выделяет невыровненный кусок памяти, а также выделяет ее с правами ReadWrite, когда у VirtualAlloc можно выбрать самому сразу, с какой защитой будет выдана страница памяти. Выровненный кусок памяти нужен для скорости. Процессор читает память в кеш кусками по 64 байта. Когда мы используем VirtuaAlloc, процессору нужно лишь подгрузить начало страницы, и он уже будет обладать всеми нужными инструкциями. В случае operator new, ему придется подгрузить невыровненный кусок памяти, который в худшем случае будет содержать всего один байт инструкций, а все остальное будет мусором, что побудит его подгрузить еще 64 байта памяти в кеш линию

Разницу в перфе оно будет иметь. Т.к. когда мы создаем хук через аналогичную функцию, мы сохраняем только нужные регистры, и имеем право изменять некоторые регистры, т.к. они являются volatile. Т.е. мы сохраняем на стек 2-3 регистра, и перезатираем 2-3 регистра, не сохраняя их, т.к. до этого вызывающая сторона уже позаботилась об их сохранении, либо не использовала их вообще. Когда мы делаем pusha и popa, мы сохраняем все регистры. Само копирование регистров не сильно ощутимо. Ощутимо количество памяти, которое мы при этом дергаем(стек).

При должной обертке, можно реализовать и такое. Например kthook научился так делать недавно - https://www.blast.hk/threads/101004/post-1063328

Эту память все еще аллоцирует система при подгрузке приложения в память с HDD. Она точно также через условный VirtualAlloc создает страницы памяти, загружает туда код, размечает секции и прочее.

Также, если мы будем создавать хуки на такой основе - мы будем раздувать размер файла, вместо того чтобы выделять нужное количество памяти когда нам нужно в рантайме.
Спасибо за развёрнутый ответ. У меня есть ещё пара вопросов:

1) Касательно этого момента:

Цитата:
Сообщение от kin4stat  

Эту память все еще аллоцирует система при подгрузке приложения в память с HDD. Она точно также через условный VirtualAlloc создает страницы памяти, загружает туда код, размечает секции и прочее.
То есть, по сути, можно сказать, что скорость работы при обращении к буферу, который был выделен через VirtualAlloc будет даже быстрее за счёт того, что там отсутствуют опкоды для pusha и popa. Следовательно и памяти (я не считаю выделенный блок в 4 кб целиком, а именно ту часть, что занята под буфер) будет занято меньше за счёт отсутствия тех двух опкодов. Надеюсь, правильно понял.

2) Вы писали о том, что постоянное такое использование приведёт к раздутию файла. Собсна, к этому у меня идёт следующий вопрос:

Цитата:
Сообщение от D3ad_Parad15e  

Ещё, как я читал, VirtualAlloc выделяет большой блок памяти (4 кб), и постоянный вызов по сути каждый раз выделяет такой блок памяти. То есть, для того, чтобы не растрачивать память попусту, стоит выделять её один раз, а дальше записывать в память, и делать смещение указателя на размер хука, а после записывать новый хук уже по данному смещению и так далее, верно?
К примеру, для записи затёртых опкодов понадобилось 6 байт, + 5 байт - call на наш код, + ещё 5 байт - прыжок на продолжение оригинала. Выходит 16 байт. Они прибавляются к указателю на выделенный ранее VirtualAlloc и при следующем хуке запись пройдёт по этому указателю и к нему прибавится вновь какой-то оффсет. И так далее.
 
Ответить с цитированием

  #7  
Старый 18.06.2022, 18:37
kin4stat
Флудер
Регистрация: 06.11.2017
Сообщений: 2,759
С нами: 4483143

Репутация: 183


По умолчанию

Цитата:
Сообщение от D3ad_Parad15e  

То есть, по сути, можно сказать, что скорость работы при обращении к буферу, который был выделен через VirtualAlloc будет даже быстрее за счёт того, что там отсутствуют опкоды для pusha и popa. Следовательно и памяти (я не считаю выделенный блок в 4 кб целиком, а именно ту часть, что занята под буфер) будет занято меньше за счёт отсутствия тех двух опкодов. Надеюсь, правильно понял.
Процессору без разницы как интерпретировать память - как инструкции или как данные. Поэтому опкоды в памяти ни на что не влияют.

Цитата:
Сообщение от D3ad_Parad15e  

К примеру, для записи затёртых опкодов понадобилось 6 байт, + 5 байт - call на наш код, + ещё 5 байт - прыжок на продолжение оригинала. Выходит 16 байт. Они прибавляются к указателю на выделенный ранее VirtualAlloc и при следующем хуке запись пройдёт по этому указателю и к нему прибавится вновь какой-то оффсет. И так далее.
просто call сделать нельзя - все поедет.

Берем из расчета: jmp в месте хука + трамплин(затертые инструкции + jmp back)

Но там еще несколько проблем, кроме раздувания памяти.

Секция данных защищена от выполнения, и сделано это не просто так.

А еще там не будет выравнивания адресов
 
Ответить с цитированием

  #8  
Старый 18.06.2022, 19:28
D3ad_Parad15e
Новичок
Регистрация: 18.06.2022
Сообщений: 4
С нами: 2057168

Репутация: 1
По умолчанию

Цитата:
Сообщение от kin4stat  

Секция данных защищена от выполнения, и сделано это не просто так.
Если секция данных - имеется ввиду то место, куда вставляется хук, то её параметры защиты можно изменить при помощи VirtualProtect.

Цитата:
Сообщение от kin4stat  

просто call сделать нельзя - все поедет.
Берем из расчета: jmp в месте хука + трамплин(затертые инструкции + jmp back)
Да, я это и имел ввиду. Но, можно же сделать jmp на адрес трамплина (со смещением от оригинала и - 5), в котором будет call на инжектнутый код, после которого идут затёртые инструкции и Jmp на смещение от тех самых затёртых инструкций (грубо говоря, чтобы код продолжал выполняться в том месте, где начинаются новые инструкции, которые не были тронуты).

Поправьте, если не прав.
 
Ответить с цитированием
Ответ





Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.