Презентация была опубликована 6 лет назад пользователемПётр Гарбузов
Похожие презентации
Презентация на тему: » Исследование кодов программ Макаренков Д.Е. Лекция по дисциплине «Компьютерная разведка»» — Транскрипт:
1 Исследование кодов программ Макаренков Д.Е. Лекция по дисциплине «Компьютерная разведка»
2 Учебные вопросы 1. Цели и задачи исследования программ 2. Методы и средства исследования кода программ 3. Общие сведения о языке ассемблера
3 Литература Рекомендованная по дисциплине Дополнительно: –Защита программного обеспечения от отладки и дизассемблирования: Учебное пособие // И.В. Аникин, В.И. Глова. Казань: КГТУ, 2003 г. –Панов А.С. Реверсинг и защита программ от взлома. СПб: BHV-Санкт-Петербург, 2006 г. –Отладчик SoftICE. Руководство пользователя. Numega Software, –К. Касперски. Образ мышления IDA PRO. М
4 Учебный вопрос 1 Цели и задачи исследования программ
5 Программа последовательность формализованных инструкций устройства управления ЭВМ, предназначенная для реализации определенного алгоритма. –Машинный код процессора –Псевдокод виртуальной среды исполнения –Скрипты –Тексты программ
6 Исследование программ Обратное проектирование (англ. reverse ingeneering ) процесс исследования и анализа машинного кода, нацеленный на понимание общих механизмов функционирования программы, а также на его перевод на более высокий уровень абстракции вплоть до восстановления текста программы на исходном языке программирования Громкие примеры: »IBM-PC BIOS »Процессор AMD am386 »Samba
7 Цели обратного проектирования Получение закрытых сведений, заложенных в программу –алгоритм работы программы –протоколы обмена данными –форматы данных –скрытые данные Обнаружение уязвимостей и недокументированных возможностей Модификация программы –отключение защитных механизмов –внедрение закладок Создание устройства или программы с аналогичными функциями
8 Задачи исследования Восстановление кода программы (или отдельных фрагментов) на языке программирования высокого уровня –Распаковка кода –Локализация нужного модуля –Идентификация кода и данных Анализ алгоритма –Определение структуры программы, назначение отдельных блоков Изучение структур данных
9 Учебный вопрос 2 Методы и средства исследования кода программ
11 Дизассемблер транслятор, преобразующий машинный код в программу на языке ассемблера. –Автоматические генерируют готовый листинг, который можно затем править в текстовом редакторе –Интерактивные позволяют изменять правила дизассемблирования в процессе работы
13 Эмуляция среды выполнения Эмуля́ция (англ. emulation) воспроизведение программными или аппаратными средствами либо их комбинацией работы других программ или устройств. –Виртуальная машина
14 Контроль памяти во время выполнения программы Поиск значений переменных в процессе выполнения программы Сравнение состояния переменных в разные моменты работы программы Установка необходимых значений переменных
15 Анализ потоков данных Изучение входных и выходных данных Прослушивание каналов передачи данных, накопление статистики, анализ –мониторы событий файловых операций обращений к реестру операций ввода-вывода –сетевые пакетные снифферы
16 Возможность защиты от исследования Команды однозначно интерпретируются процессором Данные могут быть перехвачены Среда исполнения может быть эмулирована Выводы: Защититься от исследования невозможно Но можно усложнить задачу –Защита от декомпиляции и отладки –Обфускация алгоритма и данных
17 Применение различных методов исследования Метод «черного ящика» –анализ реакции программы на различные входные данные Метод «прозрачного ящика» –изучение восстановленного кода Метод «серого ящика» –частичное восстановление кода
18 Локализация модулей Анализ изменений после ввода данных –Поиск в памяти введенных значений и контроль обращений программы к этим данным –Перехват вызовов функций ввода данных Ожидаемый вывод на экран –Поиск в памяти осмысленных последовательностей символов и контроль обращений программы к адресам, по которым хранятся эти последовательности –Перехват вызовов функций вывода данных
19 Учебный вопрос 3 Общие сведения о языке ассемблера
20 Ассемблер Язык программирования «низкого уровня» Мнемоническое обозначение машинных кодов –удобнее для восприятия человеком
21 Процессор для программ система адресации памяти набор регистров набор команд
23 Синтаксис Типичный формат записи команд: [метка:] код_операции [операнды] [;комментарий]
24 Операнды Константы Регистры Метки Ссылки
25 Пример: Команда MOV MOV адресат, источник Источник : –число –регистр –адрес ячейки в оперативной памяти Адресат: –регистр –адрес ячейки в оперативной памяти
26 Регистры процессора x86 Назначение Кол-во Регистры данных общего назначения 4AX, BX, CX, DX Указатели 2SI, DI Индексные регистры 2SP, BP Сегментные 4CS, DS, SS, ES Регистр состояния 1FLAGS Управляющие (указатель команд)1IP
27 Разрядность регистров EAX 32 разряда AX 16 разрядов AH, ALпо 8 разрядов EAX AX AHAL
28 Регистр флагов Флаги: состояния управления системные
29 Организация памяти Линейная модель Сегментированная модель Физический адрес = (сегмент, смещение) Вариант 1 : Вариант 2 : Сегмент Смещение Сегмент Смещение
30 Система команд процессора Для Pentium4 более 300 команд Пересылки данных Арифметические Битовые (логические) Обработки строк Передачи управления Управления состоянием процессора MMX, SSE, SSE II
31 Команды пересылки данных пересылки данных общего назначения обмен данными с портами ввода/вывода работы с адресами и указателями пересылки флагов
32 Арифметические команды Сложения Вычитания Умножения Деления Преобразования типов Сравнения
33 Битовые операции Логические операции –AND, OR, XOR, NOT, TEST Сдвиги Циклические сдвиги
34 Команды обработки строк Пересылка Сравнение Сканирование Загрузка и сохранение элементов строки Могут автоматически повторяться
35 Команды передачи управления Команды безусловной передачи управления: –безусловного перехода; –вызова процедуры и возврата из процедуры; –вызова программных прерываний и возврата из программных прерываний. Команды условной передачи управления: –по результату сравнения; –по состоянию определенного флага; –по содержимому регистра ЕСХ/СХ. Команды управления циклом: –организации цикла со счетчиком ЕСХ/СХ; –организации цикла со счетчиком ЕСХ/СХ с возможностью досрочного выхода из цикла по дополнительному условию.
36 Команды управления процессором Управления флагами Синхронизации Холостой ход NOP
37 Режимы адресации Вид адресации Пример Регистроваяmov ax,bx Непосредственнаяmov ax,200 Прямаяmov ax,Table Косвенная регистроваяmov ax,[bx] Адресация по базеmov ax,[bx+3] Прямая с индексированиемmov ax,Table[di] Адресация по базе с индексированиемmov ax,Table[bx][di]
Лекция 2. Тестирование программного кода. Задачи и цели тестирования программного кода. Оценка качества тестируемого кода
1. Тестирование программ
2. Статистические методы тестирования
3. Динамические методы тестирования
4. Функциональное тестирование
5. Инфраструктура процесса тестирования ПС
6. Методы поиска ошибок в программах
7. Классификация ошибок и тестов
8. Служба тестирования ПС
9. Управление процессом тестирования
При проверке правильности программ и систем рассматриваются процессы верификации, валидации и тестирования ПС, которые регламентированы в стандарте ISO/IEC 12207 жизненного цикла ПО. В некоторой зарубежной литературе процессы верификации и тестирования отождествляются.
2.1 Тестирование программ
Тестирование можно рассматривать, как процесс семантической отладки (проверки) программы, заключающийся в исполнении последовательности различных наборов контрольных тестов, для которых заранее известен результат. Т.е. тестирование предполагает выполнение программы и получение конкретных результатов выполнения тестов.
2.1.1. Статические методы тестирования
Статические методы используются при проведении инспекций и рассмотрении спецификаций компонентов без их выполнения. Техника статического анализа заключается в методическом просмотре (или обзоре) и анализе структуры программ, а также в доказательстве их правильности. Статический анализ направлен на анализ документов, разработанных на всех этапах ЖЦ, и заключается в инспекции исходного кода и сквозного контроля программы.
Кроме того, разрабатывается множество новых способов автоматизации символьного выполнения программ. Например, автоматизированное средство статического контроля для языков ориентированной разработки, инструменты автоматизации доказательства корректности и автоматизированный аппарат сетей Петри.
2.1.2. Динамические методы тестирования
Динамические методы тестирования используются в процессе выполнения программ. Они базируются на графе, связывающем причины ошибок с ожидаемыми реакциями на эти ошибки. В процессе тестирования накапливается информация об ошибках, которая используется при оценке надежности и качества ПС.
Методы «черного ящика» обеспечивают:
· анализ граничных значений;
· применение функциональных диаграмм, которые в соединении с реверсивным анализом дают достаточно полную информацию о функционировании тестируемой программы.
«Белый ящик» базируется на структуре программы, в случае «черного ящика», о структуре программы ничего неизвестно. Для выполнения тестирования с помощью этих «ящиков» известными считаются выполняемые функции, входы (входные данные) и выходы (выходные данные), а также логика обработки, представленные в документации.
2.1.3. Функциональное тестирование
Функциональные тесты создаются по внешним спецификациям функций, проектной информации и по тексту на ЯП, относятся к функциональным его характеристикам и применяются на этапе комплексного тестирования и испытаний для определения полноты реализации функциональных задач и их соответствия исходным требованиям.
В задачи функционального тестирования входят:
· идентификация множества функциональных требований;
· идентификация внешних функций и построение последовательностей функций в соответствии с их использованием в ПС;
· идентификация множества входных данных каждой функции и определение областей их изменения;
· построение тестовых наборов и сценариев тестирования функций;
· выявление и представление всех функциональных требований с помощью тестовых наборов и проведение тестирования ошибок в программе и при взаимодействии со средой.
2.2 Инфраструктура процесса тестирования ПС
Под инфраструктурой процесса тестирования понимается:
· выделение объектов тестирования;
· проведение классификации ошибок для рассматриваемого класса тестируемых программ;
· подготовка тестов, их выполнение и поиск разного рода ошибок и отказов в компонентах и в системе в целом;
· служба проведения и управление процессом тестирования;
· анализ результатов тестирования.
2.2.1 Методы поиска ошибок в программах
Международный стандарт ANSI/IEEE-729-83 разделяет все ошибки в разработке программ на следующие типы.
· ошибочная спецификация или пропущенное требование, означающее, что спецификация точно не отражает того, что предполагал пользователь;
· спецификация может содержать требование, которое невозможно выполнить на данной аппаратуре и программном обеспечении;
· проект программы может содержать ошибки (например, база данных спроектирована без средств защиты от несанкционированного доступа пользователя, а требуется защита);
· программа может быть неправильной, т.е. она выполняет несвойственный алгоритм или он реализован не полностью.
Таким образом, отказы, как правило, являются результатами одной или более ошибок в программе, а также наличия разного рода дефектов.
2.2.2. Классификация ошибок и тестов
Каждая организация, разрабатывающая ПО общесистемного назначения, сталкивается с проблемами нахождения ошибок. Поэтому приходится классифицировать типы обнаруживаемых ошибок и определять свое отношение к устранению этих ошибок.
Таблица 2.1. Ортогональная классификация дефектов IBM
| Контекст ошибки | Классификация дефектов |
| Функция | Ошибки интерфейсов конечных пользователей ПО, вызванные аппаратурой или связаны с глобальными структурами данных |
| Интерфейс | Ошибки во взаимодействии с другими компонентами, в вызовах, макросах, управляющих блоках или в списке параметров |
| Логика | Ошибки в программной логике, неохваченной валидацией, а также в использовании значений переменных |
| Присваивание | Ошибки в структуре данных или в инициализации переменных отдельных частей программы |
| Зацикливание | Ошибки, вызванные ресурсом времени, реальным временем или разделением времени |
| Среда | Ошибки в репозитории, в управлении изменениями или в контролируемых версиях проекта |
| Алгоритм | Ошибки, связанные с обеспечением эффективности, корректности алгоритмов или структур данных системы |
| Документация | Ошибки в записях документов сопровождения или в публикациях |
Исследования фирм IBM показали, чем позже обнаруживается ошибка в программе, тем дороже обходится ее исправление, эта зависимость близка к экспоненциальной. Так военно-воздушные силы США оценили стоимость разработки одной инструкции в 75 долларов, а ее стоимость сопровождения составляет около 4000 долларов.
Создаются тесты, проверяющие:
· корректность выполнения функций и правильность функционирования системы в заданных условиях;
· надежность выполнения системы;
· защиту от сбоев аппаратуры и не выявленных ошибок и др.
2.2.3 Служба тестирования ПС
2.2.4 Управление процессом тестирования
Все способы тестирования ПС объединяются базой данных, где помещаются результаты тестирования системы. В ней содержатся все компоненты, тестовые контрольные данные, результаты тестирования и информация о документировании процесса тестирования.
База данных проекта поддерживается специальными инструментальными средствами типа CASE, которые обеспечивают ведение анализа ПрО, сборку данных об их объектах, потоках данных и тому подобное. База данных проекта хранит также начальные и эталонные данные, которые используются для сопоставления данных, накопленных в базе, с данными, которые получены в процессе тестирования системы.
Контрольные вопросы и задания
1. Назовите формальные методы проверки правильности программ.
2. Какие процессы проверки зафиксированы в стандарте?
3. Какие функции у процесса верификации программ?
4. Назовите основные задачи процесса валидации программ.
5. Сравните задачи процессов верификации и валидации программ.
6. В чем отличие верификации и валидации?
7. Определите процесс тестирования.
8. Назовите методы тестирования.
9. Объясните значения терминов «черный ящик», «белый ящик».
10. Назовите объекты тестирования и подходы к их тестированию.
11. Какая существует классификация типов ошибок в программах?
12. Определите основные процессы ЖЦ тестирования ПО.
13. Наведите классификацию тестов для проверки ПО.
14. Какие задачи выполняет группа текстовиков?
15. Какая организация работ в проведении тестирования?
Динамический анализ кода с помощью Iroh.js
Владислав Власов, инженер-программист в Developer Soft и преподаватель Нетологии, написал для блога цикл статей о EcmaScript6, в первой части которого на примерах рассмотрел динамический анализ кода в EcmaScript с помощью Iroh.js.
Статический и динамический анализ кода
Средства анализа кода — полезный инструмент, позволяющий обнаруживать и выявлять ошибки и особенности в работе кода. Анализ кода бывает статическим и динамическим. В первом случае производится разбор и анализ исходного кода без его выполнения, во втором — выполнение происходит в управляемой среде-песочнице (sandboxing), предоставляющей метаинформацию об элементах приложения в процессе его выполнения.
В языке EcmaScript, обладающим утиной типизацией (duck-typing), часто используются средства статического анализа, позволяющие без выполнения обнаружить потенциально опасные ситуации в коде: несоответствия типов передаваемых и принимаемых аргументов, некорректные операции с переменными несоответствующих типов, невыполняемые секции кода и так далее. Наиболее популярными являются решения Typescript и Flow, основанные на расширении языка специальным синтаксисом.
В отличие от языков программирования, в которых строгая система типов является их неотъемлемой частью, и соответственно, описания типов переменных и объектов уже заложено в исходном коде, в EcmaScript, напротив, требуется мануальное описание, причем зависимое и различающееся для отдельных средств статической типизации. Это влечет за собой следующие недостатки:
Iroh.js — решение для динамического анализа EcmaScript-приложений
Решение, устраняющее вышеперечисленные недостатки, — динамический анализатор EcmaScript-кода Iroh.js. Iroh.js позволяет выполнять анализ и манипуляцию кода в процессе выполнения, при этом нет необходимости модифицировать исходный код или снабжать его аннотациями, как в случае со средствами статической типизации.
Iroh.js позволяет записать схему исполнения кода в режиме реального времени, получать информацию о состоянии объектов и стеке вызовов, а также управлять поведением программы на лету.
Iroh.js может быть использован для получения информации о следующих аспектах времени выполнения: дерева вызова функций, актуальных типов данных объектов и переменных, неявных преобразований в объект и примитивный тип (boxing/unboxing), включая преобразования в строку.
Функционирует Iroh.js за счет предварительного исправления кода таким образом, чтобы осуществить запись происходящих событий, не изменяя логики и схемы выполнения исходной программы. Это обеспечивает возможность добавления функций-слушателей для последующего отслеживания и реакции на действия. Iroh.js позволяет не только отслеживать поведение кода в режиме выполнения, но и модифицировать его, перехватывая вызов определенных функций, устанавливая перекрывающее значение для переменных и так далее.
Iroh.js отслеживает стек вызовов только для модифицированного кода и не может предоставить метаинформацию или модификацию на лету для нативных функций среды исполнения или импортируемых библиотечных элементов. Как правило, не требуется анализировать и модифицировать поведение библиотечного кода, но при необходимости это также возможно — область действия Iroh.js определяется кодом, который был добавлен в песочницу исполнения Iroh.js посредством предварительного patching-а.
Iroh.js может быть добавлен в состав node.js приложения в качестве npm-пакета, так и запущен непосредственно в браузере, что может быть полезно для динамического анализа небольших независимых фрагментов кода или создания демонстрационных примеров.
Пример 1. Анализ графа вызовов для функции вычисления чисел Фибоначчи
В качестве самого простого примера можно рассмотреть две реализации функции вычисления чисел Фибоначчи — наивный вариант с полным рекурсивным деревом, и оптимизированное решение с мемоизацией предыдущих результатов. Исходный код предполагаемых функций представлен ниже:
Очевидно, что дело в сохранении промежуточных вычислений и оптимизации хвостовой рекурсии. Анализатор потока исполнения на основе Iroh.js позволяет увидеть это наглядно:
Динамический анализатор позволяет с легкостью выявить, что в первом, неоптимальном случае, для расчета 6-го числа Фибоначчи число рекурсивных вызовов достигает 41, в то время как во втором — всего лишь 7. С увеличением номера рассчитываемого числа, в первом случае рост рекурсивных вызовов будет экспоненциальным, а во втором — линейным.
Хотя задача расчета чисел Фибоначчи крайне проста, и приведенная оптимизация тривиальна и широко известна, Iroh.js позволяет отследить граф вызовов функций и найти избыточные или некорректные вызовы.
Пример 2. Модификация объектов и переменных на лету
В процессе анализа поведения приложения с потенциально сложной и нелинейной логикой, может возникнуть необходимость отслеживания момента, когда значение определенной переменной или объекта поменялось, или же, наоборот, — в определенный момент инициировать изменения текущего значения на специальное. Все это можно осуществить средствами Iroh.js.
В качестве простого примера можно рассмотреть код web-страницы, которая исполняет AJAX-запросы к нескольким web-ресурсам, однако в отладочных целях необходимо перехватить обращение к некоторым из них, заменив URL-адрес на отладочную заглушку. Упрощенный фрагмент исходного кода может выглядеть так:
Iroh.js позволяет анализировать и перехватывать вызовы функций и аллокацию переменных, обеспечивая возможность модификации их значений и поведения на лету. Больше примеров можно посмотреть здесь.
Выводы
Iroh.js — мощный и функциональный инструмент для динамического анализа кода в EcmaScript-е. Этот инструмент может быть использован как для анализа кода, включая построения графа вызовов, вывода актуальных типов и значений в переменных и объектах, так и для модификации кода на лету, включая исправления кода, основанные на событиях.
Динамический анализ — довольно сложный метод, однако для EcmaScript, учитывая утиную типизацию, наличие host-объектов и нативных функций, позволяющих менять поведения кода на лету, это единственный способ для анализа и отладки кода в процессе выполнения.
Как использовать простую утилиту для поиска уязвимостей в программном коде
Graudit поддерживает множество языков программирования и позволяет интегрировать тестирование безопасности кодовой базы непосредственно в процесс разработки.

Источник: Unsplash (Markus Spiske)
Тестирование — важная часть жизненного цикла разработки программного обеспечения. Существует очень много видов тестирования, каждый из них решает свою задачу. Сегодня я хочу поговорить о поиске проблем безопасности в коде.
Очевидно, что в современных реалиях разработки программного обеспечения важно обеспечить безопасность процессов. В своё время был даже введён специальный термин DevSecOps. Под этим термином понимают ряд процедур, направленных на выявление и устранение уязвимостей в приложении. Существуют специализированные open source решения для проверки уязвимостей в соответствии со стандартами OWASP, которые описывают различные типы и поведение уязвимостей в исходном коде.
Существуют разные подходы к решению проблем безопасности, например, статическое тестирование безопасности приложений (SAST), динамическое тестирование безопасности приложений (DAST), интерактивное тестирование безопасности приложений (IAST), анализ компонентов программного обеспечения (Software Composition Analysis) и так далее.
Статическое тестирование безопасности приложений выявляет ошибки в уже написанном коде. Этот подход не требует запуска приложения, поэтому он называется статическим анализом.
Я остановлюсь на статическом анализе кода и воспользуюсь простым open source инструментом, чтобы продемонстрировать всё на практике.
Почему я выбрал open source инструмент для статического анализа безопасности кода
На то есть ряд причин: во-первых, это бесплатно, так как вы используете инструмент, разработанный сообществом единомышленников, которые хотят помочь другим разработчикам. Если у вас небольшая команда или стартап, у вас есть отличная возможность сэкономить, используя программное обеспечение с открытым исходным кодом для проверки безопасности своей кодовой базы. Во-вторых, это избавляет вас от необходимости нанимать отдельную команду DevSecOps, что ещё больше снижает ваши расходы.
Хорошие open source инструменты всегда создают с учётом повышенных требований к гибкости. Поэтому их можно использовать практически в любом окружении, охватывая широкий круг задач. Разработчикам намного проще подружить такие инструменты с системой, которую они уже построили, работая над своими проектами.
Но могут возникнуть ситуации, когда вам понадобится функция, которой нет в выбранном вами инструменте. В этом случае у вас есть возможность форкнуть его код и разработать на его основе свой собственный инструмент с необходимой вам функциональностью.
Поскольку в большинстве случаев на разработку программного обеспечения с открытым исходным кодом активно влияет сообщество, решение о внесении изменений принимают достаточно быстро и по делу: разработчики open source проекта опираются на отзывы и предложения пользователей, на их сообщения о найденных ошибках и других проблемах.
Использование Graudit для анализа безопасности кода
Для статического анализа кода можно использовать разные инструменты с открытым исходным кодом, не существует универсального инструмента для всех языков программирования. Разработчики некоторых из них следуют рекомендациям OWASP и стараются охватить как можно больше языков.
Здесь мы будем использовать Graudit, простую утилиту для командной строки, которая позволит нам найти уязвимости в нашей кодовой базе. Он поддерживает разные языки, но всё-таки их набор ограничен. Graudit разработан на основе служебной утилиты grep, которая в своё время вышла под лицензией GNU.
Существуют похожие инструменты для статического анализа кода — Rough Auditing Tool for Security (RATS), Securitycompass Web Application Analysis Tool (SWAAT), flawfinder и так далее. Но Graudit очень гибок и имеет минимальные технические требования. Тем не менее, у вас могут появиться задачи, которые Graudit решить не в состоянии. Тогда можно поискать другие варианты вот в этом списке.
Мы можем интегрировать этот инструмент в конкретный проект, или сделать его доступным для выбранного пользователя, либо — использовать его одновременно во всех наших проектах. В этом тоже проявляется гибкость Graudit. Итак, давайте сначала клонируем репо:
Теперь давайте создадим для Graudit символическую ссылку, чтобы использовать его в формате команды
Проверим, успешно ли прошла установка:
Если вы увидите что-то похожее, то, значит, всё хорошо.
Я буду тестировать один из уже существующих моих проектов. Перед запуском инструмента ему нужно передать базу данных, соответствующую языку, на котором написан мой проект. Базы данных находятся в папке
Итак, я протестировал два js-файла из моего проекта, и Graudit вывел в консоль информацию об уязвимостях в моём коде:
Можете попробовать таким же образом протестировать ваши проекты. Список баз данных для разных языков программирования можно посмотреть здесь.
Преимущества и недостатки Graudit
Graudit поддерживает множество языков программирования. Поэтому он подходит для широкого круга пользователей. Он может достойно конкурировать с любыми бесплатными или платными аналогами. И очень важно, что в проект по-прежнему вносят доработки, а сообщество не только помогает разработчикам, но и другим пользователям, которые пытаются разобраться с инструментом.
Это удобный инструмент, но пока он не всегда может точно указать, в чём именно заключается проблема, связанная с подозрительным участком кода. Разработчики продолжают дорабатывать Graudit.
Но в любом случае полезно обращать внимание на потенциальные проблемы безопасности в коде, используя подобные инструменты.
Начало.
В этой статье я рассмотрел только один из многих способов поиска уязвимостей — статическое тестирование безопасности приложений. Провести статический анализ кода легко, но это только начало. Чтобы больше узнать о безопасности вашей кодовой базы, нужно интегрировать в жизненный цикл разработки ПО другие виды тестирования.
На правах рекламы
Надёжный VPS и правильный выбор тарифного плана позволят меньше отвлекаться от разработки на неприятные проблемы — всё будет работать без сбоев и с очень высоким uptime!




