«Hello World!» на C массивом int main[]

Я хотел бы рассказать о том, как я писал реализацию «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 и использовать другой код вызова функции, но я не успел исследовать этот вопрос.

Буду рад вашим отзывам и замечаниям.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

    К вашей статье можно отсылать людей, которые думают, что на современных компьютерах больше нельзя программировать в кодах :)
    • +4
      Вместо заголовочного файла можно было бы просто объявить прототип функции printf.
      • +3
        Да, вы правы. Об этом я не думал. Посмотрим, если наберется достаточно много интересных поправок, добавлю к статье модифицированный вариант.
        • +1
          «int printf(char*f,...);» всё равно выходит длиннее "#include <stdio.h>" :)
          • +2
            Хотя можно без int, линкер сожрёт, но с варнингом. Получится на 1 (!) символ короче.
            • +1
              А у меня даже варнинга не выдает. Если ставить пробел после #include, как все нормальные люди, то получится одинаковая длина.
              printf(char*,...);
              #include <stdio.h>
              

              Варнинг у меня только один. На &ptrprintf. Я хотел добавить макрос с кастом к int, чтобы оставить ту же длину и однородный цвет в таблице массива, но решил оставить как есть.
              • +3
                (ирония)
                А если ставить пробел после запятой, как все нормальные люди, то всё равно получится на один символ больше.
                • +2
                  Тут не поспоришь :)
                  Но мне кажется, что вариант с прототипом вполне неплохой и поддерживает стиль остального кода.
                  • +6
                    Зачем вам вообще что-то в скобках? Это же C, не C++!

                    «int printf();» — законное описание функции, которая принимает «что-то там, неважно что», возврщает int, большего вам и не нужно…
                    • +2
                      Да, действительно. На тулсете 2013 студии компилирует. Однако при компиляции тулсетом v140(visual studio 2015) она кидает error:
                      error LNK2001: unresolved external symbol _printf
                      • 0
                        Скорее всего как-то манглится имя. Посмотрите в обычном «Hello, world!» чего хочет ваш объектник…
          • –7
            А еще можно сразу вызывать какойнить 'system' и одним махом столько разных программ «на Си написать» :)
            • +3
              В Windows тоже можно использовать сисколы.
              Дпугое дело, что они version-specific.
              Конкретно для вывода на консоль можно использовать NtWriteFile.
              • +1
                NtWriteFile в поток вывода? Интересно. Надо попробовать как по длине кода получится.
                • +1
                  Плохо по длине кода получится. Потому что дескриптор потока вывода еще получить надо.
                  • +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
                    


                    Пример создаст экзешник 2,5KB размером…
                    nasm hw.asm -f win32 -o hw.obj && link hw.obj /out:hw.exe /entry:main /defaultlib:kernel32
                    

                    для ml надо немного переделать (ну не люблю я его… и не пользуюсь)
                    • 0
                      Можно меньше, если выравнивание секций поменять, если я не путаю.
                      • 0
                        Если есть mingw (или cygwin), то "strip --strip-all hw.exe" оставит от экзешника 2KB
                        • 0
                          Туда еще wipedosstub, наверно можно ужать сильнее. + минимальное выравние секции в файле 64, емнип.
                      • 0
                        Что-то много. 1 КБ, не трогая выравнивание, на FASM-e.
                    • +5
                      Для консоли это не будет работать, у консоли вообще нет ядерного хэндла. WriteFile, обнаружив kernel32-хэндл консоли, вызывает WriteConsole, WriteConsole работает в конечном счёте через RPC к модулю winsrv.dll в процессе csrss. (В частности, из-за такой схемы в XP консольные окна не подхватывали темы оформления — грузить абы что в системный процесс csrss MS не хотела. В Vista для этого в цепочку добавили ещё один процесс conhost.exe, вызываемый из csrss).
                      • +1
                        Спасибо за ответ по консоли, мучавший меня так долго…
                    • +14
                      Ненормальное программирование!
                      • +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
                          Честно говоря совсем не понял для чего столько пушей и что в итоге получается. Можете немного подробнее объяснить как это работает?
                          • 0
                            Много пушей, поскольку параметров у функции много. Правда многие равны 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
                            );
                            

                            • –3
                              mark_ablov решил выпендрится и переписал вашу реализацию hello world включив в неё функциональность вывода на консоль базонезависимым кодом. Однако реализация эта будет работать скорее всего только на некоторых системах 8.1 x64
                              • 0
                                В принципе, туда еще небольшой шажок — поиск номера функции в теле NtWriteFile и будет веселее.
                                • 0
                                  Мне кажется лучше было бы просто узнать адрес NtWriteFile через PEB_LDR_DATA и вызвать ф-ю напрямую, потому что зная номер сервиса нам ещё нужно как-то попасть в ядро, а в разных разрядностях это реализовано по-разному
                                  • +1
                                    Что разрядности — в разных билдах номер может отличаться. Да, и как выяснилось ниже, NtWriteFile не катит — он не работает с псевдо-handle'ами. Надо через LDR_DATA вытаскивать WriteFile или сразу WriteConsole.
                                    • 0
                                      Дело не в номерах, а в особенностях механизмов входа в ядро для x86 и x64(WOW64). Во-первых они отличаются, во-вторых если мне не изменяет память для x64 необходимо класть параметры с учётом того что они будут обрабатываться 64-битным кодом.

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

                                      • 0
                                        Мы так и так компилируем 32 разрядный код в примере выше + стандартная точка входа из TEB. А вот номера сервисов отличаются от SP к SP, например.
                                        • 0
                                          call fs:[0C0h] в Windows Vista и выше не будет работать для x86, там используется SharedUserData->SystemCallStub
                              • 0
                                del
                              • 0
                                И что, правда работает с выводом на консоль (если не перенаправлять вывод в файл)?
                                • 0
                                  Отчего нет — по 1Ch лежит HANDLE StdOutput.
                                  • 0
                                    Хэндлы бывают разные. Вы пробовали запустить код от 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
                                      Это псевдо-хэндл. У меня на ХР выдает 7 (кстати, GetStdHandle делает именно это — роется в TEB/PEB/EPROCESS_PARAMETERS), и это рабочий handle, по крайней мере WriteFile его принимает (Nt(Zw)WriteFile не пробовал).

                                      upd: понятно, WriteFile обрабатывает это особо, а ntdll не примет. Тогда да, вы правы.
                                      • +1
                                        Рабочий для NtWriteFile? Проверяли? Что он рабочий для WriteFile, сомнений нет.
                                        • +1
                                          Да, я уже посмотрел листинг — kernel32 перенаправляет это в WriteConsoleA.
                                      • 0
                                        Вполне возможно, да.
                                        Проверял из ms vc для простоты. Тогда можно RPC слать серверу консолей. Что малость сложнее, но тоже ничего невозможного.
                                    • +2
                                      • +1
                                        Хм, интересно. Win8.1, говорите?
                                        На Win7 ожидаемо фейлится с STATUS_OBJECT_TYPE_MISMATCH:
                                        Картинка


                                        Восьмёрки под рукой нет. Посмотрел в 32-битной Win10, и там таки действительно консольный хэндл — настоящий ядерный хэндл на объект \Device\ConDrv, WriteFile не делает специальных проверок и просто вызывает NtWriteFile, а GetConsoleMode и прочие реализованы через NtDeviceIoControlFile вместо LPC к winsrv.dll (XP-)/conhost.exe (Vista+).

                                        Значит, MS таки глобально переписала консоль в районе восьмёрки. Тогда да, ваш код на восьмёрке и десятке работать будет. (Априори мне версия со столь существенными изменениями от MS показалась менее вероятной, чем версия, что вы просто не проверили код. Приношу свои извинения.) Интересно, зачем… Гугль первым результатом по \Device\ConDrv выдаёт статью с описанием обхода песочницы Хрома через него.
                                        • 0
                                          > WriteFile не делает специальных проверок и просто вызывает NtWriteFile
                                          Угу, я когда смотрел как она преобразовывает STD_OUTPUT_HANDLER не видел никаких вызовов WriteConsole, а просто обращение к данным PEBа.
                                  • +13
                                    Чрезвычайно простой и практичный подход к написанию ПО. Ждём более сложные примеры.
                                    • +2
                                      Если правда интересно, то постараюсь придумать что-то похожее. Пока времени мало, но как сдам сессию наверняка вернусь к этому. Я даже не ожидал, что будет такой интерес :)
                                      • +1
                                        По-моему это был сарказм :) Но спасибо за статью, было интересно.
                                    • +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' );
                                      }
                                      
                                      • +3
                                        Когда вижу подобный код в голове проскакивает непременное «За изобретение ставлю пять, а… по предмету — неуд». Это все очень хорошо и интересно, но практическое применение этого очень редко оправдано. Нисколько вас не упрекаю, если могло так показаться.
                                        • +6
                                          Я абсолютно с вами согласен. Это исключительный интерес. И в первую очередь проба себя в низкоуровневых областях программирования, если можно так выразиться.
                                          • +5
                                            Вместо хаба «Visual Studio» тогда уж «Ненормальное программирование».
                                          • 0
                                            Всё от задачи зависит.
                                            Если вы пишете hello world — это одно. А если антивирус — всё уже гораздо ближе к ненормальному.

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

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

                                            Так что не всякое «ненормальное программирование» даёт хрупкий и непредсказуемый результат.
                                          • +2
                                            За изобретение ставлю пять, а… по предмету — неуд

                                            Тот самый троллейбус.jpg =) (Против статьи ничего не имею, если что)
                                            • +1
                                              На самом деле неплохой прием для обфускации кода.
                                              • +8
                                                Ну разве что исходного. Дизассеблер будет выглядеть как код составленный заботливым разработчиком (:
                                                • +1
                                                  Против дизассемблирования есть другие приемы) Данный метод обфускации хорош при работе с недобросовестным заказчиком, если вдруг по договору исходный код должен передаваться. Обычно такие дяди имеют на борту студента, который может собрать софтину и проанализировать код на закладки типа «Демонстрационная Версия». Конечно подобные договора не стоит заключать, но иногда выхода нет (начальство недальновидное, принципиальная необходимость выполнить эти работы, госзаказ и т.д. и т.п.).
                                                  • +4
                                                    Конечно подобные договора не стоит заключать, но иногда выхода нет
                                                    Либо я ничего не понимаю, либо одно из двух.

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

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

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

                                                        P.S. Я, разумеется, говорю про IT компании и случай когда вас же поставщик может решить обойтись без вас в качестве посредника. Конечно переписывать системы складского учёта я не предлагаю (представить себе что фирма по внедрению 1С начнёт вдруг «грузить апельсины бочками» достаточно сложно).
                                                        • +1
                                                          Когда ваш исходный код окажется в третьей конторе, и она волшебным образом получит этот проект на исполнение после разрыва договора с вами… Я всего лишь предположил, что в случаях крайней необходимости можно этим пользоваться. Ведь проведение подобной обфускации крайне затратное дело. Лично я считаю, что с мутными товарищами, что заказчиками, что подрядчиками, лучше дел не иметь. Но директорату, как говорится, виднее. Бывает ещё так, что заказчик навязывает вам субподрядчика, который ничего не делает, но, внезапно, должен иметь доступ к исходному коду.
                                              • +2
                                                Практические знания подобного рода становятся очень сильно востребованными, когда, например, поднимаешь тулчейн для новой embedded железки: писать так не нужно, но понимать, что происходит под капотом — это просто необходимо. Особенно когда за байты война начинается.
                                              • 0
                                                На мой взгляд, экзешник большой выходит. С /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)
                                                
                                                • 0
                                                  #pragma comment(linker, "/FILEALIGN:16")
                                                  #pragma comment(linker, "/ALIGN:16")// Merge sections
                                                  

                                                  … и получить файл, не работающий на x64.
                                                  • 0
                                                    Ну так это ж «ненормальное программирование» :) Я ж не говорю о том, что исходник и так на других ОС, компиляторах и архитектурах не работает
                                                    • 0
                                                      Интересно почему не рабочий?
                                                      • +1
                                                        А это у MS спрашивать надо. Если 32-битный бинарник собран с выравниванием меньше размера страницы (и некоторыми дополнительными ограничениями), то 32-битная система его загрузит, а 64-битная — нет.
                                                        • 0
                                                          Просто насколько я знаю минимально допустимое выравнивание для PE это file:0x200, virtual:0x1000, у вас есть пруф бинарника с более низким выравниванием?
                                                          • +1
                                                            Возьмите любой драйвер.
                                                            Если настаиваете именно на exe-шнике — вот банальный MessageBox с FileAlignment = SectionAlignment = 0x20: yadi.sk/d/385e5Lhqnkybi. На 32-битной XP точно работает.
                                                            • 0
                                                              Действительно робит (проверено на w7x86), спасибо
                                                            • 0
                                                              Конечно, 16/16 отлично работает на XP, только вчера проверял.
                                                    • 0
                                                      Стесняюсь спросить, а всякие прерывания INT 21h и более системные (15 что-ли) для вывода символов на терминал уже не работают? Раньше можно покороче сделать было байт-код.
                                                      • 0
                                                        Раньше можно покороче сделать было байт-код.

                                                        Где это работало?
                                                        На сколько я знаю в windows прерывания заблокированы начиная с windows 2000.
                                                        • +3
                                                          Не совсем. Вплоть до XP SP2(?) syscall идет через int 2Eh…
                                                          но мы понимаем, что вопрос выше чуть о другом ;)
                                                      • 0
                                                        Если вы соберете под MS-DOS чистый MZ exeшник, то будет работать, и то не во всех системах.
                                                      • 0
                                                        Я тне пргограммист, поэтому спрошу прямо — во что дизассемблер разберет сгенерированный из этой рпограммы машинный код?
                                                        • 0
                                                          Собственно это указано ниже «Компилируем и смотрим дизассемблер.»
                                                          Ну, плюс секция импорта и т.п.
                                                          • 0
                                                            Ой, а я почему-то думал, что дизассемблер в фугкции собирает, типа программу на си выдает, которая компилируется в такой же ассемблерный код
                                                            Я ассемблер не знаю, собственно вопрос в том — какой «безхитростный» код даст аналогичный ассемблерный листинг? С нормальным мейном-функцией, принтэфом и строкой в параметре или что-то другое?
                                                            Т.е. вопрос собственно в том, с чем автор в итоге поигрался — только с кодом или с кодом и ассемблером или с конечной программой тоже?
                                                            • +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
                                                              
                                                              
                                                          • +2
                                                            Классная опечатка, я теперь тоже буду «the программистом»
                                                            • 0
                                                              В этом комментарии я показал во что превращается main. В то же самое, что и вставили в массив, компилятор никак не изменил этот код, потому что для него это были данные, а не код.
                                                            • +2
                                                              Как собрать рабочий экзешник на Visual Studio вообще без исходника:
                                                              link /OUT:dummy.exe /SUBSYSTEM:CONSOLE /ENTRY:ExitProcess@4 kernel32.lib

                                                              http://rsdn.ru/forum/humour/3748806.all
                                                              • 0
                                                                Автор, а куда в исполняемом файле компилятор в итоге втыкает EntryPoint — в Вашу секцию .exre? Или он в итоге в .code подставляет переход (jmp) к .exre?
                                                                • +1
                                                                  Точно так же, как если бы main был функцией. main является EntryPoint по умолчанию.
                                                                  Вызывается
                                                                  mainCRTStartup

                                                                  Из него вызывается
                                                                  __tmainCRTStartup

                                                                  Затем сам
                                                                  main

                                                                  А main лежит в секции
                                                                  .exre

                                                                  И как видно по этому адресу лежит непосредственно наш
                                                                  main
                                                                  • +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
                                                                    • 0
                                                                      /SECTION:.text,ERW
                                                                      Это что, устанавливает в секции все атрибуты? Ого.
                                                                      • 0
                                                                        Угу, потом сливаются все секции в одну, изгоняется на мороз DOS stub и получается маленький файл.
                                                                • 0
                                                                  Прошу прощения, перепутал секцию .code и .text)

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

                                                                  А сможете теперь заставить компилятор переходить сразу к выполнению .exre? Беру Вас на «слабо»! =)
                                                                  И у Вас там, похоже, еще и рантайм вкомпиливается, судя по вызовам __crtUnhandledException?
                                                                  • 0
                                                                    Я думал об этом, ещё до того как написал этот «Hello, World!», но так и не осилили. На данный момент я плохо знаю структуру exe файла и тонкости компиляторов.
                                                                    И да, в том экзешнике куча рантайма и дебага.
                                                                    • +1
                                                                      Почитайте про структуру не exe файла, а формата PE в целом (Portable Executable). Что DLL, что EXE суть один и тот же формат.
                                                                      Ничего сложного нет, и кстати формат ELF в Linux схематично очень похож на PE, так что в любом случае будет полезно)

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

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

                                                                        Ещё можно отключить внедрение манифеста, тогда экзешник будет 1536 байт.
                                                                        И всё это — стандартными средствами Студии, безо всякого шаманства с заголовками PE :-)
                                                                        • 0
                                                                          И правда ведь. Неужели я так криво пробовал настройки, что решил, что это не так работает. Спасибо, много полезного на хабре узнать можно.
                                                                          И еще, не подскажете ли такую вещь: в «Entry Point» можно любую точку входа вставить или только определенные? Где-то прочел, что линковщик ожидает определенные.
                                                                          • +1
                                                                            Любую. Определенные — это значения по умолчанию.
                                                                    • +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.
                                                                      • 0
                                                                        По ординалу?
                                                                        • 0
                                                                          Нет, по имени.
                                                                          • 0
                                                                            Просто лень в дизасм загонять — код короткий, вряд ли полноценно по имени ищет, или там упрощенное сравнение?
                                                                            • +1
                                                                              Там сравнение хэша от имени с магической константой.
                                                                              • 0
                                                                                Да, так и есть.
                                                                                Причём код для побайтного сравнения имени функции был бы ещё короче: благо, repe cmpsb занимает всего два байта.
                                                                                • 0
                                                                                  Как раз о хэше и думал, стандартный ход.
                                                                                  • +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  };
                                                                                    
                                                                                    • 0
                                                                                      Просто хэш-функции разные бывают. Это может быть и CRC32, что солидно больше места занимает.
                                                                                      • 0
                                                                                        Предлагаю вам взять в руки Студию и реализовать тот же самый код ещё короче, с использованием любой хэш-функции по вашему вкусу :-)
                                                                                        • 0
                                                                                          Зачем? Мне безразлична длина вашего кода. Я просто высказал предположение, на глаз, что судя по длине, используется простая хэш-функция. А вы уже соревнование затеваете, будто я претензии к длине кода предъявляю :) Я вчера уже ужимал HW exeшник с обычными импортами до минимума, так что свое спортивное любопытство уже удовлетворил (меньше 1к). К тому же на Си я почти не пишу.
                                                                                          И, если честно, мне просто лень переводить ваш массив обратно в код, иначе бы я не спросил про способ поиска.
                                                                                          • 0
                                                                                            Я просто высказал предположение, на глаз, что судя по длине, используется простая хэш-функция.

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

                                                                                              Кстати, сколько ваш итоговый бинарник весит?
                                                                                              • 0
                                                                                                1536 байт после отключения рантайма и манифеста.
                                                                                                Если ещё и отладочную информацию отключить, остаётся ровно килобайт.
                                                                                                Дальше, думаю, встроенными средствами Студии не ужать, придётся ковыряться в бинарнике руками.
                                                                                                • 0
                                                                                                  Ну собственно, оказалось достаточно стереть все нули из конца файла и поменять SizeOfRawData в заголовке секции, чтобы остался работоспособный экзешник в 680 байт.

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

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

                                                                                                      Активным «копанием руками в бинарнике» мне удалось уменьшить свой файл до 653 байт:
                                                                                                      base64
                                                                                                      TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAADdHerZmXyEipl8hIqZfISKhy4Niph8hIqHLhWKmHyEilJpY2iZfISKAAAAAAAAAABQRQAATAEBAPLwqFYAAAAAAAAAAOAAAgELAQkAAAAAAAACAAAAAAAAABAAAAAQAAAAEAAAAABAAAAQAAAAAgAABQAAAAAAAAAFAAAAAAAAAAAgAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5leHJlAAAAkAAAAAAQAACFAAAAAAIAAAAAAAAAAAAAAAAAAEAAAGBIZWxsbywgV29ybGQhDQoAV3JpdGVGaWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqAFRqD+gAAAAAgSwkOg4AADPSZItSMItCEP9wHP90JASDBCQQ/ItSDItSFItyKLkYAAAAM/8zwKw8YXwCLCDBzw0D+OLyi1oQgf9bvEpqixJ12YtTPItsE3gD64tVGItFIAPDSos0kAPzuQoAAACLPCTzpnXui0UkA8NmiwxQi0UcA8OLBJADw1v/0MM=

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

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

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

                                                                                                          Покажу потом, чтобы спортивный интерес не отбивать.
                                                                                                          • 0
                                                                                                            365 с импортами? Куда далее идти и так понятно — надо схлопывать MZ, PE и Optional Header.
                                                                                                            • 0
                                                                                                              Без «настоящих» импортов — точно так же ищет в памяти нужные модуль и функцию, как раньше.
                                                                                                              • 0
                                                                                                                А, ну я-то модуль с секцией импорта ужимаю. «Так» можно, конечно, еще меньше. Мне сильно мешает вкомпилированный код для SEH цепочки. Если дальше жать, это будет уже малость нечестно, потому что я покромсаю то, что создал компилятор.
                                                                                                                Сейчас у меня 640, если выкинуть «лишний» код для SEH, можно примерно 64 байта выиграть. Потом заголовки, но проблема в том, что для импорта нужен optional header.
                                                                                                                • 0
                                                                                                                  Optional header, несмотря на название, обязателен в любом случае — например, точка входа записывается именно там.

                                                                                                                  Но я в своём файле нисколько не стеснялся кромсать всё то, что создал компилятор :-)
                                                                                                                  • +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!».
                                                                                                                    • 0
                                                                                                                      Да, я уже нашёл этот пример.

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

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

                                                                                                                          — Ага-а-а! — с облегчением сказали суровые сибирские лесорубы, и ушли валить лес топорами.
                                                                                                                          • +1
                                                                                                                            Ну включите флаг IMAGE_FILE_RELOCS_STRIPPED = 1 в IMAGE_FILE_HEADER.Characteristics (смещение +16h от начала PE-заголовка, 2 -> 3). Будет работать и с принудительным ASLR.
                                                                                                                            • 0
                                                                                                                              ХитрО. Действительно, если релоков нет, не поперемещаешь.
                                                                                                                      • 0
                                                                                                                        Для 32 еще меньше, 133 байта.
                                                                                                                        • +1
                                                                                                                          Это для совсем старых систем, до какого-то сервиспака XP. На XP SP3 и позже уже минимум 252 байта (4 + sizeof(IMAGE_NT_HEADERS32)).
                                                                                                                          • 0
                                                                                                                            Нет, холостой 133 загружается без проблем. По крайней мере «not a valid win32 application» нет.
                                                                                                                            • 0
                                                                                                                              Как MZ-stub, ага. В Process Monitor посмотрите — ntvdm.exe там запускается.
                                                                                                                              • 0
                                                                                                                                Нет, PE — импорт kernel32 есть
                                                                                                                                • 0
                                                                                                                                  ntvdm.exe тоже загружает kernel32.
                                                                                                                                  • 0
                                                                                                                                    Нет, нет. PE файл, прогнал через WinDbg.
                                                                                                                                    start    end        module name
                                                                                                                                    00400000 00400104   image00400000 C (no symbols)           
                                                                                                                                    7c800000 7c8f6000   kernel32   (deferred)             
                                                                                                                                    7c900000 7c9b2000   ntdll      (pdb symbols) 
                                                                                                                                    

                                                                                                                                    Это все модули, во время BP около точки входа.
                                                                                                                                    • 0
                                                                                                                                      > 00400000 00400104
                                                                                                                                      Выглядит как 260 байт.
                                                                                                                                      • 0
                                                                                                                                        А файл — внезапно — 133.
                                                                                                                                        • 0
                                                                                                                                          На XP SP3?
                                                                                                                                          У меня на руках виртуалка с ntoskrnl.exe версии 5.1.2600.5512, в ней подобное не проходит.
                                                                                                                                          • 0
                                                                                                                                            Ага, на нем, родимом, сижу. Файл вообще веселый — file§ion align — 4, размер OptionalHeader — 4, MZ тоже по сути только 4 байта. И оно работает.

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

                                                                                                                                            5.1.2600.6419
                                                                                                                              • 0
                                                                                                                                Забавно. Если файл есть в кэше данных — может быть короче 252 байт. Если нет (тот же самый файл на сетевом диске без дополнительных условий, или тот же самый файл на свежевставленной флешке либо после перезагрузки системы при прямой загрузке в WinDbg) — будет ntvdm.
                                                                                                                                В семёрке шизофрению пофиксили, там не грузится консистентно.
                                                                                                                                • 0
                                                                                                                                  Сейчас попробую перезагрузить и сразу открыть в WinDBG, посмотрим.
                                                                                                                                  • 0
                                                                                                                                    И вправду, забавно. Но так или иначе, 133 загрузить можно B), пусть только в SP3.
                                                                                  • 0
                                                                                    На XP, кстати экзешник не запускается, если ничего не импортируешь из kernel32.dll (прямо или через другие dll).
                                                                                    • 0
                                                                                      Круто! Работает на win10. А вы сразу в ассемблере писали или на си код есть?
                                                                                      • +1
                                                                                        Сразу в ассемблере; но большую часть кода не писал сам, а утянул из гуляющих по интернету листингов, которые легко гуглятся по использованным в них «магическим значениям».
                                                                                  • –3
                                                                                    А во времена DOS труЪ-программеры делали так:
                                                                                    copy con program.exe

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