Разное

Как выучить ассемблер: Погружение в assembler. Полный курс по программированию на асме от ][ — «Хакер»

Ассемблер. Базовый синтаксис | Уроки Ассемблера

  Обновл. 29 Сен 2019  | 

Программы на ассемблере могут быть разделены на три секции:

   Секция data

   Секция bss

   Секция text

Секции ассемблера

Секция data используется для объявления инициализированных данных или констант. Данные в этой секции не могут быть изменены во время выполнения. Вы можете хранить константные значения и названия файлов в этой секции. Синтаксис объявления:

Секция bss используется для объявления переменных. Синтаксис объявления:

Секция text используется для хранения кода программы. Данная секция должна начинаться с объявления global_start, которое сообщает ядру, откуда нужно начинать выполнение программы. Синтаксис объявления:

section.text
global _start
_start:



section.text

   global _start

_start:

Комментарии

Комментарии в ассемблере должны начинаться с точки с запятой (;). Они могут содержать любой печатный символ, включая пробел. Комментарий может находиться как на отдельной строке:

; эта программа выводит сообщение на экран



; эта программа выводит сообщение на экран

Так и на строке со стейтментом:

add eax, ebx ; добавляет ebx к eax



add eax, ebx     ; добавляет ebx к eax

Стейтменты

В ассемблере есть три вида стейтментов:

   Выполняемые инструкции (или просто «инструкции»), которые сообщают процессору, что нужно делать. Каждая инструкция хранит в себе код операции (или ещё «опкод») и генерирует одну инструкцию на машинном языке.

   Директивы ассемблера, которые сообщают программе об аспектах компиляции. Они не генерируют инструкции на машинном языке.

   Макросы, которые являются простым механизмом вставки кода.

В ассемблере на одну строку приходится один стейтмент, который должен соответствовать следующему формату:

[метка] mnemonic [операнды] [; комментарий]



[метка]   mnemonic   [операнды]   [; комментарий]

Базовая инструкция состоит из названия инструкции (mnemonic) и операндов (они же «параметры»). Вот примеры типичных стейтментов ассемблера:

INC COUNT ; выполняем инкремент переменной памяти COUNT

MOV TOTAL, 48 ; перемещаем значение 48 в переменную памяти TOTAL

ADD AH, BH ; добавляем содержимое регистра BH к регистру AH

AND MASK1, 128 ; выполняем операцию AND с переменной MASK1 и 128

ADD MARKS, 10 ; добавляем 10 к переменной MARKS
MOV AL, 10 ; перемещаем значение 10 в регистр AL



INC COUNT        ; выполняем инкремент переменной памяти COUNT

 

MOV TOTAL, 48    ; перемещаем значение 48 в переменную памяти TOTAL

  

ADD AH, BH       ; добавляем содержимое регистра BH к регистру AH

  

AND MASK1, 128   ; выполняем операцию AND с переменной MASK1 и 128

  

ADD MARKS, 10    ; добавляем 10 к переменной MARKS

MOV AL, 10       ; перемещаем значение 10 в регистр AL

Первая программа

Следующая программа на языке ассемблера выведет строку Hello, world! на экран:

section .text
global _start ; необходимо для линкера (ld)

_start: ; сообщает линкеру стартовую точку
mov edx,len ; длина строки
mov ecx,msg ; строка
mov ebx,1 ; дескриптор файла (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра

section .data
msg db ‘Hello, world!’, 0xa ; содержимое строки для вывода
len equ $ — msg ; длина строки



section .text

   global _start    ; необходимо для линкера (ld)

_start:             ; сообщает линкеру стартовую точку

   mov edx,len     ; длина строки

   mov ecx,msg     ; строка

   mov ebx,1       ; дескриптор файла (stdout)

   mov eax,4       ; номер системного вызова (sys_write)

   int 0x80        ; вызов ядра

   mov eax,1       ; номер системного вызова (sys_exit)

   int 0x80        ; вызов ядра

 

section .data

msg db ‘Hello, world!’, 0xa  ; содержимое строки для вывода

len equ $ — msg              ; длина строки

Результат выполнения программы выше:

Hello, world!

Сборка программ

Убедитесь, что у вас установлен NASM. Запишите вашу программу в текстовом редакторе и сохраните её как hello.asm. Затем:

   откройте терминал;

   убедитесь, что вы находитесь в той же директории, в которой вы сохранили hello.asm;

   чтобы собрать программу, введите команду nasm -f elf hello.asm;

   если не было ошибок, то создастся объектный файл вашей программы под названием hello.o;

   чтобы ваш объектный файл прошёл линкинг и создался исполняемый файл под названием hello, введите команду ld -m elf_i386 -s -o hello hello.o;

   запустите программу, написав команду ./hello.

Если всё прошло успешно, то вам выведется Hello, world!.

Если у вас нет возможности скомпилировать программу, например, у вас нет Linux и вы пока не хотите на него переходить, то можете использовать одну из следующих онлайн-IDE:

   TutorialsPoint

   JDoodle

Примечание: Запоминать две команды выше для сборки программы на ассемблере для некоторых может быть несколько затруднительно, поэтому вы можете написать скрипт для сборки программ на ассемблере. Для этого создайте файл под названием Makefile со следующим содержанием:

all:
nasm –f elf $(source)
ld –m elf_i386 –s –o $(source) $(source).o
rm $(source).o



all:

    nasm –f elf $(source)

    ld –m elf_i386 –s –o $(source) $(source).o

    rm $(source).o

Для сборки hello.asm выполните следующие действия:

   откройте терминал;

   убедитесь, что вы находитесь в той же директории, в которой вы сохранили hello.asm и Makefile;

   введите команду make source=hello.

Оценить статью:

Загрузка…

Поделиться в социальных сетях:

Есть ли смысл изучать Ассемблер? — Хабр Q&A

Литература по Debugging, Vulnerability Analysis, Asm, ARM.

Debugging:
Bill Blunden. Software Exorcism. A Handbook for Debugging and Optimizing Legacy Code.
Brian W. Fitzpatrick. Debugging Teams. Better Productivity through Collaboration.
Butcher Paul. Debug It. Find, Repair, and Prevent Bugs in Your Code.
Debugging Linux Systems with GNU GDB.
Eric Lawrence. Debugging with Fiddler. The complete reference from the creator of the Fiddler Web Debugger.
Mario Hewardt, Daniel Pravat. Advanced Windows Debugging.
Mario Hewardt. Advanced .NET Debugging.
Matthew A. Telles, Yuan Hsieh. The Science of Debugging.
Norman Matloff. Peter Jay Salzman. THE ART OF DEBUGGING WITH GDB, DDD, AND ECLIPSE.
Rajaram Regupathy. Bootstrap Yourself with Linux-USB Stack. Design, Develop, Debug, and Validate Embedded USB Systems.
Richard Foley. Pro Perl Debugging. From Professional to Expert.
Richard H. Carver, Kuo-Chung Tai. Modern Multithreading Implementing, Testing, and Debugging Multithreaded Java and C++ & Pthreads & Win32 Programs.
Richard Stallman, Roland Pesch, Stan Shebs, et al. Debugging with GDB — the GNU Source-Level Debugger.
Steve Maguire. Debugging the Development Process.
Tarik Soulami. Inside Windows Debugging. A Practical Guide to Debugging and Tracing Strategies in Windows.
Thorsten Grötker, Ulrich Holtmann, Holger Keding, Markus Wloka. The Developer’s Guide to Debugging.
Cristina Cifuentes. Reverse Compilation Techniques.

Vulnerability Analysis:
Abraham Ghebrehiwet Ghebremedhin. Combining Static Source Code Analysis and Threat Assessment Modeling For Testing Open Source Software Security.
Adam Loe Doup´e. Advanced Automated Web Application Vulnerability Analysis.
Antti Vayrynen. Finding third-party components with binary analysis.
David B. Dewey. FINDING AND REMEDYING HIGH-LEVEL SECURITY ISSUES IN BINARY CODE.
David Brumley. Analysis and Defense of Vulnerabilities in Binary Code.
Fabian Yamaguchi. Automated Extraction of API Usage Patterns from Source Code for Vulnerability Identification.
Fabian Yamaguchi. Pattern-Based Vulnerability Discovery.
Falko Strenzke. Efficiency and Implementation Security of Code-based Cryptosystems.
Francisco Jose Marques Vieira. Realistic Vulnerability Injections in PHP Web Applications.
George Perera. PURPOSEFULLY MANUFACTURED VULNERABILITIES IN U.S. GOVERNMENT TECHNOLOGY MICROCHIPS. RISKS AND HOMELAND SECURITY IMPLICATIONS.
Guidelines for Implementation of REST.
Gustav Ahlberg. Generating web applications containing XSS and CSRF vulnerabilities.
Jay-Evan J. Tevis. AUTOMATIC DETECTION OF SOFTWARE SECURITY VULNERABILITIES IN EXECUTABLE PROGRAM FILES.
Jing Xie. INTERACTIVE PROGRAMMING SUPPORT FOR SECURE SOFTWARE DEVELOPMENT.
Mario Heiderich. Towards Elimination of XSS Attacks with a Trusted and Capability Controlled DOM.
Martin Johns. Code Injection Vulnerabilities in Web Applications — Exemplified at Cross-site Scripting.
Prateek Saxena. Systematic Techniques for Finding and Preventing Systematic Techniques for Finding and Preventing.
Rami M. F. Jnena. Modern Approach for WEB Applications Vulnerability Analysis.
Richard Wartell. REWRITING X86 BINARIES WITHOUT CODE PRODUCER COOPERATION.
Runar Moen. Creating secure software.
Ryan Dewhurst. Implementing Basic Static Code Analysis into Integrated Development Environments (IDEs) to Reduce Software Vulnerabilities.
Sidney E Valentine. PLC Code Vulnerabilities Through SCADA Systems.
Sooel Son, B.S., M.S. Toward Better Server-side Web Security.
SRUTHI BANDHAKAVI. AUTOMATED DETECTION OF INJECTION VULNERABILITIES IN WEB APPLICATIONS.
Steven Craig Hanna Jr. Attacks on Emerging Architectures.
Thomas Hofer. Evaluating Static Source Code Analysis Tools.
Tyler Bletsch. Code-Reuse Attacks. New Frontiers and Defenses.
Wolf-Steffen Rodiger. Merging Static Analysis and Model Checking for Improved Security Vulnerability Detection.
Yuchen Zhou. Improving Security and Privacy of Integrated Web Applications.

Assembler:
Blum Richard. Professional Assembly Language.
Cavanagh Joseph. X86 Assembly Language and C Fundamentals.
Duntemann Jeff. Assembly Language Step-by-Step. Programming with Linux.
Irvine K.R. Assembly Language for x86 Processors. Seventh Edition.
Irvine K.R. Assembly Language for x86 Processors. Sixth Edition.
Kusswurm D. Modern X86 Assembly Language Programming. 32-bit, 64-bit, SSE, and AVX.
Leiterman J.C. 32-64-Bit 80×86 Assembly Language Architecture.
Neveln Bob. Linux Assembly Language Programming.
Rose Chris. Assembly Language Succinctly.
Seyfarth Ray. Introduction to 64 Bit Intel Assembly Language Programming for Linux.
Hyde R. The Art of Assembly Language (Second Edition).
Dandamudi S.P. Guide to Assembly Language Programming in Linux.

ARM:
Holt W. ARM Assembly Language. Fundamentals and Techniques.
Langbridge J.A. Professional Embedded ARM Development.
Магда Ю.С. Программирование и отладка C C++ приложений для микроконтроллеров ARM.
Tay Raymond. OpenCL Parallel Programming Development Cookbook.
Mahout Vincent. Assembly Language Programming ARM Cortex-M3.
Sloss Andrew N. Symes D. Wright C. ARM System Developers Guide. Designing and Optimizing System Software.
Valvano J.W. Embedded Systems. Introduction to Arm Cortex™-M Microcontrollers. Volume 1.
Yui J. The Definitive Guide to ARM Cortex-M0 and Cortex-M0+ Processors.

Ассемблер. Арифметические инструкции | Уроки Ассемблера

  Обновл. 20 Окт 2019  | 

В этом уроке мы будем разбираться с арифметическими инструкциями в ассемблере на примере INC, DEC, ADD, SUB и пр.

Инструкция INC

Инструкция INC (от англ. «INCREMENT») используется для увеличения операнда на единицу. Она работает с одним операндом, который может находиться либо в регистре, либо в памяти.

Синтаксис инструкции INC:

INC место_назначения

Операндом место_назначения может быть 8-битный, 16-битный или 32-битный операнд.

Пример:

INC EBX ; выполняем инкремент 32-битного регистра
INC DL ; выполняем инкремент 8-битного регистра
INC [count] ; выполняем инкремент переменной count



INC EBX      ; выполняем инкремент 32-битного регистра

INC DL       ; выполняем инкремент 8-битного регистра

INC [count]  ; выполняем инкремент переменной count

Инструкция DEC

Инструкция DEC (от англ. «DECREMENT») используется для уменьшения операнда на единицу. Она работает с одним операндом, который может находиться либо в регистре, либо в памяти.

Синтаксис инструкции DEC:

DEC место_назначения

Операндом место_назначения может быть 8-битный, 16-битный или 32-битный операнд.

Пример:

segment .data
count dw 0
value db 15

segment .text
inc [count]
dec [value]

mov ebx, count
inc word [ebx]

mov esi, value
dec byte [esi]



segment .data

   count dw  0

   value db  15

segment .text

   inc [count]

   dec [value]

   mov ebx, count

   inc word [ebx]

   mov esi, value

   dec byte [esi]

Инструкции ADD и SUB

Инструкции ADD и SUB используются для выполнения простого сложения/вычитания двоичных данных размером в byte, word и doubleword, то есть для сложения или вычитания 8-битных, 16-битных или 32-битных операндов, соответственно.

Синтаксис инструкций ADD и SUB:

ADD/SUB      место_назначения, источник

Инструкции ADD/SUB могут выполняться между:

   регистром и регистром;

   памятью и регистром;

   регистром и памятью;

   регистром и константами;

   памятью и константами.

Однако, как и другие инструкции, операции типа память-в-память невозможны с использованием инструкций ADD/SUB. Операции ADD или SUB устанавливают или сбрасывают флаги переполнения и переноса.

В следующем примере мы спрашиваем у пользователя два числа, сохраняем их в регистрах EAX и EBX, затем выполняем операцию сложения, сохраняем результат в ячейке памяти res и выводим его на экран:

SYS_EXIT equ 1
SYS_READ equ 3
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1

segment .data

msg1 db «Enter a digit «, 0xA,0xD
len1 equ $- msg1

msg2 db «Please enter a second digit», 0xA,0xD
len2 equ $- msg2

msg3 db «The sum is: «
len3 equ $- msg3

segment .bss

num1 resb 2
num2 resb 2
res resb 1

section .text
global _start ; должно быть объявлено для использования gcc

_start: ; сообщаем линкеру входную точку
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80

mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num1
mov edx, 2
int 0x80

mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80

mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num2
mov edx, 2
int 0x80

mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80

; перемещаем первое число в регистр eax, а второе число — в регистр ebx
; и вычитаем ASCII ‘0’ для конвертации в десятичное число

mov eax, [num1]
sub eax, ‘0’

mov ebx, [num2]
sub ebx, ‘0’

; добавляем eax и ebx
add eax, ebx
; добавляем ‘0’ для конвертации суммы из десятичной системы в ASCII
add eax, ‘0’

; сохраняем сумму в ячейке памяти res
mov [res], eax

; выводим сумму
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, res
mov edx, 1
int 0x80

exit:

mov eax, SYS_EXIT
xor ebx, ebx
int 0x80


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

SYS_EXIT  equ 1

SYS_READ  equ 3

SYS_WRITE equ 4

STDIN     equ 0

STDOUT    equ 1

 

segment .data

 

   msg1 db «Enter a digit «, 0xA,0xD

   len1 equ $- msg1

 

   msg2 db «Please enter a second digit», 0xA,0xD

   len2 equ $- msg2

 

   msg3 db «The sum is: «

   len3 equ $- msg3

 

segment .bss

 

   num1 resb 2

   num2 resb 2

   res resb 1    

 

section .text

   global _start    ; должно быть объявлено для использования gcc

_start:             ; сообщаем линкеру входную точку

   mov eax, SYS_WRITE        

   mov ebx, STDOUT        

   mov ecx, msg1        

   mov edx, len1

   int 0x80                

 

   mov eax, SYS_READ

   mov ebx, STDIN  

   mov ecx, num1

   mov edx, 2

   int 0x80            

 

   mov eax, SYS_WRITE        

   mov ebx, STDOUT        

   mov ecx, msg2          

   mov edx, len2        

   int 0x80

 

   mov eax, SYS_READ  

   mov ebx, STDIN  

   mov ecx, num2

   mov edx, 2

   int 0x80        

 

   mov eax, SYS_WRITE        

   mov ebx, STDOUT        

   mov ecx, msg3          

   mov edx, len3        

   int 0x80

 

   ; перемещаем первое число в регистр eax, а второе число — в регистр ebx

   ; и вычитаем ASCII ‘0’ для конвертации в десятичное число

   mov eax, [num1]

   sub eax, ‘0’

   mov ebx, [num2]

   sub ebx, ‘0’

 

   ; добавляем eax и ebx

   add eax, ebx

   ; добавляем ‘0’ для конвертации суммы из десятичной системы в ASCII

   add eax, ‘0’

 

   ; сохраняем сумму в ячейке памяти res

   mov [res], eax

 

   ; выводим сумму

   mov eax, SYS_WRITE        

   mov ebx, STDOUT

   mov ecx, res        

   mov edx, 1        

   int 0x80

 

exit:    

  

   mov eax, SYS_EXIT  

   xor ebx, ebx

   int 0x80

Результат выполнения программы выше:

Enter a digit:
3
Please enter a second digit:
4
The sum is:
7

Ниже рассмотрен пример, в котором, за счёт того, что значения переменных для арифметических выражений прописаны в самом коде программы, можно получить код программы короче и проще:

section .text
global _start ; должно быть объявлено для использования gcc

_start: ; сообщаем линкеру входную точку
mov eax,’3’
sub eax, ‘0’

mov ebx, ‘4’
sub ebx, ‘0’
add eax, ebx
add eax, ‘0’

mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov ecx,sum
mov edx, 1
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра

section .data
msg db «The sum is:», 0xA,0xD
len equ $ — msg
segment .bss
sum resb 1


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

section .text

   global _start    ; должно быть объявлено для использования gcc

_start:             ; сообщаем линкеру входную точку

   mov eax,’3′

   sub     eax, ‘0’

   mov ebx, ‘4’

   sub     ebx, ‘0’

   add eax, ebx

   add eax, ‘0’

   mov [sum], eax

   mov ecx,msg

   mov edx, len

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov ecx,sum

   mov edx, 1

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov eax,1 ; номер системного вызова (sys_exit)

   int 0x80 ; вызов ядра

section .data

   msg db «The sum is:», 0xA,0xD

   len equ $ — msg  

   segment .bss

   sum resb 1

Результат выполнения программы выше:

The sum is:
7

Инструкции MUL и IMUL

Есть две инструкции для умножения двоичных данных:

   инструкция MUL (от англ. «MULTIPLY») обрабатывает данные unsigned;

   инструкция IMUL (от англ. «INTEGER MULTIPLY») обрабатывает данные signed.

Обе инструкции влияют на флаги переноса и переполнения.

Синтаксис инструкций MUL/IMUL:

MUL/IMUL множитель

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

Рассмотрим 3 разных сценария:

Сценарий №1: Когда перемножаются 2 значения типа byte — множимое находится в регистре AL, а множителем является значение типа byte в памяти или в другом регистре. Результат произведения находится в AX. Старшие 8 бит произведения хранятся в AH, а младшие 8 бит — хранятся в AL:

Сценарий №2: Когда перемножаются 2 значения типа word множимое должно быть в регистре AX, а множителем является значение типа word в памяти или в другом регистре. Например, для такой инструкции, как MUL DX, вы должны сохранить множитель в DX, а множимое — в AX. В результате получится значение типа doubleword для которого понадобятся два регистра. Часть высшего порядка (крайняя слева) сохраняется в DX, а часть нижнего порядка (крайняя справа) — сохраняется в AX:

Сценарий №3: Когда перемножаются 2 значения типа doubleword — множимое должно находится в EAX, а множителем является значение типа doubleword, хранящееся в памяти или в другом регистре. Результат умножения сохраняется в регистрах EDX и EAX. Биты старшего порядка сохраняются в регистре EDX, а биты младшего порядка — сохраняются в регистре EAX:

Пример:

MOV AL, 10
MOV DL, 25
MUL DL

MOV DL, 0FFH ; DL= -1
MOV AL, 0BEH ; AL = -66
IMUL DL



MOV AL, 10

MOV DL, 25

MUL DL

MOV DL, 0FFH ; DL= -1

MOV AL, 0BEH ; AL = -66

IMUL DL

А в следующем примере мы 3 умножаем на 2 и выводим результат:

section .text
global _start ; должно быть объявлено для использования gcc

_start: ; сообщаем линкеру входную точку

mov al,’3’
sub al, ‘0’

mov bl, ‘2’
sub bl, ‘0’
mul bl
add al, ‘0’

mov [res], al
mov ecx,msg
mov edx, len
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov ecx,res
mov edx, 1
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра

section .data
msg db «The result is:», 0xA,0xD
len equ $- msg
segment .bss
res resb 1


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

section .text

   global _start    ; должно быть объявлено для использования gcc

_start:             ; сообщаем линкеру входную точку

 

   mov al,’3′

   sub     al, ‘0’

   mov bl, ‘2’

   sub     bl, ‘0’

   mul bl

   add al, ‘0’

   mov [res], al

   mov ecx,msg

   mov edx, len

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov ecx,res

   mov edx, 1

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov eax,1 ; номер системного вызова (sys_exit)

   int 0x80 ; вызов ядра

 

section .data

msg db «The result is:», 0xA,0xD

len equ $- msg  

segment .bss

res resb 1

Результат выполнения программы выше:

The result is:
6

Инструкции DIV и IDIV

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

Инструкция DIV (от англ. «DIVIDE») используется с данными unsigned, а инструкция IDIV (от англ. «INTEGER DIVIDE») используется с данными signed.

Синтаксис инструкций DIV/IDIV:

DIV/IDIV     делитель

Делимое находится в аккумуляторе. Обе инструкции могут работать с 8-битными, 16-битными или 32-битными операндами. Данная операция влияет на все шесть флагов состояния.

Рассмотрим следующие 3 сценария:

Сценарий №1: Когда делителем является значение типа byte — предполагается, что делимое находится в регистре AX (16 бит). После деления частное переходит в регистр AL, а остаток переходит в регистр AH:

Сценарий №2: Когда делителем является значение типа word — предполагается, что делимое имеет длину 32 бита и находится в регистрах DX и AX. Старшие 16 битов находятся в DX, а младшие 16 битов — в AX. После деления 16-битное частное попадает в регистр AX, а 16-битный остаток — в регистр DX:

Сценарий №3: Когда делителем является значение типа doubleword — предполагается, что размер делимого составляет 64 бита и оно размещено в регистрах EDX и EAX. Старшие 32 бита находятся в EDX, а младшие 32 бита — в EAX. После деления 32-битное частное попадает в регистр EAX, а 32-битный остаток — в регистр EDX:

В следующем примере мы делим 8 на 2. Делимое 8 сохраняется в 16-битном регистре AX, а делитель 2 — в 8-битном регистре BL:

section .text
global _start ; должно быть объявлено для использования gcc

_start: ; сообщаем линкеру входную точку
mov ax,’8’
sub ax, ‘0’

mov bl, ‘2’
sub bl, ‘0’
div bl
add ax, ‘0’

mov [res], ax
mov ecx,msg
mov edx, len
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov ecx,res
mov edx, 1
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра

section .data
msg db «The result is:», 0xA,0xD
len equ $- msg
segment .bss
res resb 1


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

section .text

   global _start    ; должно быть объявлено для использования gcc

_start:             ; сообщаем линкеру входную точку

   mov ax,’8′

   sub     ax, ‘0’

   mov bl, ‘2’

   sub     bl, ‘0’

   div bl

   add ax, ‘0’

   mov [res], ax

   mov ecx,msg

   mov edx, len

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov ecx,res

   mov edx, 1

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov eax,1 ; номер системного вызова (sys_exit)

   int 0x80 ; вызов ядра

section .data

msg db «The result is:», 0xA,0xD

len equ $- msg  

segment .bss

res resb 1

Результат выполнения программы выше:

The result is:
4

На этом всё!

Оценить статью:

Загрузка…

Поделиться в социальных сетях:

Как понять ассемблер для AVR / Хабр

Всем добрый вечер! Веду свою трансляцию из уютного мира, который называется «ассемблер». Сразу поясню что тема касается микроконтроллеров AVR — и я пока ещё не знаю, пригодится ли этот пост тем, кто хочет использовать ассемблер для любой другой задачи. Дело в том, что я буквально несколько дней назад начал учить ассемблер с нуля — нужно сделать одно устройство — и я решил сделать в нём всё самостоятельно. Так вот — в один прекрасный день понял, что учить ассемблер абсолютно бесполезно! Ассемблер можно только понять! То есть всем тем, кто хочет программировать на ассемблере я настоятельно рекомендую детально вникнуть в то, каким образом ФИЗИЧЕСКИ работает микроконтроллер, а затем уже изучать тонкости команд.
Так вот, я пожалуй начну небольшой цикл статей, в которых буду с самого начала рассказывать как именно я понял те или иные вещи в программировании на ассемблере — думаю для тех, кто вообще не понимает что такое асм я буду как раз таким «переводчиком» с языка тех, кто в этом деле очень хорошо шарит.

Сразу скажу, что я более-менее вкурил эту тему с подачи DIHALT — поэтому эти статейки будут являться неким переводом с супер-пупер-ассемблерно-микроконтроллерного языка на язык понятный большинству людей. Ну а гуру надеюсь будут меня поправлять по ходу пьесы и если вдруг я что то объясню неправильно — то они поправят меня.
Итак первые выводы об ассемблере, которые я сделал пару дней назад, меня потрясли до глубины души — и я просидел за статьями DI HALT’а с 11 вечера до 5 утра — после чего лёг спать довольным и счастливым. Я понял суть программирования на ассемблере для микроконтроллеров.
Как же это объяснить ещё проще? Думаю нужно начать с самой сути.
***
Изначально не будем вдаваться в технические подробности (о них мы поговорим в следующей статье) — просто представьте, что есть 3 персонажа:
1. Микроконтроллер — это англичанин Стив, который приехал к русскому. Он идеально знает английский язык, но по-русски он вообще не понимает — ни единого слова. Только английский. Он проиграл в споре и обязался делать бесприкословно всё то, о чём его попросит русский.
2. Ассемблер — это переводчик Вася у которого мама англичанка а папа русский. Он знает идеально и английский и русский язык.
3.Мы —это русский, к которому приехал англичанин. Ну то есть мы это мы=) При этом мы идеально знаем русский язык и (!!!) чуть-чуть английский — самую малость, со словариком.
***
Представьте такую ситуацию — англичанин сидит у Вас в комнате на стуле. А Вы сидите себе за компом и читаете этот пост, как вдруг у Вас внезапно открылась форточка! Вот ведь незадача! Ветер дует, занавеска превратилась в парус… Было бы неплохо закрыть! Но вот ведь как лень вставать со стула, снимать ноги с системника, запихивать их в тапочки, отставлять кружку с кофе(пивом) и идти бороться со стихией. И тут Вы внезапно осознаёте, что у нас то в комнате есть проспоривший англичанин, которого самое время погонять! И вы ему так мило говорите «Дружище! Закрой форточку пожалуйста, а потом можешь опять присесть на стул!» а он сидит, смотрит на вас с недоумением и ничего не делает! Можно конечно по щам надавать — но он же тогда всё равно вас не поймёт! Тогда Вы звоните своему другу-переводчику Василию — он приходит, и садится рядом с англичанином на стул. И вы говорите — Переведи: «Стив, пойди и закрой форточку, а потом обратно сядь на стул!» Переводчик переводит на английский — англичанин понимает и идёт закрывает форточку, а затем приходит и садится на стул.
В этом моменте нужно просто понять роль ассемблера в этой цепочке «Мы-Ассемблер-Контроллер»
То есть как бы что такое ассемблер все поняли? Тогда читаем дальше.
***

Так вот, представляем такую ситуацию. Васе говоришь — «Слушай, ну короче такое дело — я калькулятор дома забыл, раздели 56983 на 2 и скажи Стиву, чтобы он столько раз отжался на кулаках» и Вася на калькуляторе считает и говорит Стиву по-английски » Отожмись на кулаках 28491 раз» Это называется «ДИРЕКТИВА» — другими словами директива это задание для Васи, результат выполнения которой это действие Стива.

Есть другая ситуация — Вы говорите Васе «Скажи Стиву, чтобы он отжался 28491 раз» и Вася просто переводит Ваши слова на английский. Это называется ОПЕРАТОР

Всё просто — есть директива и есть оператор. Оператор — это Ваше прямое указание что делать Стиву — Вася тут только переводит Ваше требование на инглиш. А Директива — это задание для самого Васи — и Вася сначала делает то, что Вы ему сказали, а потом уже в зависимости от результата говорит Стиву что-либо.

Теперь мы будем мучать англичанина регулярно! Но предварительно нужно получше познакомиться с нашим переводчиком Васей. Нужно знать следующее — Вася всегда Вас слушается беспрекословно — что ему сказали, то он и делает. Васин калькулятор не имеет десятичных знаков — если вы глянете пример с отжиманиями то 56983 \ 2 = 28491.5 — но у Васи всё после запятой обрубается — и он видит только целое число — причём неважно там будет 28491.000001 или там будет 28491.9999999 — для Васи это один фиг будет 28491 в обоих случаях. Ничего не округляется. Ещё важная информация про Васю. Вася жесток — ему пофиг на то, что Стив затрахается отжиматься двадцать восемь тысяч раз. Ему сказали — Вася перевёл. Причём не только перевёл — но и заставил сделать то, что Вы попросили. Так что если Стив помрёт на двадцать три тысячи пятьсот тринадцатом отжимании — то это будет исключительно Ваша вина.

Собственно это пока что всё. В следующем посте будем копать глубже — пока же просто достаточно понять это. Просто представить эту ситуацию и понять что к чему, кто исполняет какую роль и чем директива отличается от оператора.

А дальше мы постараемся называть всё своими именами и примерно прикинуть как же ассемблер работает с микроконтроллером по взрослому.

Учебные пособия по языку ассемблера NASM — asmtutor.com

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

sys_close ожидает 1 аргумент — дескриптор файла в EBX. Затем в EAX загружается код операции sys_close и вызывается ядро, чтобы закрыть сокет и удалить активный дескриптор файла.

Примечание:
Запустите программу и используйте команду curl http: // localhost: 9001 в другом терминале или подключитесь к тому же адресу с помощью любого стандартного веб-браузера.

socket.asm

                                ; Разъем
                                ; Скомпилируйте с помощью: nasm -f elf socket.asm
                                ; Связать с (64-битные системы требуют опции elf_i386): ld -m elf_i386 socket.o -o socket
                                ; Запустите с: ./socket

                                % включают "functions.asm"

                                РАЗДЕЛ .data
                                ; наша строка ответа
                                ответ db 'HTTP / 1.1200 OK ', 0Dh, 0Ah,' Content-Type: text / html ', 0Dh, 0Ah,' Content-Length: 14 ', 0Dh, 0Ah, 0Dh, 0Ah,' Hello World! ', 0Dh, 0Ah, 0h

                                РАЗДЕЛ .bss
                                буфер resb 255,; переменная для хранения заголовков запросов

                                РАЗДЕЛ. Текст
                                глобальный _start

                                _Начало:

                                    xor eax, eax; инициализировать некоторые регистры
                                    xor ebx, ebx
                                    xor edi, edi
                                    xor esi, esi

                                _разъем:

                                    толкнуть байт 6; создать сокет из урока 29
                                    толкнуть байт 1
                                    толкнуть байт 2
                                    mov ecx, esp
                                    mov ebx, 1
                                    mov eax, 102
                                    int 80h

                                _bind:

                                    mov edi, eax; привязать розетку из урока 30
                                    нажать dword 0x00000000
                                    нажать слово 0x2923
                                    нажать слово 2
                                    mov ecx, esp
                                    толкнуть байт 16
                                    нажать ecx
                                    толкни эди
                                    mov ecx, esp
                                    mov ebx, 2
                                    mov eax, 102
                                    int 80h

                                _Слушать:

                                    толкнуть байт 1; слушайте розетку из урока 31
                                    толкни эди
                                    mov ecx, esp
                                    mov ebx, 4
                                    mov eax, 102
                                    int 80h

                                _accept:

                                    нажать байт 0; принять розетку из урока 32
                                    толкнуть байт 0
                                    толкни эди
                                    mov ecx, esp
                                    mov ebx, 5
                                    mov eax, 102
                                    int 80h

                                _fork:

                                    mov esi, eax; вилка розетка из урока 33
                                    mov eax, 2
                                    int 80h

                                    cmp eax, 0
                                    jz _read

                                    jmp _accept

                                _читать:

                                    mov edx, 255; читать розетку из урока 33
                                    mov ecx, буфер
                                    mov ebx, esi
                                    mov eax, 3
                                    int 80h

                                    mov eax, буфер
                                    вызов sprintLF

                                _записывать:

                                    mov edx, 78; запись сокета из урока 34
                                    mov ecx, ответ
                                    mov ebx, esi
                                    mov eax, 4
                                    int 80h

                                _Закрыть:

                                    mov ebx, esi; переместить esi в ebx (принятый дескриптор файла сокета)
                                    mov eax, 6; вызвать SYS_CLOSE (код операции ядра 6)
                                    int 80h; называть ядро

                                _Выход:

                                    вызов выйти; вызовите нашу функцию выхода
                             

~ $ nasm -f elf socket.как м
~ $ ld -m elf_i386 socket.o -o сокет
~ $ ./socket

Новое окно терминала

~ $ curl http: // локальный: 9001
Привет мир!

Примечание:
Мы правильно закрыли соединения сокетов и удалили их активные файловые дескрипторы.

.

binary — лучший способ выучить язык ассемблера вне школы?

Переполнение стека

  1. Около
  2. Продукты

  3. Для команд
  1. Переполнение стека
    Общественные вопросы и ответы

  2. Переполнение стека для команд
    Где разработчики и технологи делятся частными знаниями с коллегами

  3. Вакансии
    Программирование и связанные с ним технические возможности карьерного роста

  4. Талант
    Нанимайте технических специалистов и создавайте свой бренд работодателя

  5. Реклама
    Обратитесь к разработчикам и технологам со всего мира

  6. О компании

.

Как лучше всего изучить сборку x86 на платформе Linux?

Переполнение стека

  1. Около
  2. Продукты

  3. Для команд
  1. Переполнение стека
    Общественные вопросы и ответы

  2. Переполнение стека для команд
    Где разработчики и технологи делятся частными знаниями с коллегами

  3. Вакансии
    Программирование и связанные с ним технические возможности карьерного роста

  4. Талант
    Нанимайте технических специалистов и создавайте свой бренд работодателя

  5. Реклама
    Обратитесь к разработчикам и технологам со всего мира

.

Запись сборки ARM (Часть 1)

Добро пожаловать в эту серию руководств по основам сборки ARM. Это подготовка к следующей серии руководств по разработке эксплойтов для ARM. Прежде чем мы сможем погрузиться в создание шелл-кода ARM и построение цепочек ROP, нам нужно сначала рассмотреть некоторые основы сборки ARM.

Пошагово будут рассмотрены следующие темы:

Учебное пособие по основам сборки ARM

:
Часть 1: Введение в сборку ARM
Часть 2: Регистры типов данных
Часть 3: Набор инструкций ARM
Часть 4: Инструкции памяти: загрузка и сохранение данных
Часть 5: Загрузка и сохранение нескольких частей
6: Условное выполнение и ветвление
Часть 7: Стек и функции

Чтобы следовать примерам, вам понадобится лабораторная среда на базе ARM.Если у вас нет устройства ARM (например, Raspberry Pi), вы можете настроить собственную лабораторную среду на виртуальной машине с помощью QEMU и дистрибутива Raspberry Pi, следуя этому руководству. Если вы не знакомы с базовой отладкой с помощью GDB, вы можете получить основы в этом руководстве. В этом руководстве основное внимание будет уделено 32-разрядной версии ARM, а примеры скомпилированы на ARMv6.

Почему ARM?

Это руководство обычно предназначено для людей, которые хотят изучить основы сборки ARM. Специально для тех из вас, кто интересуется написанием эксплойтов на платформе ARM.Возможно, вы уже заметили, что процессоры ARM повсюду вокруг вас. Оглядываясь вокруг, я могу насчитать в моем доме гораздо больше устройств с процессором ARM, чем процессоры Intel. Сюда входят телефоны, маршрутизаторы и, не говоря уже об устройствах Интернета вещей, продажи которых в наши дни, похоже, стремительно растут. Тем не менее, процессор ARM стал одним из самых распространенных ядер ЦП в мире. Это подводит нас к тому факту, что, как и ПК, устройства IoT подвержены неправильным злоупотреблениям при проверке ввода, таким как переполнение буфера.Учитывая широкое использование устройств на базе ARM и возможность неправильного использования, атаки на эти устройства стали гораздо более распространенными.

Тем не менее, у нас больше экспертов, специализирующихся на исследованиях безопасности x86, чем в ARM, хотя язык ассемблера ARM, пожалуй, самый простой из широко распространенных языков. Итак, почему все больше людей не уделяют внимания ARM? Возможно, потому, что существует больше учебных ресурсов, посвященных эксплуатации на Intel, чем на ARM. Подумайте о великолепных руководствах по Intel x86 Exploit, написанных Fuzzy Security или Corelan Team — подобные руководства помогают людям, интересующимся этой конкретной областью, получить практические знания и вдохновение для изучения помимо того, что описано в этих руководствах.Если вы заинтересованы в написании эксплойтов для x86, учебники Corelan и Fuzzysec станут вашей идеальной отправной точкой. В этой серии руководств мы сосредоточимся на основах сборки и написании эксплойтов на ARM.

Процессор

ARM против процессора Intel

Между Intel и ARM много различий, но главное отличие — это набор инструкций. Intel — это процессор CISC (Complex Instruction Set Computing), который имеет более крупный и многофункциональный набор инструкций и позволяет многим сложным инструкциям обращаться к памяти.Поэтому он имеет больше операций, режимов адресации, но меньше регистров, чем ARM. Процессоры CISC в основном используются на обычных ПК, рабочих станциях и серверах.

ARM является процессором RISC (вычисление с сокращенным набором инструкций) и поэтому имеет упрощенный набор инструкций (100 инструкций или меньше) и регистры более общего назначения, чем CISC. В отличие от Intel, ARM использует инструкции, которые работают только с регистрами, и использует модель памяти Load / Store для доступа к памяти, что означает, что только инструкции Load / Store могут обращаться к памяти.Это означает, что для увеличения 32-битного значения по определенному адресу памяти на ARM потребуется три типа инструкций (загрузка, увеличение и сохранение), чтобы сначала загрузить значение по определенному адресу в регистр, увеличить его внутри регистра и сохранить это обратно в память из реестра.

Уменьшенный набор команд имеет свои достоинства и недостатки. Одним из преимуществ является то, что инструкции могут выполняться быстрее, потенциально обеспечивая большую скорость (системы RISC сокращают время выполнения за счет сокращения тактовых циклов на инструкцию).Обратной стороной является то, что меньшее количество инструкций означает больший акцент на эффективном написании программного обеспечения с ограниченными доступными инструкциями. Также важно отметить, что ARM имеет два режима: режим ARM и режим большого пальца. Инструкции Thumb могут быть 2 или 4 байтами (подробнее об этом в Части 3: Набор инструкций ARM).

Еще больше различий между ARM и x86:

  • В ARM большинство инструкций можно использовать для условного выполнения.
  • Процессоры Intel серии x86 и x86-64 используют формат с прямым порядком байтов
  • Архитектура ARM была прямым порядком байтов до версии 3.С тех пор процессоры ARM стали BI-endian и имеют настройку, которая позволяет переключать endianness .

Различия не только между Intel и ARM, но и между разными версиями ARM. Цель этой серии руководств — сделать ее как можно более общей, чтобы вы получили общее представление о том, как работает ARM. Как только вы поймете основы, легко узнать нюансы для выбранной вами целевой версии ARM. Примеры в этом руководстве были созданы на 32-битной ARMv6 (Raspberry Pi 1), поэтому пояснения относятся именно к этой версии.

Названия различных версий ARM также могут сбивать с толку:

Семейство ARM Архитектура ARM
ARM7 ARM v4
ARM9 ARM v5
ARM11 ARM v6
Cortex-А ARM v7-A
Cortex-R ARM v7-R
Cortex-M ARM v7-M

Прежде чем мы сможем погрузиться в разработку эксплойтов для ARM, нам сначала нужно понять основы программирования на языке ассемблера, для чего требуются некоторые базовые знания, прежде чем вы сможете начать их ценить.Но зачем нам вообще сборка ARM, разве этого не достаточно, чтобы писать эксплойты на «нормальном» языке программирования / сценариев? Это не так, если мы хотим иметь возможность выполнять обратный инжиниринг и понимать программный поток двоичных файлов ARM, создавать собственный шелл-код ARM, создавать цепочки ARM ROP и отлаживать приложения ARM.

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

Так что же такое ассемблер? Язык ассемблера — это просто тонкий слой синтаксиса поверх машинного кода, который состоит из инструкций, закодированных в двоичных представлениях (машинном коде), что и понимает наш компьютер. Так почему бы нам просто не написать машинный код? Что ж, это было бы занозой в заднице. По этой причине мы напишем сборку ARM, которая намного проще для понимания людьми.Наш компьютер не может запускать ассемблерный код сам, потому что ему нужен машинный код. Инструмент, который мы будем использовать для сборки кода сборки в машинный код, — это GNU Assembler из проекта GNU Binutils с именем as , который работает с исходными файлами с расширением * .s.

После того, как вы написали файл сборки с расширением * .s, вам нужно собрать его с помощью as и связать с ld:

 $ как program.s -o program.o
$ ld program.o -o программа 

Давайте начнем с самого низа и перейдем к ассемблеру.На самом низком уровне у нас есть электрические сигналы в нашей цепи. Сигналы формируются путем переключения электрического напряжения на один из двух уровней, например, 0 вольт («выключено») или 5 вольт («включено»). Поскольку просто посмотрев, мы не можем легко определить, какое напряжение находится в цепи, мы предпочитаем записывать шаблоны включенных / выключенных напряжений, используя визуальные представления, цифры 0 и 1, чтобы не только представить идею отсутствия или присутствия сигнал, но также потому, что 0 и 1 — цифры двоичной системы. Затем мы группируем последовательность 0 и 1, чтобы сформировать команду машинного кода, которая является наименьшей рабочей единицей процессора компьютера.Вот пример инструкции на машинном языке:

1110 0001 1010 0000 0010 0000 0000 0001

Пока все хорошо, но мы не можем вспомнить, что означает каждый из этих шаблонов (0 и 1). По этой причине мы используем так называемые мнемоники, сокращения, чтобы помочь нам запомнить эти двоичные шаблоны, где каждой инструкции машинного кода дается имя. Эти мнемоники часто состоят из трех букв, но это не обязательно. Мы можем написать программу, используя эту мнемонику в качестве инструкций.Эта программа называется программой на языке ассемблера, а набор мнемоник, который используется для представления машинного кода компьютера, называется языком ассемблера этого компьютера. Следовательно, язык ассемблера — это самый низкий уровень, используемый людьми для программирования компьютера. Операнды инструкции идут после мнемоники (ов). Вот пример:

MOV R2, R1

Теперь, когда мы знаем, что программа сборки состоит из текстовой информации, называемой мнемоникой, нам нужно преобразовать ее в машинный код.Как упоминалось выше, в случае сборки ARM проект GNU Binutils предоставляет нам инструмент с именем как . Процесс использования ассемблера, такого как как , для преобразования из языка ассемблера (ARM) в машинный код (ARM) называется сборкой.

Таким образом, мы узнали, что компьютеры понимают (реагируют) на наличие или отсутствие напряжений (сигналов) и что мы можем представлять несколько сигналов в последовательности нулей и единиц (битов). Мы можем использовать машинный код (последовательности сигналов), чтобы заставить компьютер реагировать определенным образом.Поскольку мы не можем вспомнить, что означают все эти последовательности, мы даем им сокращения — мнемоники и используем их для представления инструкций. Этот набор мнемоник является языком ассемблера компьютера, и мы используем программу под названием Assembler для преобразования кода из мнемонического представления в машиночитаемый машинный код, точно так же, как компилятор делает для языков высокого уровня.

.

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

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

2021 © Все права защищены. Карта сайта