Как стать автором
Обновить

Комментарии 31

А можно еще приоткрыть глаза о том, что такое этот фаззинг и зачем он нужен?

Ах, упустил этот момент. Фаззинг, это когда мы специально посылаем неправильные данные в программу, чтобы убедиться, что она работает корректно.

"я определил что нужно для осуществления моей проблемы. " - обычно проблемы решают. Текст тяжело читать. Дмитрий, попробуйте вычеркнуть все лишнее

Хорошо. Только я посплю сначала, уже пора ложиться. Всю ночь не спал. Как проснусь, попробую сделать текст более комфортным. Спасибо.

ну вот и объяснение откуда взялся этот сонный бред. Надо было сначала выспаться, а потом статью писать.

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

Если у вас есть DLL заголовки зачем вам писать программу с вызовами функций из этой DLL на ассемблере, только потому что вы ассемблер любите? Так напишите ее на С++ и посмотрите результат компиляции, такое упражнение гораздо сильнее обогатит ваши знания, чем ваши самодеятельные ковыряния непонятно с какой целью.

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

указать, который не несёт за собой ничего, кроме хранения указателя в памяти.

например.

Если у вас есть DLL заголовки зачем вам писать программу с вызовами функций из этой DLL на ассемблере, только потому что вы ассемблер любите? Так напишите ее на С++ и посмотрите результат компиляции, такое упражнение гораздо сильнее обогатит ваши знания, чем ваши самодеятельные ковыряния непонятно с какой целью.

Вы меня не правильно поняли. DLL заголовки для примера, чтобы показать как выглядит код библиотеки. Точно такой же как и в Linux примере. Разумеется исходников быть не должно, это пример кода, который я не стал приводить, так как каждому разработчику и так понятно как создать библиотеку скомпилировать её на c++.

Ну поймите вы меня. Я привел пример в Linux как написать заголовок для библиотеки. Почему вы цепляетесь к словам, и не можете сами дойти до того, что в реальной ситуации заголовков для библиотек быть не должно, а пример в этом туториале я опустил, чтобы упростить себе задачу.

например.

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

Почему вы цепляетесь к словам, и не можете сами дойти до того, что в реальной ситуации заголовков для библиотек быть не должно

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

Если вы не понимаете что такое указатели, или, даже если вы можете это просто забыть, не надо вам лезть в ассемблер, ИМХО.

Если для библиотеки нет хидеров это значит вы не знаете как она работает, и, соответственно, правильность этой работы проверить никак не можете.

Как раз таки можно, что я и описываю в статье.

Если вы не понимаете что такое указатели, или, даже если вы можете это просто забыть, не надо вам лезть в ассемблер, ИМХО.

Нет, ассемблер я знаю достаточно и знаю что такое указатели. Не понимаю ваш напор в эту сторону, если вы не видите примеров кода, где я использую указатели. А так, как я помню, что указатель, это адрес на chunk в памяти, причем перед указателем хранятся вроде 16 байт данных, которые не видны были мне при исследовании, но они хранят размер выделенного места в памяти. Так функции free или delete узнает о том сколько нужно освободить памяти. Вы слишком категорично относитесь к тому кто что должен делать, не указывайте пожалуйста что я должен делать, а то вы кажетесь очень неприятным человеком.

не указывайте пожалуйста что я должен делать

мне казалось что я советую, а не указываю, в любом случае извиняюсь.

причем перед указателем хранятся вроде 16 байт данных

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

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

Что ж, возможно вы и правы. Но я изучал в своё время исходники glibc для Linux и исхожу из тех знаний, которые подчерпнул прямо из кода.

Кстати, спасибо. Думаю надо подтянуть знания в этом вопросе ещё раз и убедиться как же всё-таки всё устроено. Хотя мне кажется, что я и так всё помню.

Честно говоря не совсем понятно какую проблему/задачу решал автор.

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

Если же header просто для понимания того что в библиотеке написано внутри, а для вызывающей стороны он не известен, тогда не ясно как вызывающая сторона определила типы (сигнатуру) функций внутри динамической библиотеки - по имени экспортируемых символов? - тогда это гадание на кофейной гуще, уж извините. Откуда появилась сигнатура функции ‘getter_string’? И как опять же решался вопрос с линковкой именно с той версией реализации ‘std::string’?

Если же header просто для понимания того что в библиотеке написано внутри, а для вызывающей стороны он не известен, тогда не ясно как вызывающая сторона определила типы (сигнатуру) функций внутри динамической библиотеки - по имени экспортируемых символов? - тогда это гадание на кофейной гуще, уж извините. Откуда появилась сигнатура функции ‘getter_string’? И как опять же решался вопрос с линковкой именно с той версией реализации ‘std::string’?

Вот это правильный вопрос, и вы правильно двигаетесь в этом направлении. Дело в том, что если мы с помощью radare2 зайдем в DLL к примеру или в SO, то увидим помимо внутреннего названия, полное название функции как оно есть в c++. Да, исходников нет. типы мы получаем из библиотеки фаззера (но не все данные) и в ассемблере мы просто передаем.

Да я то никуда не двигаюсь. Я недоумеваю чего вы хотели в этой статье показать - фаззинга тут нет. Как определили сигнатуру вызываемых ф-й тоже пол слова. Как определили (видимо никак) с какой версией std::string линковаться тоже ни намека.

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

фаззинга тут нет.

Я привёл пример как можно проводить фаззинг библиотек, у которых нет заголовков, то-есть никаких определений. Вам стоит только написать уже логику построения неправильных данных. Мой пример показывает, что можно с помощью ассемблера соединить две библиотеки и использовать для фаззинга.

Как определили сигнатуру вызываемых ф-й тоже пол слова.

Тут объяснений много и не надо. С помощью radare2 открываем библиотеку и смотрим с помощью команды is. Не подумал, что на таком уровне сложности материала нужно было это прописать. Извиняюсь. Думаю, что можно дополнить эту команду. Так будет ясней.

Как определили (видимо никак) с какой версией std::string линковаться тоже ни намека.

Да, тут проблема думаю. Но вроде как можно попробовать несколько вариантов и дойти испытательным путем.

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

Ну вот есть в чужой библиотеке класс, который используется в библиотеке. Для пользователя не доступны эти заголовки, только программа и библиотека. Вот есть у класса много полей и методов. Как вы к примеру вызовете метод класса void operator++(int n)? Для C++ тут много трудностей возникает. Вам нужно правильно назвать все поля и их типы, указать все функции. А в ассемблере вам не нужно ничего этого делать. Вы просто выделяете память и всё. Ну или покажите пример, вопрос про operator++, как бы вы на C++ слинковали бы библиотеку DLL к примеру, если нет ни исходников, ни заголовков. Я такого способа не знаю, поэтому написал свой вариант.

Можно из ассемблера. Не ясно зачем. Из статьи не ясно. Я вам прозрачно намекаю что статья требует переработки. А пока что это мысли вслух.

Как вызывать функции члены классов плюсовых вам уже ниже написали, и operator++ ничем от стальных функций членов класса не отличается.

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

Ну и напоследок- попробуйте для интереса подобрать нужную версию стандартной библиотеки хотя бы, удивитесь какое нестабильное ABI даже у gcc. Не говоря про мелкософтовские библиотеки.

Всё, дополнил статью. Спасибо.

как вызывающая сторона определила типы (сигнатуру) функций внутри динамической библиотеки - по имени экспортируемых символов? -

@oktonion

Для С++ это более чем возможно, т.к. в имени функции есть вся информация о типах. Вы всегда можете извлечь эту информацию с помощью любого доступного "де-манглера" (не знаю как demangling перевести корректно). Хоть бы и онлайн.

http://demangler.com/

Но есть и локальные, c++filt например. Для Си, ясное дело, простого решения этой проблемы нет.

Да я понимаю, просто в статье то пол слова про это. Такое ощущение что что-то автор хотел сказать, но что не понятно. И явно не про фаззинг.

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

Статья напомнила мне мои исследования в начале 80-х прошлого столетия:

Тогда речь шла о межмодульных связях (комплексировании программ ), написанных на языках Фортран, Ассемблер и двух разновидностях ПЛ/1 (обычный и оптимизирующий), в среде ОС ЕС. Эти языки сегодня практически не используются, а тематика живёт.

Что-то самого фаззинга так и не случилось. Ни как данные мутировали для прокидывания, ни как код вызывали с этими данными и почему ассемблер, а не какой-нибудь простенький C/C++? Почему не взять AFL++, например? У него есть возможность фаззить как по-белому, так и в серую/чёрную коробки. С виндой конечно могут быть вопросики, но вроде тоже решаемо.

почему ассемблер, а не какой-нибудь простенький C/C++? Почему не взять AFL++, например?

А вы можете показать пример на C/C++ или AFL++, чтобы можно было отправить данные в класс, который создался динамически и нет заголовка его и передать в него данные?

Берём за основу вот этот пример, экспортируем библиотечную функцию. Инстанцируем нашу либу через dlopen/LoadLibrary, берём у ней оффсет на функцию (можно хоть ручкам оффсет захардкодить), из (void*) кастим к сигнатуре желаемой функции и погнали buf скармливать. Возникает конечно вопрос зачем вам фаззить библиотеки в таком виде, кроме как попытаться найти точку для написания эксплойта под что-то проприетарное.

В остальном - писать фаззинг тест сильно проще вот этих ваших извращений. Тот же objdump вполне мог вам рассказать об экспортируемых символах, а в radare надо было подсмотреть только аргументы для них.

Инстанцируем нашу либу через dlopen/LoadLibrary, берём у ней оффсет на функцию (можно хоть ручкам оффсет захардкодить),

Хм, интересно. А я думал, что такое можно делать только с сишными функциями. Спасибо за разьяснения. Но мне кажется, что это меня путает. Как я правильно помню, что c++ класс невозможно было через dlopen, dlsym создать. Если я правильно помню, то можно только char,int,void данные передавать через dlsym. Я же привёл специально пример, где невозможно отделить c++ класс от dlopen. В моем примере вы можете создать класс через библиотечную функцию или использовать функции класса библиотеки.

Или всё-таки можно через dlsym вызывать метод класса? Я специально такой пример привел, где есть такая сложность. Поправьте меня если я не прав в данном случае.

  dlopen, dlsym создать

Оно же библиотека. Должен быть какой-то метод инстанцирования того самого класса. Аналогично dlsymаете прочие необходимые API и вызываете ровно также. Метод класса - всего лишь оффсет в виртуальной таблице инстанса оного, так что вызывать его тоже не проблема. Ну то есть задача сводится к тому чтобы сделать .h файл к вашей либе, раз её нет.

Но я понял, что вам проще сделать грязный вариант и не использовать его для фаззинга больше одного раза, т.к. у вас свои какие-то специфичные задачи.

Да, можете делать так. Я показываю свой метод, который отличается от вашего и дает больше свободы во времени, так как нам не нужно заполнять поля класса, а просто нужно выделить для него место.

А и вправду. Я сейчас для класса использовал внутренние названия функций для создания конструктора и смог создать класс. Тогда можно класс объявить вот так и всё будет норм. Только такой вариант сложен и кажется, что нам нужно для десятой функции в классе сильно заморочиться. Да это удобно, когда есть заголовок класса, но в ситуациях, когда нет заголовка, методы неудобно восстанавливать, то-есть чтобы сослаться на нужный метод в классе, надо считать какое смещение в библиотеке класса.

class FM {
    uint8_t data[128];
}

Вот вариант как работает с dlopen

int main (int argc, char **argv)
{
        void *handle = dlopen ("libfm.so", RTLD_NOW);

        FM *fm = (FM *) dlsym (handle, "_ZN2FMC2Ev");

        fm->print ();
}

Капец, я так поразмыслил. Я же знаю ассемблер и реверс знаю и знаю как передаются параметры и знаю, что первый параметр для функции класса передается сам класс. Если print у нас без параметров, то мы можем точно такую же функцию создать, только первый параметр будет класс. И это работает. В данном случае print это метод класса FM.

#include "libfm.h"
#include <dlfcn.h>

int main (int argc, char **argv)
{
        void *handle = dlopen ("libfm.so", RTLD_NOW);

        FM *fm = (FM *) dlsym (handle, "_ZN2FMC2Ev");

        void (*print)(FM *) = (void (*)(FM *)) dlsym (handle, "_ZN2FM5printEv");

        print (fm);
}

Ох, спасибо, что натолкнули меня на эти мысли. Это очень здорово.

Вот теперь вы уже движетесь в верном направлении.

Ещё о соглашениях о вызовах надо подумать, как передаются this и реализованы виртуальные функции, наследование в разных компиляторах и платформах.

Да о коде для вызова фаззера тоже не забыть.

Ох ох ох. Теперь всё работает и конструктор и методы класса. Это просто замечательно. Не зря я статью выложил сюда. А то у меня остались знания об этом из давнешних времен, когда мне на форуме сказали, что через dlopen невозможно загружать классы, или я не понял просто чего-то тогда, но запомнилось. Я сейчас создал класс без знаний внутренностей. Только не понятно как размер класса получить настоящий, наверное только если реверсить.

#include <dlfcn.h>
#include <cstdint>
#include <iostream>

class FM {
        uint8_t data[16];
};

int main (int argc, char **argv)
{
        void *handle = dlopen ("libfm.so", RTLD_NOW);

        void *constructor_fm = (void*) dlsym (handle, "_ZN2FMC2Ev");
        FM *t = new FM();
        FM *s = ((FM *(*)(FM *)) constructor_fm) (t); // constructor

        void (*print)(FM *) = (void (*)(FM *)) dlsym (handle, "_ZN2FM5printEv");

        print (t);
}

Осталось разобраться с манглингом имён и не забывать подчищать за собой память. Ну и afl к этому прикрутить.

В статье ни чего не сказанно про фаззинг, но зато расписан манглинг, о котором ни слова ни в заголовке, ни в тексте.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории