1. Вы находитесь в архивной версии форума xaker.name. Здесь собраны темы с 2007 по 2012 год, большинство инструкций и мануалов уже неактуальны.
    Скрыть объявление

Ассеблер в *nix – удел извращенца..?

Тема в разделе "Assembler", создана пользователем Zik, 13 авг 2008.

  1. Zik

    Zik Модератор

    Регистрация:
    3 май 2007
    Сообщения:
    0
    Симпатии:
    219
    Баллы:
    0
    Да, асм в *nix таки существует. Просто многие в это почему-то отказываются верить. Данная статья скромно претендует развеять все мифы и загадки вокруг этого загадочного явления. Не ищите здесь подробного описания AT&T синтаксиса и системных вызовов – я просто попробую описать те трудности и невзгоды, которые обязательно придется преодолеть смельчаку, желающему полностью овладеть *nix-ом через асм (в смысле - научиться программировать на асме под *nix, прим. от CyberManiac-а :) ).

    Миф I:
    “Синтаксис асма под *nix в корне отличается от оного под DOS/WIN, он кривой и к нему невозможно привыкнуть”.

    Действительно, синтаксис, предложенный компанией AT&T, немного (безусловно, в лучшую сторону) отличается от Intel’овского. Но это ничего не значит. На самом деле, если бы все еще с детства начали кодировать, используя AT&T синтаксис, то интеловский очень скоро загнулся бы и не дожил до наших дней. Просто AT&T в свое время не стремилась делать свои поделки достоянием народных масс, это прерогатива MS и Intel (а вообще у AT&T уже существовала своя развитая культура и традиции, когда MS и Intel только спускались с деревьев :) ).

    А уж если кто-то переборол свои страхи и все же перешел к AT&T с Intel, того точно уже за уши не оттянешь назад. По этому поводу когда-то даже родился проект DJGPP (GNU Binutils) - асм с AT&T синтаксисом под DOS (http://www.delorie.com/djgpp/). Однако в силу непонятных причин проект не прижился.

    Если все эти доводы кому-то показались слишком хлипкими и невразумительными, то нет ничего проще - http://sf.net/projects/nasm/. Качайте себе NASM под *nix и будьте с Intel «вместе навсегда».


    Миф II:
    “Асм под *nix никому не нужен и вообще все это изврат, С – вот единственная дорога в светлое будущее”.

    Существует известная в широких кругах поговорка: «Линукс писан программистами для программистов». На самом деле полностью она звучит так: «Линукс писан сишными программистами для сишных программистов». И с этим никто не спорит. Дело в том, что любому, кто попытается сунуться в такой монастырь как *nix со своим уставом (асм), тому суждено упереться в огненные стены и рвы с крокодилами (об этом ниже). Однако не все так трагично – уменьшение размера кода в сто и более раз, увеличение производительности в десятки раз, да и чего греха таить, – само удовольствие, которое может доставить только кодинг на живом языке - все это стоит того чтобы научиться асму под *nix.

    Теперь, возможно это кого-то сильно удивит, но программирование на асме под *nix по своему стилю очень напоминает... программирование на оном под DOS (да! Именно под DOS). Многие скажут: *nix – полностью 32-х разрядная ОС, и работает в защищенном режиме, используя flat-модель памяти. При чем здесь 16-битная ОС реального режима DOS? Про такого с уверенностью можно сказать: он просто никогда не писал на асме под *nix. На самом деле можно сделать еще более шокирующее заявление: писать программы на асме в *nix НАМНОГО проще чем под DOS... Самое важное – не сойти “с пути истинного” в самом начале его...

    #1: когда только начинаешь писать на асме под *nix то возникает интересное ощущение: вроде бы ты попал в грязный пятибаксовый мотель (из тех, возле которых обязательно проходит метро и когда едет поезд на потолке дрожит дешевая люстра и мигает свет); здесь давно нет горячей воды, обои уродливыми клочьями свисают со стен, с потолка капает какая то мерзкая гадость и пахнет плесенью, все удобства – во дворе... На мотеле (подпертые кем-то неизвестным) стоят уже давно покосившиеся со временем неоновые буквы «*NIX для ассемблерщиков» (половина букв давно не горит, а половина с треском догорает). У мотеля нет своих постояльцев. Сюда заезжают лишь переночевать, чтобы на следующее утро убраться подальше...

    Самое мерзкое во всем этом то, что через единственное окно в этой конуре, через дорогу, как будто специально, вырос семизвездочный отель, весь в рекламе, бассейнах и пальмах... Прямо над входом (к которому то и дело поминутно подъезжают все более и более крутые тачки) сверкает золотом надпись: “*NIX для сишников”. Вон видно как по террасам ходят пузатые мужики в обнимку с дорогими бабами, потягивая коктейли и куря сигары, им прислуживает армия официантов и слуг; все они смеются и живут.

    Всем им наплевать на мотель напротив...

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

    Я говорю к тому, что ассеблерщик, сунувшийся в *nix не найдет практически никакой документации, описывающей системные вызовы на низком уровне. Здесь (http://www.lxhp.in-berlin.de/lhpsyscal.html) об этом можно получить кое-какую захудалую информацию, но многие функции описаны неправильно, либо вообще не описаны. Иногда порядок расположения параметров в регистрах при передаче в ту или иную функцию приходиться подбирать буквально вручную, методом научного тыка. Но на самом деле настоящего асм-кодера все это может только раззадорить..

    Для вызова любой системной функции используется команда INT 80h (вспомните DOS – там для этой цели использовался INT 21h). Параметры передаются через регистры. Номер функции – в АХ. Вся проблема в том, что найти полное описание того, в каком регистре какой параметр и для чего передается крайне проблематично, ресурс, указанный выше частично решает эту проблему.

    Когда я говорил про все неземные блага, которые предоставляются для сишников, я имел ввиду полную документированность любой запятой, с которой только можно встретиться в увлекательном процессе программирования на С под *nix.

    #2: вот это действительно самый большой фак: за всю историю существования *NIX никто не написал ни одного стоящего отладчика асм-кода (а может и написал, но не захотел поделиться с общественностью). Все что удалось найти (был перерыт буквально весь Интернет и опрошены десятки знающих людей) – ALD (Assembly Linux Debugger). Все что про него можно сказать – да, он действительно чем-то круче MS debug-а. Вот только чем именно - сказать довольно сложно. Все остальные отладчики дальше С-шного кода ничерта не видят. Писать программы без отладчика (а тем более на асме) – это верх извращенческого гения.

    Конечно, существует еще и скромный аналог сайса под Линукс – PrivateICE (http://sourceforge.net/projects/pice). Единственная проблемка – на последних версиях ядер Линукса он не компилируется (вот вам и переносимость С).

    #3: проблема заголовочных файлов. Чуть ли не большую часть времени желающему покодить на асме в *nix придется провести за увлекательным поиском необходимой информации по заголовочным файлам. Искать придется практически все буквенные названия, которые могут встретиться в процессе (это и названия самих системных вызовов, и параметров, передаваемых в них, и вообще любых переменных). Все они как будто специально порастасканы по тысячам *.h – файлов, которые находятся в самых неожиданных местах. Сишнику в семизвездочном отеле достаточно всего лишь щелкнуть пальцем и сделать include ….h – все работает. Ассемблерщику в мотелишке напротив – сначала понять к чему тот или иной параметр, затем устроить поиск по всем системным директориям, найти заголовочный файл, содержащий этот параметр, затем скопировать его в «свой», который будет понимать GAS или NASM (или, если ассемблерщик попался реальный, то запомнить его, и везде использовать численные значения параметров ?), а в довершение еще и усадить в правильный регистр перед отправкой в недра функции.

    #4: прога, написанная для *nix на асме теряет переносимость на другие *nix-платформы. Для перекомпиляции под другую *nix платформу придется изрядно повозиться, в некоторых случаях проще переписать заново весь код, чем переделывать старый с Linux под BSD. Но BSD пока не так распространен, а самые попсовые версии линуксов (Red Hat, Mandrake, ASP) используют одно и то же ядро, и данный фак вообщем-то не так уж страшен как его малюют.
    Ну вот, а теперь вы сами убедитесь, что писать программы на асме под Линукс не сложнее чем под DOS, а может даже и проще.

    Рассмотрим пример написания простейшего клиент-серверного приложения, использующего в качестве взаимодействия стек протоколов TCP/IP (подразумевается, что вы более-менее знакомы с сетевым программированием, и знаете хотя бы, что такое сокет).

    Клиентское приложение посылает серверу символьную строку; Сервер шифрует символьную строку по следующему алгоритму шифрования: выполняет замену одного символа – буквы, на символ располагающийся на две позиции правее в алфавите, для последнего и предпоследнего символов в алфавите выполняется кольцевой сдвиг, например, для «Y» это будет буква «А» для «Z» - «В» (это шифр Цезаря). Сервер отправляет зашифрованное сообщение обратно; Клиент выбирает шифрограмму и выводит на экран.

    Сообщения, подлежащие шифрованию, вводятся с клавиатуры. Программа сервера работает в бесконечном цикле.

    Ну что ж, начнем с сервера. Я по ходу пьесы попытаюсь максимально комментировать происходящее. Начну с того, что наш сервер будет демоном (для пущего понту). Что такое демон? Демон на языке DOS-ассемблерщиков – резидент. Все. Так что ничего страшного.
    Код:
        # *******************************************************
        # Server (daemon)
        # by Broken Sword [HI-TECH]
        # (for Linux based on Intel x86 only)
    
        # [email protected]
        # www.wasm.ru
    
        # Compile Instructions:
        # -------------------------------------------------------
        # as server.s
        # ld --strip-all -o server a.out
        # *******************************************************
    
        # *******************************************************
    
        # этот файлик содержит выдранные из какого-то файла
        # определения системных вызовов (см. FUCK #3)
        .include		"syscalls.inc"		
        # а этот – все остальные, которые только могут 
        # встретиться
    
        .include 	"def.inc"		        
        		.text					    # начало сегмента кода
        # метка, с которой все начинается (нужно чтоб она была 
        # глобальной)
        		.globl		_start		 	
    
        _start:							
        # итак, начинаем лепить нашего демона
        # процесс создания демона в *.nix и создание резидента в DOS в корне различаются
        # начинается любой демон с того, что нужно создать дочерний процесс.
        # Создать дочерний процесс в линуксе проще 
        # пареной репы – достаточно поместить номер сис.
        # вызова в EAX и сделать «а-ля int 21h», т.е. int 80h
        		movl		$SYS_fork,%EAX
        		int		$0x80		
        # все. 
        # Теперь у нас параллельно сосуществуют ДВА процесса:
        # родительский (в котором исполнялись все предыдущие 
        # команды) и дочерний. Что же содержит дочерний код? 
        # А все то же самое, что и родительский.
        # Т.е. важно понять, что # весь нижеследующий (и выше тоже)
        # код находиться в памяти в ДВУХ разных местах.
        # Как процессор переключается между 
        # ними (и всеми остальными живыми процессами) 
        # – читайте «Переключение задач» в интеловском мануале. 
        test		%EAX,%EAX		
        # вот эту команду необходимо осознать.
        # Прежде всего, важно понять, что данная команда
        # существует и в родительском 
        # и в дочернем процессах (об этом выше).
        # Следовательно выполниться она и там и там.
        # Все дело в том, что после 
        # int 80h родительскому процессу вернется PID сына
        # (в EAX ессесно, вообще все возвращается в EAX, как и в винде)
        # а что же вернется сыне? Правильно, нолик.
        # Именно поэтому следующий jmp будет выполнен
        # в дочернем процессе и 
        # не будет выполнен в родительском.
    
        # ребенок улетает на метку _cont1
        		jz		_cont1			
        # ...а в это время, в родительском процессе:
        		xorl		%EBX,%EBX		# EBX=status code
        		xorl		%EAX,%EAX		#
        		incl		%EAX			# SYS_exit 
        # завершаем родительский процесс.
        		int		$0x80			
    
        # Теперь все дети 
        # управляются процессом INIT
    
    
        _cont1:
        		movl		$SYS_setsid,%EAX
        # сделаем нашего ребенка главным в группе
        		int		$0x80			
        		
        		movl		$1,%EBX			# SIGHUP
        		movl		$1,%ECX			# SIG_IGN
        		movl		$SYS_signal,%EAX
        # далее сигнал SIGHUP будет игнорироваться
        		int		$0x80			
        							
        		movl		$SYS_fork,%EAX
        # наш ребенок уже подрос и теперь сам может родить сына
        		int		$0x80			
        # (по сути – это уже внук нашему изначальному 
        # родительскому процессу)
    
        # EAX=0 в дочернем и EAX=PIDдочернего в родительском
    
        		test		%EAX,%EAX		
    
        jz		_cont2			
    
        # внук нашего родительского (которого уже давно нет в 
        # живых) улетает на метку _cont2, однако отец все еще 
        # жив!!! (все как в мексиканском сериале)
        		
        		xorl		%EBX,%EBX		# EBX=status code
        		xorl		%EAX,%EAX		#
        		incl		%EAX			# SYS_exit
        		int         $0x80			
    
        # вот уже и отец отправлен к деду на небеса (да, 
        # злостная программка, недаром демоном зовется)
    
        # ..а в это время внучок получает все наследство и 
    
        _cont2:
    
        # продолжает жить
        # далее, после того,
        # как все кровавые разборки и отцеубийства благополучно завершены,
        # внучок,продавший душу демону, 
        # преспокойно создает сокет.
        # Дело в том, что в линуксе есть такие системные вызовы,
        # для вызова которых их номер не 
        # помещается в EAX.
        # Вместо этого в EAX помещается номер функции-мультиплексора,
        # реализующий вызов конкретной 
        # функции номер которой помещается в EBX.
        # Так, например, происходит при вызове IPC и SOCKET-функций.
        # Кроме того, 
        # при вызове SOCKET-функций параметры располагаются не в регистрах,
        # а в стеке. Смотри как все просто:
    
        		pushl		$0			# протокол
        		pushl		$SOCK_STREAM		# тип
    
        		pushl		$AF_INET			# домен
        # ECX должен указывать на кадр в стеке, содержащий 
        		movl		%ESP,%ECX		
        # параметры, такая уж у него судьба...
        		movl		$SYS_SOCKET,%EBX		
        # а вот это уже номер той самой конкретной функции
        # SOCKET – создать сокет
        							
        		movl		$SYS_socketcall,%EAX
        	
        # в EAX - номер функции мультиплексора (по сути он 
        # просто перенаправит вызов в функцию, указанную в EBX
        		
               int         $0x80
    
        # сокет создан! Ура товарищи. В EAX возвратиться его дескриптор.
    
        # «очистим» стек (по сути это выражение придумано специально
        # для HL-программистов, на самом деле ничего не 
        # очищается, данную операцию необходимо производить только
        # для того чтобы в дальнейшем не произошло переполнение 
        # стека, но в таких маленьких программках это делать вовсе
        # не обязательно):
    
        		addl		$0xC,%ESP
    
        		movl		%EAX,(sockfd)		
    
        # сохраним дескриптор созданного сокета в переменной
        # sockfd
    
        # далее необходимо осуществить операцию BIND,
        # которая называется «привязка имени сокету»,
        # хотя суть этого названия 
        # слабо отражает смысл происходящего на самом деле.
        # На самом деле BIND просто назначает конкретному сокету IP-
        # адрес и порт, через который с ним можно взаимодействовать:
    
        # размер передаваемой структуры (вообще подобран 
        		pushl		$0x10
        # методом тыка, потому что логически непонятно почему 
        # именно 16)
    
        # указатель на структуру sockaddr_in
    
               pushl		$sockaddr_in
        # дескриптор нашего сокета
        		pushl		%EAX			
        # ECX указывает на параметры в стеке
        		movl		%ESP,%ECX		
        # номер функции BIND – в EBX
        		movl		$SYS_BIND,%EBX
        # функция-мультиплексор
        		movl		$SYS_socketcall,%EAX
        		int		$0x80
        # теперь сокет «привязан» к конкретному IP-шнику и порту
        # поднимем ESP на место
        		addl		$0xC,%ESP		
        # далее что-либо подробно описывать я не вижу смысла,
        # любой желающий сам без труда разберется, опираясь на 
        # полученные выше знания.
        		pushl		$0			# backlog
        		movl		(sockfd),%EAX
        		pushl		%EAX
        		movl		%ESP,%ECX
        		movl		$SYS_LISTEN,%EBX
        		movl		$SYS_socketcall,%EAX
        		int         $0x80
        		addl		$0x8,%ESP
    
        _wait_next_client:
        		pushl		$0			# addrlen
        		pushl		$0			# cliaddr
        		movl		(sockfd),%EAX
        		pushl		%EAX			# sockfd
        		movl		%ESP,%ECX
        		movl		$SYS_ACCEPT,%EBX
        		movl		$SYS_socketcall,%EAX
        		int		$0x80
        		addl		$0xC,%ESP
    
        		movl		%EAX,(connfd)
        		
        		movl		$SYS_fork,%EAX
        		int		$0x80			# create child process
        		test		%EAX,%EAX
        		jnz		_wait_next_client
    
        _next_plain_text:
        		movl		(connfd),%EBX
        		movl		$buf,%ECX		# ECX->buf
        		movl		$1024,%EDX		# 1024 bytes
        		movl		$SYS_read,%EAX
        		int		$0x80			# wait plain_text
    
        		movl		$buf,%ESI
        		movl		%ESI,%EDI
        		movl		%EAX,%ECX
        		movl		%EAX,%EDX
        _encrypt:
        		lodsb
        		cmp		$0x41,%AL		# A
        		jb		_next
        		cmp		$0x5A,%AL		# Z
        		ja		_maybe_small
        		incb		%AL
        		incb		%AL			# encryption ;)
        		cmp		$0x5A,%AL
        		jle		_next
        		sub		$26,%AL
        _maybe_small:
        		cmp		$0x61,%AL		# a
        		jb		_next
        		cmp		$0x7A,%AL		# z
        		ja		_next
        		incb		%AL
        		incb		%AL			# encryption ;)
        		cmp		$0x7A,%AL
        		jle		_next
        		sub		$26,%AL
        _next:
        		stosb
        		loop		_encrypt
        		
        		movl		(connfd),%EBX
        		movl		$buf,%ECX		# ECX->chiper_text
        		movl		$SYS_write,%EAX
        		int		$0x80			# send plain_text
        		
        		jmp		_next_plain_text
        # *****************************************************
        		.data
        sockfd:		.long		0
        connfd:		.long		0
        sockaddr_in:	
        sin_family:	.word		AF_INET
        sin_port:	.word		0x3930			# port:12345
        sin_addr:	.long		0			# INADDR_ANY
        buf:
        # *****************************************************
    
    
    
        #Клиент пишется по аналогии с сервером,
        # думаю сами без труда разберетесь:
    
    
        # *********************************************************
        # Client
        # by Broken Sword [HI-TECH]
        # (for Linux based on Intel x86 only)
    
        # [email protected]
        # www.wasm.ru
    
        # Compile Instructions:
        # ---------------------------------------------------------
        # as client.s
        # ld --strip-all -o client a.out
        # *********************************************************
    
        # *********************************************************		
               .include		"syscalls.inc"
        		.include 	"def.inc"
        		.text
        		.globl		_start
        _start:
        		pushl		$0			# protocol
        		pushl		$SOCK_STREAM		# type
        		pushl		$AF_INET			# domain
        		movl		%ESP,%ECX
        		movl		$SYS_SOCKET,%EBX
        		movl		$SYS_socketcall,%EAX
        		int		$0x80
        		addl		$0xC,%ESP
    
        		movl		%EAX,(sockfd)
    
        		pushl		$0x10			# addrlen
        		pushl		$sockaddr_in
        		pushl		%EAX			# sockfd
        		movl		%ESP,%ECX
        		movl		$SYS_CONNECT,%EBX
        		movl		$SYS_socketcall,%EAX
        		int		$0x80
        		addl		$0xC,%ESP
    
        _next_plain_text:
        		xorl		%EBX,%EBX		# stdin
        		movl		$buf,%ECX		# ECX->buf
        		movl		$1024,%EDX		# 1024 bytes
        		movl		$SYS_read,%EAX
        		int		$0x80			# read from stdin
    
        		movl		(sockfd),%EBX
        		movl		$buf,%ECX		# ECX->plain_text
        		movl		%EAX,%EDX		# bytes read
        		movl		$SYS_write,%EAX
        		int		$0x80			# send plain_text
    
        		movl		$SYS_read,%EAX
        		int		$0x80			# wait chiper_text
        		
        		xorl		%EBX,%EBX
        		incl		%EBX			# EBX=1 (stdout)
        		movl		$SYS_write,%EAX
        		int		$0x80			# disp chiper_text
        		
        		jmp		_next_plain_text
        # *********************************************************
        		.data
        sockfd:		.long		0
        sockaddr_in:	
        sin_family:	.word		AF_INET
        sin_port:	.word		0x3930			# port:12345
        sin_addr:	.long		0x0100007F		# 127.0.0.1
        buf:
        # *********************************************************
    
    
    Вот так вот все просто (вся сложность на самом деле заключена в понимании стека TCP/IP, а не в том, как закодировать все эти действия на асме).

    Все - запускайте бесов server, а за ним client.

    p.s. использовать данное приложение для шифрования важных данных на диске не рекомендуется ).


    Благодарности (в алфавитном порядке :)):


    Aquila [HI-TECH]
    CyberManiac [HI-TECH]
    Edmond [HI-TECH]
    Vladimir [HI-TECH]​
     
    1 человеку нравится это.

Поделиться этой страницей