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>

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