Что собираемся делать ?
  Объект исследования: программа Ulead COOL 3D, триальная защита
  Инструменты: Soft-Ice (под '98), немного мозгов
  Цель: изучение win32-программ, обладающих защитой от отладки
  Примечание: ничего особо нового, простая техника, SEH, etc...
  Ниже я собираюсь исследовать защиту этой проги, которая позволяет пользователю использовать ее функциональность только в течении календарного месяца (30 дней) или купить возможность ее использования неограниченно во времени (вид входного окошка после инсталляции, инсталлятор UC3D35TBYB_E.exe):
 
  Что она вообще делает ? Примерно - это какой-то графический редактор (вид после нажатия кнопки "TRY"):
 
 Таким образом в первом приближении сразу после инсталляции данная программа позволяет полноценно работать, но в стартовом окошке напоминает о том, что до конца использования осталось 30 дней ("Remaining Days - 30") и предлагает, видимо, купить разрешение на полное использование ("BUY").
 Поисследуем реакцию программы на изменение системной даты. Поменяем текущую дату на один день вперед:
 
 Ага, программа предупреждает нас, что осталось на 1 день меньше. Если перевести дату ровно на месяц вперед, то кнопка "TRY" становится недоступна и число оставшихся дней равно 0:
 
  Кнопка "TRY" недоступна. Попробуем поймать программу на том, как она закрывает для доступа кнопку и поисследовать, чем она при этом руководствуется. Win32-приложение обычно делает это при помощи API-функции EnableWindow:
 EnableWindow(Id_Объекта_Окна,0/1-закрыть/открыть Объект)
 Устанавливаем на машине дату таковой, чтобы программа решила, что осталось 0 дней для использования, устанавливаем breakpoint в SoftIce:
 bpx EnableWindow
  запускаем программу, получаем break и видим в отладчике место вызова (оказалось, код принадлежит некоей xsystem.dll, которую можно найти в том каталоге, куда инсталлировалась программа Ulead COOL 3D):
026D7F5F: mov edi,User32!EnableWindow push edi mov edi,esp pushad lea esi,[ebp+2C63h] lodsb cmp al,90h jnz 026D7F8A push 03E8h push dword ptr [edi+8] call [ebp+2B50h] push 0 ; FALSE - disable button "TRY" push eax call [ebp+2B54h] ; Точка вызова EnableWindow 026D7F8A: popad ...
  Итак, программа анализирует первый байт некоторой структуры (lea esi,[ebp+2C63h], lodsb, cmp al,90h), сравнивает его с 90h, если он оказывается таковым, то кнопка "TRY" становится недоступной (переход на метку 026D7F8A). Попробуем проверить гипотезу о том, что все, что нужно для "освобождения" программы от проверки даты - это сделать прямо здесь достпуной кнопку "TRY". Попробуем сделать это прямо в отладчике: установим break на адрес 026D7F5F:
 bpx LoadLibraryA ; Ждем загрузки xsystem.dll
  ...
 bc 0 ; clear breakpoint at LoadLibraryA
 bpx 026D7F5F ; Set breakpoint
  Ctrl+D...
  и попытаемся дождаться срабатывания break'а и "руками" поменять в отладчике значение регистра al сразу после lodsb: но вместо того, чтобы получить break на этом (026D7F5F) адресе, мы получаем GPF... Видно (в Sice'е), что машина пытается исполнять какой-то мусор в xsystem.dll.
  ...Так мы выяснили, что программа не только контролирует системную дату, но и сопротивляется нашим попыткам ее трейсить ;) Наверное, программа как-то динамически модифицирует свой код - например по адресу 026D7F5F - и break, который мы пытаемся установить, мы устанавливаем в то место, которое впоследствии будет изменено. Скажем, самораспаковывающийся или саморасшифровывающийся код может быть причиной тому. Попробуем как-то обойти это ограничение и вспоминаем про то, что программа неминуемо должна узнать системную дату - ведь ей необходимо ее сравнить с чем-то типа даты инсталляции. Win32-приложение обычно делает это при помощи API-функции GetSystemTime:
  GetSystemTime(offset структуры типа SYSTEMTIME)
SYSTEMTIME STRUCT wYear WORD ? wMonth WORD ? wDayOfWeek WORD ? wDay WORD ? wHour WORD ? wMinute WORD ? wSecond WORD ? wMilliseconds WORD ? SYSTEMTIME ENDS
  Устанавливаем break на эту API-функцию:
 bpx GetSystemTime
  И обнаруживаем, что вызов произошел из следующего места (опять xsystem.dll):
026D6DE3: lea esi,[ebp-100h] push esi call [ebp-4] ; call GetSystemTime pop ebx 026D6DEE: movzx edx,word ptr [esi] ; d esi -> D3 07 03 00 05 00 1C ; edx= 07D3h shl edx,8 mov dl,[esi+02] ; Месяц shl edx,8 mov dl,[esi+06] ; День ; edx=07D3031C push edx
  Уже видно, как программа работает с датой: код по адресу ~026D6DE3 получает косвенным call'ом дату от Win, собирает в 32 битах edx год (07D3h=2003), месяц (03) и день (1Ch=28) - 28 марта 2003 года в данном случае, делает что-то еще и управление попадает к коду в ~026D7F5F, который уже знает, в валидном ли интервале дат мы находимся и закрывает кнопку "TRY", если это не так. Но мы помним, что нужно все же попробовать подменить результат манипуляций с датой прямо перед принятием решения о disable кнопки "TRY". Ранее нам сделать это не удалось - видимо, после загрузки dll в память ее (?-пока не знаем) код самомодифицируется и мешает ставить полноценные break'и. Однако есть надежда, что в момент выполнения кода по адресу ~026D6DE3 - кода, спрашивающего дату у Win - код в ~026D7F5F расшифрован и доступен для отладки. Повторяем попытку установить break на 026D7F5F, но уже после того, как мы попали в 026D6DE3 и опять получаем GPF ! Почему, ведь код уже явно виден в отладчике ? Может, дело в том, что адрес API EnableWindow прописывается динамически прямо в команду mov edi,const_addr ? Пробуем установить break на команду загрузки регистра esi адресом структуры (lea esi,[ebp+2C63h]) и ... оказываемся в нужном месте:
026D7F5F: mov edi,User32!EnableWindow ... 026D7F68: lea esi,[ebp+2C63h] lodsb ; al = 90h если запрещенный период, иначе всегда 6Ah cmp al,90h jnz 026D7F8A push 03E8h push dword ptr [edi+8] call [ebp+2B50h] push 0 ; FALSE - disable button "TRY" push eax call [ebp+2B54h] ; Точка вызова EnableWindow 026D7F8A: popad ...
  Ура !? Что ж, проверим: модифицируем (в отладчике) регистр al после лодсб и отпускаем программу...
 
  ...Так, кнопка "TRY" доступна (хотя Remaining Days = 0). Жмем ее и видим всего лишь вот этот MessageBox:
 
  Вот и познакомились ;) Оказывается, защиту зовут "xLok". А разрешить жать на кнопку - не значит получить доступ к функциональности ;(
  Но что если данными для проверки валидности даты для кода в ~026D7F5F является не байт, а слово или даже что-то большее ? На такую мысль может навести пара команд "lea esi,[ebp+2C63h], lodsb" вместо "cmp byte ptr [ebp+2C63h],90h", хотя это может быть "приколом" компиллятора. Внимательнее поизучаем байты в структуре, адресуемой по [ebp+2C63h] для двух дат: для даты в интервале работы и вне ее:
Работа возможна: 6A 01 FF 73 08 FF 95 54 ... Работа запрещена: 90 90 FF 73 08 E8 9D 00 ...
  Установим дату вне интервала работы, опять остановимся в отладчике в точке 026D7F68, поменяем "неверные" байты на "верные" (90 90 FF ... -> 6A 01 FF ... ) и "отпустим" программу. MessageBox'а от xLok'а больше нет, но есть банальный GPF...
  Следовательно, с большой вероятностью можно заключить, что защита выполняет (динамически) расшифровку какой-то части рабочего кода и снятие всех ее проверок после расшифровки приводит к неизбежному краху, так как этот код расшифровывается неверно вне интервала работы программы. Видимо, защита использует дату (точнее, не саму дату, а некоторую функцию от нее, которая постоянна на интервале работы программы) как ключ шифрования части своего кода. Проверим это: в этом случае будет достаточно подменить системную дату, которую защита спрашивает у Win. В отладчике это легко сделать: опять устанавливаем break на GetSystemTime, возвращаемся в xsystem.dll к коду в 026D6DEE и "патчим" значение структуры SYSTEMTIME (или значение edx чуть ниже) датой инсталляции... Все OK, программа отлично работает и ничего не запрещает.
  Здорово, вроде бы осталось поменять команды инициализации edx (код в 026D6DEE и ниже) на что-то вроде таких:
026D6DEE: ; Было так: ; movzx edx,word ptr [esi] ; d esi -> D3 07 03 00 05 00 1C ; shl edx,8 ; mov dl,[esi+02] ; Месяц ; shl edx,8 ; mov dl,[esi+06] ; День ; А будет так: mov edx,Const_Valid_Date nop ... nop push edx
  и все будет работать ? Пробуем найти опкоды оригинальных инструкций в DLL:
и не находим их ! Этого следовало ожидать еще тогда, когда стало ясно, что код самомодифицирующийся. Что делать дальше ? Может быть, имеет смысл попробовать найти расшифровшик, проанализировать его алгоритм и "пропатчить" нужные байты в соответствии с алгоритмом ? Пусть алгоритм дешифровщика состоит в наложении xor-последовательности на байты DLL:Команды Опкод movzx edx,w [esi] 0F B7 16 shl edx,8 C1 E2 08 mov dl,[esi+2] 8A 56 02 ...
Команды Опкод Шифро-Опкод в DLL (Опкод) xor (Шифро-Опкод в DLL) movzx edx,w [esi] 0F B7 16 B5 2A 1A BA 9D 0C shl edx,8 C1 E2 08 C4 45 2E 05 A7 26 ...
  В этом случае не составило бы труда подготовить те "новые" опкоды, которые мы собираемся поместить на место старых - нужно выполнить над ними операцию с той же xor-последовательностью - как это делает пока неизвестный нам расшифровщик:
Команды Опкод_новый (Опкод) xor (Шифро-Опкод в DLL) Результат mov edx,07D3031Ch BA 1C 03 D3 07 BA 9D 0C 05 A7 00 81 0F... nop ...
  Помещаем в файл xsystem.DLL по смещению (в файле) 6DEE подготовленные таким образом байты и пытаемся смотреть, что же сделал с ними расшифровщик - а в результате видим такой вот MessageBox:
 
  Да, защите удалось не только скрыть результат работы расшифровщика (если он вообще отработал) но и определить модификацию собственного кода. Неплохо ! Как же ему это удалось ? Попробуем "отловить" его на обращении к памяти, содержащей измененные коды:
 bpm 026D6DEE
  и, действительно, наблюдаем следующий код:
026D510C: push esi push ecx push eax ... 026D5115: mov ecx,49A0h xor edx,edx xor eax,eax @@GetCRC: lodsb ; esi=026D6DEFh add edx,eax dec ecx jnz @@GetCRC ... xchg edx,eax ; eax->1FBFFAh pop edx pop ecx pop esi add dword ptr [esp],5 026В515A: ret
  Очень интересный код ! Похоже на подсчет CRC (возвращает в eax), причем регион подсчета CRC включает и критичные для нас адреса в ~026D6DEE. Следовательно, прежде чем воевать с их расшифровщиком, нам нужно победить подсчет контрольной суммы. Открыты ли байты подпрограммы подсчета CRC в DLL ?
026D510C: Команда / Опкод push esi / 56h push ecx / 51h push eax / 52h ...
  Ищем их в файле DLL по смещению 510Ch и находим ! Следовательно, мы можем без особой боязни заменить оригинальную подпрограмму подсчета CRC на что-то вроде:
@@NewCRCProc: mov eax,1FBFFAh add dword ptr [esp],5 ret @@EndOfNewCRC: db (026D515Bh-026D510Ch) - (@@EndOfNewCRC-@@NewCRCProc) dup(90h) @@NewCRCProcDone:
  При этом мы вернем нужное где-то дальше CRC (в eax) без всякого подсчета - что зря процессор гонять, хитро вернемся назад (add dword ptr [esp],5) и забьем все остальное место nop'ами. Патчим таким образом DLL и получаем ... GPF ! Оба-на, защита перестала детектировать сосбтвенную модификацию но и программа "рухнула" ;( Неужели мы неправильно составили новый код подсчета CRC ?... Стоп, а ведь мы еще раньше поменяли байты, относящиеся к получению даты (в ~026D6DEE) ! Установим break на API GetSystemTime, вернемся к коду получения даты в DLL и видим, что расшифровщик явно сделал не то, что мы ожидали:
Команды_новые Результат работы расшифровщика / Опкод 026D6DEE: mov edx,07D3031Ch sbb byte ptr [esi] / 1D 82 1E 1D nop mov bl,7Fh / B3 7F ...
  Следовательно, алгоритм расшифровщика производит расшифровку байт в ~026D6DEE в зависимости от исходных байт. Т.е. это не простое наложение XOR-последовательности; расшифровка каждого байта зависит от того, как был расшифрованы все предидущие. Алгоритм расшифровщика нам по-прежнему неизвестен и мы не можем даже пытаться осуществлять атаку на его шифр. Откатим пока эти изменения, оставив лишь получение CRC и продолжим следить в отладчике за ходом выполнения программы защиты после возврата из подпрограммы подсчета CRC:
026D5366: sub eax,1FBfFAh ; если пришло верное CRC=1FBfFAh, то в eax будет 0 pop ebx jz 026D5382 ... 026D5382: mov [ebp+3C16h],ebx mov edx,[ebp+3C36h] push dword ptr fs:[0] mov fs:[0],esp push eax push ebp jmp 026D53A1 ... 026D53A1: pushfd invalid (0F1h) db 0BEh,0F0h,...
  Если продолжить в отладчике пошагово трассировать код с 026D53A2, то налетаешь на GPF. Но сразу бросаются в глаза явно вычурные для win32-кода команды работы с сегментными регистрами (mov fs:[0],esp). Если уж приложение взялось за это, то явно не с добрыми намерениями ;) На самом деле таким образом в windows пользовательским программам дозволяется установить свой обработчик исключительных ситуаций и в случае таковых получить управление и попытаться их обработать (интересно, кому-нибудь удавалось обрабатывать их инчае как выдать сообщение "Программа выполнила ... и будет закрыта" ?...). Далее мы увидим, что иногда такой обработчик может выполнять некоторые "полезные" действия.
  В двух словах о таких обработчиках. Эта штука называется "SEH" - structure exeption handler. Для того, чтобы обработчик правильно получил управление, необходимо:
- поместить в стек смещение обработчика; - сохранить смещение старого обработчика на том же стеке; - записать по селектору из FS, смещению 0 содержимое esp
  Сам обработчик вызвается в C-формате, при этом получая кучу параметров - указатель на структуру регистров в момент исключения и т.п.:
SEHproc proc C pExcept: dword, pFrame: dword, pContext: dword, pDispatch: dword PrintException pExcept ... ret SEHproc endp
  Но самое главное - это то, что он может вернуться в достаточно произвольное место кода (включая и вызвавшее исключение), при этом уведомив диспечер о том, что исключение им обработано (например, mov eax, ExceptionContinueExecution перед выходом) и то, что он имеет право записи в сегмент команд (это необходимо для исправления "дефектного" кода). Таким образом, обработчик исключения защиты явно собирается именно этим заниматься: подать управление на неверный опкод, поймать expeption, дешифровать некоторые байты, вернуться опять на неверный опкод (не обязательно первый), и так до тех пор, пока необходимый код не будет полностью расшифрован.
  Где же защита разместила свой обработчик ? Нет нужды копаться в командах, предшествующих коду установки SEH (~026D5382), можно просто посмотреть стек в этот момент (скажем, после выполнения mov fs:[0],esp):
 d esp
  Оказыватся, адрес у обработчика равен 026D5040, а вот и он сам:
026D5040: pushad mov edx,0C8EC918Bh call 026D504B 026D504B: pop edi ; edi-> 026D504B, получение текущего смещения mov esi,36h add esi,edi ; esi=026D5081h mov ecx,94h push esi sub esi,9 mov edi,esi xor eax,eax 026D5061: lodsb xor eax,edx rol edx,5 imul edx,edx,0FB712715h add eax,0AB358CDFh stosb dec ecx jnz 026D5061 026D5078: db 0F4h,041h,0ECh,076h,03Ah... 026D5081: ...
  Итак, обработчик SEH сразу после получения управления начинает что-то расшифровывать с адреса 026D5078 и ниже довольно незамысловатым алгоритмом, немного странно реализованным (вместо add eax,0AB358CDFh достаточно add al,0DFh). Размер байт для дешифровки невелик (mov ecx,94h). Дожидаемся возможности установить break в 026D5078 - после первого же stosb там появлется pop esi и после срабатывания break'а видим весь расшифрованный код:
026D5078: pop esi mov eax,[esp+28h] mov [eax+4],esi ; <-026D5081h popad 026D5081: push ebp mov ebp,esp push esi,edi,ebx,ecx,edx mov eax,[ebp+10h] mov edi,[eax+0B8h] 026D5093: mov edx,2 026D5097: jmp 026D5099 026D5099: call 026D509E 026D509E: pop ebx ; ebx<-текущее смещение 026D509E add ebx,0FFFFFFE3h ; ebx=026D5081h add edx,[eax+0B4h] mov [ebx+12h],edx ... mov ebx,0BB51B5E3h test esi,esi jz 026D50CE xor [esi],bl 026D50CE: cmp byte ptr [edi],09Dh jz 026D50DF xor [edi],bl or dword ptr [eax+0C0h],100h 026D50DF: xor ebx,ebx mov [eax+4],ebx, mov [eax+8],ebx, mov [eax+10h],ebx mov dword ptr [eax+18h],101h mov [edx],edi xor eax,eax push eax, push eax, dec eax, push eax, mov eax,ofs Kernel!FlushInstructionCashe call eax xor eax,eax pop edx,ecx,ebx,edi,esi,ebp ret ; Последняя расшифрованная команда 026D510Ch:
  Что же любопытного в раскрывшемся коде ? Прежде всего обратим внимание на характерный код в 026D5081 - push ebp, mov ebp,esp. Это явно оформлено начало какой-то процедуры, и она заканчивается в 026D510B. Командами
mov eax,[esp+28h] mov [eax+4],esi ; <-026D5081h
  декриптор определил адрес нового обработчика SEH - теперь это будет код в 026D5081. При следующих исключениях всегда будет вызываться именно эта процедура, что-то расшифровывающая командами:
xor [esi],bl 026D50CE: cmp byte ptr [edi],09Dh jz 026D50DF xor [edi],bl or dword ptr [eax+0C0h],100h 026D50DF:
  Но в данный момент код получения даты (в ~026D6DEE) еще не расшифрован. Может быть, только что расшифрованная SEH-подпрограмма 026D5081 и есть ее декриптор ? Устанавливаем break на начало 026D5081:
 bpx 026D5081
  и наблюдаем, как раз за разом управление переходит к SEH-обработчику. Он явно расшифровывает какой-то код и хотелось бы остановить программу в тот момент, когда он полностью закончит свою работу. Довольно утомительно было бы жать Ctrl-D столько раз, поэтому я написал небольшой код, заменяющий собой распаковщик SEH-обработчика:
; Новые команды по адресу 026D5040 (первоначальный SEH-обработчик) @@NewBytes: pushad call @@GetOfsNewBytes @@GetOfsNewBytes: pop esi add esi,offset @@ArtBytes - @@GetOfsNewBytes mov eax,[esp+28h] mov [eax+4],esi popad db 0EBh ; jmp на адрес 026D5081 db (@@NewBytes+40h) - $ @@ArtBytes: pushad call @@GetOfs @@GetOfs: pop eax inc word ptr [eax+(offset CounterCalls - offset @@GetOfs)] mov ax,word ptr [eax+(offset CounterCalls - offset @@GetOfs)] popad db 0EBh ; jmp на адрес 026D5081 db (@@NewBytes+40h) - $ CounterCalls dw 0 @@DoneNewBytes: LastNops db (@@DoneOldBytes - @@OldBytes) dup (90h) ; nop only
  Новый код в отличии от старого не декриптует SEH-обработчик (это
можно сделать прямо в файле xsystem.dll и не обременять код графического
редактора дешифрацией), а устанавливает новый адрес SEH-обработчика - это
будет подпрогрммка @@ArtBytes, которая только лишь вычисляет число call'ов
SEH'а и передает jmp'ом управление на старый адрес 026D5081. Теперь в Sice'е
можно ставить
 bpx   Заранее, конечно, число call'ов SEH'а неизвестно, но простым
методом деления "отрезка" пополам можно довольно быстро добраться до конца
работы SEH-обработчика. Трейсим последний вызов по SEH-обработчика и видим,
что:
  - Даже когда он закончил свою работу, код получения даты (в ~026D6DEE)
нерасшифрован;
  - Последний свой xor SEH-обработчик выполнил с адресом 026D540Ch:
  Таким образом, SEH-обработчик-дешифровщик не расшифровал код
получения даты. Но что за байты появились по адресу 026D540Ch ?...
  О, старые знакомые ! :) Опять переносимый (call 026D5412, pop esi)
код дешифрует нижележащие байты. Ставим break в конец цикла, дожидаемся
его окончания и с нетерпением смотрим ... нет, не байты после 026D5440, а
байты получения даты - ~026D6DEE. Они расшифрованы, теперь - ура - мы знаем
кто их декриптует и этот кто-то (026D540C - последний дешифровщик) крайне
мал, чтобы возится с его расшифровщиком (обработчик SEH'а) - проще вбить
его, уже нам известный на то же место, где сейчас лежит его зашифрованный
собрат. Только тогда нужно будет дезактивировать его расшифровщик
(обработчик SEH'а) - а то он все испортит ;) Как же это сделать ? Вернемся
к моменту инсталляции SEH'а:
  Что будет, если вместо invalid-команды, намеренно сделанной для
генерации исключения, сделать переход на дешифровщик в 026D540C, который
теперь, как мы планируем, будет записан нами в открытом виде прямо в DLL ?
SEH-обработчик явно не вызовется, осталось опасность потерять стек при таком
прыжке. Но парная команда к pushfd - последняя команда перед вызовом
SEH-обработчика через исключение - popfd:
  как будто бы явно написана разработчиками защиты с целью восстановить
состояние процесса после отработки SEH'а. Так и поступим. Остался последний
момент: где разместить код, который поменяет пресловутые команды получения
даты на одну спокойную mov edx,Const_Valid_Date и парочку nop'ов ?
  Ниже привожу свое решение, возможно немного надуманное. Поскольку этот
код хотелось бы разместить после отработки расшифровщика 026D540C, т.е. тогда,
когда уже код от 026D5440 и далее расшифрован, то он должен быть после
последней команды расшифровывающего цикла:
  Вместо двух байт короткого jmp'а сложновато сделать такое, но если
немного пооптимизировать код расшифровщика и вспомнить, что после удаления
старого кода подсчета CRC осталась куча места, то можно написать следующее:
  Этой командой мы закроем выполнение SEH'а:
  Это код нового, открытого декриптора по адресу 026D540C:
  Это новый код подсчета CRC вместе с подпрограммой модификации
кода получения даты в 026D6DEE:
  Вот вроде бы и все ;) Хотя далее есть еще несколько пугающих
кусков типа:
  которые заставили меня лишний раз заглянуть в Ральфа Брауна, но
особой опасности они не представляют ;)
Немного извращений или граффити
  После реализации подмены системной даты и многократных смен системной
даты в целях выяснинения особенностей защиты программы можно наблюдать
интересный эффект:
 
  xsystem.dll уже изменена, все работает, но вот с числом оставшихся
дней что-то не то. Как бы не мешает, но некрасиво... Любопытен тот факт,
что обратная замена исходной DLL дела не меняет: число оставшихся дней
меняется, но как-то "криво". Есть и такой "баг": если даже взять нетронутую
версию программы, перевести дату на некоторое число дней вперед, запустить
программу, а следом вернуть дату на место, то при первом запуске программа
не позволяет работать (число оставшихся дней равно 0, кнопка "TRY" недоступна).
  Анализ кода xsystem.dll показал, что после того, как системная
дата получена программой, выполняется следующий код:
  - из некоторой области памяти достатется число, которое
"расшифровывается" xor-ом и далее (код здесь не приводится) трактуется
как дата начала работы программы (инсталляции). Поэтому для завершенности
изменений в программе можно поменять команду "xor eax,7895ADEBh"
на "mov eax,Const_Valid_Date". В этом случае программа будет выдавать
30 дней до конца trial'а. Но можно немного поизвращаться и оставить
число Remaining Days вот таким:
 
 
Исходный текст патчера
  Ниже приводится текст почти полный текст программы, выполняющей
описанные выше действия. Некоторые вспомогательные подпрограммы типа
GetCL пропущены.
xor [esi],bl ; edi=026D540Ch, там 75h, bl=3Eh
026D50CE:
cmp byte ptr [edi],09Dh
...
026D540C:
popfd
call 026D5412 ; get current offset
026D5412:
pop esi
add esi,02Eh
mov edi,esi
mov ecx,0CF9h
mov edx,9548E9F7h
026D5425:
lodsd
xor eax,edx
add eax,54ADF121h
xor eax,ecx
rol edx,5
imul edx,edx,70C967BEh
xor edx,ecx
stosd
dec ecx
jnz 026D5425
jmp 026D5450
026D5440:
...
...
026D5382:
mov [ebp+3C16h],ebx
mov edx,[ebp+3C36h]
push dword ptr fs:[0]
mov fs:[0],esp
push eax
push ebp
jmp 026D53A1
...
026D53A1:
pushfd
invalid (0F1h)
db 0BEh,0F0h,...
026D540C:
popfd
call 026D5412 ; get current offset
...
026D540C:
...
dec ecx
jnz 026D5425
; Здесь должен быть код, вставляющий команду mov edx,Const_Valid_Date
jmp 026D5450
026D5440:
...
; Этот jump мы поместим на место всяких invalid'ов, чтобы SEH не вызывался
; Сам SEH-обработчик оставим в покое
@@NewJumpAt53A2:
jmp $+(540Ch-53A2h)
@@NewJumpAt53A2Done:
@@Decryptor2:
popfd
call @@GetOfsDecryptor2
@@GetOfsDecryptor2:
pop esi
;; db 081h,0C6h,02Eh,000h,000h,000h ; add esi,2Eh - в оригинале
;; был именно вариант из 6-ти байт
add esi,2Eh
mov edi,esi
mov ecx,0CF9h
mov edx,9548E9F7h
@@DecryptLast:
lodsd
xor eax,edx
add eax,54ADF121h
xor eax,ecx
rol edx,5
imul edx,edx,70C967BEh
xor edx,ecx
stosd
;; dec ecx
;; jnz @@DecryptLast
loop @@DecryptLast
; Переходим на кусок кода, который разместили внутри подпрограммы
; подсчета CRC
mov si,510Ch+(offset @@PatchAt026D6DEE-offset @@NewCRCProc)
jmp esi
@@Decryptor2Done:
;
; New subprogram 026D510Ch - calculate CRC + patch code at 026D6DEE
;
@@NewCRCProc:
mov eax,1FBFFAh
add dword ptr [esp],5
ret
@@PatchAt026D6DEE:
;
; Необходимо:
; 1. Пропатчить байты в ~026D6DEE - подменить дату
; 2. Пропатчить байты в ~026D6E0B - подменить дату для вычисления Remaining Days
; 3. Вернуться в 026D5450
;
; Сделать mov edx,07D3031Ch ; Year + Month + Day -> 0BAh,01Ch,003h,0D3h,007h
mov si,6DF7h
mov byte ptr [esi],0BAh ; mov edx,...
@@PatchCommand:
mov dword ptr [esi+1],07D3031Ch ; Year + Month + Day
mov byte ptr [esi+5],90h ; nop
; Пропатчить получение стратовой даты для вычисления Remaining Days -
; - сделать mov edx,07D3031Ch вместо xor eax,7895ADEBh по адресу 6E0Bh
mov si,6E0Bh
mov byte ptr [esi],0B8h ; mov eax,...
@@PatchCommandRemainingDays:
mov dword ptr [esi+1],07D3031Ch ; Year + Month + Day
; Возвращаемся
mov si,5450h
jmp esi
026D6C52:
lea esi,[ebx-100h]
...
call 026D7623
int 62h ; !!!
add eax,0FCfC4C4h
...
026D6E03:
mov ecx,eax
mov eax,[ebp+3202h]
xor eax,7895ADEBh
test eax,eax
...
; ***
; Патч проги Ulead COOL 3D 3.5 ;)
; ***
.386
.model flat,stdcall
option casemap :none ; case sensitive
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
; ------------------------------------
; Read text at end of module for usage
GetCL PROTO :DWORD,:DWORD
; ------------------------------------
.data?
bRead dd ? ; Байт прочитано
bWrite dd ? ; Байт записано
hConsole dd ?
.code
start:
cld
; Получаем хэндл консоли
invoke GetStdHandle,STD_OUTPUT_HANDLE
mov dword ptr hConsole,eax
; ***
; Открываем лог
; ***
.data
LogFileName db "patch_xsystem01.log",0
.data?
LogDescr dd ?
.code
; Открыть лог, если есть он уже
invoke CreateFile,offset LogFileName,GENERIC_WRITE,FILE_SHARE_WRITE+FILE_SHARE_READ,\
0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
mov LogDescr,eax ; Save file descriptor
inc eax
jnz @@LogFileOpen
; Видимо, нет. Тогда попробовать создать
invoke CreateFile,offset LogFileName,GENERIC_WRITE,FILE_SHARE_WRITE+FILE_SHARE_READ,\
0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
mov LogDescr,eax ; Save file descriptor
inc eax
jnz @@LogFileOpen
.data
OpenErrorMsg db "Log file not created !",0Ah,0
.code
push offset OpenErrorMsg
call Write_Log
jmp Exit
@@LogFileOpen:
; Встать в конец лога
invoke SetFilePointer,LogDescr,0,NULL,FILE_END
jmp @@SkipBytes
;
; New subprogram 026D510Ch - calculate CRC + patch code at 026D6DEE
;
@@NewCRCProc:
mov eax,1FBFFAh
add dword ptr [esp],5
ret
@@PatchAt026D6DEE:
;
; Необходимо:
; 1. Пропатчить байты в ~026D6DEE - подменить дату
; 2. Вернуться в 026D5450
;
; Сделать mov edx,07D3031Ch ; Year + Month + Day -> 0BAh,01Ch,003h,0D3h,007h
mov si,6DF7h
mov byte ptr [esi],0BAh ; mov edx,...
@@PatchCommand:
mov dword ptr [esi+1],07D3031Ch ; Year + Month + Day
mov byte ptr [esi+5],90h ; nop
; Пропатчить получение стратовой даты для вычисления Remaining Days -
; - сделать mov edx,07D3031Ch вместо xor eax,7895ADEBh по адресу 6E0Bh
mov si,6E0Bh
mov byte ptr [esi],0B8h ; mov eax,...
@@PatchCommandRemainingDays:
mov dword ptr [esi+1],07D3031Ch ; Year + Month + Day
; Возвращаемся
mov si,5450h
jmp esi
@@EndOfNewCRC:
db (026D515Bh-026D510Ch) - (@@EndOfNewCRC-@@NewCRCProc) dup(90h)
@@NewCRCProcDone:
@@Decryptor2:
popfd
call @@GetOfsDecryptor2
@@GetOfsDecryptor2:
pop esi
;; db 081h,0C6h,02Eh,000h,000h,000h ; add esi,2Eh
add esi,2Eh
mov edi,esi
mov ecx,0CF9h
mov edx,9548E9F7h
@@DecryptLast:
lodsd
xor eax,edx
add eax,54ADF121h
xor eax,ecx
rol edx,5
imul edx,edx,70C967BEh
xor edx,ecx
stosd
;; dec ecx
;; jnz @@DecryptLast
loop @@DecryptLast
;; jmp $+(5450h-543Eh)
mov si,510Ch+(offset @@PatchAt026D6DEE-offset @@NewCRCProc)
jmp esi
@@Decryptor2Done:
@@NewJumpAt53A2:
jmp $+(540Ch-53A2h)
@@NewJumpAt53A2Done:
@@SkipBytes:
;
; Читаем дату, которую надо будет записать в файл
;
.data
Year dw 2003
Month db 03
Day db 28
.data?
PatchDate db 128 dup(?)
.code
invoke GetCL,1,offset PatchDate
cmp eax,1
jz @@DatePresent
.data
NoDateMsg db 0Ah,"No specifed date in command line ! Patch with 2003/03/28",0Ah,0
.code
@@NoDateSpec:
push offset NoDateMsg
call Write_Log
jmp @@DateDone
@@DatePresent:
mov esi,offset PatchDate
call Str_Len
test ecx,ecx
jz @@NoDateSpec
cld
; Year
lodsd
mov ebx,eax
mov ecx,4
xor ebp,ebp
@@GetYear:
imul ebp,ebp,10
movzx eax,bl
shr ebx,8
sub al,'0'
add ebp,eax
loop @@GetYear
mov eax,ebp
mov Year,ax
lodsb
; Month
xor eax,eax
xor ebx,ebx
lodsw
sub ax,'00'
mov bl,ah
mov ah,10
mul ah
add bl,al
mov Month,bl
lodsb
; Day
xor eax,eax
xor ebx,ebx
lodsw
sub ax,'00'
mov bl,ah
mov ah,10
mul ah
add bl,al
mov Day,bl
; Display patched date
.data
PatchDateHead db 0Ah,"Patch date: "
PatchDateStr db "????/??/??",0Ah,0h
.code
movzx eax,word ptr Year
mov ebx,1000
mov edi,offset PatchDateStr
call DecChar
movzx eax,byte ptr Month
mov ebx,10
mov edi,offset PatchDateStr+5
call DecChar
movzx eax,byte ptr Day
mov ebx,10
mov edi,offset PatchDateStr+8
call DecChar
push offset PatchDateHead
call Write_Log
@@DateDone:
;
; Открываем DLL
;
.data
IniFileName db "xsystem.dll",0
.data?
IniDescr dd ?
.code
invoke CreateFile,offset IniFileName,GENERIC_WRITE or GENERIC_READ,\
FILE_SHARE_READ,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
mov IniDescr,eax ; Save file descriptor
cmp eax,0ffffffffh
jnz @@DestFileOpen
.data
DestFileNotOpenMsg db "xsystem.dll not open (not exist ?)! Process aborted !",0Ah,0
.code
push offset DestFileNotOpenMsg
call Write_Log
jmp Exit
@@DestFileOpen:
;
; Патчим DLL
;
;
; Новая процедура без CRC
;
; Встать в нужное место
invoke SetFilePointer,IniDescr,0510Ch,NULL,FILE_BEGIN
; Копируем новый код в сегмент данных для установки даты
.data?
Data_@@NewCRCProc db (@@NewCRCProcDone-@@NewCRCProc) dup(?)
.code
mov ecx,@@NewCRCProcDone-@@NewCRCProc
mov esi,offset @@NewCRCProc
mov edi,offset Data_@@NewCRCProc
lea ebx,[edi+(@@PatchCommand-@@NewCRCProc)+3]
lea edx,[edi+(@@PatchCommandRemainingDays-@@NewCRCProc)+3]
rep movsb
mov ax,Year
shl eax,16
mov ah,Month
mov al,Day
mov [ebx],eax
.data
YearCurr dw 2005
MonthCurr db 04
DayCurr db 13
.code
mov ax,YearCurr
shl eax,16
mov ah,MonthCurr
mov al,DayCurr
mov [edx],eax
; Записываем новый код подсчета CRC
invoke WriteFile,IniDescr,offset Data_@@NewCRCProc,(026D515Bh-026D510Ch),offset bWrite,NULL
cmp dword ptr bWrite,(026D515Bh-026D510Ch)
jz @@PatchCRCOK
.data
DestFileNotPatchedCRCMsg db "xsystem.dll (CRC) not patched: I/O error !",0Ah,0
.code
push offset DestFileNotPatchedCRCMsg
call Write_Log
@@PatchCRCOK:
;
; Записать декриптор2 в незашифрованном виде (в 026D540Ch)
;
; Встать в нужное место
invoke SetFilePointer,IniDescr,0540Ch,NULL,FILE_BEGIN
invoke WriteFile,IniDescr,offset @@Decryptor2,(5440h - 540Ch),offset bWrite,NULL
cmp dword ptr bWrite,(5440h - 540Ch)
jz @@WriteDec2OK
.data
DestFileNotWriteDec2Msg db "xsystem.dll: decryptor2 not writed I/O error !",0Ah,0
.code
push offset DestFileNotWriteDec2Msg
call Write_Log
@@WriteDec2OK:
;
; Записать переход на новый декриптор (2)
;
; Встать в нужное место
invoke SetFilePointer,IniDescr,053A2h,NULL,FILE_BEGIN
mov ebp,offset @@NewJumpAt53A2Done - offset @@NewJumpAt53A2
invoke WriteFile,IniDescr,offset @@NewJumpAt53A2,ebp,offset bWrite,NULL
cmp dword ptr bWrite,ebp
jz @@WriteDecJump2OK
.data
DestFileNotWriteDecJump2Msg db "xsystem.dll: decryptor2(jump) not writed I/O error !",0Ah,0
.code
push offset DestFileNotWriteDecJump2Msg
call Write_Log
@@WriteDecJump2OK:
; ***
; Выход из программы: закрытие дескрипторов и т.п.
; ***
Exit:
.data
EndWorkMsg db 0Ah,"All done... ;)",0Ah,0
.code
push offset EndWorkMsg
call Write_Log
; Закрыть открытые файлы
cmp dword ptr IniDescr,-1
jz @@SkipCloseIniFile
invoke CloseHandle,IniDescr
@@SkipCloseIniFile:
; Закрыть лог
invoke CloseHandle,LogDescr
invoke ExitProcess,0
; ***
; Подпрограмма записи в лог и на консоль конца строки (0Ah)
; ***
Write_EndStr_ToLog proc
.data
EndStr db 0Ah,0
.code
push offset EndStr
call Write_Log
ret
Write_EndStr_ToLog endp
; ***
; Подпрограмма записи в лог и на консоль одновременно
; ***
; [ebp+8] - адрес строки для записи
Write_Log proc
push ebp
mov ebp,esp
; Получить длину строки
mov esi,dword ptr [ebp+8] ; Адрес строки
call Str_Len
test ecx,ecx
jz @@ExitWrite_Log
; Запись на консоль
push ecx
push NULL
push offset bWrite
push ecx ; Длина строки
push dword ptr [ebp+8] ; Адрес строки
push hConsole
call WriteFile
pop ecx
; Запись в протокол
push NULL
push offset bWrite
push ecx ; Длина строки
push dword ptr [ebp+8] ; Адрес строки
push LogDescr
call WriteFile
@@ExitWrite_Log:
pop ebp
ret 4
Write_Log endp
; ***
; Get lenght of string
; esi=addr of string; result: ecx=length
; ***
Str_Len proc
xor ecx,ecx
push eax
push esi
@@GetStrLen:
lodsb
test al,al
jz @@ExitStrLen
inc ecx
jmp @@GetStrLen
@@ExitStrLen:
pop esi
pop eax
ret
Str_Len endp
; ************* DecChar: Подпрограмма форматирования строки из числа ***********
; eax=number to digit, edi=offset result string in format 00000000(@n[ebx])
; ebx=начальный делитель
DecChar proc
pushad
pushfd
cld
@GetDec:
xor edx,edx
div ebx
add al,'0'
stosb
push edx
mov eax,ebx
xor edx,edx
mov ebx,10
div ebx
mov ebx,eax
pop eax
test ebx,ebx
jnz @GetDec
popfd
popad
ret
DecChar endp
...
обсудить | все отзывы (0) | |
[23512; 49; 8.89] |
|
|