Перейти к содержанию

Лидеры

Популярный контент

Показан контент с высокой репутацией 02.05.2021 в Сообщения

  1. я ставлю плюсы не за правильность, а за активность и участие, плюс/минус ИМХО - нравится/не нравится мне понравилось. Тем более и более опытные конечно поправят.
    1 балл
  2. @Garik66 спросил - как использовать createthread, чтобы он не крашился. Начал отвечать и это слегка вышло за рамки простого ответа в тему, поэтому решил выделить это в отдельный топик. Я не очень хорошо знаю assembler, но попробую описать возможные причины. createthread создает поток и просит его выполнить call с адресом переданной функции. Например в данном случае createthread(my_function) в отдельном потоке выполнит call my_function: И тут вступает в дело стек, да. Вот эта штука (в Memory View вызывается через ПКМ на правом нижнем окне и выборе пункта Full stack): После выполнения call my_function мы попадаем в нашу функцию, а в стек записывается адрес, куда нужно вернуться после выполнения этой функции (my_function). Собственно за возврат куда нужно и отвечает ret. Он берет первую запись из стека и делает jmp в правильное место. Собственно сам адрес возврата виден в стеке на скриншоте - это KERNEL32.BaseThreadInitThunk+24. Без ret ассемблер вывалится за пределы функции и попытается выполнить команду - add [eax],al , хотя для нас это просто участок пустой памяти - 00 00. Ну а так как в EAX ничего приличного нет, то программа выдает ошибку - сорян, не могу записать в данный участок памяти и падает. Поэтому ret обязателен. Из этого вытекает то, что при подходе к ret в последней записи в стеке (адрес в регистр ESP) должна быть с правильным адресом возврата, который был нам передан из call my_function. И основная причина крашей в том, что регистр ESP указывает не на тот адрес при выполнении команды ret. Программа выполняет jmp на неизвестный нам адрес и крешится из-за невозможности выполнить команды, на которые она попала (как с add [eax],al). А что собственно двигает стек и адрес регистра ESP? А двигают стек наши любимые команды push и pop. Собственно, когда мы пытаемся вызывать функцию и передаем параметры через push #100, на самом деле выполняются две команды - sub esp,4 и mov [esp],#100. Первая уменьшает адрес вершины стека на 4 байта, а вторая записывает на вершину стека значение 100. А pop eax сдвигает вершину в другую сторону - делает mov eax, [esp] и дальше сдвигает вершину стека назад add esp,4. Поэтому после того, как мы сдвинули стек с помощью push при передаче параметров в функцию - после её выполнения нужно сдвинуть стек обратно! Собственно следующий пример почти 100% скрешится: Мы ввели новую функцию second_function, которая берет переданный аргумент и записывает его в EAX. Вроде бы всё просто, передали в функцию 100, получили в EAX ответ, а всё равно креш. Почему? А это видно на следующем скриншоте - на вершине стека теперь хранится значение 0x64 (=100): Теперь когда выполнится ret он попытается прыгнуть на адрес 0x00000064, а не на 0x760E8744 как должен был. Как же этого избежать? Есть несколько путей: сохраняем значение регистра esp и возвращаем его перед ret внимательно следим за стеком по мере выполнения нашей функции и к концу программы он сам будет в правильном месте В целом первый способ очень неплох. Мы можем сохранить адрес регистра esp в памяти и вернуть его прямо перед вызовом ret. Например: Но это не совсем assembler-way, поэтому я предпочитаю следить за стеком самостоятельно. Для этого нужно обращать внимание на то, как со стеком работает вызываемая функция. Например, наша функция second_function не следит за стеком. Поэтому если мы делаем push для передачи параметров, то мы так же должны сдвинуть его назад. Это можно сделать через pop указав неиспользуемый регистр или просто добавить к адресу ESP 4 байта (размер адреса в 32 битной системе, в 64-битной - это 8 байт): Собственно таким образом мы двигаем стек за функцией. И в зависимости от количества переданных push - на такое же количество нужно сдвинуть и регистр ESP. Например для 3-х аргументов нужно выполнить 3 pop или добавить к esp 3 * 4 (3 - количество сдвигов, 4 - размер инструкции): В результате мы получим следующий код: Единственное, есть небольшое исключение - функции, которые сами заботятся о стеке, после их вызова не нужно сдвигать регистр esp, просто делаем push и call. Их довольно просто отличить, посмотрим на примере следующего кода: Основное отличие - после call функции которая сама двигает стек не идет ни каких add esp,8 или не выполняются pop: Так же можно обратить внимание на код самой функции, а точнее на ret 8. Параметр 8 указывает программе, что нужно не просто взять адрес из вершины стека и прыгнуть на него, а так же после этого сдвинуть стек на 8 байт, т.е. выполнить ret и add esp,8. Собственно и получается, что add делаем не мы, а сама функция. Вот и получается, что следя за стеком и тем, как вызываемые функции заботятся о стеке - мы не получим крашей в вызове createthread.
    1 балл
×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.