Полнофункциональный Locker

Сегодня мы будем разбирать хорошо
известную многим программу администратор
компьютерных клубов и интернет кафе с
завидным названием Locker версии 4.85.

Оказалась последняя версия программы,
которую автор защищал самолично. Начиная с
пятой версии программа защищается с
помощью аппаратного ключа, Guardant. Видимо
автор посчитал имеющуюся защиту
недостаточной и перейдя на аппаратный ключ,
что само по себе увеличило стоимость
программы в среднем на 20 долларов, надеется
повысить покупаемость своей программы,
которая теперь высылается бандеролью
вместе с ключом и целым CD диском.

Теперь перейдем непосредственно к нашей
подопытной программе.

Итак, в демо-версии нам предоставляют на
администрирование четыре компьютера, для
ознакомления с работой программы так
сказать. После чего мы должны решить,
оплатить программу и расширить количество
компьютеров или продолжать ей пользоваться
с этим ограничением, в противном случае мы
должны уничтожить все имеющиеся у нас копии
программы.

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

Из всех этих компьютеров работают лишь
четыре первых. Не обманули. Проверить это
можно следующим образом: нажать правой
клавишей мыши на любой из первых четырех
компьютеров и попытаться его оплатить.
Возникнет сообщение о том что "Компьютер
№N не подключен", теперь пробуем
повторить туже самую процедуру с другими
компьютерами, скажем с седьмым, никаких
сообщений при этом выведено не будет.
Теперь закрываем программу и начнем
исследование.

Инструменты, которыми мы будем
пользоваться:

  • отладчик SoftIce
  • дизассемблер Win32Dasw
  • hex-редактор hiew
  • дампер, PE редактор LordPE
  • ImportReconstructor 1.6 final
  • PEID v. 0.9

Для начала посмотрим чем запакован
главный файл программы "Locker.exe", а ежели
не запакован, то на чем написан. Сделаем это
с помощью того же PEID, загрузим в PEID "Locker.exe"
и в меню программы выберем "hardcore scan",
увидим следующее: "ASPack 2.12 -> Alexey Solodovnikov".
Ну что ж, вполне  обычный упаковщик PE
файлов и снова от знакомого нам Алексея
Солодовникова.

Распакуем его. Запустим LordPE, загрузим "Locker.exe"
в него и посмотрим значения полей EntryPoint и
ImageBase.

EntryPoint = 002A9001
ImageBase = 00400000
002A9001 + 00400000 = 006A9001

Теперь запустим саму программу, вызовем
SoftIce и введем "addr locker" и затем "bpx
006A9001", отпустим отладчик, выйдем из
программы и вновь ее запустим. Окажемся в
SoftIce:

017F:006A9001 60 PUSHAD
017F:006A9002 E803000000 CALL 006A900A
017F:006A9007 E9EB045D45 JMP 45C794F7
017F:006A900C 55 PUSH EBP

после инструкции PUSHAD, введем в SoftIce "bpm
esp+4" и нажмем F5, после чего отладчик снова
всплывет в этом месте:

017F:006A93B0 7508 JNZ 006A93BA ; мы
здесь
017F:006A93B2 B801000000 MOV EAX,00000001
017F:006A93B7 C20C00 RET 000C
==> 006A93BA 6800144000 PUSH 00401400 ; 00401400=OEP
- настоящая точка входа в программу
017F:006A93BF C3 RET

протрассируем этот момент до последнего RET
и окажемся в точке входа программы:

017F:00401400 B800D05500 MOV EAX,0055D000 ; мы
здесь
017F:00401405 BB00000000 MOV EBX,00000000
017F:0040140A DBE3 FINIT
017F:0040140C E8BFFFFFFF CALL 004013D0

теперь запомним первые два байта B800 и
заменим их на EBFE (прыжок на себя), введем
команду "e eip" и затем EBFE. Закроем SoftIce,
запустим LordPE, и выберем наш зацикленный
процесс из списка задач, после чего делаем
"full dump" и сохраняем в файл.
Восстанавливаем импорт с помощью
ImportReconstructor и фиксим созданный нами дамп.
После чего остается лишь подправить два
измененных нами байта на точке входа в
программу с EBFE, обратно на B800. Делаем это с
помощью hiew. Программа окончательно
распакована.

Теперь запустим дизассемблер Win32Dasm и
дизассемблируем в нем наш только что
распакованный файл. Получаем листинг,
оказалось программа написана на Кларионе, о
чем свидетельствуют ее функции. Собственно,
нам надо найти место проверки количества
компьютеров, вариантов для этого
действительно много. Попробуем
воспользоваться поиском по строкам,
находим интересующее нас место:

* Reference To: c55runx.Cla$PushString, Ord:016Dh
:0050C3C6 FF1564975900 Call dword ptr [00599764] ; вызов
кларионовской функции Cla$PushString
* Reference To: c55runx.Cla$StackConcat, Ord:01B7h
:0050C3CC FF15E4975900 Call dword ptr [005997E4] ; вызов
кларионовской функции Cla$StackConcat
* Possible StringData Ref from Data Obj ->"не подключен!"
:0050C3D2 B8043B5800 mov eax, 00583B04
:0050C3D7 BB0D000000 mov ebx, 0000000D
* Reference To: c55runx.Cla$PushString, Ord:016Dh
:0050C3DC FF1564975900 Call dword ptr [00599764] ; вызов
кларионовской функции Cla$PushString

на уровень выше этого кода и происходит
проверка:

* Referenced by a CALL at Address:
|:0048146C
|
:00481370 C8000002 enter 0000, 02
:00481374 60 pushad ; сохраняет
регистры
:00481375 0FB71D98E55800 movzx ebx, word ptr [0058E598] ; в
ebx, помещается порядковый номер компьютера,
который мы выбрали
:0048137C 0FB705A4425600 movzx eax, word ptr [005642A4] ; в
eax, помещается заветное число-константа 4
:00481383 39C3 cmp ebx, eax ; здесь
идет сравнение, не превышает ли порядковый
номер выбранного нами компьютера, числа 4
:00481385 0F8FBB000000 jg 00481446 ; и
если превышает, прыгает на 00481446 и выходит
иначе продолжает
:0048138B 8B75FC mov esi, dword ptr [ebp-04]
:0048138E 0FB7864CFEFFFF movzx eax, word ptr [esi+FFFFFE4C]
:00481395 39C3 cmp ebx, eax
:00481397 0F8FA9000000 jg 00481446
:0048139D 0FB7864EFEFFFF movzx eax, word ptr [esi+FFFFFE4E]
:004813A4 39C3 cmp ebx, eax
:004813A6 0F8F9A000000 jg 00481446
:004813AC A161E65800 mov eax, dword ptr [0058E661]

* Reference To: c55runx.Cla$DPushULong, Ord:0081h
:004813B1 FF15B0955900 Call dword ptr [005995B0] ; вызов
кларионовской функции Cla$DPushULong
:004813B7 B800000000 mov eax, 00000000

* Reference To: c55runx.Cla$DPushLong, Ord:007Ch
:004813BC FF15A8955900 Call dword ptr [005995A8] ; вызов
кларионовской функции Cla$DPushLong

* Reference To: c55runx.Cla$DecDistinct, Ord:005Ch
:004813C2 FF1554955900 Call dword ptr [00599554] ; вызов
кларионовской функции Cla$DecDistinct
:004813C8 83F800 cmp eax, 00000000
:004813CB 7579 jne 00481446
:004813CD 8D8650FEFFFF lea eax, dword ptr [esi+FFFFFE50]
:004813D3 BB14000000 mov ebx, 00000014

* Reference To: c55runx.Cla$PushString, Ord:016Dh
:004813D8 FF1564975900 Call dword ptr [00599764] ; вызов
кларионовской функции Cla$PushString

* Reference To: c55runx.Cla$Stack2DStack, Ord:01ADh
:004813DE FF15C8975900 Call dword ptr [005997C8] ; вызов
кларионовской функции Cla$Stack2DStack

; вырезано

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00481385(C), :00481397(C), :004813A6(C), :004813CB(C)
:00481446 61 popad ; восстанавливает
сохраненные регистры
:00481447 C9 leave
:00481448 C3 ret ; выход из
процедуры

Т.о. нам надо лишь изменить значение 4 по
адресу [005642A4] на большее, положим 50 hex, а это 80
компьютеров. Смотрим по этому адресу в hiew,
но числа 4 там не оказывается, очевидно
программа записывает его туда при загрузке.
С помощью SoftIce отлавливаем где происходит
изменение значения и находим по адресу
004698B4:

* Reference To: c55runx.Cla$DPushLong, Ord:007Ch
:004698A2 FF15A8955900 Call dword ptr [005995A8] ; вызов
кларионовской функции Cla$DPushLong

* Reference To: c55runx.Cla$DecSub, Ord:0061h
:004698A8 FF1568955900 Call dword ptr [00599568] ; вызов
кларионовской функции Cla$DecSub

* Reference To: c55runx.Cla$DPopLong, Ord:0074h
:004698AE FF1594955900 Call dword ptr [00599594] ;
:004698B4 66A3A4425600 mov word ptr [005642A4], ax ; программа
записывает по адресу [005642A4] большое число
:004698BA B803000000 mov eax, 00000003 ; после
чего в eax заносится 3
:004698BF 66833DA442560000 cmp word ptr [005642A4], 0000 ; сравнивается
записанное с 0
:004698C7 7614 jbe 004698DD ; и если
меньше или равно нулю прыгает на 004698DD
:004698C9 66833DA442560063 cmp word ptr [005642A4], 0063 ; сравнивается
записанное с 63
:004698D1 770A ja 004698DD ; если
больше прыгает на 004698DD
:004698D3 66833DA442560063 cmp word ptr [005642A4], 0063 ; сравнивается
записанное с 63
:004698DB 7508 jne 004698E5 ; если
не равно прыгает на 004698E5

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004698C7(C), :004698D1(C)
:004698DD 6640 inc ax ; увеличивает
регистр eax на один, как мы помним там было 3
:004698DF 66A3A4425600 mov word ptr [005642A4], ax ; и
помещает по адресу [005642A4], теперь мы знаем
откуда оно там берется

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004698DB(C)
:004698E5 B828EA5600 mov eax, 0056EA28
:004698EA BB00000000 mov ebx, 00000000

* Reference To: c55runx.Cla$PushString, Ord:016Dh
:004698EF FF1564975900 Call dword ptr [00599764] ; вызов
кларионовской функции Cla$PushString
:004698F5 B828EA5600 mov eax, 0056EA28
:004698FA BB03000000 mov ebx, 00000003

Теперь делаем следующее, по адресу 004698B4
вместо команды:

mov word ptr [005642A4], ax

записываем команду:

mov word ptr [005642A4], 50

и делаем безусловный прыжок на 004698E5

Внесем изменения в файл, для этого запустим
hiew, откроем наш файл, перейдем по адресу
004698B4, выберем режим дизассемблера и запишем
туда вышеприведенную команду:

mov w,[005642A4], 50

после чего

jmp 698E5

Сохраняем изменения и выходим. Теперь
запускаем программу и проверяем - не
работает. С помощью SoftIce смотрим на смещение
куда мы записали 50, там снова оказывается
злосчастное 4, очевидно что-то мы пропустили.
В действительности, после наших
манипуляции происходит очередная проверка
и снова туда записывается 4. Это происходит
по адресу 469C9F:

:00469C93 FF155C955900 Call dword ptr [0059955C]

* Reference To: c55runx.Cla$DPopLong, Ord:0074h
:00469C99 FF1594955900 Call dword ptr [00599594] ; вызов
кларионовской функции Cla$DPopLong
:00469C9F 66A3A4425600 mov word ptr [005642A4], ax ; ax = 4

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00469AA8(C), :00469AD5(U), :00469C57(C), :00469C7E(U)

Просто уберем эту команду, забив ее nop'ами.
Для этого повторим процедуру с hiew, перейдем
по адресу 00469C9F, выберем режим hex редактора и
введем 6 nop (90). Сохраняем, выходим.

Снова запускаем программу и пробуем
оплатить, к примеру, седьмой компьютер,
получаем должное сообщение, как и в случае
первых четырех, что компьютер не подключен.
Таким образом работа выполнена, теперь все,
а вернее 80 компьютеров, как мы указали,
обрабатываются.

А теперь самый интересный момент. Казалось
бы все успешно функционирует,
обрабатываются все 80 компьютеров. Но.
Проходит буквально пару минут и компьютеры
снова перестают реагировать. Смотрим в
отладчике на адрес куда мы записали
количество компьютеров - все на месте.

Возвращаемся на то место, где происходит
проверка:

:0048138B 8B75FC mov esi, dword ptr [ebp-04]
:0048138E 0FB7864CFEFFFF movzx eax, word ptr [esi+FFFFFE4C] ; в
[esi+FFFFFE4C] сначала находится C7, а после
обработки таймера 4.
:00481395 39C3 cmp ebx, eax ; здесь
идет сравнение, не превышает ли порядковый
номер выбранного нами компьютера
вышеописанного числа
:00481397 0F8FA9000000 jg 00481446 ; если
превышает  - выходит.
:0048139D 0FB7864EFEFFFF movzx eax, word ptr [esi+FFFFFE4E]

Вот мы и нашли причину данного феномена. А
дело все в том, что в программе присутствует
еще одна, скрытая проверка. После первой
проверки идет еще одна, только проявляется
она не сразу. Вначале по адресу [esi+FFFFFE4C]
находился С7, этого нам вполне достаточно и
проверка с порядковым номером выбранного
нами компьютера проходит успешно. Но через
установленный по таймеру промежуток
времени, по этому адресу [esi+FFFFFE4C] заносится
злосчастное 4 и проверка, естественно, дает
отрицательный результат. Таким образом
теперь нам надо пропатчить процедуру
таймера так, чтобы он также возвращал
значение 50 hex = 80 dec.

С помощью SoftIce находим, где происходи
изменение значения:

* Reference To: gsmlib.GETV@Fsbsbsb, Ord:0042h
:0048A545 FF15D09A5900 Call dword ptr [00599AD0]
:0048A54B 8D8E16FDFFFF lea ecx, dword ptr [esi+FFFFFD16]
:0048A551 894DDC mov dword ptr [ebp-24], ecx
:0048A554 89C8 mov eax, ecx
:0048A556 BB14000000 mov ebx, 00000014

* Reference To: c55runx.Cla$PopString, Ord:0159h
:0048A55B FF1530975900 Call dword ptr [00599730] ; вызов
кларионовской функции Cla$PopString
:0048A561 66C7864EFEFFFFC700 mov word ptr [esi+FFFFFE4E], 00C7 ; здесь
мы поменяем C7 на количество наших
компьютеров
:0048A56A 8B8694FEFFFF mov eax, dword ptr [esi+FFFFFE94]
:0048A570 C7405C1E000000 mov [eax+5C], 0000001E
:0048A577 89C8 mov eax, ecx
:0048A579 BB14000000 mov ebx, 00000014

* Reference To: c55runx.Cla$PushString, Ord:016Dh
:0048A57E FF1564975900 Call dword ptr [00599764]

слегка изменим инструкцию по адресу 0048A561 с
mov word ptr [esi+FFFFFE4E], 00C7 на mov word ptr [esi+FFFFFE4E], 0050

после чего надо пропатчить еще один момент:

* Reference To: c55runx.Cla$DPushLong, Ord:007Ch
:0048AA25 FF15A8955900 Call dword ptr [005995A8] ; вызов
кларионовской функции Cla$DPushLong
:0048AA2B B80A000000 mov eax, 0000000A

* Reference To: c55runx.Cla$DPushLong, Ord:007Ch
:0048AA30 FF15A8955900 Call dword ptr [005995A8] ; вызов
кларионовской функции Cla$DPushLong

* Reference To: c55runx.Cla$DecDivide, Ord:005Eh
:0048AA36 FF155C955900 Call dword ptr [0059955C] ; вызов
кларионовской функции Cla$DecDivide

* Reference To: c55runx.Cla$DPopLong, Ord:0074h
:0048AA3C FF1594955900 Call dword ptr [00599594] ; вызов
кларионовской функции Cla$DPopLong
:0048AA42 6689864EFEFFFF mov word ptr [esi+FFFFFE4E], ax ; ax=4
и заносится по адресу [esi+FFFFFE4E], этого нам не
надо - nop'им эту инструкцию

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0048A72F(C), :0048A773(U), :0048A9E5(C), :0048AA17(U)
:0048AA49 61 popad

По адресу 0048AA42 убираем инструкцию mov word ptr
[esi+FFFFFE4E], ax.

Теперь производим эти изменения в hiew: как
обычно запускаем hiew, открываем в нем наш
файл, выбираем режим дизассемблера,
переходим по адресу 0048A561, жмем F2 и заменяем
mov word ptr [esi+FFFFFE4E], 00C7 на mov word ptr [esi+FFFFFE4E], 0050,
после чего сохраняем F9 и переходим на адрес
0048AA42, где nop'им 7-мибайтовую инструкцию mov word
ptr [esi+FFFFFE4E], a, после чего сохраняем изменение
и выходим.

Исключительно в образовательных целях и
все такое...