Динамическая загрузка данных
«Динамическая загрузка» означает, что данные будут подгружаться по запросу. Например, PHP код может динамически оправлять какие-либо данные в Webix компонент.
Динамическая загрузка необходима в случае с большими объемами данных, т.к. она позволяет подгружать данные в компонент небольшими порциями. Изначально какая-то часть данных отправляется в компонент, а позже остальные порции подгружаются динамически (напр. при скролле или переключении страниц).
Для линейных структур (большинство компонентов Webix работает с такими структурами) вы можете:
- указать элемент, с которого необходимо начать загрузку
- указать кол-во элементов, которое будет подгружаться при скролле/постраничной навигации.
Для иерархических структур, напр., Tree и TreeTable вы можете:
- указать ID узла, в который необходимо загрузить данные.
Обратите внимание, что динамическая загрузка будет работать только если сервер поддерживает такую функциональность.
Статье по теме
- Динамическая загрузка линейных данных
- Динамическая загрузка иерархических данных
Обнаружение динамической загрузки в приложениях Android с помощью /proc/maps

TL; DR: Благодаря динамической загрузке авторы вредоносных программ могут скрытно загружать вредоносный код в свое приложение, чтобы избежать обнаружения. Мы можем обнаружить такую загрузку через сгенерированный ядром файл /proc/[PID]/maps приложения.
Недавно мы создали простой скрипт, который позволяет нам обнаруживать динамическую загрузку в приложениях для Android. Это дало нам хорошую возможность обсудить динамическую загрузку в целом в этом блоге.
Динамическая загрузка и связывание:
Чтобы понять остальную часть блога, важно пройтись по основам связывания и загрузки, а также выделить различия между связыванием и загрузкой, статическим и динамическим.
Что связывает:
Процесс компиляции состоит из нескольких частей. Связывание — это последний шаг перед тем, как мы получим работоспособный выполнимый. Программа обычно представляет собой нечто большее, чем простой автономный файл, и для работы она полагается на другие библиотеки или файлы. Таким образом, вам недостаточно скомпилировать свой код в машинный код, чтобы иметь возможность его запускать, но также каким-то образом ссылке различные файлы в единый исполняемый файл.
Как это делается на практике? После компиляции и сборки ассемблер выдает объектные файлы, которые обычно соответствуют каждому модулю в вашей программе. Такие файлы могут быть перемещаемыми или исполняемыми. Объектные файлы первого типа должны пройти компоновку, прежде чем их можно будет запустить, а файлы второго типа могут выполняться немедленно. В экосистеме GNU/Linux обычное расширение объектного файла — .o. Общие объектные файлы, известные как библиотеки, представляют собой перемещаемые объектные файлы, которые предназначены для использования многими различными программами и не могут запускаться сами по себе. В GNU/Linux они имеют расширение .so (общий объект), тогда как в Windows они имеют расширение .dll (библиотека динамической компоновки).
Затем компоновщик берет файлы, разрешает символы (функции и переменные) и указывает им правильные адреса памяти, записывая все в таблицу символов исполняемого файла. Ради производительности также телепортации вашего кода, чтобы связанные фрагменты кода в конечном итоге отображались на близлежащие адреса памяти, независимо от того, как вы изначально организовали свою программу, поскольку удобочитаемость больше не является проблемой, а производительность является главным приоритетом.

Множество разных имен исполняемого файла:
И в Linux, и в Windows эти, казалось бы, разные расширения имеют один и тот же базовый формат, что и исполняемые файлы. В Windows это PE (Portable Executable) и обычно имеют расширения .exe и .dll, а в Linux это ELF (Executable and Linkable File) и расширения .bin, .so или .o (Да , объектные файлы, о которых мы упоминали ранее, также являются ELF).
Динамическое связывание:
Мы говорим, что что-то было динамически связано, когда этот процесс выполняется не во время компиляции, а во время загрузки или во время выполнения. Что подводит нас к загрузке: загрузчик просто копирует содержимое вывода компоновщика в память и запускает программу. Когда используется динамическая компоновка, процесс компоновки происходит непосредственно перед загрузкой, при запуске программы, что часто приводит к путанице между ними.
Динамическая загрузка, с другой стороны, означает, что части кода могут быть загружены в память в любой момент во время выполнения. Эти два процесса могут и часто выполняются вместе.
Так почему это проблема?
Динамическая загрузка, безусловно, полезна. Например, вместо того, чтобы загружать все библиотеки, которые ваша программа будет использовать во время загрузки, вы можете загружать их только тогда, когда вам нужно их использовать, таким образом используя меньше памяти, или только условно загружать их в определенных случаях.
Но он также предоставляет разработчикам вредоносных программ простой способ скрыть свой вредоносный код. Они могут поместить весь свой законный код в APK и переместить весь вредоносный код в DEX (исполняемый файл Dalvik), который приложение будет загружать, а затем динамически загружать во время использования, и, таким образом, их приложение будет выглядеть безобидным при базовой статической проверке. АПК.
Как загружаются классы DEX:
Android предлагает возможность динамической загрузки файлов .dex с использованием класса с именем ДексКлассЗагрузчик. Чтобы загрузить класс, нам просто нужно написать:
// Init the loader DexClassLoader dexClassLoader = new DexClassLoader(path_to_dex, null, null, parent_class); // Load the class: Class dynamic_class = dexClassLoader.loadClass("DynamicClass"); // Load a method we could call it Method method = dynamic_class.getMethod("method1");
И затем мы можем использовать метод invoke() для использования метода.
Файл /proc/[PID]/maps:
В Unix все это файл, и даже если он на самом деле не один, он обрабатывается и используется как один. Сюда входят структуры данных ядра, и Linux не является исключением из этого правила. Ядро Linux позволяет нам получать доступ к своим структурам данных и читать их через каталог /proc/. псевдофайловая система. Каждый процесс имеет свою собственную папку в /proc/[PID]. Файлы и подпапки здесь содержат много полезной и важной информации о процессе, но сегодня мы сосредоточимся только на одном файле: /proc/[PID]/maps.
/proc/[PID]/maps отображает диаграмму отображаемой памяти процесса. Когда мы говорим «отображенная память», мы имеем в виду сегмент виртуальной памяти, который однозначно соответствует файлу. Это сопоставление позволяет приложению изменять файлы и обращаться к ним путем чтения и записи непосредственно в память. Это означает, что когда программа обращается к файлу, это в конечном итоге записывается в ее файл /proc/[PID]/maps.
/proc/[PID]/maps также показывает нам, какие разрешения есть у процесса для каждого сегмента. Это может помочь нам определить, какие файлы процесс редактировал и какие файлы он читал.
Вот как выглядит короткий сегмент обычного файла /proc/PID/maps:
7f9cefbf7000-7f9cefbf8000 r--p 00000000 103:03 1589169 /usr/lib/libXcomposite.so.1.0.0 7f9cefbf8000-7f9cefbf9000 r-xp 00001000 103:03 1589169 /usr/lib/libXcomposite.so.1.0.0 7f9cefbf9000-7f9cefbfa000 r--p 00002000 103:03 1589169 /usr/lib/libXcomposite.so.1.0.0 7f9cefbfa000-7f9cefbfb000 r--p 00002000 103:03 1589169 /usr/lib/libXcomposite.so.1.0.0 7f9cefbfb000-7f9cefbfc000 rw-p 00003000 103:03 1589169 /usr/lib/libXcomposite.so.1.0.0 7f9cefbfc000-7f9cefc08000 r--p 00000000 103:03 1579223 /usr/lib/libxcb.so.1.1.0 7f9cefc08000-7f9cefc1b000 r-xp 0000c000 103:03 1579223 /usr/lib/libxcb.so.1.1.0 7f9cefc1b000-7f9cefc24000 r--p 0001f000 103:03 1579223 /usr/lib/libxcb.so.1.1.0 7f9cefc24000-7f9cefc25000 r--p 00027000 103:03 1579223 /usr/lib/libxcb.so.1.1.0 7f9cefc25000-7f9cefc26000 rw-p 00028000 103:03 1579223 /usr/lib/libxcb.so.1.1.0 7f9cefc26000-7f9cefc27000 r--p 00000000 103:03 1577111 /usr/lib/libX11-xcb.so.1.0.0 7f9cefc27000-7f9cefc28000 r-xp 00001000 103:03 1577111 /usr/lib/libX11-xcb.so.1.0.0 7f9cefc28000-7f9cefc29000 r--p 00002000 103:03 1577111 /usr/lib/libX11-xcb.so.1.0.0 7f9cefc29000-7f9cefc2a000 r--p 00002000 103:03 1577111 /usr/lib/libX11-xcb.so.1.0.0 7f9cefc2a000-7f9cefc2b000 rw-p 00003000 103:03 1577111 /usr/lib/libX11-xcb.so.1.0.0 7f9cefc2b000-7f9cefc47000 r--p 00000000 103:03 1584005 /usr/lib/libX11.so.6.3.0 7f9cefc47000-7f9cefcd1000 r-xp 0001c000 103:03 1584005 /usr/lib/libX11.so.6.3.0
Каждая строка в файле записывает один сегмент памяти в непрерывном адресном пространстве виртуальной памяти, выделенном для процесса.
- Адрес – В первом столбце показаны начальный и конечный адреса сегмента.
- Разрешения. – В этом столбце показано, какие разрешения есть у процесса для сегмента. р/ж/х обычные чтение/запись/выполнение, а последняя буква s or p, что означает общий или частный, соответственно.
- Офсет – Это смещение от начала файла, чтобы можно было вычислить начальный адрес отображаемых данных. Иногда сегмент не отображается из файла (в этом случае столбец пути будет иметь идентификатор характера сегмента, как описано ниже), и в этом случае смещение просто остается равным 0.
- Устройство – Если сегмент был отображен из файла, в этом столбце отображается шестнадцатеричный номер устройства, на котором хранится файл.
- Инод (индексный узел) – Если сегмент получен из файла, это номер inode файла.
- Path – Это путь к файлу, если он есть. Это может быть [куча], [стек] или [всдо] если сегмент является одноименной структурой.
Наш скромный сценарий:
В вредоносных приложениях для Android динамическая загрузка кода обычно выполняется из домашнего каталога приложения, поэтому файлы, загружаемые в память из этого словаря (или других общих мест), должны отображаться в файле карт. Чтобы автоматизировать задачу проверки этого файла, мы создали очень простой скрипт, который использует регулярное выражение для поиска строки «/data/data» в файле карт заданного PID на подключенном устройстве и возвращает совпадающие строки. /data/data — это домашний каталог приложений, в котором хранятся файлы и данные. Это единственное место, откуда приложение может загружать файлы DEX.

Взаимодействие с ADB через Python иногда было проблемой, так как использование оболочки может быть неудобным, но мы нашли полезный набор инструментов под названием pwntools которые обеспечивают именно эту функциональность. Они предлагают широкий набор функций, предназначенных для помощи во взломе и прототипировании. Обязательно проверьте это.
Вы можете увидеть полный сценарий, который мы используем здесь. Github суть.
Что такое статическая и что такое динамическая загрузка класса в Java?
Когда стартует java приложение, все классы, которые так или иначе Ваше приложение создает через new , (те классы которые Вы импортируете директивой import и и те классы которые импортируются в тех классах которые Вы импортируете и так далее) будут загружены загрузчиком классов перед запуском приложения.
Это и есть статическая загрузка классов.
Также в java есть возможность загружать классы после старта, вызвав:
Class.forName("path.to.class.ClassName");
после чего можно будет создавать экземпляры этого класса через Reflection Api .
Это и есть динамическая загрузка.
Второй вариант является ненадежным с точки зрения целостности программы, так как Вам руками надо обеспечить попадание этого класса в область, в которой его увидит загрузчик классов, тогда как при статической загрузке это проверяет компилятор.
Однако это дает и огромные возможности по созданию действительно модульных систем, когда интерфейсы и их реализации разделены физически по разным jar, и jar c интерфейсом есть в изначальном приложении, а реализации может вообще еще не быть, классы могут быть загружены и выгружены динамически, без перезапуска основного процесса, что в свою очередь дает возможность строить гибкие приложения.
Динамическая загрузка, эксплуатация и выгрузка сборок в .NET
Довольно часто перед разработчиком встаёт вопрос о расширении основного алгоритма однотипными задачами. Например, агрегаторы различных сервисов, которые предоставляют единый интерфейс пользователю, делая запросы сотне-другой поставщиков услуг. Задача стоит таким образом, чтобы основное ядро могло динамически загружать сборки с различными реализациями некоторого интерфейса. Никакой непосильной работы для программиста .NET здесь изначально не предвидится. Если термин «отражение» Вам известен, Вы вероятно уже хотите пройти мимо? Но в этом топике речь пойдёт не про отражение как таковое… я расскажу как это сделать наиболее «чисто». Т.е. с одним нюансом — исследуемые сборки нужно выгрузить после их эксплуатации.
Итак задача: нужно создать консольное приложение ExtensionLoader, которое при старте динамически из своего каталога загружает все библиотеки, в которых был найден класс, реализующий интерфейс IExtension:
interface IExtension
После загрузки найденных библиотек приложение создаёт экземпляр для каждого найденного класса (с IExtension) и выполняет единственный метод GetExtensionName(), результат выводит в консоль.
Звучит как тестовое задание, — на самом деле так оно и было…
Один нюанс в этом задании был очень интересен: Архитектура приложения должна быть построена таким образом, чтобы была возможность выгрузки всех подгружаемых библиотек. Вот здесь и начинается самое интересное. Быстрое «гугление» не дало готовых решений. Анализ возникшей проблемы занял определённое время и я хочу поделится им… надеясь получить конструктивный отклик, да и чтобы не валялось зря…
Начнём с общей библиотеки с описанием интерфейса. Само-собой это будет отдельный проект, чтобы его можно было подключать как к расширению, так и к ядру системы не цепляя за собой все реализации этого интерфейса. Это, вроде как, очевидно. Наша библиотека, по заданию, будет содержать один интерфейс. Он уже описан в задании, поэтому не буду повторять.
Далее реализуем интерфейс IExtension. Тоже очевидно, что это будет отдельный проект, который в нашем примере будет содержать одну реализацию заданного интерфейса.
public class Extension1 : MarshalByRefObject, IExtension < public Extension1() < >public string GetExtensionName() < return "Extension 1 from " + AppDomain.CurrentDomain.FriendlyName; >>
Пытливый читатель уже заметил некоторые излишества, которых он не ожидал здесь увидеть. А именно наследование от MarshalByRefObject. Фишка здесь в следующем: Если мы не отнаследуемся от MarshalByRefObject, при попытке выполнить метод с помощью отражения, сборка будет загружена в текущий домен приложения из которого осуществляется вызов метода. Следствием этого будет являться невозможность выгрузки сборки, поскольку сборки по отдельности не выгружаются (только полностью всем доменом приложения). Задание будет провалено.
Далее приведу «скучные методы», которые можно не разглядывать. Данные методы реализованы, в качестве примера, в приложении загрузчике. Их назначение сугубо утилитарно решаемой задаче.
/// /// Возвращает перечисление содержащее плагины. /// /// Домен, в который будут загружатся исследуемые сборки. private static IEnumerable EnumerateExtensions(AppDomain domain) < IEnumerablefileNames = Directory.EnumerateFiles(domain.BaseDirectory, "*.dll"); if (fileNames != null) < foreach (string assemblyFileName in fileNames) < foreach (string typeName in GetTypes(assemblyFileName, typeof(IExtension), domain)) < System.Runtime.Remoting.ObjectHandle handle; try < handle = domain.CreateInstanceFrom(assemblyFileName, typeName); >catch (MissingMethodException) < continue; >object obj = handle.Unwrap(); IExtension extension = (IExtension)obj; yield return extension; > > > > /// /// Возвращает перечисление имён классов, которые реализуют заданный интерфейс. /// Сборка загружаются в указанный домен. /// /// Имя файла анализируемой сборки /// Искомый интерфейс /// Домен для загрузки сборки. /// Перечисление полных имён классов. private static IEnumerable GetTypes(string assemblyFileName, Type interfaceFilter, AppDomain domain) < Assembly asm = domain.Load(AssemblyName.GetAssemblyName(assemblyFileName)); Type[] types = asm.GetTypes(); foreach (Type type in types) < if (type.GetInterface(interfaceFilter.Name) != null) < yield return type.FullName; >> >
Далее два тоже простых метода, но более важных в контексте статьи. Методы могут показаться странными за счёт того, что в них всего по одному вызову, но это всего лишь последствия упразднения ненужного для данной статьи функционала.
////// Создаёт новый домен. /// /// Папка для поиска загружаемых сборок. ///Новый домен приложения. static AppDomain CreateDomain(string path) < AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = path; return AppDomain.CreateDomain("Temporary domain", null, setup); >////// Выгружает домен приложения. /// /// Домен подлежащий выгрузке. static void UnloadDomain(AppDomain domain)
Ну, и на конец, метод Main… который всё это «пользует»
static void Main(string[] args) < // Создаём домен приложения, в котором будут выполняться расширения. AppDomain domain = CreateDomain(Directory.GetCurrentDirectory()); try < // Получаем список экземпляров расширений. IEnumerableextensions = EnumerateExtensions(domain); foreach (IExtension extension in extensions) // Выполняем метод расширения. Выполнение происходит в другом домене. Console.WriteLine(extension.GetExtensionName()); // Выгрузка домена приведёт к ожидаемому результату. Ни одна сборка не зависнет. UnloadDomain(domain); > finally < domain = null; GC.Collect(2); >Console.ReadKey(); >