Программа 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