Я хотел бы рассказать о том, как я писал реализацию «Hello, World!» на C. Для подогрева сразу покажу код. Кого интересует как до этого доходил я, добро пожаловать под кат.

#include <stdio.h>
const void *ptrprintf = printf;
#pragma section(".exre", execute, read)
__declspec(allocate(".exre")) int main[] =
{
    0x646C6890, 0x20680021, 0x68726F57,
    0x2C6F6C6C, 0x48000068, 0x24448D65,
    0x15FF5002, &ptrprintf, 0xC314C483
};


Предисловие


Итак, начал я с того, что нашел эту статью. Вдохновившись ею, я стал думать, как сделать это на windows.

В той статье вывод на экран был реализован с помощью syscall, но в windows мы сможем использовать только функцию printf. Возможно я ошибаюсь, но ничего иного я так и не нашел.

Набравшись смелости и взяв в руки visual studio я стал пробовать. Не знаю, зачем я так долго возился с тем, чтобы подставлять entry point в настройках компиляции, но как выяснилось позже компилятор visual studio даже не кидает warning если main является массивом, а не функцией.

Основной список проблем, с которыми мне пришлось столкнуться:

1) Массив находится в секции данных и не может быть исполнен
2) В windows нет syscall и вывод нужно реализовать с помощью printf

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

Решение проблемы «исполняемых данных»


Первая проблема, с которой я столкнулся, ожидаемо оказалось то, что простой массив хранится в секции данных и не может быть исполнен, как код. Но немного покопав stackoverflow и msdn я все же нашел выход. Компилятор visual studio поддерживает препроцессорную директиву section и можно объявить переменную так, чтобы она оказалась в секции с разрешением на исполнение.

Проверив, так ли это, я убедился, что это работает и функция массив main спокойно исполняет opcode ret и не вызывает ошибки «Access violation».

#pragma section(".exre", execute, read)
__declspec(allocate(".exre")) char main[] = { 0xC3 };

Немного ассемблера


Теперь, когда я мог исполнять массив нужно было составить код который будет выполняться.

Я решил, что сообщение «Hello, World» я буду хранить в ассемблерном коде. Сразу скажу, что ассемблер я понимаю достаточно плохо, поэтому прошу сильно тапками не кидаться, но критика приветствуется. В понимании того, какой ассемблерный код можно вставить и не вызывать лишних функций мне помог этот ответ на stackoverfow
Я взял notepad++ и с помощью функции plugins->converter->«ASCII -> HEX» получил код символов.

Hello, World!

48656C6C6F2C20576F726C6421

Далее нам нужно разделить по 4 байта и положить на стек в обратном порядке, не забыв перевернуть в little-endian.

Делим, переворачиваем.
Добавим в конец терминальный ноль.

48656C6C6F2C20576F726C642100

Делим с конца на 4 байтные hex числа.

00004865 6C6C6F2C 20576F72 6C642100

Переворачиваем в little-endian и меняем порядок на обратный

0x0021646C 0x726F5720 0x2C6F6C6C 0x65480000


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

#include <stdio.h>
const void *ptrprintf = printf;
void main() {
    __asm {
        push 0x0021646C ; "ld!\0"
        push 0x726F5720 ; " Wor"
        push 0x2C6F6C6C ; "llo," 
        push 0x65480000 ; "\0\0He"
        lea  eax, [esp+2] ; eax -> "Hello, World!"
        push eax ; указатель на начало строки пушим на стек
        call ptrprintf ; вызываем printf
        add  esp, 20 ; чистим стек
    }
}

Компилируем и смотрим дизассемблер.

00A8B001 68 6C 64 21 00       push        21646Ch  
00A8B006 68 20 57 6F 72       push        726F5720h  
00A8B00B 68 6C 6C 6F 2C       push        2C6F6C6Ch  
00A8B010 68 00 00 48 65       push        65480000h  
00A8B015 8D 44 24 02          lea         eax,[esp+2]  
00A8B019 50                   push        eax  
00A8B01A FF 15 00 90 A8 00    call        dword ptr [ptrprintf (0A89000h)]  
00A8B020 83 C4 14             add         esp,14h  
00A8B023 C3                   ret  

Отсюда нам нужно взять байты кода.

Чтобы вручную не убирать ассемблерный код можно воспользоваться регулярными выражениями в notepad++.
Регулярное выражение для последовательности после байтов кода:

 {2} *.*

Начало строк можно убрать с помощью плагина для notepad++ TextFx:

TextFX->«TextFx Tools»->«Delete Line Numbers or First Word», выделив все строки.

После чего у нас уже будет почти готовая последовательность кода для массива.

68 6C 64 21 00
68 20 57 6F 72
68 6C 6C 6F 2C
68 00 00 48 65
8D 44 24 02
50
FF 15 00 90 A8 00 ; После FF 15 следующие 4 байта должны быть адресом вызываемой фунцкии
83 C4 14
C3


Вызов функции с «заранее известным» адресом


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

#include <stdio.h>
const void *ptrprintf = printf;
void main()
{
    void *funccall = &ptrprintf;
    __asm {
        call ptrprintf
    }
}



Как видно в указателе лежит именно тот самый вызываемый адрес. То, что нужно.

Собираем все вместе


Итак, у нас есть последовательность байт ассемблерного кода, среди которых нам нужно оставить выражение, которое компилятор преобразует в адрес, нужный нам для вызова printf. Адрес у нас 4 байтный(т.к. пишем для код для 32 разрядной платформы), значит и массив должен содержать 4 байтные значения, причем так, чтобы после байт FF 15 у нас шел следующий элемент, куда мы и будем помещать наш адрес.

Путем нехитрых подстановок получаем искомую последовательность.
Берем полученную ранее последовательность байт нашего ассемблерного кода. Отталкиваясь от того, что 4 байта после FF 15 у нас должны составлять одно значение форматируем под них. А недостающие байты заменим на операцию nop с кодом 0x90.

90 68 6C 64
21 00 68 20
57 6F 72 68
6C 6C 6F 2C
68 00 00 48
65 8D 44 24 
02 50 FF 15
00 90 A8 00 ; адрес для вызова printf
83 C4 14 C3

И опять составим 4 байтные значения в little-endian. Для переноса столбцов очень полезно использовать многострочное выделение в notepad++ с комбинацией alt+shift:

646C6890
20680021
68726F57
2C6F6C6C
48000068
24448D65
15FF5002
00000000 ; адрес для вызова printf, далее будет заменен на выражение
C314C483


Теперь у нас есть последовательность 4 байтных чисел и адрес для вызова функции printf и мы можем наконец заполнить наш массив main.

#include <stdio.h>
const void *ptrprintf = printf;
#pragma section(".exre", execute, read)
__declspec(allocate(".exre")) int main[] =
{
    0x646C6890, 0x20680021, 0x68726F57,
    0x2C6F6C6C, 0x48000068, 0x24448D65,
    0x15FF5002, &ptrprintf, 0xC314C483
};

Для того чтобы вызывать break point в дебаггере visual studio надо заменить первый элемент массива на 0x646C68CC
Запускаем, смотрим.



Готово!

Заключение


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

Оставлю тут все приведенные ссылки:

Статья «main usually a function»
Описание section на msdn
Некоторое объяснение ассемблерного кода на stackoverflow

И на всякий случай оставлю ссылку на 7z архив с проектом под visual studio 2013

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

Буду рад вашим отзывам и замечаниям.

Комментарии (143)


  1. bigfatbrowncat
    25.01.2016 18:31
    +27

    Статья действительно «для самых маленьких» и это — отлично. Прекрасный способ «порвать шаблон» людям, которые, немного попрограммировав на каком-то языке (например, Си), не понимают, как работает компилятор и что на самом деле получается на выходе. Одно дело зазубрить «компилятор превращает исходный текст программы в машииный код», и совсем другое — иллюстрированно и по шагам проделать это самому и увидеть, что получилось.

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


  1. mayorovp
    25.01.2016 18:38
    +4

    Вместо заголовочного файла можно было бы просто объявить прототип функции printf.


    1. ComradeAndrew
      25.01.2016 18:53
      +3

      Да, вы правы. Об этом я не думал. Посмотрим, если наберется достаточно много интересных поправок, добавлю к статье модифицированный вариант.


    1. Joric
      25.01.2016 18:55
      +1

      «int printf(char*f,...);» всё равно выходит длиннее "#include <stdio.h>" :)


      1. Joric
        25.01.2016 18:59
        +2

        Хотя можно без int, линкер сожрёт, но с варнингом. Получится на 1 (!) символ короче.


        1. ComradeAndrew
          25.01.2016 19:06
          +1

          А у меня даже варнинга не выдает. Если ставить пробел после #include, как все нормальные люди, то получится одинаковая длина.

          printf(char*,...);
          #include <stdio.h>
          

          Варнинг у меня только один. На &ptrprintf. Я хотел добавить макрос с кастом к int, чтобы оставить ту же длину и однородный цвет в таблице массива, но решил оставить как есть.


          1. bigfatbrowncat
            25.01.2016 19:35
            +3

            (ирония)
            А если ставить пробел после запятой, как все нормальные люди, то всё равно получится на один символ больше.


            1. ComradeAndrew
              25.01.2016 19:38
              +2

              Тут не поспоришь :)
              Но мне кажется, что вариант с прототипом вполне неплохой и поддерживает стиль остального кода.


            1. khim
              25.01.2016 19:56
              +6

              Зачем вам вообще что-то в скобках? Это же C, не C++!

              «int printf();» — законное описание функции, которая принимает «что-то там, неважно что», возврщает int, большего вам и не нужно…


              1. ComradeAndrew
                25.01.2016 20:02
                +2

                Да, действительно. На тулсете 2013 студии компилирует. Однако при компиляции тулсетом v140(visual studio 2015) она кидает error:

                error LNK2001: unresolved external symbol _printf


                1. khim
                  25.01.2016 21:27

                  Скорее всего как-то манглится имя. Посмотрите в обычном «Hello, world!» чего хочет ваш объектник…


  1. xiWera
    25.01.2016 18:44
    -7

    А еще можно сразу вызывать какойнить 'system' и одним махом столько разных программ «на Си написать» :)


  1. mark_ablov
    25.01.2016 18:45
    +3

    В Windows тоже можно использовать сисколы.
    Дпугое дело, что они version-specific.
    Конкретно для вывода на консоль можно использовать NtWriteFile.


    1. ComradeAndrew
      25.01.2016 19:09
      +1

      NtWriteFile в поток вывода? Интересно. Надо попробовать как по длине кода получится.


      1. mayorovp
        25.01.2016 20:08
        +1

        Плохо по длине кода получится. Потому что дескриптор потока вывода еще получить надо.


      1. sebres
        25.01.2016 20:54
        +1

        Ну есть жеж WriteConsoleA…
        handle на stdout получается через GetStdHandle(-11)

        push -11
        call GetStdHandle
        

        под спойлером пример для nasm+link
        extern _ExitProcess@4, _GetStdHandle@4, _WriteConsoleA@20
        
        %define ExitProcess _ExitProcess@4
        %define GetStdHandle _GetStdHandle@4
        %define WriteConsoleA _WriteConsoleA@20
        
        global _main
        
        section .data
        msg                 db "Hello World!", 13, 10, 0
        msg.len             equ $ - msg
        
        section .text
        _main:
          push -11
          call GetStdHandle
        
          push 0
          push 0
          push msg.len
          push msg
          push eax
          call WriteConsoleA
        
          push 0
          call ExitProcess
        


        1. MacIn
          25.01.2016 21:22

          Можно меньше, если выравнивание секций поменять, если я не путаю.


          1. sebres
            25.01.2016 22:04

            Если есть mingw (или cygwin), то "strip --strip-all hw.exe" оставит от экзешника 2KB


            1. MacIn
              25.01.2016 22:30

              Туда еще wipedosstub, наверно можно ужать сильнее. + минимальное выравние секции в файле 64, емнип.


        1. pravic
          25.01.2016 23:39

          Что-то много. 1 КБ, не трогая выравнивание, на FASM-e.


    1. grechnik
      25.01.2016 22:24
      +5

      Для консоли это не будет работать, у консоли вообще нет ядерного хэндла. WriteFile, обнаружив kernel32-хэндл консоли, вызывает WriteConsole, WriteConsole работает в конечном счёте через RPC к модулю winsrv.dll в процессе csrss. (В частности, из-за такой схемы в XP консольные окна не подхватывали темы оформления — грузить абы что в системный процесс csrss MS не хотела. В Vista для этого в цепочку добавили ещё один процесс conhost.exe, вызываемый из csrss).


      1. DigitalSmile
        26.01.2016 14:55
        +1

        Спасибо за ответ по консоли, мучавший меня так долго…


  1. rPman
    25.01.2016 18:46
    +14

    Ненормальное программирование!


  1. mark_ablov
    25.01.2016 19:42
    +1

    Как-то так. Разве что 0x1A0007 я глянул у себя в системе (8.1), для других версий винды оно другое.

        __asm {
                xor edi, edi
                push 0x0021646C
                push 0x726F5720
                push 0x2C6F6C6C
                push 0x65480000
                lea  eax, [esp + 2]
                push edi
                push edi
                mov ebx, esp
                push 13
                push eax
                push ebx ; output pointer to IoStatusBlock, does not matter if we overrite some data in stack
                push edi
                push edi
                push edi
                ; get output file handler from TEB/PEB
                mov eax, fs:[030h]
                mov eax, [eax+10h]
                mov eax, [eax+1Ch]
                push eax
                push 0 ; ret addr, skipped by syscall handler
                ; call func
                mov eax, 1A0007h ; NtWriteFile, check syscall # for your windows version
                call fs:[0C0h]
                add esp, 38h ; 10h string + 24h syscall stack + 4h ret
        }
    


    1. ComradeAndrew
      25.01.2016 20:03
      +1

      Честно говоря совсем не понял для чего столько пушей и что в итоге получается. Можете немного подробнее объяснить как это работает?


      1. saferif
        25.01.2016 21:20

        Много пушей, поскольку параметров у функции много. Правда многие равны NULL. Описание функции по ссылке.

        NTWriteFile, он же ZwWriteFile
        NTSTATUS ZwWriteFile(
          _In_     HANDLE           FileHandle,
          _In_opt_ HANDLE           Event,
          _In_opt_ PIO_APC_ROUTINE  ApcRoutine,
          _In_opt_ PVOID            ApcContext,
          _Out_    PIO_STATUS_BLOCK IoStatusBlock,
          _In_     PVOID            Buffer,
          _In_     ULONG            Length,
          _In_opt_ PLARGE_INTEGER   ByteOffset,
          _In_opt_ PULONG           Key
        );
        


      1. JKornev
        25.01.2016 21:24
        -3

        mark_ablov решил выпендрится и переписал вашу реализацию hello world включив в неё функциональность вывода на консоль базонезависимым кодом. Однако реализация эта будет работать скорее всего только на некоторых системах 8.1 x64


        1. MacIn
          25.01.2016 22:44

          В принципе, туда еще небольшой шажок — поиск номера функции в теле NtWriteFile и будет веселее.


          1. JKornev
            26.01.2016 00:48

            Мне кажется лучше было бы просто узнать адрес NtWriteFile через PEB_LDR_DATA и вызвать ф-ю напрямую, потому что зная номер сервиса нам ещё нужно как-то попасть в ядро, а в разных разрядностях это реализовано по-разному


            1. MacIn
              26.01.2016 02:32
              +1

              Что разрядности — в разных билдах номер может отличаться. Да, и как выяснилось ниже, NtWriteFile не катит — он не работает с псевдо-handle'ами. Надо через LDR_DATA вытаскивать WriteFile или сразу WriteConsole.


              1. JKornev
                26.01.2016 13:16

                Дело не в номерах, а в особенностях механизмов входа в ядро для x86 и x64(WOW64). Во-первых они отличаются, во-вторых если мне не изменяет память для x64 необходимо класть параметры с учётом того что они будут обрабатываться 64-битным кодом.

                Так что да, лучше юзать что-то высокоуровневое


                1. MacIn
                  26.01.2016 15:32

                  Мы так и так компилируем 32 разрядный код в примере выше + стандартная точка входа из TEB. А вот номера сервисов отличаются от SP к SP, например.


                  1. JKornev
                    26.01.2016 15:42

                    call fs:[0C0h] в Windows Vista и выше не будет работать для x86, там используется SharedUserData->SystemCallStub


      1. dordzhiev
        25.01.2016 23:02

        del


    1. grechnik
      25.01.2016 22:29

      И что, правда работает с выводом на консоль (если не перенаправлять вывод в файл)?


      1. MacIn
        25.01.2016 22:41

        Отчего нет — по 1Ch лежит HANDLE StdOutput.


        1. grechnik
          25.01.2016 22:49

          Хэндлы бывают разные. Вы пробовали запустить код от mark_ablov?

          #include <stdio.h>
          #include <intrin.h>
          
          int main()
          {
          	printf("%08x\n", ((int**)__readfsdword(0x30))[0x10/4][0x1C/4]);
          	return 0;
          }
          

          Win7 WOW64:
          C:\> test | more
          0000006C
          C:\> test
          0000000F
          

          Первый запуск — перенаправление вывода, 0x6C — нормальный ядерный хэндл. Второй запуск — вывод напрямую на консоль, хэндл, как легко видеть, ненормальный (ядерные хэндлы всегда делятся на 4).


          1. MacIn
            25.01.2016 23:03
            +1

            Это псевдо-хэндл. У меня на ХР выдает 7 (кстати, GetStdHandle делает именно это — роется в TEB/PEB/EPROCESS_PARAMETERS), и это рабочий handle, по крайней мере WriteFile его принимает (Nt(Zw)WriteFile не пробовал).

            upd: понятно, WriteFile обрабатывает это особо, а ntdll не примет. Тогда да, вы правы.


            1. grechnik
              25.01.2016 23:05
              +1

              Рабочий для NtWriteFile? Проверяли? Что он рабочий для WriteFile, сомнений нет.


              1. MacIn
                25.01.2016 23:09
                +1

                Да, я уже посмотрел листинг — kernel32 перенаправляет это в WriteConsoleA.


          1. mark_ablov
            26.01.2016 06:58

            Вполне возможно, да.
            Проверял из ms vc для простоты. Тогда можно RPC слать серверу консолей. Что малость сложнее, но тоже ничего невозможного.


      1. mark_ablov
        26.01.2016 06:53
        +2


        1. grechnik
          26.01.2016 14:50
          +1

          Хм, интересно. Win8.1, говорите?
          На Win7 ожидаемо фейлится с STATUS_OBJECT_TYPE_MISMATCH:

          Картинка


          1. mark_ablov
            26.01.2016 14:57

            > WriteFile не делает специальных проверок и просто вызывает NtWriteFile
            Угу, я когда смотрел как она преобразовывает STD_OUTPUT_HANDLER не видел никаких вызовов WriteConsole, а просто обращение к данным PEBа.


  1. ababo
    25.01.2016 19:53
    +13

    Чрезвычайно простой и практичный подход к написанию ПО. Ждём более сложные примеры.


    1. ComradeAndrew
      25.01.2016 20:57
      +2

      Если правда интересно, то постараюсь придумать что-то похожее. Пока времени мало, но как сдам сессию наверняка вернусь к этому. Я даже не ожидал, что будет такой интерес :)


      1. Arkham
        26.01.2016 11:29
        +1

        По-моему это был сарказм :) Но спасибо за статью, было интересно.


  1. Eivind
    25.01.2016 20:37
    +6

    А еще можно попробовать переписать main «на лету»:

    #include <stdint.h>
    #include <stdio.h>
    #include <sys/mman.h>
    #include <unistd.h>
    
    int main()
    {
        uintptr_t offset = 0x71;
        size_t pagesize = sysconf( _SC_PAGESIZE );
        char* m = (char*)&main;
        uintptr_t pagestart = ( (uintptr_t)m ) & -pagesize;
        mprotect( (void*)pagestart, ( (uintptr_t)m ) - pagestart + offset, PROT_READ | PROT_WRITE | PROT_EXEC );
        m[offset] = 'e';
        printf( "H%cllo World!\n", 'a' );
    }
    


  1. leremin
    25.01.2016 21:34
    +3

    Когда вижу подобный код в голове проскакивает непременное «За изобретение ставлю пять, а… по предмету — неуд». Это все очень хорошо и интересно, но практическое применение этого очень редко оправдано. Нисколько вас не упрекаю, если могло так показаться.


    1. ComradeAndrew
      25.01.2016 21:41
      +6

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


      1. pravic
        25.01.2016 23:46
        +5

        Вместо хаба «Visual Studio» тогда уж «Ненормальное программирование».


        1. ComradeAndrew
          26.01.2016 17:11

          Поменял


      1. klirichek
        28.01.2016 20:53

        Всё от задачи зависит.
        Если вы пишете hello world — это одно. А если антивирус — всё уже гораздо ближе к ненормальному.

        Во времена XP писал плагин для Outlook Express — так там чтобы заюзать его собственную функцию, показывающую диалог выбора папок для писем и возвращающую результат делал страшный костыль: ставил хук на создание окна; затем имитировал команду меню приложения «перейти в папку» (которая как раз приводила к вызову искомой функции), в хуке ловил появляющееся окно, дальше брал адрес локальной переменной (которая, разумеется, создаётся на стеке) и от этого адреса раскручивал вверх память, проверяя каждое слово, не является ли оно адресом из исполнимой части экзешника процесса (для этого сперва парсил заголовки PE, чтобы понять, в каком адресном диапазоне находится именно его код). Причём сделать это нужно было дважды: первый раз под эти условия попадает адрес возврата из оконной функции (тот, что лежит на стеке) а следующий — адрес возврата из искомой функции выбора диалога уже в недрах экзешника. После этого в нужном адресе отступаем назад на код команды call, и из этого машкода выцарапываем смещение нужной функции. Вычисляем адрес, проверяем, что он попадает в секцию relocation экзешника — и тогда да, это то, что нужно.

        Так вот этот «костыль» успешно пошёл в продакшн; успешно взлетел на 64-битной винде; успешно взлетел позже на winmail (когда пришла vista), и насколько мне известно, пошёл «в тираж» и далее.

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


    1. ateraefectus
      25.01.2016 21:59
      +2

      За изобретение ставлю пять, а… по предмету — неуд

      Тот самый троллейбус.jpg =) (Против статьи ничего не имею, если что)


    1. Wedmer
      25.01.2016 22:37
      +1

      На самом деле неплохой прием для обфускации кода.


      1. ComradeAndrew
        25.01.2016 22:40
        +8

        Ну разве что исходного. Дизассеблер будет выглядеть как код составленный заботливым разработчиком (:


        1. Wedmer
          26.01.2016 00:22
          +1

          Против дизассемблирования есть другие приемы) Данный метод обфускации хорош при работе с недобросовестным заказчиком, если вдруг по договору исходный код должен передаваться. Обычно такие дяди имеют на борту студента, который может собрать софтину и проанализировать код на закладки типа «Демонстрационная Версия». Конечно подобные договора не стоит заключать, но иногда выхода нет (начальство недальновидное, принципиальная необходимость выполнить эти работы, госзаказ и т.д. и т.п.).


          1. khim
            26.01.2016 01:27
            +4

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

            Не знаю как там насчёт «нодобросовестного заказчика», но передача подобных кодов — это уж точно «недобросовестный исполнитель».


            1. Wedmer
              26.01.2016 03:30

              Критерием «недобросовестного исполнителя» является передача частично обфусцированного кода или факт самой передачи кода?


              1. khim
                26.01.2016 16:32
                +1

                Сама передача кода мне кажется вообще «дефолтным» поведением (зачем мне компонент без исходников?), хотя, конечно, она должна оговариваться в договоре. А вот передача обфусцированного кода — это уже повод для того, чтобы разорвать отношения и больше никогда к подобному поставщику не обращаться.

                Причём независимо от того, будут ли в принципе выданы коды лучше компонент, полученный от такого поставщика переписать при первой возможности: всех оставленныъ лазеек простым просмотром не выявить, а если разбираться досконально и вычитывать каждую строчку — так можно и с нуля всё сделать примерно за те же деньги.

                Другое дело если вам передают компонент не специально для вас разработанный, а такой, в который соответствующая компания вложила годы разработки. Тут нужно явно оговорить, что, наоборот, передаются только бинарники, исходники не передаются. А переписывать ли такой компонент — нужно судить по бизнес-обстановке. Если он вам нужен «всерьёз и надолго» (то есть на нём ваш бизнес «висеть» будет) — лучше переписать.

                P.S. Я, разумеется, говорю про IT компании и случай когда вас же поставщик может решить обойтись без вас в качестве посредника. Конечно переписывать системы складского учёта я не предлагаю (представить себе что фирма по внедрению 1С начнёт вдруг «грузить апельсины бочками» достаточно сложно).


                1. Wedmer
                  26.01.2016 17:47
                  +1

                  Когда ваш исходный код окажется в третьей конторе, и она волшебным образом получит этот проект на исполнение после разрыва договора с вами… Я всего лишь предположил, что в случаях крайней необходимости можно этим пользоваться. Ведь проведение подобной обфускации крайне затратное дело. Лично я считаю, что с мутными товарищами, что заказчиками, что подрядчиками, лучше дел не иметь. Но директорату, как говорится, виднее. Бывает ещё так, что заказчик навязывает вам субподрядчика, который ничего не делает, но, внезапно, должен иметь доступ к исходному коду.


    1. monah_tuk
      26.01.2016 07:58
      +2

      Практические знания подобного рода становятся очень сильно востребованными, когда, например, поднимаешь тулчейн для новой embedded железки: писать так не нужно, но понимать, что происходит под капотом — это просто необходимо. Особенно когда за байты война начинается.


  1. Nomad1
    26.01.2016 00:22

    На мой взгляд, экзешник большой выходит. С /nodefaultlib и некоторым колдунством можно получить чуть больше исходник, но ужаться до ~500 байт.

    Как-то так
    #pragma comment(linker, "/NODEFAULTLIB")
    #pragma comment(linker, "/FILEALIGN:16")
    #pragma comment(linker, "/ALIGN:16")// Merge sections
    #pragma comment(linker, "/MERGE:.rdata=.data")
    #pragma comment(linker, "/MERGE:.text=.data")
    #pragma comment(linker, "/MERGE:.reloc=.data")
     
    // Favour small code
    #pragma optimize("gsy", on)
    


    1. grechnik
      26.01.2016 00:31

      #pragma comment(linker, "/FILEALIGN:16")
      #pragma comment(linker, "/ALIGN:16")// Merge sections
      

      … и получить файл, не работающий на x64.


      1. Nomad1
        26.01.2016 00:35

        Ну так это ж «ненормальное программирование» :) Я ж не говорю о том, что исходник и так на других ОС, компиляторах и архитектурах не работает


      1. JKornev
        26.01.2016 01:52

        Интересно почему не рабочий?


        1. grechnik
          26.01.2016 02:57
          +1

          А это у MS спрашивать надо. Если 32-битный бинарник собран с выравниванием меньше размера страницы (и некоторыми дополнительными ограничениями), то 32-битная система его загрузит, а 64-битная — нет.


          1. JKornev
            26.01.2016 13:08

            Просто насколько я знаю минимально допустимое выравнивание для PE это file:0x200, virtual:0x1000, у вас есть пруф бинарника с более низким выравниванием?


            1. grechnik
              26.01.2016 15:01
              +1

              Возьмите любой драйвер.
              Если настаиваете именно на exe-шнике — вот банальный MessageBox с FileAlignment = SectionAlignment = 0x20: yadi.sk/d/385e5Lhqnkybi. На 32-битной XP точно работает.


              1. JKornev
                26.01.2016 15:49

                Действительно робит (проверено на w7x86), спасибо


            1. MacIn
              26.01.2016 15:35

              Конечно, 16/16 отлично работает на XP, только вчера проверял.


  1. PapaBubaDiop
    26.01.2016 00:34

    Стесняюсь спросить, а всякие прерывания INT 21h и более системные (15 что-ли) для вывода символов на терминал уже не работают? Раньше можно покороче сделать было байт-код.


    1. ComradeAndrew
      26.01.2016 00:40

      Раньше можно покороче сделать было байт-код.

      Где это работало?
      На сколько я знаю в windows прерывания заблокированы начиная с windows 2000.


      1. MacIn
        26.01.2016 00:51
        +3

        Не совсем. Вплоть до XP SP2(?) syscall идет через int 2Eh…
        но мы понимаем, что вопрос выше чуть о другом ;)


        1. PapaBubaDiop
          26.01.2016 01:15

          Картина проясняется, спасибо.


    1. MacIn
      26.01.2016 00:50

      Если вы соберете под MS-DOS чистый MZ exeшник, то будет работать, и то не во всех системах.


  1. imwode
    26.01.2016 03:56

    Я тне пргограммист, поэтому спрошу прямо — во что дизассемблер разберет сгенерированный из этой рпограммы машинный код?


    1. MacIn
      26.01.2016 04:22

      Собственно это указано ниже «Компилируем и смотрим дизассемблер.»
      Ну, плюс секция импорта и т.п.


      1. imwode
        26.01.2016 04:28

        Ой, а я почему-то думал, что дизассемблер в фугкции собирает, типа программу на си выдает, которая компилируется в такой же ассемблерный код
        Я ассемблер не знаю, собственно вопрос в том — какой «безхитростный» код даст аналогичный ассемблерный листинг? С нормальным мейном-функцией, принтэфом и строкой в параметре или что-то другое?
        Т.е. вопрос собственно в том, с чем автор в итоге поигрался — только с кодом или с кодом и ассемблером или с конечной программой тоже?


        1. MacIn
          26.01.2016 04:48
          +3

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

          Это называется декомпилятор.

          с чем автор в итоге поигрался

          Да ни с чем. Написал на ассемблере вызов printf, выписал шестнадцатеричные коды и вставил. Поиграв в ассемблер (программу). Эквивалентно обычному printf(«Hello world»), только написано с подвывертом.
          Например, так же в старые времена в Бейсике работали с мышью:
          MouseData:
          DATA 55,89,E5,8B,5E,0C,8B,07,50,8B,5E,0A,8B,07,50,8B
          DATA 5E,08,8B,0F,8B,5E,06,8B,17,5B,58,1E,07,CD,33,53
          DATA 8B,5E,0C,89,07,58,8B,5E,0A,89,07,8B,5E,08,89,0F
          DATA 8B,5E,06,89,17,5D,CA,08,00
          ...
          SUB MouseDriver (Ax, Bx, Cx, Dx)
           
          DEF SEG = VARSEG(Mouse$)
          Mouse = SADD(Mouse$)
          CALL Absolute(Ax, Bx, Cx, Dx, Mouse)
          
          END SUB
          
          


    1. laphroaig
      26.01.2016 12:21
      +2

      Классная опечатка, я теперь тоже буду «the программистом»


    1. ComradeAndrew
      26.01.2016 14:53

      В этом комментарии я показал во что превращается main. В то же самое, что и вставили в массив, компилятор никак не изменил этот код, потому что для него это были данные, а не код.


  1. rprokop
    26.01.2016 14:17
    +2

    Как собрать рабочий экзешник на Visual Studio вообще без исходника:
    link /OUT:dummy.exe /SUBSYSTEM:CONSOLE /ENTRY:ExitProcess@4 kernel32.lib

    http://rsdn.ru/forum/humour/3748806.all


  1. jacob1237
    26.01.2016 14:22

    Автор, а куда в исполняемом файле компилятор в итоге втыкает EntryPoint — в Вашу секцию .exre? Или он в итоге в .code подставляет переход (jmp) к .exre?


    1. ComradeAndrew
      26.01.2016 14:51
      +1

      Точно так же, как если бы main был функцией. main является EntryPoint по умолчанию.
      Вызывается

      mainCRTStartup


      1. MacIn
        26.01.2016 15:38
        +1

        Это можно указать прямо:
        link.exe /ALIGN:16 /FILEALIGN:16 /FORCE:UNRESOLVED /SUBSYSTEM:WINDOWS /SECTION:.text,ERW /MERGE:.rdata=.text /MERGE:_INIT_=.text /MERGE:_EXIT_=.text /ENTRY:Start$qqsuiuiui Hello.obj system.obj kernel32.lib user32.lib /out:Hello.exe


        1. ComradeAndrew
          26.01.2016 17:55

          /SECTION:.text,ERW
          Это что, устанавливает в секции все атрибуты? Ого.


          1. MacIn
            26.01.2016 18:48

            Угу, потом сливаются все секции в одну, изгоняется на мороз DOS stub и получается маленький файл.


  1. jacob1237
    26.01.2016 15:19

    Прошу прощения, перепутал секцию .code и .text)

    В общем понятно, компилятор-таки городит городули с JMP к новой секции.
    Просто технически ничего не мешает поменять адрес EntryPoint сразу на .exre, чтобы избежать лишних переходов.

    А сможете теперь заставить компилятор переходить сразу к выполнению .exre? Беру Вас на «слабо»! =)
    И у Вас там, похоже, еще и рантайм вкомпиливается, судя по вызовам __crtUnhandledException?


    1. ComradeAndrew
      26.01.2016 17:58

      Я думал об этом, ещё до того как написал этот «Hello, World!», но так и не осилили. На данный момент я плохо знаю структуру exe файла и тонкости компиляторов.
      И да, в том экзешнике куча рантайма и дебага.


      1. jacob1237
        26.01.2016 18:49
        +1

        Почитайте про структуру не exe файла, а формата PE в целом (Portable Executable). Что DLL, что EXE суть один и тот же формат.
        Ничего сложного нет, и кстати формат ELF в Linux схематично очень похож на PE, так что в любом случае будет полезно)

        Вот вроде бы неплохо структурированная информация: cs.usu.edu.ru/docs/pe
        Раньше это все было на wasm.ru, но сейчас у них так какие-то обновления происходят.


        1. ComradeAndrew
          26.01.2016 21:20

          Спасибо, вы кстати. Не так давно искал где бы понятнее и подробнее можно об этом почитать.


      1. tyomitch
        26.01.2016 21:04

        Так а ничего мудрёного же нет.

        В свойствах проекта, в дереве «Configuration Properties -> Linker -> Advanced», первый пункт «Entry Point» установить в «main».
        Всё.
        Теперь из проекта компилируется экзешник размером 2560 байт — без рантайма, без __tmainCRTStartup, без __crtUnhandledException, и даже без секции .text.

        Ещё можно отключить внедрение манифеста, тогда экзешник будет 1536 байт.
        И всё это — стандартными средствами Студии, безо всякого шаманства с заголовками PE :-)


        1. ComradeAndrew
          26.01.2016 21:19

          И правда ведь. Неужели я так криво пробовал настройки, что решил, что это не так работает. Спасибо, много полезного на хабре узнать можно.
          И еще, не подскажете ли такую вещь: в «Entry Point» можно любую точку входа вставить или только определенные? Где-то прочел, что линковщик ожидает определенные.


          1. mayorovp
            26.01.2016 21:43
            +1

            Любую. Определенные — это значения по умолчанию.


  1. tyomitch
    26.01.2016 16:19
    +3

    Ну-ка, братцы, а вот так работает?
    Без printf и без сисколов! Только массив и больше ничего!
    Ищет в памяти kernel32, и потом у него в таблице экспорта ищет WriteFile:

    #pragma section(".exre", execute, read)
    __declspec(allocate(".exre")) int main[] = {
    0x0a0d2168, 0x726f6800, 0x6f68646c, 0x6857202c,
    0x6c6c6548, 0x6a54006a, 0x0483540d, 0xd2330c24,
    0x30528b64, 0xff10428b, 0x8bfc1c70, 0x528b0c52,
    0x28728b14, 0x000018b9, 0x33ff3300, 0x613cacc0,
    0x202c027c, 0x030dcfc1, 0x8bf2e2f8, 0xff81105a,
    0x6a4abc5b, 0xd975128b, 0x8b555756, 0x6c8b3c53,
    0xeb037813, 0x8b184d8b, 0xfb03207d, 0x8b4933e3,
    0xf3038f34, 0xd233c033, 0x74c43aac, 0x0dcac107,
    0xf4ebd003, 0x791ffa81, 0xe075e80a, 0x0324558b,
    0x0c8b66d3, 0x1c558b4a, 0x048bd303, 0xebc3038a,
    0x5f5dcc01, 0x83d0ff5e, 0xc310c4};
    
    Проверял на Windows 7.


    1. MacIn
      26.01.2016 16:34

      По ординалу?


      1. tyomitch
        26.01.2016 17:52

        Нет, по имени.


        1. MacIn
          26.01.2016 18:47

          Просто лень в дизасм загонять — код короткий, вряд ли полноценно по имени ищет, или там упрощенное сравнение?


          1. grechnik
            26.01.2016 20:05
            +1

            Там сравнение хэша от имени с магической константой.


            1. tyomitch
              26.01.2016 21:07

              Да, так и есть.
              Причём код для побайтного сравнения имени функции был бы ещё короче: благо, repe cmpsb занимает всего два байта.


            1. MacIn
              26.01.2016 23:20

              Как раз о хэше и думал, стандартный ход.


              1. tyomitch
                27.01.2016 01:25
                +1

                Я на первопроходство и не претендовал; но длина кода тут совершенно ни при чём. Вот код с побайтовым сравнением, он даже на три байта короче вышел.

                #pragma section(".exre", execute, read)
                __declspec(allocate(".exre")) int main[] = {
                0x0a0d2168, 0x726f6800, 0x6f68646c, 0x6857202c,
                0x6c6c6548, 0x6a54006a, 0x0483540d, 0xd2330c24,
                0x30528b64, 0xff10428b, 0x8bfc1c70, 0x528b0c52,
                0x28728b14, 0x000018b9, 0x33ff3300, 0x613cacc0,
                0x202c027c, 0x030dcfc1, 0x8bf2e2f8, 0xff81105a,
                0x6a4abc5b, 0xd975128b, 0x6a555756, 0x46656865,
                0x57686c69, 0x8b746972, 0x6c8b3c53, 0xeb037813,
                0x8b18558b, 0xc3032045, 0x90348b4a, 0x0ab9f303,
                0x8b000000, 0x75a6f3fc, 0x24458bef, 0x8b66c303,
                0x458b500c, 0x8bc3031c, 0xc3039004, 0x5d0cc483,
                0xd0ff5e5f, 0xc310c483  };
                


                1. MacIn
                  27.01.2016 04:47

                  Просто хэш-функции разные бывают. Это может быть и CRC32, что солидно больше места занимает.


                  1. tyomitch
                    27.01.2016 12:19

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


                    1. MacIn
                      27.01.2016 17:16

                      Зачем? Мне безразлична длина вашего кода. Я просто высказал предположение, на глаз, что судя по длине, используется простая хэш-функция. А вы уже соревнование затеваете, будто я претензии к длине кода предъявляю :) Я вчера уже ужимал HW exeшник с обычными импортами до минимума, так что свое спортивное любопытство уже удовлетворил (меньше 1к). К тому же на Си я почти не пишу.
                      И, если честно, мне просто лень переводить ваш массив обратно в код, иначе бы я не спросил про способ поиска.


                      1. tyomitch
                        27.01.2016 17:29

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

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


                        1. MacIn
                          27.01.2016 17:44
                          +1

                          Нет-нет, мой ход мыслей был таков «для сложного хеша кода маловато, наверно что-то простое. Ну вряд ли же это просто сравнение». Т.е. обычное сравнение почти не рассматривалось. Я просто плохо подобрал слова в вопросе «код короткий, вряд ли полноценно по имени ищет, или там упрощенное сравнение?» здесь и имелось в виду, что вряд ли сравнение — это неспортивно, наверно короткая х.ф.

                          Кстати, сколько ваш итоговый бинарник весит?


                          1. tyomitch
                            27.01.2016 18:10

                            1536 байт после отключения рантайма и манифеста.
                            Если ещё и отладочную информацию отключить, остаётся ровно килобайт.
                            Дальше, думаю, встроенными средствами Студии не ужать, придётся ковыряться в бинарнике руками.


                          1. tyomitch
                            27.01.2016 18:17

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

                            (У меня система x64, на ней /FILEALIGN:16 не запускается.)

                            А у вас сколько байт было в итоге?


                            1. MacIn
                              27.01.2016 18:26

                              816 после линкреа без копания руками, но с выравниванием по 16. Можно ужать еще за счет заголовков.
                              Ага, 716 с одной секцией. Еще 256 dosstub а надо извлечь.


                              1. tyomitch
                                27.01.2016 19:56

                                Чтобы запускалось на x86, /FILEALIGN должен быть как минимум 512, т.е. все заголовки вместе взятые меньше чем 512 байт занимать не могут.

                                Активным «копанием руками в бинарнике» мне удалось уменьшить свой файл до 653 байт:

                                base64
                                TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAADdHerZmXyEipl8hIqZfISKhy4Niph8hIqHLhWKmHyEilJpY2iZfISKAAAAAAAAAABQRQAATAEBAPLwqFYAAAAAAAAAAOAAAgELAQkAAAAAAAACAAAAAAAAABAAAAAQAAAAEAAAAABAAAAQAAAAAgAABQAAAAAAAAAFAAAAAAAAAAAgAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5leHJlAAAAkAAAAAAQAACFAAAAAAIAAAAAAAAAAAAAAAAAAEAAAGBIZWxsbywgV29ybGQhDQoAV3JpdGVGaWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqAFRqD+gAAAAAgSwkOg4AADPSZItSMItCEP9wHP90JASDBCQQ/ItSDItSFItyKLkYAAAAM/8zwKw8YXwCLCDBzw0D+OLyi1oQgf9bvEpqixJ12YtTPItsE3gD64tVGItFIAPDSos0kAPzuQoAAACLPCTzpnXui0UkA8NmiwxQi0UcA8OLBJADw1v/0MM=


                                1. MacIn
                                  27.01.2016 20:15

                                  Чтобы запускалось на x86, /FILEALIGN должен быть как минимум 512, т.е. все заголовки вместе взятые меньше чем 512 байт занимать не могут.

                                  Минимальный FILEALIGN — 16. Я довел до 640 и бросил, после работы покопаюсь еще.


                                  1. tyomitch
                                    27.01.2016 20:43

                                    Да, извиняюсь, я имел в виду «Чтобы запускалось на x64, /FILEALIGN должен быть как минимум 512». Комментаторы выше это отмечали тоже.

                                    Я тем временем додавил свой экзешник до 365 байт, и он у меня по-прежнему запускается на x64 :-)

                                    Покажу потом, чтобы спортивный интерес не отбивать.


                                    1. MacIn
                                      27.01.2016 20:58

                                      365 с импортами? Куда далее идти и так понятно — надо схлопывать MZ, PE и Optional Header.


                                      1. tyomitch
                                        27.01.2016 21:02

                                        Без «настоящих» импортов — точно так же ищет в памяти нужные модуль и функцию, как раньше.


                                        1. MacIn
                                          27.01.2016 21:42

                                          А, ну я-то модуль с секцией импорта ужимаю. «Так» можно, конечно, еще меньше. Мне сильно мешает вкомпилированный код для SEH цепочки. Если дальше жать, это будет уже малость нечестно, потому что я покромсаю то, что создал компилятор.
                                          Сейчас у меня 640, если выкинуть «лишний» код для SEH, можно примерно 64 байта выиграть. Потом заголовки, но проблема в том, что для импорта нужен optional header.


                                          1. tyomitch
                                            27.01.2016 21:58

                                            Optional header, несмотря на название, обязателен в любом случае — например, точка входа записывается именно там.

                                            Но я в своём файле нисколько не стеснялся кромсать всё то, что создал компилятор :-)


                                            1. grechnik
                                              29.01.2016 13:48
                                              +1

                                              Теоретически минимальный размер файла, способного запускаться на x64, — 268 байт (4 + sizeof(IMAGE_NT_HEADERS64)). И если «не стесняться кромсать всё, что создал компилятор» и вообще делать PE-файл руками, то такой файл сделать можно: code.google.com/archive/p/corkami/wikis/PE.wiki, «universal tiny PE» (там есть ссылка «nightly builds», ведущая на dropbox, в котором есть архив pe.rar, в котором есть файл tiny.exe). Он выводит строку " * 268b universal tiny PE" вместо «Hello, World!».


                                              1. tyomitch
                                                29.01.2016 14:00

                                                Да, я уже нашёл этот пример.

                                                Я правильно понимаю, что он не запустится на системах с принудительным DEP?


                                                1. grechnik
                                                  29.01.2016 14:10
                                                  +1

                                                  Теоретически — должен. Если SectionAlignment < PageSize, то ядро маппит вообще всё, начиная с заголовка, как PAGE_EXECUTE_READWRITE. 32-битный загрузчик из WOW64-подсистемы потом пытается исправить атрибуты секций (из-за чего файлы, просто собранные с /align:16, и падают), но не трогает заголовок.
                                                  Практически — не проверял.


                                                  1. tyomitch
                                                    29.01.2016 15:42

                                                    Проверил. Работает. Поразительно.

                                                    Но с принудительным ASLR всё-таки валится.

                                                    — Ага-а-а! — с облегчением сказали суровые сибирские лесорубы, и ушли валить лес топорами.


                                                    1. grechnik
                                                      29.01.2016 16:32
                                                      +1

                                                      Ну включите флаг IMAGE_FILE_RELOCS_STRIPPED = 1 в IMAGE_FILE_HEADER.Characteristics (смещение +16h от начала PE-заголовка, 2 -> 3). Будет работать и с принудительным ASLR.


                                                      1. MacIn
                                                        29.01.2016 17:06

                                                        ХитрО. Действительно, если релоков нет, не поперемещаешь.


                                              1. MacIn
                                                29.01.2016 17:06

                                                Для 32 еще меньше, 133 байта.


                                                1. grechnik
                                                  29.01.2016 17:15
                                                  +1

                                                  Это для совсем старых систем, до какого-то сервиспака XP. На XP SP3 и позже уже минимум 252 байта (4 + sizeof(IMAGE_NT_HEADERS32)).


                                                  1. MacIn
                                                    29.01.2016 17:17

                                                    Нет, холостой 133 загружается без проблем. По крайней мере «not a valid win32 application» нет.


                                                    1. grechnik
                                                      29.01.2016 17:25

                                                      Как MZ-stub, ага. В Process Monitor посмотрите — ntvdm.exe там запускается.


                                                      1. MacIn
                                                        29.01.2016 17:26

                                                        Нет, PE — импорт kernel32 есть


                                                        1. grechnik
                                                          29.01.2016 17:28

                                                          ntvdm.exe тоже загружает kernel32.


                                                          1. MacIn
                                                            29.01.2016 17:37

                                                            Нет, нет. PE файл, прогнал через WinDbg.

                                                            start    end        module name
                                                            00400000 00400104   image00400000 C (no symbols)           
                                                            7c800000 7c8f6000   kernel32   (deferred)             
                                                            7c900000 7c9b2000   ntdll      (pdb symbols) 
                                                            

                                                            Это все модули, во время BP около точки входа.


                                                            1. grechnik
                                                              29.01.2016 17:40

                                                              > 00400000 00400104
                                                              Выглядит как 260 байт.


                                                              1. MacIn
                                                                29.01.2016 17:42

                                                                А файл — внезапно — 133.


                                                                1. grechnik
                                                                  29.01.2016 17:47

                                                                  На XP SP3?
                                                                  У меня на руках виртуалка с ntoskrnl.exe версии 5.1.2600.5512, в ней подобное не проходит.


                                                                  1. MacIn
                                                                    29.01.2016 17:53

                                                                    Ага, на нем, родимом, сижу. Файл вообще веселый — file§ion align — 4, размер OptionalHeader — 4, MZ тоже по сути только 4 байта. И оно работает.

                                                                    Хех, & sect преобразовалось в параграф.

                                                                    5.1.2600.6419


                                                    1. grechnik
                                                      29.01.2016 18:47

                                                      Забавно. Если файл есть в кэше данных — может быть короче 252 байт. Если нет (тот же самый файл на сетевом диске без дополнительных условий, или тот же самый файл на свежевставленной флешке либо после перезагрузки системы при прямой загрузке в WinDbg) — будет ntvdm.
                                                      В семёрке шизофрению пофиксили, там не грузится консистентно.


                                                      1. MacIn
                                                        29.01.2016 18:48

                                                        Сейчас попробую перезагрузить и сразу открыть в WinDBG, посмотрим.


                                                      1. MacIn
                                                        29.01.2016 19:07

                                                        И вправду, забавно. Но так или иначе, 133 загрузить можно B), пусть только в SP3.


    1. rprokop
      26.01.2016 16:58

      На XP, кстати экзешник не запускается, если ничего не импортируешь из kernel32.dll (прямо или через другие dll).


    1. ComradeAndrew
      26.01.2016 19:05

      Круто! Работает на win10. А вы сразу в ассемблере писали или на си код есть?


      1. tyomitch
        26.01.2016 19:22
        +1

        Сразу в ассемблере; но большую часть кода не писал сам, а утянул из гуляющих по интернету листингов, которые легко гуглятся по использованным в них «магическим значениям».


        1. ComradeAndrew
          26.01.2016 19:25

          Понял, спасибо.


  1. MrYuran
    26.01.2016 16:43
    -3

    А во времена DOS труЪ-программеры делали так:
    copy con program.exe