Декомпилятор С++

Тема в разделе "WASM.RESEARCH", создана пользователем Vam, 16 июл 2008.

  1. s0larian

    s0larian New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    489
    Адрес:
    Крыжёпполь
    vam, опечатка в паре символов... Перефразирую:

    Код (Text):
    1. class Interface
    2. {
    3. public:
    4.     virtual ~Interface()
    5.     {
    6.     }
    7.     virtual void Method() = 0;
    8. };
    Предположим что у тебя есть список объектов классов которые реализуют вот этот интерфейс. Ессно, для каждого класса реализация разная. At run time, у тебя есть указатели на instances и, ессно, их vtables.

    То есть, код делает три операции
    - получает указатель на объект
    - через него получает таблицу виртуальных методов
    - из этой таблицы получает указатель на метод

    Ессно, сами таблицы (vtables) генерируются at compile time, но объект, использующий одну из этих таблиц, инстанциируется at run time. То есть, значение этого указателя и, следовательно, используемый vtable коду (и декомпилеру) не известен.
     
  2. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    Уважаемый s0larian, это всё мне очень хорошо известно, ты привел "классический" пример реализации виртуальности, он декомпилятором во всех вариациях обрабатывается. Run time, как такового в декомпиляторе нет, но его заменяет многопроходный анализатор кода.
    Но усложним пример, в одну переменную будем записывать указатели на разные и необязательно родственные классы (переменная в этом случае может выступать аналогом union), далее, при каких-то определенных условиях осуществить вызов указанной выше функции. Декомпилятору абсолютно неважно какая функция будет выполняться в run time в этом месте, главное, что шаблон вызова будет одним и тем же для всех вызываемых здесь функций.
    Примечание: шаблон вызова - это совокупность параметров объявления функции и включает в себя: тип возвращаемого значения, типы и кол-во аргументов, конвенцию вызова и имя функции без scope.
    Если декомпилятор построит или найдет шаблон вызова для этого места, то задача 100% решена. Все параметры шаблона, кроме последнего (имя), определяются в месте вызова функции по коду или анализом фрейма стека. Самое сложное - это найти имя, именно имя, а не точку входа в функцию или её адрес, которых может быть множество.
    В данном случае имя функции можно найти только через scope, выходя через него на соответствующую ячейку vftable, а scope в свою очередь находим анализом всех присвоений вызывающей функцию переменной.
     
  3. crypto

    crypto Active Member

    Публикаций:
    0
    Регистрация:
    13 дек 2005
    Сообщения:
    2.533
    Vam
    Коллективный разум пока бессилен :) (или скромно молчит). Решите проблему, мы Вам поаплодируем.
     
  4. s0larian

    s0larian New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    489
    Адрес:
    Крыжёпполь
    Vam, я не понимаю что ты пытаешься решить. Вот как я вижу проблему №1: имея несколько вызовов виртуальных ф-ций через указатели, декомпилер должен выдать что-то типа этого:

    Код (Text):
    1. Class4 *p1 = (Class4 *)something;
    2. p1->Method2(...);
    3.  
    4. Class10 *p2 = (Class10 *)something_else;
    5. p2->Method4(...);
    То есть, ты показываешь пользователю то что есть в коде: указатель, класс и ф-цию класса. Ессно если пользователь переименует что-то, то ты должен обновить все вызовы.

    Проблема №2 в том как определить аргументы. IMHO тут надо дать возможность пользователю определить/исправить т.к. ну уж очень много тут вариантов. В любом случае, этот вопрос отличен от декодирования/показа виртуальных вызовов.

    Так?
     
  5. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    s0larian
    Нет, не совсем так. Приведу пример исходного кода, не после декомпилятора, а реального, из которого получен экзешник и всё станет понятно:
    Код (Text):
    1. class A
    2. {
    3.     virtual void vFunc(void*, int);
    4. };
    5.  
    6. class B
    7. {
    8.     virtual void vFunc(int);
    9. };
    10.  
    11. class C
    12. {
    13.     void*   m_pObject;
    14.     void Func1(...);
    15.     void Func2(...);
    16. };
    17.  
    18. void C::Func1(...)
    19. {
    20.     if(...)
    21.         m_pObject = new A();
    22.     else if(...)
    23.         m_pObject = new B();
    24.     else if(...)
    25.         m_pObject = new char[100];
    26.     else
    27.         m_pObject = NULL;
    28. }
    29.  
    30. void C::Func2(...)
    31. {
    32.     if(...)
    33.         ((A*)m_pObject)->vFunc(aa, 3);     // s1
    34.     else if(...)
    35.         ((B*)m_pObject)->vFunc(1);          // s2
    36.     ...
    37. }
    Вот этот код, ессно после компиляции, и предстоит восстановить декомпилятору. В Func2 он встречает "неразрешимую" ситуацию, не может правильно восстановить строки s1 и s2.
    Вот, что у него получается при декомпиляции в строках s1 и s2:
    m_pObject->???(aa, 3); // s1
    m_pObject->???(1); // s2
    как видим, аргументы восстановлены, а имя вызываемой функции неизвестно. Вот этот вопрос и хотел обсудить, как правильно восстановить этот код.

    Все другие варианты виртуальности, даже для производных классов по указателю на базовый класс
    Код (Text):
    1. class A
    2. {
    3.     virtual void v1(int) = 0;
    4. };
    5.  
    6. class B : public A
    7. {
    8.     virtual void v1(int);
    9. };
    10.  
    11. class C : public A
    12. {
    13.     virtual void v1(int);
    14. };
    15.  
    16. class D
    17. {
    18.     A*  m_pObject;
    19.     void Func1(...);
    20.     void Func2(...);
    21. };
    22.  
    23. void D::Func1(...)
    24. {
    25.     if(...)
    26.         m_pObject = new B();
    27.     else if(...)
    28.         m_pObject = new C();
    29. }
    30.  
    31. void C::Func2(...)
    32. {
    33.     m_pObject->v1(0);
    34. }
    декомпилятором восстанавливаются правильно.

    ------------------------------------------------------------
    ЗЫ: Первоначально я думал, что для разрешения вопроса виртуальности достаточно определить тип вызывающей переменной, но это не так, любая переменная в точке вызова виртуальной функции может быть явно преобразована программистом к другому типу.
    Сам признак преобразования определим (в дебаг варианте кода) вот этими строками из поста №119
    Код (Text):
    1. .text:007689F4                 mov     ecx, [ebp+var_20]
    2. .text:007689F7                 mov     [ebp+var_1C], ecx
    и задача состоит в определении типа преобразованной переменной.
     
  6. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    Нет, я сказал совсем не то, что ты подумал, поясняю.
    В var_20 сохраняется компилятором переменная m_pObject типа void*, а в var_1C сохраняется уже преобразованная переменная (имени у нее нет, увы temporary) типа A*, ну а далее идет вызов функции...
    Это же только признак преобразования, если бы явного преобразования не было, то var_1C в коде бы отсутствовала и использовалась далее только бы var_20.
     
  7. s0larian

    s0larian New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    489
    Адрес:
    Крыжёпполь
    Vam, то есть при отсутствии наследования ты видишь отсутствие vtable и след-но прямой вызов?
     
  8. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    Вот реальный исходник такой функции
    Код (Text):
    1. zCPar_Symbol::~zCPar_Symbol()
    2. {
    3.     if(m_pDataAddr && !f.tNoMemory && f.tTrueAlloc)
    4.     {
    5.         int type = f.tType;
    6.         if((type == T_FLOAT) || (type == T_INT))
    7.         {
    8.             if(f.tNumber > 1)
    9.                 delete (int*)m_pDataAddr;
    10.         }
    11.         else if(type == T_STRING)
    12.         {
    13.             if(f.tNumber <= 1)
    14.                 delete (zSTRING*)m_pDataAddr;
    15.             else
    16.                 delete[] (zSTRING*)m_pDataAddr;
    17.         }
    18.     }
    19.     m_pDataAddr = NULL;
    20.     m_pParent = NULL;
    21.     m_pNext = NULL;
    22. }
    Пояснения: это кусок парсера, который разбирает текст и в переменную m_pDataAddr типа void* записывается значение ключевых слов, которое может быть и массивом. Например, ВВВ = 100, в m_pDataAddr будет записано 100, тип int, если ВВВ = {100, 200, 300}, то m_pDataAddr = new int[3], если ВВВ = "строка", то m_pDataAddr = new zSTRING("строка") и т.д. Класс zSTRING - строковый контейнер и имеет виртуальный деструктор. Остальное понятно из текста.
    Я свой пример для лучшего понимания, конечно, немного утрировал, но это не влияет на асм реализацию.
    s0larian
    Я такого не говорил, отсутствии наследования - не значит отсутствие vtable, а отсутствие vtable - не значит прямой вызов.
    Тип вызова (прямой или косвенный) определяется по асм инструкции call ..., если параметр инструкции имеет тип immediate const и его значение попадает в сегмент .text кода, то это прямой вызов.
     
  9. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    Вот ещё один очень простой исходник реальной функции
    Код (Text):
    1. template<class INDEX, class DATA>
    2. void zCSparseArray<INDEX, DATA>::DestructIndex(void* index) const
    3. {
    4.     ((INDEX*)index)->~INDEX();
    5. }
    Здесь может вызваться вообще что угодно, или обычный или виртуальный деструктор или вообще ничего (пустая функция) в зависимости от контекста использования шаблона.
     
  10. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    Ну принципиально нельзя в общем случае установить, что за виртуальная ф-ция вызывается в данной точке. Это зависит от внешних раздражителей программы - входных данных, действий юзера и т.п.
    Т.е. придется моделировать не саму программу, а всю среду исполнения, в т.ч. среду обитания юзера. :)
     
  11. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    Зачем так пессимистично, порассуждаем, если что не так, поправьте.
    Если есть виртуальная функция, значит есть класс и обязательно с конструктором. Если виртуальная функция вызывается значит должен быть указатель на класс, прямой - тип pointer, косвенный - адрес статического объекта. Значит должен существовать сам объект в памяти - динамический или статический, чтобы на него получить указатель. А создание объекта в памяти будет выполнено через вызов конструктора, а в конструкторе однозначно определяется таблица виртуальных функций. Цепочка замкнулась и этой информации нам достаточно чтобы разрешить ситуацию, при условии, что мы будем отслеживать тип каждой переменной.
    Определим три точки отслеживания переменной А - присвоение (А = ввв), изменение (А += 1) и использование (А-> или В = А), в скобках приведены, конечно, не все варианты.
    На каждую переменную заводим массив типа - в него складываем типы присвоения в точке присвоения. Точка изменения здесь не интересна, т.к. тип не меняется, а изменяется только значение. В точке использования анализируем типы в массиве и выбираем нужный... И всё, задача в принципе решена.
     
  12. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    Vam
    Давайте возьмём более простой случай: в некоторой точке программы делается косвенный вызов ф-ции (т.е. вызов ф-ции по указателю). В общем случае установить какая именно ф-ция вызывается, нельзя, вот пример:

    Код (Text):
    1. typedef void (*FuncPtr)();
    2. FuncPtr GetFuncPtrDependingOnUserInput()
    3. {
    4.     switch (getchar())
    5.     {
    6.     case 'A':
    7.         return FuncA;
    8.     case 'B':
    9.         return FuncB;
    10.         //...
    11.     case 'Z':
    12.         return FuncZ;
    13.     }
    14.     //...
    15. }
    16.  
    17. //...
    18.  
    19. FuncPtr pFuncPtr = GetFuncPtrDependingOnUserInput();
    20. pFuncPtr(); // Как вы определите, которая из ф-ций FuncX здесь вызывается? Это зависит от того, что ввёл юзер, а не только от свойств программы.
    То же самое и с вызовом виртуальной ф-ции, только там ещё сложнее - уровень косвенности в 2 раза выше.
     
  13. void

    void New Member

    Публикаций:
    0
    Регистрация:
    31 янв 2009
    Сообщения:
    1
    null and void :)
     
  14. Stariy

    Stariy Member

    Публикаций:
    0
    Регистрация:
    22 окт 2003
    Сообщения:
    529
    Адрес:
    Russia
    прочитал всю тему, осознал свое ничтожество:-(((( даже перестало казаться, что зарплату занижают и пора б ругаться опять...
    выражаю глубочайшее уважение топикстартеру.
     
  15. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    green
    А зачем нам это нужно? Мы ведь восстанавливаем только исходный код, а не делаем эвристический эмулятор программы. В исходном коде в месте вызова и будет pFuncPtr();, я уже говорил, чтобы записать в общем виде вызов функции нам нужно построить её шаблон. Здесь параметры шаблона, как возвращаемое значение и аргументы определяем в точке вызова, имя, если не находим его в явном виде, будет сконструировано из места (адреса) размещения переменной FuncPtr pFuncPtr = GetFuncPtrDependingOnUserInput(); при такой записи переменная может быть локальная (стековая или регистровая при оптимизации), или глобальная в инициализированной области памяти данных.
    Возможные записи вызова функции в созданных исходниках будут соответственно такими: pFuncPtr(); __$Var40(); __$ebx(); funcptr_00C1155A(); Здесь всё просто и интерактивность не нужна.
     
  16. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    Вопрос: Можно ли по содержимому двоичного файла (exe, dll, drv и т.д.) точно сказать каким компилятором создан этот файл, если да, то по какой информации?
    На данный момент конкретно интересует, можно ли различить файлы компилированные разными версиями VS. (6, 7, 2003, 2005, 2008).
     
  17. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    версия линкера есть в заголовке
     
  18. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    GoldFinch
    А более конкретно можно, как я знаю это одно поле DWORD типа, какое число (номер версии) соответствует конкретному VS линковщику. Не один же у них линковщик для всех студий.
     
  19. GoldFinch

    GoldFinch New Member

    Публикаций:
    0
    Регистрация:
    29 мар 2008
    Сообщения:
    1.775
    тебе осталось только узнать версии линкеров всех студий
     
  20. crypto

    crypto Active Member

    Публикаций:
    0
    Регистрация:
    13 дек 2005
    Сообщения:
    2.533
    Vam
    Анализируй стартап-код, может он для разных версий в чем-то отличен?
    ЗЫ
    Можно еще посмотреть, как версия определяется в IDA