Программа InDuLgEo V3-B горит пламенем на экране, печатает текст и трезвонит, как старый телефон.
- 01H Дизассемблируем программу и патчим так, чтобы печатала другой текст. 
- 02H Узнаем, как программа работает с динамиком IBM PC и издает звуки, сыграем другую мелодию. 
- 03H Выясним, как программа рисует пламя, окрасим в другой цвет, изобразим горящий текст. 
Будем есть слона по частям: сперва расскажу, как сменить текст, а про звук и видео - в следующий раз.
Запускаем программу
Программа работает под:
- MS-DOS 
- Windows XP (SP2 или ранее) 
- DOSBox 0.74+ 
Под DOSBox пригодится Turbo Debugger, DEBUG.EXE или другой отладчик.

Поищем строки в .com-файле, тогда найдем и код, что печатает текст. Не повезло - строк в файле не вижу, значит, код расшифровывает строки перед тем, как печатает.
Распаковываем программу
MS-DOS загружает .com-файл по адресу 100h и выполняет программу с первого байта.
      //
      // ram 
      // ram:0000:0100-ram:0000:05a2
      //
      assume DF = 0x0  (Default)
0000:0100 81 fc 78 f5     CMP        SP,0xf578
0000:0104 77 02           JA         LAB_0000_0108
0000:0106 cd 20           INT        0x20
                      LAB_0000_0108                                   XREF[1]:     0000:0104(j)  
0000:0108 b9 a3 04        MOV        CX,0x4a3
0000:010b be a3 05        MOV        SI,0x5a3
0000:010e bf 18 f5        MOV        DI,0xf518
0000:0111 bb 00 80        MOV        BX,0x8000
0000:0114 fd              STD
0000:0115 f3 a4           MOVSB.REP  ES:DI,SI
0000:0117 fc              CLD
0000:0118 87 f7           XCHG       DI,SI
0000:011a 83 ee c8        SUB        SI,-0x38
0000:011d 57              PUSH       DI
0000:011e 57              PUSH       DI
0000:011f e9 a2 f3        JMP        LAB_0000_f4c4
0000:0122 45              ??         45h    E
0000:0123 64              ??         64h    d
0000:0124 64              ??         64h    d
0000:0125 79              ??         79h    y
Программа копирует себя в другую область памяти и передает управление туда - так она прячет точку входа от дизассемблера. Найдем смещение точки входа f4c4h от начала кода.
Инструкция MOVSB.REP копирует CX байтов c адреса DS:SI на адрес ES:DI. Программа устанавливает флаг DF, поэтому копирует байты задом наперед: начинает с 5a3h и двигается к 100h.
16-битный процессор способен адресовать
2^16 байтов = 2^6 Кб = 64 Кбпамяти. Intel научили 16-битный процессор 8088 адресовать2^20 = 1 Мбпамяти или2^16 * 2^4 = 16сегментов памяти размера 64 Кб.Intel дали 16-битному процессору 8088 20-битную шину адреса, чтобы увеличить оперативную память до
2^20 = 1 Мб. 8088 составляет 20-битный адрес из двух 16-битных регистров - сегмента и смещения - сдвигает регистр сегмента влево на 4 бита и складывает со смещением. Пример:
адрес следующей инструкции в сегменте кода:
(cs << 4) + ip
адреса в сегменте данных:
(ds << 4) + si,(ds << 4) + di,(ds << 4) + 46Ch
вершина стека в сегменте стека:
(ss << 4) + sp
Программа занимает 4a3h байтов - от 100h до 5a2h, поэтому SI указывает на следующий за последней инструкцией байт 5a3h. Программа копирует 4a3h байтов, и DI указывает на f075h = f518h - 4a3h, но первый байт программы 100h еще не скопирован в f075h.
0000:05a2 c3              RET   ; Последяя инструкция в файле InDuLgEo_V3-B.CoM
Программа обменивает указатели SI и DI, увеличивает SI на 38h и дважды толкает DI в стек. Позже это нам пригодится.
0000:0118 87 f7           XCHG       DI,SI ; DI=100h, SI=f075h
0000:011a 83 ee c8        SUB        SI,-0x38 ; SI=f0adh
0000:011d 57              PUSH       DI ; 100h
0000:011e 57              PUSH       DI ; 100h
Вычислим смещение точки входа f4c4h от начала области памяти f075h:
f4c4h - f075h = 44fh
Добавим 100h и получим адрес 54fh, дизассемблируем код:
                      LAB_0000_054e                                   XREF[1]:     0000:0552(j)  
0000:054e a4              MOVSB      ES:DI,SI
                      LAB_0000_054f                                   XREF[1]:     0000:057f(j)  
0000:054f e8 34 00        CALL       FUN_0000_0586                                    undefined FUN_0000_0586()
0000:0552 72 fa           JC         LAB_0000_054e
0000:0554 41              INC        CX
                      LAB_0000_0555                                   XREF[1]:     0000:055a(j)  
0000:0555 e8 29 00        CALL       FUN_0000_0581                                    undefined FUN_0000_0581()
0000:0558 e3 35           JCXZ       LAB_0000_058f
0000:055a 73 f9           JNC        LAB_0000_0555
0000:055c 83 e9 03        SUB        CX,0x3
0000:055f 72 06           JC         LAB_0000_0567
0000:0561 88 cc           MOV        AH,CL
0000:0563 ac              LODSB      SI
0000:0564 f7 d0           NOT        AX
0000:0566 95              XCHG       AX,BP
                      LAB_0000_0567                                   XREF[1]:     0000:055f(j)  
0000:0567 31 c9           XOR        CX,CX
0000:0569 e8 15 00        CALL       FUN_0000_0581                                    undefined FUN_0000_0581()
0000:056c 11 c9           ADC        CX,CX
0000:056e 75 08           JNZ        LAB_0000_0578
0000:0570 41              INC        CX
                      LAB_0000_0571                                   XREF[1]:     0000:0574(j)  
0000:0571 e8 0d 00        CALL       FUN_0000_0581                                    undefined FUN_0000_0581()
0000:0574 73 fb           JNC        LAB_0000_0571
0000:0576 41              INC        CX
0000:0577 41              INC        CX
                      LAB_0000_0578                                   XREF[1]:     0000:056e(j)  
0000:0578 41              INC        CX
0000:0579 8d 03           LEA        AX,[BP + DI]
0000:057b 96              XCHG       AX,SI
0000:057c f3 a4           MOVSB.REP  ES:DI,SI
0000:057e 96              XCHG       AX,SI
0000:057f eb ce           JMP        LAB_0000_054f
Цикл 054e-0552 снова копирует байты, пока CF=1. Заглянем в FUN_0000_0586 - наверняка, она влияет на CF.
                      FUN_0000_0586                                   XREF[2]:     0000:054f(c), 
                                                                                   FUN_0000_0581:0000:0581(c)  
0000:0586 01 db           ADD        BX,BX
0000:0588 75 04           JNZ        LAB_0000_058e
0000:058a ad              LODSW      SI
0000:058b 11 c0           ADC        AX,AX
0000:058d 93              XCHG       AX,BX
                      LAB_0000_058e                                   XREF[1]:     0000:0588(j)  
0000:058e c3              RET
Процедура удваивает BX, но, если получает 0, загружает в BX слово из памяти. Вспоминаем двоичную арифметику:
2 * 0 = 0
0000H << 1 = 0000H
2 * (-0) = 0
8000H << 1 = 10000H 
старший бит не влез в 16 бит, поэтому отбрасываем, получаем 0
Значит, процедура читает слово из памяти, когда BX = 0 или BX = -0. Чему равен BX?
0000:0111 bb 00 80        MOV        BX,0x8000
Запустим отладчик и убедимся, что BX=8000h, когда программа прыгает к f4c4h. Цикл 054e-0552 копирует байты поверх исходного образа программы, начиная с адреса 100h. Заметает следы или пишет новый код на место старого?
Лезть глубже пока не хочется. Осматриваю код и замечаю:
054f: CALL FUN_0000_0586
...
0558: JCXZ LAB_0000_058f
...
057f: JMP LAB_0000_054f
Цикл 054f-057f похож на while(true), а 0558: JCXZ - на if (0 == CX) break;. Выход из цикла ведет извилистой тропой к 05a2: RET, которая снимает адрес со стека и передает ему управление.
Отладчик подскажет, что на стеке лежит адрес 100h, который 011e: PUSH DI туда толкнула. Процессор возвращается к первой точке входа, но теперь выполняет другой код. Значит, код 054e-05a2 распаковывает программу. Не будем изучать распаковщик, чтобы не поседеть раньше времени.
Сохраним программу на диск, чтобы изучить дизассемблером. Исходный .com-файл занимал область памяти 100h-5a2h, но теперь распух до 65ch. Автор программы машет нам ручкой:
563h: "------------------------------------   Hello, Cracker, looking at my code !   Hope you like! :P                      See you in hell of binary code.        By _/\\_-=InDuLgEo 2011=-_/\\_           ------------------------------------"
Дизассемблируем программу
Программа MS-DOS работает с экраном с помощью системных вызовов, процедур BIOS и пишет в область памяти, которую компьютер проецирует на экран. Инструкция INT выполняет системный вызов или процедуру BIOS. Код содержит инструкции INT трех видов:
- INT 0x10: Процедуры BIOS для работы с видео
- INT 0x16: Процедуры BIOS для работы с клавиатурой
- INT 0x21: Системный вызов MS-DOS
Вот их адреса:
- 0000:012e INT 0x10, AH=00hУстановить режим экрана,- AL=13h
- 0000:018e INT 0x10, AH=00hУстановить режим экрана,- AL=13h
- 0000:028b INT 0x10, AH=00hУстановить режим экрана,- AL=13h
- 0000:032a INT 0x10, AH=02hПереместить курсор на строку- DHи столбец- DL
- 0000:0331 INT 0x21, AH=09hНапечатать строку по адресу- DS:DX
- 0000:033e INT 0x21, AH=09hНапечатать строку по адресу- DS:DX
- 0000:0345 INT 0x21, AH=09hНапечатать строку по адресу- DS:DX
- 0000:034f INT 0x21, AH=09hНапечатать строку по адресу- DS:DX
- 0000:0359 INT 0x21, AH=09hНапечатать строку по адресу- DS:DX
- 0000:0363 INT 0x21, AH=09hНапечатать строку по адресу- DS:DX
- 0000:036d INT 0x21, AH=09hНапечатать строку по адресу- DS:DX
- 0000:0377 INT 0x21, AH=09hНапечатать строку по адресу- DS:DX
- 0000:03b1 INT 0x16, AH=01hПроверить, введен ли символ с клавиатуры
- 0000:03b7 INT 0x16, AH=00hПолучить символ с клавиатуры
- 0000:03bc INT 0x10, AH=00hУстановить режим экрана,- AL=03h
- 0000:03d4 INT 0x16, AH=01hПроверить, введен ли символ с клавиатуры
- 0000:03fb INT 0x21, AH=09hНапечатать строку по адресу- DS:DX
- 0000:0561 INT 0x21, AH=4ChЗавершить программу, вернуть код- AL
Программа восемь раз вызывает MS-DOS Display String, а мы видим восемь строк на экране.
0000:0339 ba 1b 04        MOV        DX,0x41b
0000:033c b4 09           MOV        AH,0x9
0000:033e cd 21           INT        0x21
0000:0340 ba 6d 04        MOV        DX,0x46d
0000:0343 b4 09           MOV        AH,0x9
0000:0345 cd 21           INT        0x21
0000:0347 e8 ac 00        CALL       FUN_0000_03f6                                    undefined FUN_0000_03f6()
0000:034a ba 95 04        MOV        DX,0x495
0000:034d b4 09           MOV        AH,0x9
0000:034f cd 21           INT        0x21
0000:0351 e8 a2 00        CALL       FUN_0000_03f6                                    undefined FUN_0000_03f6()
0000:0354 ba bd 04        MOV        DX,0x4bd
0000:0357 b4 09           MOV        AH,0x9
0000:0359 cd 21           INT        0x21
0000:035b e8 98 00        CALL       FUN_0000_03f6                                    undefined FUN_0000_03f6()
0000:035e ba e5 04        MOV        DX,0x4e5
0000:0361 b4 09           MOV        AH,0x9
0000:0363 cd 21           INT        0x21
0000:0365 e8 8e 00        CALL       FUN_0000_03f6                                    undefined FUN_0000_03f6()
0000:0368 ba 0d 05        MOV        DX,0x50d
0000:036b b4 09           MOV        AH,0x9
0000:036d cd 21           INT        0x21
0000:036f e8 84 00        CALL       FUN_0000_03f6                                    undefined FUN_0000_03f6()
0000:0372 ba 44 04        MOV        DX,0x444
0000:0375 b4 09           MOV        AH,0x9
0000:0377 cd 21           INT        0x21
FUN_0000_03f6 переводит курсор экрана к следующей строке - печатает строку "\r\n".
Ранее код 0240-0285 вызывает процедуру FUN_0000_03e8 для строк, что печатает. Эта процедура расшифровывает строку побайтовым XOR.
0000:0240 b9 1a 00        MOV        CX,0x1a
0000:0243 be 01 04        MOV        SI,0x401
0000:0246 e8 9f 01        CALL       FUN_0000_03e8                                    undefined FUN_0000_03e8()
0000:0249 b9 29 00        MOV        CX,0x29
0000:024c be 1b 04        MOV        SI,0x41b
0000:024f e8 96 01        CALL       FUN_0000_03e8                                    undefined FUN_0000_03e8()
0000:0252 b9 29 00        MOV        CX,0x29
0000:0255 be 44 04        MOV        SI,0x444
0000:0258 e8 8d 01        CALL       FUN_0000_03e8                                    undefined FUN_0000_03e8()
0000:025b b9 28 00        MOV        CX,0x28
0000:025e be 6d 04        MOV        SI,0x46d
0000:0261 e8 84 01        CALL       FUN_0000_03e8                                    undefined FUN_0000_03e8()
0000:0264 b9 28 00        MOV        CX,0x28
0000:0267 be 95 04        MOV        SI,0x495
0000:026a e8 7b 01        CALL       FUN_0000_03e8                                    undefined FUN_0000_03e8()
0000:026d b9 28 00        MOV        CX,0x28
0000:0270 be bd 04        MOV        SI,0x4bd
0000:0273 e8 72 01        CALL       FUN_0000_03e8                                    undefined FUN_0000_03e8()
0000:0276 b9 28 00        MOV        CX,0x28
0000:0279 be e5 04        MOV        SI,0x4e5
0000:027c e8 69 01        CALL       FUN_0000_03e8                                    undefined FUN_0000_03e8()
0000:027f b9 28 00        MOV        CX,0x28
0000:0282 be 0d 05        MOV        SI,0x50d
0000:0285 e8 60 01        CALL       FUN_0000_03e8                                    undefined FUN_0000_03e8()
                      FUN_0000_03e8                                   XREF[9]:     0000:0246(c), 0000:024f(c), 
                                                                                   0000:0258(c), 0000:0261(c), 
                                                                                   0000:026a(c), 0000:0273(c), 
                                                                                   0000:027c(c), 0000:0285(c), 
                                                                                   0000:03f3(j)  
0000:03e8 8a 04           MOV        AL,byte ptr [SI]
0000:03ea 34 18           XOR        AL,0x18
0000:03ec 88 04           MOV        byte ptr [SI],AL
0000:03ee 46              INC        SI
0000:03ef 49              DEC        CX
0000:03f0 83 f9 00        CMP        CX,0x0
0000:03f3 75 f3           JNZ        FUN_0000_03e8
0000:03f5 c3              RET
Применим XOR к тем же байтам в файле и увидим те же строки, что на экране.
def xorString(str, b):
  result = ''
  for c in str:
    result += chr(ord(c) ^ b)
  
  return result
def stringFromHex(hexedString):
  str = ''
  for b in hexedString.split(' '):
    str += chr(int(b, 16))
    
  return str
ciphertext = '24 47 37 44 47 35 25 51 76 5c 6d 54 7f 5d 77 38 2a 28 29 29 25 35 47 37 44 47 3c 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 3c 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 5d 36 57 36 5e 36 22 31 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 3c 51 76 6c 6a 77 36 22 31 38 29 35 26 38 42 20 28 59 38 20 37 29 2e 38 5a 71 6c 6b 38 30 5a 79 6b 71 7b 35 59 4b 55 31 3c 2a 35 26 20 28 20 20 38 2e 2c 28 53 5a 38 30 5c 77 6b 35 27 31 38 38 2b 35 26 5f 79 4c 7d 4b 22 48 37 4d 76 51 60 31 3c 35 59 6a 6c 38 57 7e 38 4a 7d 6e 7d 6a 6b 7d 38 5d 76 7f 71 76 7d 7d 6a 71 76 7f 35 38 38 5d 76 72 77 61 38 51 6c 39 3c 35 4a 7d 4e 7d 4a 6b 5d 6a 37 5b 77 5c 7d 4a 35 38 4b 7d 7d 38 75 61 38 6f 7d 7a 23 38 35 26 70 6c 6c 68 6b 22 37 37 3c 38 38 6b 71 6c 7d 6b 36 7f 77 77 7f 74 7d 36 7b 77 75 37 6b 71 6c 7d 37 71 76 7c 6d 74 7f 7d 77 7d 7c 7c 61 37 38 38 3c'
key = 0x18
print(xorString(stringFromHex(ciphertext), key))
<_/\_-=InDuLgEo 2011=-_/\_$****************************************$***************E.O.F.:)*****************$Intro.:) 1-> Z80A 8/16 Bits (Basic-ASM)$2->8088 640KB (Dos-?)  3->GaTeS:P/UnIx)$-Art Of Reverse Engineering-  Enjoy It!$-ReVeRsEr/CoDeR- See my web; ->https://$  sites.google.com/site/indulgeoeddy/  $
Заменим строки в файле другими - правим распакованную программу, что сохранили в файл DUMP.COM:
- Заменим инструкцию - 0000:03ea 34 18 XOR AL,0x18на- 0000:03ea 34 00 XOR AL,0, или две- NOP, чтобы отключить шифрование.
- Запишем свой текст вместо зашифрованных строк. 
Пусть программа печатает одну строку, а остальные семь вызовов Display String получат пустые строки. Строки лежат в части 301h-434h файла DUMP.COM. Запишем строку Cracked by sa2304 по смещению 301h от начала файла, а остальные байты заменим символом $.
Строка MS-DOS оканчивается символом
$
dd if=cracked-by.txt bs=1 count=308 of=DUMP.COM seek=769 conv=notrunc
Запустим DUMP.COM, убедимся, что программа печатает новый текст.

Ссылки
До встречи!
 
           
 

NeriaLab
Статья интересная, но выявлено несколько допущений:
Хороший Turbo Debugger трудно найти и если вы используете какой-то конкретный из всего списка указанного на сайте old-dos, то указывайте какой именно из списка, там не все корректно работают. Я хороший нашел в Turbo Assember 4.0 (TASM) (последняя строка)
Сам DOSBox "дохлик", у меня стоят DOSBox 0.74.3 и DOSBox-X (этот форк DB развивается)
Почему то не увидел совета по использованию PCEmu для использования DOS/Windows софта на современных машинах? DOS программы, по разному могут себя вести в DB и под PCEmu
Если Вы рекомендуете использовать TD, то почему листинги даёте из Ghidra? Ghidra не видит упаковки таких как PKLITE и других... в TD, можно в работающем коде, хотя бы догадаться. Надо уже иметь опыт в реверсе, чтобы работать с упакованными данными в Ghidra или в IDA
sa2304 Автор
Спасибо за дополнения.
Это мой первый реверс под MS-DOS, так что я не претендую на звание профессора :) Удовлетворил любопытство - нашел повод поработать с 16-битным ассемблером под MS-DOS и познакомиться с Ghidra.
Работает первый же TurboDebugger из списка, аерсия 3.1 от 1992. В том и прелесть реверса - каждый пробует и выбирает инструменты, что работают у него :) Не работает TD.EXE, бросаем, берем другой отладчик.
Автор crackme указал в README, что программа работает под MS-DOS, WinXP и DOSBox - так и скопировал.
Вот с этим согласен :) Для меня дизассемблер еще ни разу не опознал упаковщик автоматически. Сохраняю распакованную программу с помощью отладчика.
Еще один намек, что файл упакован - мы не найдем инструкций
INTв исходном файле, хотя, кажется, они должны быть.NeriaLab
Почему, найти можно - это проходил на одной из моих любимых игр созданной на Watcom C + 2ная упаковка PKLITE + UPX В этом году еще руки не дошли до продолжения, так как начал создавать дизассемблер для DOS под Windows, Именно для того, чтобы точно видеть упаковщики и можно было их "выпиливать", такие как: ExePack, LzExe, PKLite, TityProg, Upx
sa2304 Автор
На каждую новую версию упаковщика ведь нужен новый распаковщик? Наверное, универсального и всесильного распаковщика никогда не создадут, но задача интересная - автоматически опознать и вырубать хотя бы один распаковщик. А затем еще один, и еще... :)
Нашел файл с сигнатурами для PeID - говорит, содержит 1832 сигнатуры, но это только для PE-файлов. Сколько еще алгоритмов можно выдумать для других форматов - не счесть. Пожалуй, составлять сигнатуры упакованных или зараженных файлов - то еще искусство.
NeriaLab
Да, универсального распаковщика нет, у меня есть исходники для всех известных, кроме PKLite. А вот распаковщики для самого PKLite, пересоздавались и одна из версий называется PKLite Arena, другая PKLite 1.2 и т.д.
sa2304 Автор
Интересно, что упаковщик предлагает опцию
-x expand a compressed file. Жаль, что от иных программ остаются только скриншоты и воспоминания.Упаковщики сжимают файл - за это их и любили? Двоичные файлы сжимать трудно, но когда на дискете всего 1.44 Мб, радовались каждому лишнему байту?
Должно быть, порядочный крекер распаковывал программу, побеждал защиту, а затем упаковывал не хуже оригинала :)
NeriaLab
Сейчас не нужно снова упаковывать программу, после дизассемблирования. Её лучше переписать, как одни из примеров - KeeperFX is an open-source remake and fan expansion of Dungeon Keeper
sa2304 Автор
Ух ты, это та забавная игра, в которой играешь за могущественное зло, заставляешь работать местную нечисть, а она не всегда делает то, что приказано?:)
NeriaLab
А Вы что-то другое хотели от нечисти? ;)
sa2304 Автор
Привыкаешь в играх к абсолютной власти над юнитами, но юниты Dungeon Keeper живут сами по себе? Получил приказ, пошел выполнять, пока шел - забыл за чем посылали :) Видимо, нечисть древняя, забывчивая. А, может, бессмертная, потому и заряжена пофигизмом. Сегодня один начальник, завтра - другой.
Еще не играл, но слышал, заинтересовало.
NeriaLab
Похоже, разработчики Dungeon Keeper просто взяли патч-ноты из реального офиса: это не случайные юниты, а менеджеры в прошлой жизни. Получили задачу - пошли, забыли. Напомнили - обиделись и пошли пить чай. А если повезёт - сами начнут строить себе отдельный логово-офис с видом на пропасть. Единственное, чего им не хватает - это "важной встречи, которая ни к чему не ведёт"
sa2304 Автор
Вспомнил из "Зияющих высот":
Зато Dungeon Keeper дает право творцу свалить вину за поражение на подчиненных - мол, у меня-то руки прямые, а вот они мне все испортили. Такой аргумент не пройдет в Civilization.