Win32ASM Консольный ввод, томограф IDA и скальпель SoftICE

  d8ef8794     

В прошлый раз мы разобрались


В прошлый раз мы разобрались с консольным выводом. Сегодня - разберемся с консольным вводом. Для этого напишем простую программу, запрашивающую строку символов, а затем её же (строку) и выводящую. Это будет очередной маленький шажок, который для вас вполне может стать решающим, так как именно эта программа впоследтвии послужит нам жертвой для негуманных экспериментов, в которых мы впервые используем два хирургических инструмента - отладчик SoftIce (на правах скальпеля) и дизассемблер IDA Pro (на правах томографа). И если при виде окровавленных внутренностей программы вам не поплохеет, более того - если вам ЭТО понравится, значит вы имеете неплохие шансы стать либо серийным маньяком (крэкером), либо - исследователем программ (реверсером). Все же остальные профессии отныне перестанут для вас существовать ;)
  Итак, открываем ASM Editor и набиваем там следующий текст: .386 .model flat, stdcall option casemap :none ; case sensitive
; ######################################################
include \tools\masm32\include\windows.inc include \tools\masm32\include\kernel32.inc includelib \tools\masm32\lib\kernel32.lib
; ######################################################
.data
Msg1 db "Type something > " Msg2 db "You typed > " ConsoleTitle db 'Input & Output',0
; ######################################################
.code
; ######################################################
Main proc LOCAL InputBuffer[128] :BYTE ;буффер для ввода LOCAL hOutPut :DWORD ;хэндл для вывода LOCAL hInput :DWORD ;хэндл для ввода LOCAL lpszBuffer :DWORD ;адрес буфера LOCAL nRead :DWORD ;прочитано байт LOCAL nWriten :DWORD ;напечатано байт
;устанавливаем титл окна invoke SetConsoleTitle, addr ConsoleTitle
;получаем хэндл для вывода invoke GetStdHandle, STD_OUTPUT_HANDLE mov hOutPut, eax
;печатаем "Type something > " invoke WriteConsole, hOutPut, addr Msg1, 17, addr nWriten,NULL
;получаем хэндл для ввода invoke GetStdHandle,STD_INPUT_HANDLE mov hInput, eax


Представьте себе картину: накачанный колесами и протеиновыми коктейлями шварценеггер бьет со всей дури по боксерской груше. Та отлетает в сторону, а потом по каким-то абсолютно нефизическим законам кинемотографа возвращается назад и бьет этому боксеру по морде лица, в результате чего шварценеггер отлетает на несколько метров, и, обязательно задев что-нибудь из мебели, размазывается соплями по стене - к неописуемому удовольствию зрителя. Посмотрев на такую картину, Станиславский бы сказал: "не верю"! А вот дZенствующей программер, увидя это безобразие, подумал бы: "ба! Да совсем как стек. Помницца, в одной из своих кулхацкерных прог я на похожие грабли как раз и напоролся".


  Ранее мы уже разобрались с очередностью записи в стек и чтения из а него. Напомню, что доступ к стеку осуществляется в соответствии с принципом LIFO (Last In First Out – Последним Пришел, Первым Ушел). Однако это отнюдь не единственное, что нам нужно знать о стеке - конечно же, если мы не собираемся время от времени получать "отдачу" от "боксерской груши" ;).
  Нам уже хорошо известно, что стек можно использовать для временного хранения данных – с его помощью мы выкручивались из такой проблемы, как недостаточное для полета нашей фантазии количество регистров. То есть мы временно сохраняли значения регистров в стеке, активно юзали их для наших нужд, а потом снова восстанавливали "статус кво", за исключением, в большинстве случаев, одного-единственного регистра, в котором хранился результат проделанной работы.
  Также известно, что в инструкциях процессоров от Интел переменные (которые в памяти) не могут выступать в качестве приемника и источника одновременно, то есть инструкция: mov [dwVar1],[dwVar2]
не проходит. А выкрутиться из такой ситуации можно двумя способами - либо использовать в качестве посредника регистр (которых, как всегда, мало): mov eax,[dwVar2] mov [dwVar1],eax
либо задействовав стек: push [dwVar2] pop [dwVar1]
Кстати, недавно в почтовой рассылке RTFM_helpers прошло обсуждение того, как можно копировать из памяти в память - там было упомянуто, например, использование movs. А если подумать, можно найти и другие нетривиальные способы.
  Для тех, кто ещё не понял. Вот этот кусок кода: push 1 push 2 push 3 pop eax pop ebx pop ecx
делает то же самое, что и следующий код (если только не считать разницу в скорости, размере кода и побочных эффектах): mov eax,3 mov ebx,2 mov ecx,1
  Сомневающиеся могут проверить под отладчиком:). Также попробуйте сравнить: push 1 pop eax
и психоделическое: sub esp,4 mov dword ptr [esp],1 mov eax,[esp] add esp,4
  Вы можете мне не поверить, но эти два куска кода тоже "делают" одно и то же, хотя последний и кажется плодом больного воображения.
  - Что за esp такой? – спросите вы.
  Пошире откройте глаза и слушайте – сейчас я поведаю вам страшные тайны. ;)


; вводим invoke ReadConsole, hInput, addr InputBuffer, 10, ADDR nRead, NULL
;печатаем "You typed > " invoke WriteConsole, hOutPut, addr Msg2, 12, addr nWriten, NULL
;печатаем то, что ввели invoke WriteConsole, hOutPut, addr InputBuffer, nRead, addr nWriten, NULL
;задержка, чтобы полюбоваться invoke Sleep, 2000d
;выход invoke ExitProcess,0 Main endp
; ######################################################
end Main
ПРИМЕЧАНИЕ: Ручной подсчет числа выводимых символов хорошим стилем программирования, конечно же, не назовешь :(. Немного попозже я расскажу, как делать это дело правильно ;). И да простят меня продвинутые программеры...
  Строка LOCAL InputBuffer[128] :BYTE резервирует 128 байт памяти под строку символов, которую мы будем запрашивать при помощи апишной функции ReadConsole. Вот ее описание: BOOL ReadConsole( HANDLE hConsoleInput, // handle to console input buffer LPVOID lpBuffer, // data buffer DWORD nNumberOfCharsToRead, // number of characters to read LPDWORD lpNumberOfCharsRead, // number of characters read LPVOID lpReserved // reserved );
  Попутно даю урок английского языка, который сам знаю хреново (академиев не кончал, но высшее образование вам даду): "number of characters to read" переводится как "число символов, подлежащих чтению", а "number of characters read" - как "число прочитанных символов". Наверное. Согласитесь, это существенная разница ;).

Содержание раздела