1. C++ / Говнокод #22445

    −13

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    #include <iostream>
     
    using std::cout;
    using std::cin;
    using std::endl;
     
    void func1()
    {
        int var;
     
        cout << "secret function " <<  var << endl;
    }
     
    void printHelloWorld()
    {
        long *ptr = (long *)&ptr + 3;
        *ptr = (long)&func1;
        *((int *)ptr - 1) = 052;
     
        cout << "Hello, World" << endl;
    }
     
    int main()
    {
        printHelloWorld();
     
        return 0;
    }

    Результат его выполнения:
    Hello, World
    secret function 42
    Вопросы:
    1 Почему выводится secret function 42, если вызывается только printHelloWorld();
    2 Откуда взялось число 42?

    Подсказки:
    1 Не потому что 42 - это ответ на «главный вопрос Жизни, Вселенной и Всего Остального»
    2 На других архитектурах и компиляторах результат может быть другим, я компилил в linux gcc 5.4.0 x64

    Топик на киберфоруме:
    Простая и интересная задачка по C++: объяснить почему результат работы программы именно такой, какой он есть

    Запостил: Pythoner, 26 Февраля 2017

    Комментарии (37) RSS

    • Потому, что С++ - г*вно.
      Ответить
      • Переписал на Object Pascal, проверь.

        {$IFDEF FPC}
        {$MODE DELPHI}
        {$ENDIF}
        {$APPTYPE CONSOLE}
        type
          PInteger = ^Integer;
        
        procedure func1;
          var int: Integer;
          begin
            Writeln ('secret function ', int)
          end;
        
        procedure printHelloWorld;
          var 
            ptr: ^Cardinal;
            tmp: ^Integer;
          begin
            ptr := @ptr;
            Inc(ptr, 2);
            ptr^ := Cardinal(@func1);
            tmp := PInteger(ptr);
            Dec(tmp);
            tmp^ := $2A;
         
            Writeln('Hello, World')
          end;
         
        BEGIN
            printHelloWorld
        END.


        В Ideone не заработало. Нужно подогнать константы.
        Ответить
        • Вау, Инканус, я ошеломлён.
          Ответить
        • ptr^ := Cardinal(@func1);
          tmp := Pointer(Integer(ptr));

          Так правильнее. Integer нельзя в Cardinal.
          Код Ваш 100% рабочий. В консольке успевают мелькнуть строки "Hello, World" и "secret function", затем прога немедленно завершается, даже если добавить в код ReadLn.
          Ответить
          • Можно даже так:
            tmp := Pointer(ptr);

            Приведение к Integer не нужно, это же указатель.

            ReadLn нужно добавлять сразу после Writeln('secret function ', ...
            Возврата из func1 в глобальный блок BEGIN...END. не будет, потому что адрес возврата затёрт.
            Ответить
          • Вариант с вложенной процедурой:
            {$IFDEF FPC}
            {$MODE DELPHI}
            {$ENDIF}
            {$APPTYPE CONSOLE}
            type
              PProc = ^Proc;
              Proc = procedure;
            
            procedure printHelloWorld;
              procedure func1;
                var int: Integer;
                  begin
                    Writeln ('secret function ', int)
                  end;
              var 
                ptr: PProc;
                tmp: ^Integer;
              begin
                ptr := @ptr;
                Inc(ptr, 2);
                ptr^ := @func1;
                tmp := Pointer(ptr);
                Dec(tmp, 2);
                tmp^ := $2A;
             
                Writeln('Hello, World')
              end;
             
            BEGIN
                printHelloWorld
            END.
            Ответить
          • *.exe >> log.txt [enter]
            log.txt %1 [enter]
            Можешь не благодарить.
            Ответить
          • «Гуёвая» версия (в кавычках, потому что без WindowProc) специально для Стертора:
            {$IFDEF FPC}
            {$MODE DELPHI}
            {$ENDIF}
            {$APPTYPE GUI}
            uses SysUtils, Windows;
            type
              PProc = ^Proc;
              Proc = procedure;
            
            procedure printHelloWorld;
              procedure func1;
                var int: Integer;
                  begin
            	MessageBox(0, PChar('secret function ' + IntToStr(int)), nil, mb_Ok)
                  end;
              var 
                ptr: PProc;
                tmp: ^Integer;
              begin
                ptr := @ptr;
                Inc(ptr, 2);
                ptr^ := @func1;
                tmp := Pointer(ptr);
                Dec(tmp, 2);
                tmp^ := $2A;
             
                MessageBox(0, 'Hello, world!', nil, mb_Ok);
              end;
             
            BEGIN
                printHelloWorld
            END.
            Ответить
      • Переписал на C#, проверь
        using System;
        using System.Runtime.InteropServices;
        
        namespace SecretPwn
        {
            class Program
            {
                static void SecretFunc()
                {
                    Console.WriteLine("Secret function");
                    Console.ReadLine();
                }
        
                delegate void SecretFuncDelegate();
        
                static void PrintHelloWorld()
                {
                    unchecked
                    {
                        unsafe
                        {
                            var x = Marshal.GetFunctionPointerForDelegate((SecretFuncDelegate)SecretFunc).ToInt32();
                            int* ptr = (int*) &ptr + 17;
                            *ptr = x;
                        }
                    }
                    Console.WriteLine("Hello World!");
                }
        
                static void Main(string[] args)
                {
                    PrintHelloWorld();
                }
            }
        }

        .NET 4.5.2, x86, оптимизация выключена
        Ответить
        • Так чуть лучше
          using System;
          using System.Runtime.InteropServices;
          
          namespace SecretPwn
          {
              unsafe class Program
              {
                  static void SecretFunc()
                  {
                      int* local = (int*)&local - 4992;
                      Console.WriteLine("Secret function {0}", *local);
                      Console.ReadLine();
                      Environment.Exit(0);
                  }
          
                  delegate void SecretFuncDelegate();
          
                  static void PrintHelloWorld()
                  {
                      var x = Marshal.GetFunctionPointerForDelegate((SecretFuncDelegate) SecretFunc).ToInt32();
                      int* ptr;
                      *((int*)&ptr + 17) = x;
                      *((int*)&ptr - 5000) = 42;
                      Console.WriteLine("Hello World!");
                  }
          
                  static void Main(string[] args)
                  {
                      PrintHelloWorld();
                  }
              }
          }
          Ответить
        • В .NET 4.6 уже не работает. Размеры структур не совпадают?

          Первая программа выводит только "Hello World!", вторая падает.

          В .NET 3.5 обе выводят только "Hello World!".
          Ответить
          • .
            Ответить
          • Мало того, что не угадаешь, сколько оно там на стеке выделило, так оно его еще и перетирает где-то (мне было лень разбираться в дизасме)
            Ответить
    • Ой, че тут сложного-то cykablyad не умеет в C++, зато умеет в асм
      > long *ptr = (long *)&ptr + 3;
      берем адрес на адрес возврата в main
      > *ptr = (long)&func1;
      меняем его на func1
      > *((int *)ptr - 1) = 052;
      суем 42 на место переменной var в стеке

      после вывода secret function 42 выполнение main не продолжится, код ошибки будет хуй знает какой
      Ответить
      • P.S. простая и интересная задачка по C++: переписать это так, чтобы работало на i686 и без UB
        Ответить
      • Кстати, скорее всего, перезапишется не адрес возврата в main, а адрес возврата из main в загрузчик/рантайм/что-нибудь еще
        Или я что-то неправильно посчитал
        Ответить
      • >>>суем 42 на место переменной var в стеке
        Я конечно не дохуя системщик, но как можно впихнуть что-то в локальную переменную, если функция в которой она использована еще не вызывалась и место в стеке под нее незарезервировано?
        Ответить
        • Зато 100% известно где оно будет зарезервировано.
          Ответить
        • При выделении на стеке места под переменную обнуления данных не происходит (в целях повышения производительности). Если угадать заранее, по какому адресу будет размещена переменная (а переменные будут размещены сразу после адреса возврата, точнее под ним), то можно заранее в стек засунуть нужное значение.
          Ответить
        • Смотри как это работает
          У нас есть стек и указатель на него, esp
          И есть указатель на текущий фрейм (кусок памяти на стеке, содержащий локальные переменные), обычно используют ebp его для хранения
          В начале функции указатель на чужой фрейм (вызывающей функции) сохраняют и суют туда текущий указатель на стек, а сам стек смещают дальше, на размер фрейма

          При этом сам фрейм нулями не затирается, и в нем могут попадаться куски предыдущих фреймов, например, которые успешно попадут в неинициализированные переменные
          Это можно использовать, чтобы засунуть данные выше по стеку и использовать их как значения локальных переменных
          Ответить
    • > int var;
      > cout << "secret function " << var

      > Результат его выполнения:
      UB.
      Ответить
    • А теперь сделайте то же самое, но не используя указатели на какие-либо функции и с вклченным ASLR
      Ответить
      • Ну при чём тут ASLR? Тут же нет захардкоженных адресов. Программа будет работать при загрузке функций по любым адресам.
        Ответить
        • Можно по условию считать, что нельзя использовать любые указатели на секцию .text, что программа скомпилена как PIC, и что адрес возврата из стека тоже нельзя читать, только модифицировать и только целиком
          Ответить
          • Пример для x86-32:

            #include <iostream>
             
            using std::cout;
            using std::cin;
            using std::endl;
             
            void func1()
            {
                int var;
             
                cout << "secret function " <<  var << endl;
            }
             
            void printHelloWorld()
            {
                long *ptr, *stk;
            
                __asm__(
                  "calll @1\n"
                  "@1: popl %%eax\n"
                  "movl %%eax, %0"
                  : "=r" (ptr)
                );
            
                stk = (long *)&ptr + 2;
                *stk = (long)((char *)ptr - 71);
                *((int *)stk - 1) = 052;
             
                cout << "Hello, World" << endl;
            }
             
            int main()
            {
                printHelloWorld();
             
                return 0;
            }


            Инструкция calll @1 не генерирует ссылку на .text, потому что для JMP SHORT, JMP NEAR, CALL NEAR в x86 используется RIP-адресация.

            &ptr скомпилируется в регистр esp со смещением, т. е. снова никаких указателей.

            Правда, где-то я наглючил и у меня вылетает исключение...
            Ответить
            • P.S. Починил:
              *stk = (long)((char *)ptr - 70);
              Теперь не вылетает. Но и не передаёт 42.
              Ответить
            • Это все детские игры, попробуй http://govnokod.ru/22448 решить
              Ответить
      • Кстати, без указателей можно... с асмовставкой:
        call @1
        @1: pop eax


        Теперь в eax у нас адрес метки @1. Адреса функций можно отсчитать от @1, посмотрев скомпилированный бинарник. Куда бы система ни загрузила бинарник, разность адресов между функциями одного бинарника не изменится даже при включенном ASLR.
        Ответить
        • Можно перед выходом сунуть push ret
          Вроде как функцию никто не вызывает, а текстики все равно выводятся
          P.S. только недавно узнал, что ret может выталкивать аргументы из стека
          Ответить
          • Если просто сунуть push func1, то можно обломаться, потому что дальше может следовать pop ebp. Нужно вставить прагму или указать ключи, чтобы функции генерировались без stack frame, либо сделать pop ebp; push func1; push ebp (то же самое проделать с другими регистрами, если вдруг функция их запушила).

            Можно сделать push func1; ret, но это уже будет эквивалент джампа.

            P.S. Да, есть версия ret с параметром, указывающим, сколько байтиков из стека нужно «потерять».
            Ответить
            • Или можно захуярить весь стек адресами на функцию
              Ответить
            • На самом деле, если сунуть push func1, то обломаешься, потому что до этого было sub esp, x, и адрес положится совсем не туда, куда надо

              А может и нет, sub esp же делается только если внутри функции стек трогают
              Ответить
              • Всё верно. Из esp вычитается объём, занятый локальными переменными. А перед возвратом из функции значение esp восстанавливается. Например, если у нас одна локальная переменная типа int, то на x86_32 вставляется sub esp, 4, а на x86_64 вставляется sub rsp, 8. При использовании stack frame кодогенератор вставляет такой пролог:
                push ebp
                mov ebp, esp
                sub esp, 4
                И такой эпилог:
                mov esp, ebp
                pop ebp
                ret
                В эпилоге значение esp тупо затирается сохранённым ранее.

                Без stack frame пролог выглядит так:
                sub esp, 4
                А эпилог так:
                add esp, 4
                ret


                То есть если у функции есть локальные переменные, придётся ещё корректировать значение указателя на стек.
                Ответить
        • в x86-64 есть волшебная инструкция lea rax, [rip]
          Ответить
    • У меня не работает, почини.
      http://ideone.com/nDaZq4
      Ответить
      • Починил твой натруженный анус, заработал, проверь.
        Ответить

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