┌──┌─┐┌──
──┘├─┘──┘ Presents
┐  ┌┐┐┌─┤ VMag, Issue 3, 1 January 1999
└─┘┘ ┘└─┘ ─────────────────────────────
                                                 ╔════════════════════════════╗
                                                 ║     Version: 1.0           ║
                                                 ║     Release: 27-Nov-1998y  ║
                                                 ║  Created by: Hard Wisdom   ║
                                                 ╚════════════════════════════╝

─[27-Nov-1998y]─[v1.0]─────────────────────────────────────────────────────────
 Первоначально созданный файл (по совету Psychomancer'a)
───────────────────────────────────────────────────────────────────────────────


             Некоторые методы и особенности процесса анализа ПО
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


[] Содержание
~~~~~~~~~~~~~
 [0] Преамбула..............................................................___
 [1] Введение в анализ программного обеспечения.............................___
 [2] Некоторые методы проведения(противодействия) анализа(у) ПО.............___
  [2.1] Введение в язык IDC программы IDA-Pro v3.7..........................___
  [2.2] Ключеделы-ключеделы... или как проводить сверку.....................___
  [2.3] Что есть код, а что данные, и где есть кто?.........................___
 [Z] Заключение.............................................................___


[0] Преамбула
~~~~~~~~~~~~~
   Итак,  здесь  вы  сможете   прочитать   некоторое   количество   бессистемно
изложенной информации посвященной  теме  анализа  ПО  (некоторые  называют  это
Reverse-Engineering, можете  использовать  тот  термин,  который  вам  нравится
больше всего). Тем, у кого будут чесаться руки, в  предвкушении  написания  ну,
например, новой защиты для некоторой программы, хочется  заметить,  что  лучший
метод уже предложен: GNU's General Public License


[1] Виды анализа программного обеспечения (ПО)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Достаточно  часто  возникают  проблемы  поиска  недостающей   информации   о
функционировании различного программного  обеспечения.  Не  отрицая  безусловно
важной роли документации в данном вопросе, хочется отметить большую  полезность
и уместность метода Reverse-Engineering,  который  позволяет  во  всех  спорных
случаях внести ясность в решаемый вопрос. Я не стану приводить всех  аргументов
за и против или затрагивать моральные аспекты, оставим это неудачникам. Итак:
   Можно говорить о 2х обширных группах методов:  статические  и  динамические.
Под статическим методом понимается получение информации  о  программе  анализом
ее кода (исходного, бинарного и т.п.) При этом все  сводится,  как  правило,  к
формулированию  сложных  запросов  поиска   требуемой   информации.   Например:
поиском/заменой в ДОС программе фрагмента "Out  61h,Al"  на  "Nop;  Nop"  можно
"отключить" надоедливое  звучание  PC  Speaker  (контекст  команды  Out  61h,Al
должен определять человек и человек должен принимать решения о  проведении  или
отказе от модификации). Но что будет, если звук  выводится  системным  вызовом:
Mov Ax,0E07h; Int 10h ? Безусловно, необходимо будет выдать  второй  запрос  на
поиск и т.п. Динамический анализ - осуществляется анализ потоков  выполнения  в
программе, а так же  изменение  входных/выходных  данных  (регистры,  память  и
т.п.). Безусловно, во всех случаях необходима некоторая минимальная  информация
об исследуемой программе: для бинарного  кода  -  целевой  процессор  и  формат
запускаемого файла + операционное окружение; для  исходных  текстов  -  язык  +
библиотечное   окружение.   Затем     происходит     постепенное     уменьшение
неопределенности применительно к исследуемой программе. Хочется  заметить,  что
при решении многих задач анализ всего кода программы (здесь и далее  понимается
как исходный текст, так  и  бинарная  (откомпилированная)  версия)  определенно
избыточен,  поэтому  достаточно  важным  является  как  можно   более   близкое
начальное приближение к интересующему фрагменту кода.


[2] Некоторые методы проведения анализа ПО
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Существует  большое  количество  средств  проведения  анализа  ПО,  впрочем,
удачными можно назвать только несколько, и то - с натяжкой.  Перечислять  здесь
готовые продукты, наверное, смысла не имеет,  но  привести  несколько  примеров
работы с ними - можно.


[2.1] Введение в язык IDC программы IDA-Pro v3.7
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Интерактивный  дизассемблер  IDA-Pro  располагает   внутренним   макроязыком
синтаксически достаточно близко похожим на  Си.  Появившись  достаточно  давно,
только в версии 3.7 он стал достаточно устойчивым и  функционально  богатым.  В
подтверждение своих слов могу  привести  2  программы  на  IDC,  которые  нафиг
вешали мою систему под IDA v3.5 (или выдавали некорректные результаты).

─[Begin: ERRIDC_1.PAS]─────────────────────────────────────────────────────────
Program IDC_Test;

Procedure TestProc;
Var I:Integer;
Begin
 WriteLn('static Test() {');
 For I:=1 To 2100 Do Begin
  WriteLn(' if (',I,'!=',I,') Message("(',I,') - <>\n");');
 End;
 WriteLn('}'); WriteLn;
End;

Begin
 TestProc;
 WriteLn('static main() {');
 WriteLn(' Message("IDC Crazy Conditions!\n"); Test();');
 WriteLn('}'); WriteLn;
End.
─[End: ERRIDC_1.PAS]───────────────────────────────────────────────────────────

─[Begin: ERRIDС_2.PAS]─────────────────────────────────────────────────────────
Program IDC_Test;

Procedure TestProc;
Var I:LongInt;
Begin
 WriteLn('static Test() {');
 WriteLn(' auto i;');
 Write(' i=0');
 For I:=0 To 31 Do Write('+(',I);
 For I:=0 To 31 Do Write(')');
 WriteLn(';');
 WriteLn('}'); WriteLn;
End;

Begin
 TestProc;
 WriteLn('static main() {');
 WriteLn(' Message("IDA Stack overflow!\n"); Test();');
 WriteLn('}'); WriteLn;
End.
─[End: ERRIDC_2.PAS]───────────────────────────────────────────────────────────

Примеры достаточно простые (для генерации файла IDC не на экран,  а  в  файл  -
используйте перенаправление консоли,  например  так:  ERRIDC_2.EXE  >BUG2.IDC),
приводят  к  переполнению  стека  исполнителя  виртуальной  машины  IDC  или  к
некорректной компиляции. Я не уверен, что  версия  3.7  полностью  свободна  от
названных выше недостатков. Кстати, подобным методом  углючить  Borland  Pascal
v7.0  мне  не  удалось.  Недостатком  следует  назвать  так  же  малый   размер
максимальной скомпилированной программы (менее 64 кБ), а так  же  невозможность
отката сделанных изменений в базе дизассемблирования, так что  ошибаться  можно
только один раз.
   Выше я уже  говорил  о  различных  методах  анализа  ПО.  Давайте  попробуем
определить размещение процедуры шифрования в исследуемом файле. Как правило,  в
доморощенных методах шифрования by  obscurity  используется  операция  XOR,  но
следует заметить, что XOR часто используется для  очистки  регистров  (например
наряду с: XOR AX,AX; SUB AX,AX  и  проч.)  Попробуем  сформулировать  следующий
запрос: "хочу все XOR с разными регистрами в качестве операндов!"  Сделаем  это
таким образом:

─[Begin: _HW_LIB.IDC]──────────────────────────────────────────────────────────
// ------------------------------------------------------Main Library
//
// ---------------------------------------------Find the XOR commands
static findxor(from) {
 auto ea,sz,s,op1,op2;
 ea=from; for (;;) {
  s=GetMnem(ea); op1=GetOpnd(ea,0); op2=GetOpnd(ea,1);
  if (s!="" && s=="xor" && op1!=op2) {
//    ^^^^^^^^^^^^^^^^^ ------------------------------------ IDA Suxx
   Message("Necessary XOR placed at address: "+
           form("%.8lX",ea)+"h   In format: "+s+" "+op1+","+op2+"\n");
   Jump(ea); return ea;
  } sz=ItemSize(ea); ea=ea+sz;
  if (ea>=MaxEA()) return 0;
 }
}
// ------------------------------------------------------------------
─[End: _HW_LIB.IDC]────────────────────────────────────────────────────────────

─[Begin: _FNDXOR.IDC]──────────────────────────────────────────────────────────
// ---------------------------To find one XOR with different operands
#include <idc.idc>
#include <_hw_lib.idc>

static main() {
 Message("'FIND XOR' has been loaded. Researching. . .\n");
 if (!findxor(ScreenEA()))
  Message("Not found, researching process was terminated.\n");
}
// ------------------------------------------------------------------
─[End: _FNDXOR.IDC]────────────────────────────────────────────────────────────

─[Begin: _FNDXORS.IDC]─────────────────────────────────────────────────────────
// --------------------------To find all XORs with different operands
#include <idc.idc>
#include <_hw_lib.idc>

static main() {
 auto i,n; n=ScreenEA(); i=MinEA();
 Message("'FIND XORs' has been loaded. Researching. . .\n");
 while (i=findxor(i)) i=i+ItemSize(i);
 Message("No more found, researching process was terminated.\n");
 Jump(n);
}
// ------------------------------------------------------------------
─[End: _FNDXORS.IDC]───────────────────────────────────────────────────────────

Внутри IDA v3.7 надо нажать  клавишу  <F2>  и  указать  имя  требуемого  файла:
_FNDXOR.IDC для поиска одной команды или _FNDXORS.IDC  для  поиска  сразу  всех
команд. Итак, возьмем например VSERVER.VXD, который ведает  шаровыми  ресурсами
в Microsoft Network локальной рабочей станции под управлением W95.  VSERVER.VXD
112892 bytes v4.00.1111 (от русского маздая). Итак, что мы имеем:

Compiling file 'C:\HW\LANG\IDAPRO\idc\_FNDXORS.IDC'...
Executing function 'main'...
'FIND XORs' has been loaded. Researching. . .
Necessary XOR placed at address: 00000656h   In format: xor dh,[edi+9]
Necessary XOR placed at address: 0000065Ch   In format: xor [edi+9],dh
Necessary XOR placed at address: 00009581h   In format: xor al,cl
Necessary XOR placed at address: 00009C35h   In format: xor ecx,esi
Necessary XOR placed at address: 0000DB10h!!!In format: xor bl,dl
Necessary XOR placed at address: 0000DB43h!!!In format: xor bl,dl
Necessary XOR placed at address: 00012103h   In format: xor [edx],edi
Necessary XOR placed at address: 000126BCh   In format: xor [esi],ebp
Necessary XOR placed at address: 000126BEh   In format: xor [eax],al
Necessary XOR placed at address: 000126C7h   In format: xor bl,cs:[eax+30h]
Necessary XOR placed at address: 000126CBh   In format: xor [edx],dh
Necessary XOR placed at address: 000126F0h   In format: xor [esi],ch
Necessary XOR placed at address: 000126F2h   In format: xor [edx],esi
Necessary XOR placed at address: 000134FCh   In format: xor ah,[ebp+6Eh]
Necessary XOR placed at address: 00013508h   In format: xor [ebp+6Eh],esp
Necessary XOR placed at address: 00013578h   In format: xor al,[eax]
Necessary XOR placed at address: 00013580h   In format: xor ah,[ebp+6Eh]
Necessary XOR placed at address: 0001358Ch   In format: xor al,[eax]
Necessary XOR placed at address: 00013594h   In format: xor ah,[ebp+6Eh]
Necessary XOR placed at address: 000135A0h   In format: xor [eax],eax
Necessary XOR placed at address: 000135A8h   In format: xor [ebp+6Eh],esp
Necessary XOR placed at address: 000135B4h   In format: xor [eax],eax
Necessary XOR placed at address: 000135BCh   In format: xor [ebp+6Eh],esp
No more found, researching process was terminated.

Страшно? Много? Нажимая <ESC>  просмотрим  список:  автоанализ  IDA  достаточно
кривой и все ложные срабатывания  на  текст  (трактуемый  как  код)  быстренько
пропускаем, Итак: DB10h и DB43h - очень  интересно,  4  первых  срабатывания  -
явные фрагменты неистовства оптимизации сишного компилера ;-) Хе-хе, 30 сек.  и
что мы имеем:

    0000DB2F             mov     dl, [ebp+arg_0]
    0000DB32             mov     eax, [ebp+arg_4]
    0000DB35             mov     ecx, [ebp+arg_8]
    0000DB38   LOC_DB38:
    0000DB38             mov     [ebp+var_1], dl
    0000DB3B             ror     [ebp+var_1], 1
    0000DB3E             mov     dl, [ebp+var_1]
    0000DB41             mov     bl, [ecx]
    0000DB43             xor     bl, dl
    0000DB45             mov     [eax], bl
    0000DB47             inc     ecx
    0000DB48             inc     eax
    0000DB49             dec     esi
    0000DB4A             jnz     short LOC_DB38

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


[2.2] Ключеделы-ключеделы... или как проводить сверку
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Итак,  многие  из  вас  писали  ключеделы,  регистраторы  и   проч.   Многие
отхакивали проверки нафиг (тоже разумно). Безусловно,  что  где  тонко,  там  и
рвется. Никто из вас не обращал внимания на ключемейкеры? Ну ка, например:

─[Begin: LASM20R.C]────────────────────────────────────────────────────────────
#include <string.h>
#include <stdio.h>

char* MakeReg(unsigned char* UserName, unsigned char* UserId,
                                       unsigned char* Password) {
 unsigned long S1=13,S2=0;
 while (*UserName) { S2+=S1*(*UserName++); S1=(S1+ 37)*3; }
 while (*UserId  ) { S2+=S1*(*UserId++  ); S1=(S1+197)*7; }
 S2%=0xF4240; sprintf(Password,"%lu",S2); return Password;
}

void main(int argc, char* argv[]) {
 char s1[256],s2[256],s3[256];
 puts("(C) 25-Nov-1998y by Hard Wisdom \"LASM v2.0 KeyGen\" v1.0");
 puts("                                ~~~~~~~~~~~~~~~~~~");
 if (argc<2) { printf("User name: "); gets(s1); s1[strlen(s1)]=0;
 } else strcpy(s1,argv[1]);
 if (argc<3) { printf("  User ID: "); gets(s2); s2[strlen(s2)]=0;
 } else strcpy(s2,argv[2]);
 printf("<Password: %s>\n",MakeReg(s1,s2,s3));
}
─[End: LASM20R.C]──────────────────────────────────────────────────────────────

Это пример ключегенератора  для  "Lite  Assembler",  великолепная  вещь  одного
японца, полностью совместима с  MASM  v6.0  и  т.п.  В  чем  основная  проблема
подобных  проверок  регистрационных  ключей?   Правильно,   в   их   (проверок)
обозримости. Никто не мешает нам работать  не  только  с  бинарным  кодом  (как
поступают многие), но и с исходным текстом так же. (В  свое  время  на  УКНЦ  я
работал с паскалем  компиляющим  сквозь  MACRO-11  (ассемблер),  так  же  может
поступать Turbo C и огромное множество других продуктов, а если  вспомнить  еще
скремблеры?  Типа  PLM,  SAT  и  проч.).  Приведем  пример  кряк-ми,  решающего
вышеописанную проблему. Хочу заметить, что это _ТОЛЬКО_ПРИМЕР_,  никто  вам  не
запрещает выполнять параллельно  2  генерации  кода  (прямого  -  шифрование  и
обратного - расшифровка). Итак, опять мусорим сырцами:

─[Begin: HWCRKMEG.C]───────────────────────────────────────────────────────────
/*******************************************************************/
/* (C) by Hard Wisdom "The CrackMe Generator from Hard Wisdom"     */
/*                                        v1.0 from 06-Apr-1998y   */
/* Status: Alpha version                                           */
/*******************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/*-----------------------------------------------------------------*/
void str2file(FILE* f, char* s) {
 fprintf(f,"%s\n",s);
}

/*-----------------------------------------------------------------*/
void simpleop(FILE* f) {
 int op1,op2,op3; unsigned long op0;
 op1=random(0x10); op2=random(0x10); op3=random(0x10);
 op0=((unsigned long)random(RAND_MAX)<<10)+random(RAND_MAX);
 switch (random(15)) {
  case 0: fprintf(f," w[%d]+=0x%lX;\n",op1,op0); break;
  case 1: fprintf(f," w[%d]-=0x%lX;\n",op1,op0); break;
  case 2: fprintf(f," w[%d]^=0x%lX;\n",op1,op0); break;

  case 3: fprintf(f," w[%d]+=w[%d]^0x%lX;\n",op1,op2,op0); break;
  case 4: fprintf(f," w[%d]^=w[%d]+0x%lX;\n",op1,op2,op0); break;
  case 5: fprintf(f," w[%d]-=w[%d]^0x%lX;\n",op1,op2,op0); break;
  case 6: fprintf(f," w[%d]^=w[%d]-0x%lX;\n",op1,op2,op0); break;

  case 7: fprintf(f," w[%d]=w[%d]^w[%d];\n",op3,op1,op2); break;
  case 8: fprintf(f," w[%d]=w[%d]+w[%d];\n",op3,op1,op2); break;
  case 9: fprintf(f," w[%d]=w[%d]-w[%d];\n",op3,op1,op2); break;

  case 10: fprintf(f," w[%d]++;\n",op1); break;
  case 11: fprintf(f," w[%d]--;\n",op1); break;

  case 12: fprintf(f," w[%d]^=(w[%d]<<1)^(w[%d]>>1);\n",op3,op1,op2); break;
  case 13: fprintf(f," w[%d]^=(w[%d]<<3)^(w[%d]>>3);\n",op3,op1,op2); break;
  case 14: fprintf(f," w[%d]^=(w[%d]<<9)^(w[%d]>>9);\n",op3,op1,op2); break;
 }
}

/*-----------------------------------------------------------------*/
void _combinedop(FILE* f, int level) {
 char b_op[4]="+-^"; char u_op[8][3]={"-","~","--","++","","","",""};
 int op,i,ii,n; char lspaces[256]; unsigned long op0;

 op0=((unsigned long)random(RAND_MAX)<<10)+random(RAND_MAX);
 for (i=0;i<=level;lspaces[i++]=' '); lspaces[i]=0;
 fprintf(f,"\n%s(\n%s ",lspaces,lspaces);
 op=random(0x10); i=random(8); fprintf(f,"(%sw[%d])",u_op[i],op);
 n=random(10)+5; while (n--) { op=random(0x10); i=random(8); ii=random(3);
  fprintf(f,"%c",b_op[ii]);
  if (random(10)>8 && level<=3) _combinedop(f,level+1);
  else if (random(10)>5) fprintf(f,"0x%lX",op0);
  else fprintf(f,"(%sw[%d])",u_op[i],op);
 } fprintf(f,"\n%s)\n%s",lspaces,lspaces);
}

void combinedop(FILE* f) {
 int op; op=random(0x10); fprintf(f," w[%d]=",op);
 _combinedop(f,0); fprintf(f,";\n");
}

/*-----------------------------------------------------------------*/
void trivialops(FILE* f, int mincnt, int dev) {
 int i,i0; i0=random(dev+1)+mincnt; for (i=1;i<=i0;i++)
  if (random(10)>8) combinedop(f); else simpleop(f);
};

/*-----------------------------------------------------------------*/
void blockop(FILE* f,int level, int mincnt, int dev) {
 int i,i0,n,op,flg; i0=random(dev+1)+mincnt;
 op=random(0x10); flg=random(2); n=random(14)+10;
 if (flg) fprintf(f," if (w[%d] && %d) {\n",op,n);
 else fprintf(f," for (i[%d]=0;i[%d]<%d;i[%d]++) {\n",level,level,n,level);
 for (i=1;i<=i0;i++) {
  if (random(10)>8 && level<0x0F) blockop(f,level+1,mincnt,dev);
  else trivialops(f,1,0);
 } fprintf(f," }\n"); 
}

/*-----------------------------------------------------------------*/
void funcop(FILE* f, int level) {
 fprintf(f,"void flow%d(unsigned long from, unsigned long* to) {\n",level);
 str2file(f," unsigned long w[0x10]; int i[0x10];");
 str2file(f," w[0]=from;");
 str2file(f," for (i[0]=1;i[0]<=0x0F;w[i[0]++]=w[0]);");
 blockop(f,0,4,4); blockop(f,0,4,4);
 blockop(f,0,4,4); blockop(f,0,4,4);
 str2file(f," for (i[0]=1;i[0]<=0x0F;w[0]^=w[i[0]++]);");
 str2file(f," *to=w[0];}");
}

/*-----------------------------------------------------------------*/
void maincode(FILE* f) {
 int i,i0;
 randomize(); i0=random(6)+10;
 for (i=1;i<=i0;i++) funcop(f,i);
 str2file(f,"");
 str2file(f,"int check_n_get(char* name, unsigned long ser,");
 str2file(f,"                            unsigned long* res) {");
 str2file(f," unsigned long hash; int i;");
 str2file(f," hash=0x00FEED00; while (*name)");
 str2file(f,"  hash+=((hash^*name)<<3)+((hash^*name)>>3),name++;");
 str2file(f,"");
 for (i=1;i<=i0;i++) {
  fprintf(f," flow%d(hash,&hash); flow%d(hash,&hash);",i,i0-i+1);
  fprintf(f," printf(\"▐\");\n");
 } str2file(f,"");
 str2file(f," *res=hash; return *res==ser;");
 str2file(f,"}");
}

/*-----------------------------------------------------------------*/
void generate(FILE* f) {
 str2file(f,"/*-----------------------*/");
 str2file(f,"/*---start of crack me---*/");
 str2file(f,"/*-----------------------*/");
 str2file(f,"#include <stdio.h>");
 str2file(f,"");
 maincode(f);
 str2file(f,"");
 str2file(f,"void main() {");
 str2file(f," int r;");
 str2file(f," unsigned long ser; char name[256];");
 str2file(f," printf(\"           Accepted only KEYMAKERS !\\n\");");
 str2file(f," printf(\"══════════════════════════════════════════════\\n\");");
 str2file(f," printf(\" \\\"HW-CrackME\\\" v1.0\\n\");");
 str2file(f," printf(\" ~~~~~~~~~~~~ (C) 06-Apr-1998y by Hard Wisdom\\n\");");
 str2file(f," printf(\"──────────────────────────────────────────────\\n\");");
 str2file(f," printf(\" ■ Registration Name: \");");
 str2file(f," gets(name);");
 str2file(f," printf(\" ■     Serial Number: \");");
 str2file(f," scanf(\"%lX\",&ser);");
 str2file(f," printf(\" ■          Checking: \");");
 str2file(f," r=check_n_get(name,ser,&ser);");
 str2file(f," printf(\" Ok\\n\");");
 str2file(f," if (!r)");
 str2file(f,"  printf(\" ■ Your serial number is NOT Accepted!\\n\");");
 str2file(f," else");
 str2file(f,"  printf(\" √ Well done, You hack it !!! Wise Guy.\\n\");");
 str2file(f," printf(\"══════════════════════════════════════════════\\n\");");
 str2file(f," printf(\"            FidoNet: 2:461/133.69\\n\");");
 str2file(f,"");
 str2file(f,"#ifdef crackhw");
 str2file(f," printf(\" ■ Real serial number is %.8lX\\n\",ser);");
 str2file(f,"#endif");
 str2file(f,"}");
 str2file(f,"/*-----------------------*/");
 str2file(f,"/*---!stop of crack me---*/");
 str2file(f,"/*-----------------------*/");
}

/*-----------------------------------------------------------------*/
void main(int argc, char* argv[]) {
 FILE* f;
 printf("(C) 06-Apr-1998y by Hard Wisdom \"The CrackMe Generator\" v1.0\n");
 printf("                                ~~~~~~~~~~~~~~~~~~~~~~~\n");
 if (argc<=1) {
  printf("USAGE: HWCRKMEG.EXE NameOfSourceFile - to generate in...\n");
  return;
 };

/*-----------------------------------------------------------------*/
 if ((f=fopen(argv[1],"w"))==NULL) {
  printf("Can't create output file '%s'!\n",argv[1]);
  return;
 }; generate(f);
 if (ferror(f)) {
  printf("Disk I/O error occured during generation!\n");
  return;
 }; fclose(f);                                     /* Generated OK */
 printf("Well done! ;-) Use the TC to compile it.\n");
}
/*******************************************************************/
/*                             The End                             */
/*******************************************************************/
─[End: HWCRKMEG.C]─────────────────────────────────────────────────────────────

Я думаю, что ключедел в данном  случае  будет  написать  проблематично,  а  для
битхака, насколько  я  понимаю,  тоже  есть  свои  пути  противодействия.  Один
момент: компилять надо TC v2.0 и второе: генерация  случайна,  иногда  компилер
не сможет покомпилять сгенерированный  код  по  техническим  причинам  (слишком
большая  глубина  вложенности  или  слишком  большой   файл),   это   обходится
повторными  перегенерациями.  К  сожалению,  приведенный  выше  метод  создания
исходного текста невозможно полностью излечить от непредсказуемости,  но  можно
автоматически проверять статистические характеристики полученного кода.


[2.3] Что есть код, а что данные, и где есть кто?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Я думаю, что все  знают  программу  "Слово  и  Дело  v8.0",  очень  неплохой
текстовый редактор Гутникова для ДОСа. Я полагаю, что многим известны так же  и
некоторые крякми некоторых уважаемых людей: NOR (C) Solar  Designer  или  NORAC
(C) Code Master. Разговор пойдет сейчас именно об этом:  выполнение  кода,  как
это делает процессор и зачем это делают  остальные  (те,  которые  не  являются
процессорами).
   Я уже говорил, что в процессе анализа  некоторой  программы  мы  располагаем
определенной   начальной   информацией   (целевой   процессор   выполнения,   в
частности). А что, если у  нас  нет  даже  этого?!  Именно,  тогда  большинство
утилит (SoftIce, IDA, HIEW, Sourcer e.t.c.) будут бесполезны, прийдется  писать
что-то свое. Именно так появились в свое  время:  DeClipper,  Valcyria,  ReFox,
DeVB,  JAD,  DeInsh  и  проч.  Хм,  интересно  отметить,  что  само   по   себе
использование   неизвестного   процессора   еще    не     является     гарантом
"невзламываемости" кода, примеров тому - масса.  Рассмотрим  механизм  проверки
ключей программы "Слово и  Дело"  (W&D).  Рискуя  навлечь  на  себя  ваш  гнев,
приведу листинг Пи-кода проверки серийного номера целиком:

─[Begin: WD80WOW.LST]──────────────────────────────────────────────────────────
   This part of code is used far away in program
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     0:  │ 11│  0│ 71│142│  LOAD  R2,18318
     4:  │ 15│   │   │   │  MOV   R4,R2
     5:  │ 19│   │   │   │  LOAD  R2,<CONST>
     6:  │ 13│   │   │   │  MOV   R3,R2
     7:  │ 14│   │   │   │  MOV   R2,R4
     8:  │  5│   │   │   │  DEC   R2
     9:  │  6│   │   │   │  INC   R3
    10:  │  2│   │   │   │  BEQ   R2==0,13
    11:  │  1│  8│   │   │  JMP   8
    13:  │ 11│  0│  1│ 87│  LOAD  R2,343
    17:  │ 10│   │   │   │  MUL   R2,R3
    18:  │ 13│   │   │   │  MOV   R3,R2
    19:  │ 18│   │   │   │  SUB   R2,R3
    20:  │ 20│   │   │   │  ROLO  R4,R3,R2
    21:  │ 11│  1│ 61│  7│  LOAD  R2,81159
    25:  │ 20│   │   │   │  ROLO  R4,R3,R2
    26:  │ 18│   │   │   │  SUB   R2,R3
    27:  │  8│   │   │   │  INC   R4
    28:  │  3│   │   │   │  BEQ   R2>0,31
    29:  │  1│ 33│   │   │  JMP   33
    31:  │  1│ 26│   │   │  JMP   26
    33:  │ 13│   │   │   │  MOV   R3,R2
    34:  │ 11│  1│ 62│218│  LOAD  R2,81626
    38:  │ 23│   │   │   │  ADD   V2,V3
    39:  │ 43│   │   │   │  NOP
    40:  │ 56│   │   │   │  NOP
    41:  │ 71│   │   │   │  NOP
    42:  │ 82│   │   │   │  NOP
    43:  │ 20│   │   │   │  ROLO  R4,R3,R2
    44:  │ 20│   │   │   │  ROLO  R4,R3,R2
    45:  │ 11│  0│ 65│ 23│  LOAD  R2,16663
    49:  │ 20│   │   │   │  ROLO  R4,R3,R2
    50:  │ 20│   │   │   │  ROLO  R4,R3,R2
    51:  │  2│   │   │   │  BEQ   R2==0,54
    52:  │  1│ 56│   │   │  JMP   56
    54:  │  1│ 60│   │   │  JMP   60
    56:  │  5│   │   │   │  DEC   R2
    57:  │ 20│   │   │   │  ROLO  R4,R3,R2
    58:  │  1│ 51│   │   │  JMP   51
    60:  │ 20│   │   │   │  ROLO  R4,R3,R2
    61:  │ 20│   │   │   │  ROLO  R4,R3,R2
    62:  │ 23│   │   │   │  ADD   V2,V3
    63:  │ 22│   │   │   │  MOV   R1,R2
    64:  │ 21│   │   │   │  HALT  R1

   PreCalc1
   ~~~~~~~~
    65:  │ 82│   │   │   │  NOP
    66:  │ 11│  1│134│160│  LOAD  R2,100000
    70:  │ 13│   │   │   │  MOV   R3,R2
    71:  │ 19│   │   │   │  LOAD  R2,<CONST>
    72:  │ 20│   │   │   │  ROLO  R4,R3,R2
    73:  │ 19│   │   │   │  LOAD  R2,<CONST>
    74:  │ 18│   │   │   │  SUB   R2,R3
    75:  │ 20│   │   │   │  ROLO  R4,R3,R2
    76:  │ 20│   │   │   │  ROLO  R4,R3,R2
    77:  │ 18│   │   │   │  SUB   R2,R3
    78:  │  8│   │   │   │  INC   R4
    79:  │  3│   │   │   │  BEQ   R2>0,82
    80:  │  1│ 84│   │   │  JMP   84
    82:  │  1│ 77│   │   │  JMP   77
    84:  │ 14│   │   │   │  MOV   R2,R4
    85:  │  5│   │   │   │  DEC   R2
    86:  │  2│   │   │   │  BEQ   R2==0,89
    87:  │  1│ 95│   │   │  JMP   95
    89:  │ 11│  0│ 49│  2│  LOAD  R2,12546
    93:  │  1│185│   │   │  JMP   185
    95:  │  5│   │   │   │  DEC   R2
    96:  │  2│   │   │   │  BEQ   R2==0,99
    97:  │  1│105│   │   │  JMP   105
    99:  │ 11│  0│ 37│234│  LOAD  R2,9706
   103:  │  1│185│   │   │  JMP   185
   105:  │  5│   │   │   │  DEC   R2
   106:  │  2│   │   │   │  BEQ   R2==0,109
   107:  │  1│115│   │   │  JMP   115
   109:  │ 11│  0│ 47│ 49│  LOAD  R2,12081
   113:  │  1│185│   │   │  JMP   185
   115:  │  5│   │   │   │  DEC   R2
   116:  │  2│   │   │   │  BEQ   R2==0,119
   117:  │  1│125│   │   │  JMP   125
   119:  │ 11│  0│ 58│  1│  LOAD  R2,14849
   123:  │  1│185│   │   │  JMP   185
   125:  │  5│   │   │   │  DEC   R2
   126:  │  2│   │   │   │  BEQ   R2==0,129
   127:  │  1│135│   │   │  JMP   135
   129:  │ 11│  0│ 45│128│  LOAD  R2,11648
   133:  │  1│185│   │   │  JMP   185
   135:  │  5│   │   │   │  DEC   R2
   136:  │  2│   │   │   │  BEQ   R2==0,139
   137:  │  1│145│   │   │  JMP   145
   139:  │ 11│  0│ 31│ 10│  LOAD  R2,7946
   143:  │  1│185│   │   │  JMP   185
   145:  │  5│   │   │   │  DEC   R2
   146:  │  2│   │   │   │  BEQ   R2==0,149
   147:  │  1│155│   │   │  JMP   155
   149:  │ 11│  0│ 53│ 97│  LOAD  R2,13665
   153:  │  1│185│   │   │  JMP   185
   155:  │  5│   │   │   │  DEC   R2
   156:  │  2│   │   │   │  BEQ   R2==0,159
   157:  │  1│165│   │   │  JMP   165
   159:  │ 11│  0│ 34│218│  LOAD  R2,8922
   163:  │  1│185│   │   │  JMP   185
   165:  │  5│   │   │   │  DEC   R2
   166:  │  2│   │   │   │  BEQ   R2==0,169
   167:  │  1│175│   │   │  JMP   175
   169:  │ 11│  0│ 39│163│  LOAD  R2,10147
   173:  │  1│185│   │   │  JMP   185
   175:  │ 11│  0│ 62│  9│  LOAD  R2,15881
   179:  │185│   │   │   │  NOP
   180:  │178│   │   │   │  NOP
   181:  │123│   │   │   │  NOP
   182:  │ 82│   │   │   │  NOP
   183:  │ 94│   │   │   │  NOP
   184:  │ 64│   │   │   │  NOP
   185:  │ 75│   │   │   │  NOP
   186:  │ 43│   │   │   │  NOP
   187:  │ 56│   │   │   │  NOP
   188:  │ 22│   │   │   │  MOV   R1,R2
   189:  │ 21│   │   │   │  HALT  R1

   PreCalc2
   ~~~~~~~~
   190:  │ 42│   │   │   │  NOP
   191:  │ 52│   │   │   │  NOP
   192:  │ 87│   │   │   │  NOP
   193:  │ 19│   │   │   │  LOAD  R2,<CONST>
   194:  │ 42│   │   │   │  NOP
   195:  │ 52│   │   │   │  NOP
   196:  │ 87│   │   │   │  NOP
   197:  │ 20│   │   │   │  ROLO  R4,R3,R2
   198:  │ 11│  0│  0│ 76│  LOAD  R2,76
   202:  │ 82│   │   │   │  NOP
   203:  │  8│   │   │   │  INC   R4
   204:  │ 75│   │   │   │  NOP
   205:  │  5│   │   │   │  DEC   R2
   206:  │ 64│   │   │   │  NOP
   207:  │  6│   │   │   │  INC   R3
   208:  │132│   │   │   │  NOP
   209:  │  3│   │   │   │  BEQ   R2>0,212
   210:  │  1│214│   │   │  JMP   214
   212:  │  1│202│   │   │  JMP   202
   214:  │ 12│   │   │   │  MOV   R2,R3
   215:  │  1│180│   │   │  JMP   180
   217:  │ 19│   │   │   │  LOAD  R2,<CONST>
   218:  │ 15│   │   │   │  MOV   R4,R2
   219:  │  4│   │   │   │  INC   R2
   220:  │  6│   │   │   │  INC   R3
   221:  │  8│   │   │   │  INC   R4
   222:  │ 11│  0│ 10│  0│  LOAD  R2,2560
   226:  │ 20│   │   │   │  ROLO  R4,R3,R2
   227:  │ 22│   │   │   │  MOV   R1,R2
   228:  │ 18│   │   │   │  SUB   R2,R3
   229:  │  3│   │   │   │  BEQ   R2>0,232
   230:  │ 29│   │   │   │  NOP
   231:  │ 21│   │   │   │  HALT  R1
   232:  │ 52│   │   │   │  NOP
   233:  │ 24│   │   │   │  FUCK
   234:  │255│   │   │   │  NOP

─[End: WD80WOW.LST]────────────────────────────────────────────────────────────

Мнемоники, полагаю, всем понятны. При вызове  интерпретатора  Пи-кода  на  вход
передается параметр <CONST>, и на выходе командой HALT  возвращается  некоторый
результат. Честно говоря, в данном случае применение интерпретируемого  Пи-кода
выглядит несколько странно. Автор не нашел лучшего  применения,  чем  выполнять
XLAT по табличке. 8-() А ведь мог навернуть, так же  странно  выглядят  участки
NOP'ов в коде,  возможно  это  obscurity.  Дабы  не  быть  голословным  приведу
напоследок окончательный вариант ключегенератора.

─[Begin: WD80K.C]──────────────────────────────────────────────────────────────
// --------------------------------------------------------
// (C) 08-Feb-1998y by Hard Wisdom "WD v8.0 KeyMaker" v2.0
// --------------------------------------------------------

#include <stdio.h>
#include <string.h>

void main(int argc, char* argv[])
{
 char s[20]="";
 unsigned long key; long num; int i;
 unsigned int dk[10]={19559,27409,13769,2354,8486,
                      13576,13467,11689,14889,27106};
 unsigned int da[10]={12546,9706,12081,14849,11648,
                      7946,13665,8922,10147,15881};

 printf("(C) 08-Feb-1998y by Hard Wisdom \"WD v8.0 KeyMaker\" v2.0\n");
 printf("                                ~~~~~~~~~~~~~~~~~~\n");
 if (argc>1) strcpy(s,argv[1]);
 else {printf("Enter the product number: "); scanf("%s",&s);};
 strupr(s); if (strlen(s)<3 || s[1]!='-') {
  printf("Error: bad product number prefix, must be 'X-' where X is alpha!\n");
  return;
 }; if (s[0]!='E')
  printf("Warning: generation can be incorrect, required prefix 'E-' only!\n");
 strcpy(s,&s[2]); sscanf(s,"%lu",&key);
 key=((key / 10)-dk[key % 10]+892)*5/4+7546;
 if (key<0 || key>99999) {
  printf("Error: invalid product number has been entered!\n");
  return;
 };
 printf("Well, generating registration numbers for %s...\n",s);
 for(i=1;i<11;i++){
  num=key-da[i-1]; if (num<0) num=num+100000*i; else num=num+100000*(i-1);
  printf(" %2d%s registration key is %6lu\n",i,i==1?"st":i==2?"nd":"th",num);
 }; printf("Make your choice ;-) Software must be FREE!");
}
// --------------------------------------------------------

─[End: WD80K.C]────────────────────────────────────────────────────────────────

Весьма просто, не так ли? А ведь битхак W&D штука очень нетривиальная,  я  могу
только посочувствовать тому, кто возьмется поправить  хотя  бы  байтик  в  коде
программы. Ну, вот, например: W&D позиционируется за  конец  себя  в  файле  на
диске  (по  фиксированному  смещению)  и  читает  пару   килобайт   в   таблицу
прерываний! Если файл имеет оригинальную длинну, то все в  порядке,  произойдет
ошибка  чтения.  Но  что  будет,  если  файл  распаковать?  Правильно,  вектора
прерываний будут затерты хвостом программы. И ведь никаких явных сравнений!  Но
это только  начало,  желающие  -  вооружайтесь  отладчиком,  там  есть  на  что
посмотреть.

[Z] Заключение
~~~~~~~~~~~~~~
   Надеюсь, что вам было интересно.  Думаю,  что  в  будущем  стану  продолжать
вести этот  файл.  Естественно,  что  все  это  и  много  других  сопутствующих
материалов можно взять на моей домашней страничке.  По  идеологии  данное  эссе
противопоставлено своему собрату NASTYxxx.TXT (to URL: please  ;-)  За  сим  не
прощаюсь.

           Home Page: www.geocities.com/SiliconValley/Hills/7827
              E-Mail:         [email protected]