International Obfuscated C Code Contest IOCCC — это соревнование, целью которого является создание самой малоизвестной программы на языке C. В 1986 году Джим Хейг выиграл конкурс со своим (почти) неразборчивым шифратором Морзе. Давайте посмотрим на его код и попытаемся понять его шаг за шагом.
#define DIT ( #define DAH ) #define __DAH ++ #define DITDAH * #define DAHDIT for #define DIT_DAH malloc #define DAH_DIT gets #define _DAHDIT char _DAHDIT _DAH_[]="ETIANMSURWDKGOHVFaLaPJBXCYZQb54a3d2f16g7c8a90l?e'b.s;i,d:" ;main DIT DAH{_DAHDIT DITDAH _DIT,DITDAH DAH_,DITDAH DIT_, DITDAH _DIT_,DITDAH DIT_DAH DIT DAH,DITDAH DAH_DIT DIT DAH;DAHDIT DIT _DIT=DIT_DAH DIT 81 DAH,DIT_=_DIT __DAH;_DIT==DAH_DIT DIT _DIT DAH;__DIT DIT'\n'DAH DAH DAHDIT DIT DAH_=_DIT;DITDAH DAH_;__DIT DIT DITDAH _DIT_?_DAH DIT DITDAH DIT_ DAH:'?'DAH,__DIT DIT' 'DAH,DAH_ __DAH DAH DAHDIT DIT DITDAH DIT_=2,_DIT_=_DAH_; DITDAH _DIT_&&DIT DITDAH _DIT_!=DIT DITDAH DAH_>='a'? DITDAH DAH_&223:DITDAH DAH_ DAH DAH; DIT DITDAH DIT_ DAH __DAH,_DIT_ __DAH DAH DITDAH DIT_+= DIT DITDAH _DIT_>='a'? DITDAH _DIT_-'a':0 DAH;}_DAH DIT DIT_ DAH{ __DIT DIT DIT_>3?_DAH DIT DIT_>>1 DAH:'\0'DAH;return DIT_&1?'-':'.';}__DIT DIT DIT_ DAH _DAHDIT DIT_;{DIT void DAH write DIT 1,&DIT_,1 DAH;}
Страшно не правда ли? И все же такая конкуренция для программистов…
При компиляции этой программы с помощью gcc мы сталкиваемся с множеством предупреждений и ошибок, в основном о необъявленных переменных и неявных объявлениях. Давайте посмотрим повнимательнее и попробуем исправить этот код и сделать его намного более понятным.
Макросы
Один из очевидных моментов в этом коде — это присутствие повсюду макросов, что делает его беспорядочным и нечитаемым, поэтому, прежде всего, нам нужно заменить их на их значения, чтобы сделать его читабельным.
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #define DIT ( #define DAH ) #define __DAH ++ #define DITDAH * #define DAHDIT for #define DIT_DAH malloc #define DAH_DIT gets #define _DAHDIT char char _morse[]=”ETIANMSURWDKGOHVFaLaPJBXCYZQb54a3d2f16g7c8a90l?e’b.s;i,d:”; char _tr(int c); int _putchar(char c); /* MAIN FUNCTION */ int main(void) { char *string, *c, *next, *morsecpy, *gets(char *); for (string = malloc(81), next = string++; gets(string); _putchar(‘\n’)) { for (c = string; *c; _putchar(*morsecpy ? _tr(*next) : ‘?’), _putchar(‘ ‘), c++) { for (*next = 2, morsecpy = _morse; *morsecpy && (*morsecpy != (*c >= ‘a’ ? *c & 223 : *c)); (*next)++, morsecpy++) { if (*morsecpy >= ‘a’) *next += *morsecpy — ‘a’; else *next += 0; } } } return (0); } /* TRANSLATE FUNCTION */ char _tr(int c) { if (c > 3) _putchar(_tr(c >> 1)); else _putchar(‘\0’); if (c & 1) return (‘-’); else return (‘.’); } /* PUTCHAR FUNCTION */ int _putchar(char c) { return (write(1 , &c , 1)); }
Это моя деобфусцированная версия кода. Теперь давайте объясним каждый шаг, который привел к этому коду.
1- Отступ кода:
После замены макросов на их значения и для того, чтобы лучше понять логику кода, который у нас есть, я решил избавиться от ненужных пробелов и сделать отступ кода, когда это необходимо.
Кроме того, я добавил несколько комментариев к коду, чтобы он был понятнее и проще для читателя.
2- Замена имен функций и переменных:
Мы видим в коде, что имена переменных и функций неудобны для чтения (DIT, DAH…), поэтому я решил изменить их для большей ясности.
- _DIT станет строкой
- DAH_ станет c
- DIT_ станет следующим
- _DAH_[] станет _морзе
- _DIT_ станет morsecpy
PS: благодаря макросу _DAHDIT мы знаем, что все эти переменные имеют тип char *.
Проанализировав функции, я понял, что __DIT() делает то же самое, что и функция putchar() из стандартной библиотеки. Поэтому я назвал ее _putchar(). Затем я изменил название функции _DAH() на _tr().
Шаг 3: Добавление стандартных библиотек
Раньше у нас не было стандартных библиотек, включенных в наш файл, и мы использовали функции, определенные в этих библиотеках, такие как adgets() или malloc(). Компилятору это очень не понравилось, поэтому, чтобы он скомпилировался, я включил их в начало файла:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h>
В процессе разработки программного обеспечения написание кода является сложной частью. Если вы не организуете все с самого начала — особенно для больших проектов — процессы кодирования и последующее управление кодом могут оказаться не только трудоемкими, но и большой головной болью.