Интро.
Купил я как-то на Горбушке диск с хорошей программкой. Карта Москвы, автодороги, прокладка маршрута, GPS-навигация и прочее. Пришел домой, вставил диск в комп, понаслаждался. Правда, подуставший сидюк хрюкал натужно. И решил я переписать ее на винт, дабы грузилась быстрее, диск не царапался, и из кресла не приходилось бы вставать каждый раз, когда нужна прога. Давайте разберемся, что из этого получится.
На диске было немного папок, меня интересовала лишь папка RAMS0402.
Пациент:
Атлас Автодорог. Улицы Москвы. Выпуск RAMS-04/02
Симптомы:
не работает с винчестера.
Обследование:
Исполняемый файл один, wmapgps.exe, куча файлов с ресурсами. Программа не упакована и написана на Microsoft Visual C++ 5.0.
Запускаем wmapgps.exe. Появляется симпатичное окошко с заставкой программы, а потом вываливается надпись «В следующий раз убедитесь, что CD «Атлас Автодорог. Улицы Москвы» вставлен в CD-привод. А пока программа завершается» с кнопкой ОК. Вставляем компакт с прогой в
сидюк, снова запускаем wmapgps.exe. Результат аналогичный.
Предположительный диагноз:
Программа привязана к компакт-диску.
Инструментарий:
- мозги;
- прямые руки;
- комп;
- SoftIce;
- W32Dasm;
- QView или другой 16-ричный редактор;
- FAR (из него удобно запускать мой любимый
QView :);
Лечение.
Копируем wmapgps.exe в original.exe – резервная копия.
Копируем wmapgps.exe в wmapgps.w32 – для дизассемблера.
Запускаем original.exe. Программа ничего не выводит и завершает работу. Следовательно, есть привязка к имени запускаемого файла. Нам это не сильно мешает, разве что мы решим переименовать рабочий файл, но зачем нам лишний гимор? Будем работать с начальным файлом –
wmapgps.exe. За проверку типа диска у нас отвечает функция GetDriveTypeA. Запускаем Symbol Loader из пакета SoftIce, загружаем в него пациента, жмем вторую слева кнопку на панели инструментов (Load), и мы в дебаггере. Ставим точку останова (брякпойнт) на функцию
GetDriveTypeA:
bpx GetDriveTypeA
Выходим из SoftIce, нажав Ctrl-D или F5, и тем самым разрешаем программе запуститься.
Тут же снова вываливаемся в дебаггер – сработал наш брякпойнт. Мы оказываемся в модуле KERNEL32. Жмем F12, чтобы выйти в то место, откуда вызвалась эта функция. Оказываемся в модуле OLEAUT32. Осечка!!! Это не то, что нам нужно. Нам нужно засечь место, откуда в модуле wmapgps.exe идет вызов функции GetDriveTypeA. Поэтому снова жмем F5, когда вывалимся в дебаггер – F12. Опять 19h. То есть, опять OLEAUT32. Спокойствие, только спокойствие. Повторяем эти действия 5 или 6 раз (то есть еще 3-4 раза) и наконец оказываемся в модуле WMAPGPS. Тут мы видим такие строки:
00440CEF CALL [KERNEL32!GetDriveTypeA]
00440CF5 CMP EAX, 05
00440CF8 JZ 00440D15
00440CFA LEA ECX, [ESP+0C]
00440CFE MOV DWORD PTR [ESP+0000009C], FFFFFFFF
00440D09 CALL 004512B4
00440D0E XOR AL, AL
00440D10 JMP 00440EC5
Функция GetDriveTypeA в регистре EAX возвращает значение, для CD – число 05, для жесткого диска 03. По адресу 00440CF5 стоит проверка, не CD ли это? Если да, то выполняется условный переход по адресу 00440CF8. Нам нужно, чтобы он выполнялся всегда. Для этого следует подредактировать наш файл. Вот здесь нам и пригодится W32Dasm. Я не люблю считать вручную смещение в файле, где находится команда с нужным мне адресом. W32Dasm прекрасно это сделает и без нас.
Загружаем W32Dasm, открываем в нем файл wmapgps.w32, проходит некоторое время, и у нас готов ассемблерный листинг. В меню выбираем Goto->Goto Code Location, вводим нужный нам адрес 00440CF8 и оказываемся в нужном нам месте. В строке статуса видим надпись:
Code Data @: 00440CF8 @Offset 400F8h in File:wmapgps.exe
Число 400F8h, которое идет после слова Offset – и есть нужное нам смещение в файле, где нам нужно замутить наш патч
(некоторые могут спросить, а нафиг нам вообще этот софтайс, если в дизассемблере можно вызвать список импортируемых функций, найти нужную функцию, дважды кликнуть, и мы окажемся в нужном месте в коде? Отвечаю: Софтайс рулит!!!).
Запускаем FAR, в нем пишем:
qview wmapgps.exe (т. е. запускаем 16-ричный редактор
QView).
Жмем F4 несколько раз, пока не переключимся в кодовый режим. Далее F5, вводим смещение 400F8, и мы в нужном месте. Включаем режим редактирования Alt-F3, далее Tab, чтобы перейти в окно с командами, меняем команду JZ 00040115 на JMP 00040115. Далее Enter, Tab, Alt-F9 для сохранения изменений.
Жмем Esc, выходим из редактора. Файл пропатчен. Можно запускать.
Запускаем. Опа! Редкая птица Обломинго машет нам своим розовым крылом. Программа не только не работает, но и перестала выводить лого и сообщение. И все из-за одного измененного байта. Следовательно, есть еще один уровень защиты, а именно проверка контрольной суммы. Нам нужно ее убить. Но сначала найдем гнездо этой твари.
Посмотрим список строковых ресурсов (String Data References) в W32Dasm. Там есть такие подозрительные строки:
“LCRC”
“Lcrc failed”
Эти строки встречаются только в одном месте в нашем файле. Вот кусок кода:
* Possible StringData Ref from Data Obj ->"LCRC"
|
:0044CEAA 68C4784600 push 004678C4
:0044CEAF 51 push ecx
:0044CEB0 8BF8 mov edi, eax
:0044CEB2 E8A9F6FFFF call 0044C560
:0044CEB7 83C408 add esp, 00000008
:0044CEBA 8B10 mov edx, dword ptr [eax]
:0044CEBC C64424480B mov [esp+48], 0B
:0044CEC1 52 push edx
* Reference To: MSVCRT.atoi, Ord:0238h
|
:0044CEC2 FF155C9C4600 Call dword ptr [00469C5C]
:0044CEC8 83C404 add esp, 00000004
:0044CECB 33DB xor ebx, ebx
:0044CECD 663BF8 cmp di, ax
:0044CED0 8D4C2424 lea ecx, dword ptr [esp+24]
:0044CED4 0F95C3 setne bl
:0044CED7 C644244801 mov [esp+48], 01
* Reference To: MFC42.MFC42:NoName0006, Ord:0320h
|
:0044CEDC E8D3430000 Call 004512B4
:0044CEE1 84DB test bl, bl
:0044CEE3 7440 je 0044CF25 прыгаем, если BL=0,
иначе выполняем код “Lcrc failed ”
* Possible StringData Ref from Data Obj ->"Lcrc failed"
|
:0044CEE5 68B8784600 push 004678B8
:0044CEEA 8BCE mov ecx, esi
:0044CEEC E89F090000 call 0044D890
:0044CEF1 8D4C2410 lea ecx, dword ptr [esp+10]
:0044CEF5 C644244800 mov [esp+48], 00
Итак, по адресу 0044CEE3 находится условный переход, который происходит, если значение регистра BL равно 0. Это значение, скорее всего, изменяет процедура, вызов которой располагается по адресу 0044CEDC. Поставим брякпойнт на нее и проверим. Загрузим наш файл в Symbol Loader, попадем в дебаггер и дадим команду:
bpx 44CEDC
Далее нажмем F5 для продолжения. Вываливаемся в дебаггер, запоминаем значение в BL, нажимаем F10 – исполняем процедуру, и видим, что значение в BL не изменилось.
Смотрим выше и видим такой кусок:
:0044CECD 663BF8 cmp di, ax
:0044CED0 8D4C2424 lea ecx, dword ptr [esp+24]
:0044CED4 0F95C3 setne bl
То есть сравнивается значение регистров DI и AX, и если они не равны, то в BL заносится 1. Вот оно! В регистрах DI и AX находятся действительная и эталонная контрольные суммы соответственно. Несложно софтайсом проверить, поставив брякпойнт на адрес 44CECD, что в оригинальном файле эти значения равны, а в пропатченной – нет. Соответственно, в первом случае BL равен 0, а во втором – 1.
Нам же нужно, чтобы там всегда был 0. Вместо команды
SETNE BL
мы можем вставить команды
XOR BL, BL
NOP
Последняя команда нужна потому, что SETNE BL имеет размер 3 байта, а XOR BL, BL – всего 2 байта. Третий байт мы заполняем пустой командой, размер которой 1 байт.
Для этого патча запускаем FAR с QView.
Переключаемся в кодовый режим, жмем F5, чтобы перейти к нужному смещению, и вспоминаем, что не знаем этого смещения. Лезем в W32Dasm, перемещаем зеленую полоску на команду SETNE BL и в строке статуса видим:
Code Data @: 0044CED4 @Offset 4C2D4h in File:wmapgps.exe
Нужное нам смещение – 4C2D4. Лезем обратно в Qview, вводим смещение, включаем режим редактирования и правим текущую команду как описано выше. Сохраняем изменения, выходим. Можно запускать.
Запускаем. Смотрим на знакомое до боли окошко с предложением засунуть этот компакт-диск куда подальше... Вспоминаем маму автора программы. Курим, успокаивая дрожь в руках. Включаем мозги. Кроме проверки типа диска программа может использовать проверку на количество свободного места. Это делается функцией GetDiskFreeSpaceA. В W32Dasm смотрим список импортируемых функций, находим нужную нам, дважды кликаем – есть, причем только в одном месте. Да еще и недалеко от места, где идет проверка на тип диска. Смотрим кусок кода:
* Reference To: KERNEL32.GetDiskFreeSpaceA, Ord:00DBh
|
:00440DAF FF15CC994600 Call dword ptr [004699CC]
:00440DB5 85C0 test eax, eax
:00440DB7 751B jne 00440DD4
(____вырезано____)
:00440DD4 8B542420 mov edx, dword ptr [esp+20]
:00440DD8 8B442418 mov eax, dword ptr [esp+18]
:00440DDC 0FAF54241C imul edx, dword ptr [esp+1C]
:00440DE1 6800001000 push 00100000
:00440DE6 52 push edx
:00440DE7 50 push eax
* Reference To: KERNEL32.MulDiv, Ord:01AAh
|
:00440DE8 FF157C994600 Call dword ptr [0046997C]
(____вырезано____)
* Reference To: MSVCRT.atoi, Ord:0238h
|
:00440E21 8B3D5C9C4600 mov edi, dword ptr [00469C5C]
:00440E27 C684249C00000002mov byte ptr [esp+0000009C], 02
:00440E2F 8B48F8 mov ecx, dword ptr [eax-08]
:00440E32 85C9 test ecx, ecx
:00440E34 740A je 00440E40
:00440E36 50 push eax
:00440E37 FFD7 call edi
:00440E39 83C404 add esp, 00000004
:00440E3C 3BF0 cmp esi, eax
:00440E3E 7C15 jl 00440E55
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00440E34(C)
|
:00440E40 8B442410 mov eax, dword ptr [esp+10]
:00440E44 8B48F8 mov ecx, dword ptr [eax-08]
:00440E47 85C9 test ecx, ecx
:00440E49 7443 je 00440E8E
:00440E4B 50 push eax
:00440E4C FFD7 call edi
:00440E4E 83C404 add esp, 00000004
:00440E51 3BF0 cmp esi, eax
:00440E53 7E39 jle 00440E8E
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00440E3E(C)
|
:00440E55 8D4C2410 lea ecx, dword ptr [esp+10]
(____вырезано____)
* Reference To: MFC42.MFC42:NoName0006, Ord:0320h
|
:00440E85 E82A040100 Call 004512B4
:00440E8A 32C0 xor al, al
:00440E8C EB37 jmp 00440EC5
Много текста? Вкратце поясню. Че-то там считается, с чем-то сравнивается... Лично мне влом вникать во весь этот бред, поэтому давайте пойдем по пути наименьшего сопротивления. Логика такова: протрассировать дебаггером данный кусок программы, запуская ее с компакт-диска и с винчестера, записывая выполнение условных переходов. Их всего 6, они выделены. В результате получим, что первые 5 переходов совпадают, и только переход по адресу 00440E53 при запуске с компакта выполняется, а при запуске с винта – нет. Но это легко исправить. Заменим команду JLE 00440E8E на JMP 00440E8E. Запускаем FAR, из него QView, идем по смещению 40253 (да, не забыли посмотреть его в W32Dasm) и правим. Сохраняем изменения. Можно запускать.
Запускаем. Смотрим на окошко. Успокаиваемся. Склеиваем клавиатуру. Что еще мы забыли? А как насчет привязки к имени диска? А ведь где-то между проверкой типа диска и свободного места был вызов функции GetVolumeInformationA. Она возвращает имя тома и еще немного разной полезной информации. Ладно, попробуем взять ее хитростью (я имею ввиду защиту, а вы о чем подумали). Посмотрим имя компакт-диска. Это “RAMS0402”, без кавычек, заглавными буквами. А теперь поменяем имя раздела, с которого мы запускаем нашу прогу. У меня это диск D: с именем MEDIA. “Мой компутер”-> диск D: -> Свойства. Пишем метку тома RAMS0402. Попробуем запустить.
БИНГО! Все работает. Ты крут, кракер. Мы ее сделали. Ленивые могут идти спать, а мы поиграемся еще немного. Ведь кряк не закончен. Было бы верхом крутизны заставить прогу работать в разделе с любой меткой тома. После вызова GetVolumeInformationA в выделенную область памяти копируется метка тома с завершающим нулем и другая разная инфа. Далее метка тома (в оригинале должна быть RAMS0402) неким образом шифруется, куда-то копируется и далее используется. Разбираться было влом, и я подумал: а не записать ли нам вручную строку RAMS0402 в эту область памяти? Как нам это сделать? Посмотрим кусок кода:
* Reference To: KERNEL32.GetVolumeInformationA, Ord:014Fh
|
:00440D37 FF15C4994600 Call dword ptr [004699C4]
:00440D3D 85C0 test eax, eax
:00440D3F 751B jne 00440D5C
:00440D41 8D4C240C lea ecx, dword ptr [esp+0C]
:00440D45 mov dword ptr [esp+0000009C], FFFFFFFF
* Reference To: MFC42.MFC42:NoName0006, Ord:0320h
|
:00440D50 E85F050100 Call 004512B4
:00440D55 32C0 xor al, al
:00440D57 E969010000 jmp 00440EC5
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00440D3F(C)
|
:00440D5C 8BB424A4000000 mov esi, dword ptr [esp+000000A4]
:00440D63 8B9C24A8000000 mov ebx, dword ptr [esp+000000A8]
:00440D6A 8D542434 lea edx, dword ptr [esp+34]
После вызова функции идет проверка, не возвращен ли код ошибки. Если все ОК, по адресу 00440D3F прыгаем по адресу 00440В5С. Выходит, что код с адреса 00440D3D по 00440D5C не используется. А это 31 байт. Вот сюда мы и запишем наш кусочек кода. Но куда мы будем писать нужную строку? С помощью софтайса несложно увидеть, что по адресу 00440D6A инструкция LEA EDX, DWORD PTR [ESP+34] загружает в регистр DX число, которое является адресом буфера, куда пишется метка тома. Следовательно, наш код будет выглядеть так:
LEA EDX, DWORD PTR [ESP+34]
MOV DWORD PTR [EDX], 534D4152 “RAMS”
MOV DWORD PTR [EDX+4], 32303430 “0402”
MOV BYTE PTR [EDX+8], 0 завершающий 0
Вся эта радость занимает 21 байт, поэтому разбавим ее 10-ю байтами NOP-ов. Смотрим смещение в W32Dasm для адреса 00440D3D (это 4013D), запускаем QView и по данному смещению вписываем 4 данных инструкции и еще 10 инструкций NOP. Сохраняем изменения, меняем обратно метку тома на ту, что была раньше (у меня – MEDIA). Можно запускать.
Запускаем. @%@#$%@!!!!!!!!! Работает! Не может быть! Запускаем еще раз 10, чтобы убедиться наверняка.
Ну и напоследок сделаем еще вот как: в FAR зададим команду:
fc /b original.exe wmapgps.exe > patch.txt
В результате получим файл patch.txt:
Сравнение файлов original.exe и wmapgps.exe
000400F8: 74 EB
0004013D: 85 8D
0004013E: C0 54
0004013F: 75 24
00040140: 1B 34
00040141: 8D C7
00040142: 4C 02
00040143: 24 52
00040144: 0C 41
00040145: C7 4D
00040146: 84 53
00040147: 24 C7
00040148: 9C 42
00040149: 00 04
0004014A: 00 30
0004014B: 00 34
0004014C: FF 30
0004014D: FF 32
0004014E: FF C6
0004014F: FF 42
00040150: E8 08
00040151: 5F 00
00040152: 05 90
00040153: 01 90
00040154: 00 90
00040155: 32 90
00040156: C0 90
00040157: E9 90
00040158: 69 90
00040159: 01 90
0004015A: 00 90
0004015B: 00 90
00040253: 7E EB
0004C2D4: 0F 30
0004C2D5: 95 DB
0004C2D6: C3 90
На первой позиции идет смещение в файле, далее значение по этому смещению в файле-оригинале, и, наконец, значение в пропатченном файле. Юзайте на здоровье.
Заключение:
пациент успешно вылечен.
Для авторов программы:
Я диск честно купил, и копия у меня всего одна. А этот опус носит образовательный характер и предназначен для разработчиков программных защит. Автор не несёт никакой ответственности за возможное использование материалов данной статьи в противозаконных целях.
🙂
С вами был Nick aka Crazy (cascara@mail.ru).