 |

18.06.2022, 11:42
|
|
Постоянный
Регистрация: 04.06.2020
Сообщений: 620
С нами:
3127813
Репутация:
133
|
|
* Вопрос первый: есть ли какие-то различия между new[] и VirtualAlloc в данном случае?
Связанно это с тем, что каждая зона памяти имеет свои "права" (Чтение, запись и исполнение)
Выделяя память через new[] или malloc, CRT выделяет память только с правами на чтение и запись (в теории, это нигде не прописано), а выделяя память через VirtualAlloc мы явно указываем, что мы потом хотим исполнять данный кусок памяти
Память вещь сложная, по мере изучения cs (computer science) и операционных систем разница между ними станет тебе более явной
Вопрос второй: фактически, можно ведь создать что-то вроде своего обработчика через naked?
В теории, это можно сделать, но есть главная проблема - DLL'ки размещаются в памяти динамически (т.е. при каждом запуске в другом месте), а функции вызываются относительным адресом (вроде в x86 есть инструкция для вызова функции по реальному адресу, поищи инфу в инете)
И да, производительность естественно меньше, но разница мизерная, и ради нескольких миллисекунд не думаю, что стоит заморачиваться
И третий вопрос. В случае, если вариант два является более производительным за счёт того, что там не выделяется динамически память, возможно ли сделать это как-то статически, дабы не создавать naked функцию?
Проблема опять же в "правах" зон памяти
Зона статической памяти(там, где хранятся статические переменные) не имеет права на исполнение
|
|
|

18.06.2022, 14:04
|
|
Новичок
Регистрация: 18.06.2022
Сообщений: 4
С нами:
2057168
Репутация:
1
|
|
Сообщение от RedHolms
Связанно это с тем, что каждая зона памяти имеет свои "права" (Чтение, запись и исполнение)
Выделяя память через new[] или malloc, CRT выделяет память только с правами на чтение и запись (в теории, это нигде не прописано), а выделяя память через VirtualAlloc мы явно указываем, что мы потом хотим исполнять данный кусок памяти
Память вещь сложная, по мере изучения cs (computer science) и операционных систем разница между ними станет тебе более явной
В теории, это можно сделать, но есть главная проблема - DLL'ки размещаются в памяти динамически (т.е. при каждом запуске в другом месте), а функции вызываются относительным адресом (вроде в x86 есть инструкция для вызова функции по реальному адресу, поищи инфу в инете)
И да, производительность естественно меньше, но разница мизерная, и ради нескольких миллисекунд не думаю, что стоит заморачиваться
Проблема опять же в "правах" зон памяти
Зона статической памяти(там, где хранятся статические переменные) не имеет права на исполнение
Спасибо за ответ. Ещё, как я читал, VirtualAlloc выделяет большой блок памяти (4 кб), и постоянный вызов по сути каждый раз выделяет такой блок памяти. То есть, для того, чтобы не растрачивать память попусту, стоит выделять её один раз, а дальше записывать в память, и делать смещение указателя на размер хука, а после записывать новый хук уже по данному смещению и так далее, верно?
|
|
|

18.06.2022, 14:08
|
|
Постоянный
Регистрация: 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 обнови документацию для новичков, ктхук в массы!
|
|
|

18.06.2022, 14:09
|
|
Постоянный
Регистрация: 04.06.2020
Сообщений: 620
С нами:
3127813
Репутация:
133
|
|
Сообщение от D3ad_Parad15e
Спасибо за ответ. Ещё, как я читал, VirtualAlloc выделяет большой блок памяти (4 кб), и постоянный вызов по сути каждый раз выделяет такой блок памяти. То есть, для того, чтобы не растрачивать память попусту, стоит выделять её один раз, а дальше записывать в память, и делать смещение указателя на размер хука, а после записывать новый хук уже по данному смещению и так далее, верно?
Тут не уверен, но скорее всего да
Вся память поделена на страницы, размерами по 4 кб, как раз таки эти страницы и имеют права на чтение, запись и исполнение
Очень упрощённо говоря, это наименьшая единица выделения памяти
|
|
|

18.06.2022, 14:57
|
|
Флудер
Регистрация: 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 создает страницы памяти, загружает туда код, размечает секции и прочее.
Также, если мы будем создавать хуки на такой основе - мы будем раздувать размер файла, вместо того чтобы выделять нужное количество памяти когда нам нужно в рантайме.
|
|
|

18.06.2022, 17:58
|
|
Новичок
Регистрация: 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 и при следующем хуке запись пройдёт по этому указателю и к нему прибавится вновь какой-то оффсет. И так далее.
|
|
|

18.06.2022, 18:37
|
|
Флудер
Регистрация: 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)
Но там еще несколько проблем, кроме раздувания памяти.
Секция данных защищена от выполнения, и сделано это не просто так.
А еще там не будет выравнивания адресов
|
|
|

18.06.2022, 19:28
|
|
Новичок
Регистрация: 18.06.2022
Сообщений: 4
С нами:
2057168
Репутация:
1
|
|
Сообщение от kin4stat
Секция данных защищена от выполнения, и сделано это не просто так.
Если секция данных - имеется ввиду то место, куда вставляется хук, то её параметры защиты можно изменить при помощи VirtualProtect.
Сообщение от kin4stat
просто call сделать нельзя - все поедет.
Берем из расчета: jmp в месте хука + трамплин(затертые инструкции + jmp back)
Да, я это и имел ввиду. Но, можно же сделать jmp на адрес трамплина (со смещением от оригинала и - 5), в котором будет call на инжектнутый код, после которого идут затёртые инструкции и Jmp на смещение от тех самых затёртых инструкций (грубо говоря, чтобы код продолжал выполняться в том месте, где начинаются новые инструкции, которые не были тронуты).
Поправьте, если не прав.
|
|
|
|
 |
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|