#опытным
Ну что. Пора раскрывать карты.
Правильных ответов здесь несколько(не зря был квиз с множественным ответом) и все зависит от версии компилятора и опций компиляции. Интереснее всего смотреть, что получится при использовании например gcc9 без опций.
А получится вот что. Программа успешно скомпилируется и на консоли появится сообщение:
You are the best!
Да, да. Это она про вас, наши подписчики.
Дело вот в чем. Есть такой варнинг в gcc: warning: 'main' is usually a function [-Wmain].
Погодите, main "обычно" является функцией. Это что, может быть не так?
В С нет или не было прям жесткого требования на тип символа main. Он в целом может быть и массивом:
char main[10];
хоть указателем на функцию:
void (main)();
И вот здесь начинается пространство для экспериментов. Адрес main - это место, с которого код начинается исполняться. Если мы каким-то образом запихаем инструкции ассемблера в массив, то мы сможем выполнить код в такой программе!
Ну а дальше дело техники. Пишем прокси программу:
void main() {
__asm__ (
// print You are the best!
"movl $1, %eax;\n" /* 1 is the syscall number for write */
"movl $1, %ebx;\n" /* 1 is stdout and is the first argument */
// "movl $message, %esi;\n" /* load the address of string into the second argument*/
// instead use this to load the address of the string
// as 16 bytes from the current instruction
"leal 16(%eip), %esi;\n"
"movl $18, %edx;\n" /* third argument is the length of the string to print*/
"syscall;\n"
// call exit (so it doesn't try to run the string Hello World
// maybe I could have just used ret instead
"movl $60,%eax;\n"
"xorl %ebx,%ebx; \n"
"syscall;\n"
// Store the You are the best! inside the main function
"message: .ascii \"You are the best!\\n\";"
);
}Компилируем ее и запускаем gdb для дизассемблирования main:
(gdb) disass main
Dump of assembler code for function main:
0x0000000000001129 <+0>: endbr64
0x000000000000112d <+4>: push %rbp
0x000000000000112e <+5>: mov %rsp,%rbp
0x0000000000001131 <+8>: mov $0x1,%eax
0x0000000000001136 <+13>: mov $0x1,%ebx
0x000000000000113b <+18>: lea 0x10(%eip),%esi # 0x1152 <main+41>
0x0000000000001142 <+25>: mov $0x12,%edx
0x0000000000001147 <+30>: syscall
0x0000000000001149 <+32>: mov $0x3c,%eax
0x000000000000114e <+37>: xor %ebx,%ebx
0x0000000000001150 <+39>: syscall
0x0000000000001152 <+41>: pop %rcx
0x0000000000001153 <+42>: outsl %ds:(%rsi),(%dx)
0x0000000000001154 <+43>: jne 0x1176 <__libc_csu_init+6>
...
Ну и пожалуйста. Слева четко видим шестнадцатиричные числа, которые и представляют собой тело функции main.
Единственное, что осталось - вывести эти числа в десятиричной форме по 4 байта, как инты:
(gdb) x/16dw main
0x1129 <main>: -98693133 -443987883 440 113408
0x1139 <main+16>: -1922629632 4149 1227264 84869120
0x1149 <main+32>: 15544 266023168 1970231557 1701994784
0x1159 <main+48>: 1701344288 1936024096 -1878384268 258392925
Делаем из этих чиселок массив и готово! Закодированная программа будет выполняться.
В более новых версиях компиляторов эту лавочку прикрыли, потому что на gcc10 и более такая прога сегфолтится.
Но в любом случае, очень прикольно, что есть такая возможность.
Можете поиграться с кодом на годболте. Также можно почитать статью, в которой автор подробно расписал историю исследования возможности так писать код.
Благодарю @PyXiion за предоставление материалов для этого поста.
Be amazed. Stay cool.
#fun #OS