Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 3 октября!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

Драйвер принтера Lexmark Z705 и водяные знаки

Обсудить статью на форуме

Массу крэкерских инструментов, видеоуроков и статей вы сможете найти на видеокурсе от нашего сайта. Подробнее здесь.

Автор: RedAbishai <RedAbishai@tut.by>

Драйвер принтера Lexmark Z705 и водяные знаки.


Инструменты:
FAR с плагинами ProcList (Process List), Resource (Resource Browser)
HIEW или QVIEW
WinSight32 (опционально)
IDA


Преамбула.

Стоял у меня под 98-й форточкой цветной струйный принтер Lexmark Z705 (драйвер версии 1.0.11.0 2003 года выпуска, версии GUI:1.0.11.0, LCS:9.35, Процессор печати:9.35, PSWX:1.0.0.0, IJ Core: 1.0.43.0), печатал нормально, и все было хорошо, пока по спецзаказу не попробовал я нарисовать плакат с долларовыми купюрами (ну там "знай валюту", "способы обнаружения подделок", и в том же духе). Принтер начал печатать, напечатал примерно четверть изображения, выкинул недопечатанный лист и остановился. На экране я увидел окно подсказки "Справочная система Windows" с сообщением:

Обнаружена банкнота

Текущее задание на печать отменено.

Печать текущего задания невозможна, поскольку это устройство не поддерживает незаконную печать банкнот. Для получения дополнительной информации посетите узел: http://www.rulesforuse.org/

Мне это сообщение не понравилось, скажу даже больше - я просто офигел! На отсканированном журнальном плакатике с подкрашенными стрелочками и кружочками и с небольшим масштабированием бакс распознать!
Но так как больше цветных принтеров в радиусе досягаемости не было, по указанному адресу отправляться не хотелось, а работу делать все-таки надо, приговор был однозначным - побороть, чтобы этот Lexmark больше и не заикался про банкноты.


1. Предварительная подготовка.


Итак, надо определить "виноватого" - это вам не программа, про которую хотя бы известно, в каком каталоге она "прописана".

Что у нас есть? Окно подсказки (и еще соображения о том, что каким-то образом замешан драйвер принтера, а его файлы скорее всего валяются где-то в каталоге операционной системы) - это наша отправная точка. С помощью winsight32 или Process List из FAR можно определить, что окно с заголовком "Справочная система Windows" соответствует процессу WINHLP32.EXE - то есть мы практически однозначно видим обыкновенный файл подсказки с расширением HLP, выводимый, например (это я уже теперь такой умный :) и знаю, чем выводить), с помощью API-функции WinHelp:


BOOL WinHelp(
  HWND hWndMain,    // handle of window requesting Help
  LPCTSTR lpszHelp, // address of directory-path string
  UINT uCommand,    // type of Help
  DWORD dwData      // additional data
);


Ищу подходящий файл помощи. Самый простой (но не очень надежный) вариант - поиск по фрагменту содержимого (например, по строке "www.rulesforuse.org") - приводит нас к файлу %SYSTEMDIRECTORY%\LXBLNOTE.HLP. Его удалось найти только потому, что содержимое файла не было пожато - с другими файлами подсказки такое могло и не получиться, пришлось бы искать другими способами. Запустив его, видим уже знакомое сообщение. Рядом лежит куча DLL-ок, начинающихся с тех же букв LXBL - драйвера принтера Lexmark, что наводит на определенные размышления - истина где-то рядом.

Хорошо, я продвинулся чуть вперед - знаю, каким образом выводится сообщение. Следующий шаг - определить, кто его выводит. Халява не пройдет - поиск по строке "LXBLNOTE.HLP" ничего не дает, кроме INF и PNF-файлов, что не есть гуд. Пытаюсь укорачивать строку поиска и когда остается лишь "NOTE.HLP", получаю результат - строка находится в Unicode-виде в LXBLCF32.DLL (я пользуюсь поиском по Alt-F7 в FAR с опцией Using character table: All character tables).

Искомая строка скорее всего находится в ресурсах - на это указывает ее кодировка (Unicode) и расположение в хвосте файла, после VERSION_INFO. Отлично, проверим гипотезу и пошаримся по ресурсам этой DLL-ки. В FAR, находясь в файловой панели на имени DLL, жму Ctrl-PgDn, оказываюсь в ресурсах, копирую всю секцию STRING во временный каталог и прохожу по нему поиском той же строки "NOTE.HLP". Номер строкового ресурса - 1042, его содержимое - "%MODEL_PREFIX%note.hlp >main2". Рядом лежат похожие строки ("%MODEL_PREFIX%pr.inf", "%MODEL_PREFIX%ic9x.dll" - и файлы подходящие тоже существуют: LXBLPR.IBF, LXBLIC9X.DLL). Понятно теперь, что %MODEL_PREFIX% - префикс файлов для конкретной модели принтера, в нашем случае %MODEL_PREFIX%=LXBL. Типа унификация исходников. Поэтому и целиком строку "LXBLNOTE.HLP" найти нельзя.

Что теперь? Бегло изучаю LXBLCF32.DLL - ничего интересного, из названий экспортируемых функций становится понятно, что это в основном хранилище строковых ресурсов, запрашиваемых по идентификатору, и функции для "добычи" этих самых ресурсов. Развивая мысль дальше, можно предположить:
а) в некоем файле X лежит проверка подготовленных для печати данных на водяные знаки;
б) примерно там же находится загрузка строки с именем HLP-файла из LXBLCF32.DLL по идентификатору ресурса 1042 (0412h) - что-нибудь типа
push 0412h
call CfgLoadStrById ; имя я дал чисто условное
ц) после этого идет запуск системы помощи с загруженным именем файла.

Ищу файл, импортирующий LXBLCF32.DLL. Я опять начал с банального поиска файла по содержимому - облом. Потом, вспомнив про %MODEL_PREFIX%, сократил строку поиска до "CF32.DLL". Результат - список из 10 файлов:
LXBLCF32.DLL
LXBLFT32.DLL
LXBLPP32.DLL
LXBLPR.DRV
LXBLPR.INF
lxblpr.PNF
LXBLPR32.DLL
LXBLTO32.DLL
LXBLUI.DLL
LXBLXC.DLL
Вывожу их на временную панель - они еще пригодятся.

Второй шаг поиска - в этих файлах все тем же FAR ищем шестнадцатиричное значение 12 04 00 00 (4 байта, т.е. 32-разрядное). Нет такого. Значение 12 04 (16-разрядное число) - есть в следующих файлах:
LXBLCF32.DLL
LXBLFT32.DLL
LXBLPP32.DLL
LXBLPR.DRV
LXBLPR32.DLL
LXBLTO32.DLL
LXBLUI.DLL
LXBLXC.DLL

То есть можно сказать, что в указанных файлах _может_ присутствовать инструкция ассемблера типа "push 0412h" или "mov куда-то,0412h".

Гружу HIEW, прошариваю файлы в режиме дизассемблирования в поисках тех же байт, находящихся в окружении осмысленных команд, в LXBLPR.DRV нахожу вот такую строку:
00013277: 681204 push 00412
- это единственный подходящий кандидат на препарирование, в остальных файлах указанные байты встречаются посреди груд бессмысленного мусора (причем сами файлы не запакованы/закриптованы). Файл имеет структуру NE, инструкции 16-битные, поэтому 32-битный код типа (68)12040000 и не был найден. Логично. (Еще повезло, что идентификатор ресурса не влазит в один байт - 32-битный код типа 6A0D "push 0000000Dh" пришлось бы искать гораздо дольше. Хотя я могу ошибаться.)

В принципе можно было бы сразу сузить диапазон поиска и искать только команду push с нужным аргументом, если есть уверенность, что была использована именно она - QView и Hiew это позволяют. Но у меня такой уверенности не было, и я решил: "медленно, но верно".


2. Дизассемблирование.


Запускаю IDA, гружу LXBLPR.DRV. Написан на MSC или MSVC первой версии. Соответственно используются сигнатуры mv16rwin (MSC v6.0/v7.0 & MSVC v1.0/v1.5 windows runtime). Жду, пока закончится анализ. Смещение 13277 - в листинге ему соответствует адрес cseg05:06C7:

cseg05:06C5                 push    dx              ; int
cseg05:06C6                 push    ax              ; int
cseg05:06C7                 push    1042            ; ResId
cseg05:06CA                 push    0               ; int
cseg05:06CC                 push    0               ; int
cseg05:06CE                 lea     ax, [bp+vHelpFName]
cseg05:06D1                 push    ss              ; int
cseg05:06D2                 push    ax              ; int
cseg05:06D3                 push    0               ; int
cseg05:06D5                 push    1Eh             ; int
cseg05:06D7                 call    _getcfgstr


Процедура, которую я задним числом обозвал _getcfgstr, делает следующее:

...
cseg12:024D                 call    LOADLIBRARYEX32W
cseg12:0252                 mov     word ptr [bp+vhLib], ax
cseg12:0255                 mov     word ptr [bp+vhLib+2], dx
...
cseg12:025C                 push    word ptr [bp+vhLib+2]
cseg12:025F                 push    ax
cseg12:0260                 mov     ax, offset aGetcfgstr ; "GetCfgStr"
cseg12:0263                 push    ds
cseg12:0264                 push    ax
cseg12:0265                 call    GETPROCADDRESS32W
cseg12:026A                 mov     word ptr [bp+vGetCfgStr], ax
cseg12:026D                 mov     word ptr [bp+vGetCfgStr+2], dx
...
cseg12:028E                 push    [bp+vResId]
...
cseg12:029A                 push    word ptr [bp+vGetCfgStr+2]
cseg12:029D                 push    ax
...
cseg12:02AA                 call    _CALLPROCEX32W
...
cseg12:02B6                 push    word ptr [bp+vhLib+2]
cseg12:02B9                 push    word ptr [bp+vhLib]
cseg12:02BC                 call    FREELIBRARY32W


то есть:
1. Грузит нашу DLL-ку LXBLCF32.DLL - LOADLIBRARYEX32W("LXBLCF32.DLL",vhLib,...)
2. Получает из нее адрес экспортируемой функции GetCfgStr с красноречивым и саморазъясняющим названием - GETPROCADDRESS32W(vhLib,"GetCfgStr")
3. Запускает ее с указанием нашего идентификатора ресурса - CALLPROCEX32W(5,...,vGetCfgStr,...,vResId,...) и получает нужную строку
4. Выгружает библиотеку - FREELIBRARY32W(vhLib)

Возвращаемся из этой проанализированной процедуры назад, к адресу cseg05:06C7 и смотрим дальше. А дальше идет составление полного пути запуска:

cseg05:06E0                 lea     ax, [bp+szHelp]
cseg05:06E4                 push    ss
cseg05:06E5                 push    ax              ; LPSTR
cseg05:06E6                 push    260             ; UINT
cseg05:06E9                 call    GETSYSTEMDIRECTORY
cseg05:06EE                 lea     ax, [bp+szHelp]
cseg05:06F2                 push    ss
cseg05:06F3                 push    ax              ; LPSTR
cseg05:06F4                 push    ds
cseg05:06F5                 push    offset asc_2776F ; "\\"
cseg05:06F8                 call    LSTRCAT
cseg05:06FD                 lea     ax, [bp+szHelp]
cseg05:0701                 push    ss
cseg05:0702                 push    ax              ; LPSTR
cseg05:0703                 lea     ax, [bp+vHelpFName]
cseg05:0706                 push    ss
cseg05:0707                 push    ax              ; LPCSTR
cseg05:0708                 call    LSTRCAT


и запуск системы помощи

cseg05:0724                 les     bx, [bp+arg_0]
cseg05:0727                 push    word ptr es:[bx+1D8h] ; hwndMain
cseg05:072C                 lea     ax, [bp+szHelp]
cseg05:0730                 push    ss
cseg05:0731                 push    ax              ; lpszHelp
cseg05:0732                 push    1               ; usCommand
cseg05:0734                 push    word ptr [bp+ulData+2]
cseg05:0738                 push    word ptr [bp+ulData] ; ulData
cseg05:073C                 call    WINHELP


Итак, я оказался в нужном месте - вывод раздражающего сообщения происходит именно здесь, в этом участке кода, оформленном в отдельную небольшую процедуру (я обозвал ее ShowMoneyMsg), и на нее есть всего лишь одна ссылка:

cseg05:06A4 ShowMoneyMsg    proc far ; CODE XREF: SendRGBImage2+20Cp


Иду по этой ссылке в вызывающий код, вижу следующий фрагмент (здесь я уже произвел предварительный анализ, дал некоторым функциям и переменным более человечные имена для простоты восприятия):

cseg05:01EA                 push    word ptr [bp+vStruct+2]
cseg05:01ED                 push    word ptr [bp+vStruct] ; aStruct
cseg05:01F0                 call    SendRGBImage
cseg05:01F5                 or      al, al
cseg05:01F7                 jg      short loc_E982  ; result>0
cseg05:01F9 loc_E969:                               ; CODE XREF: SendRGBImage2+3C9j
cseg05:01F9                 les     bx, [bp+ImgStruct]
cseg05:01FC                 inc     al
cseg05:01FE                 mov     byte ptr es:[bx+1DCh], 1
cseg05:0204                 jz      short l_E979_money ; result=-1
cseg05:0206                 jmp     l_EC74_exit
cseg05:0209 ; ---------------------------------------------------------------------------
cseg05:0209 l_E979_money:                           ; CODE XREF: SendRGBImage2+204j
cseg05:0209                 push    es              ; result=-1
cseg05:020A                 push    bx
cseg05:020B                 push    cs
cseg05:020C                 call    near ptr ShowMoneyMsg
cseg05:020F                 jmp     l_EC74_exit
cseg05:0212 ; ---------------------------------------------------------------------------
cseg05:0212 loc_E982:                               ; CODE XREF: SendRGBImage2+14Ej
cseg05:0212                                         ; SendRGBImage2+1F7j


После вызова call SendRGBImage идет проверка результата в al, возможные значения - положительные (01h-7Fh), ноль (00h) и минус единица (FFh). Попытка "на ура" сразу же, недолго думая, забить проверку и поставить безусловный переход не помогает - драйвер не ругается, но и не печатает. Зри в корень! (c) Козьма Прутков. Поэтому смотрю нутро процедуры SendRGBImage. Опа, а это всего лишь переходник, вызывающий процедуру IPCsend_rgb_image из 32-битной библиотеки LXBLIC9X.DLL. Напускаю на нее IDA, медитирую над кодом ;)


.text:10002320 ; Exported entry   5. IPCsend_rgb_image
.text:10002320                 public IPCsend_rgb_image
.text:10002320 IPCsend_rgb_image proc near


- то, что мне надо. Точнее, мне надо узнать, когда она возвращает в al отрицательный результат (FFh). Жму F12, смотрю граф процедуры. Из имеющихся пяти точек выхода три заканчиваются одинаково: xor al,al и на выход. Остаются две. Первая:

.text:100023CF                 cmp     bErrStatus, bl
.text:100023D5                 jnz     short l_100023E3_malloc_or_exit_0
.text:100023D7                 or      al, 0FFh
.text:100023D9                 pop     edi
.text:100023DA                 pop     esi
.text:100023DB                 pop     ebp
.text:100023DC                 pop     ebx
.text:100023DD                 add     esp, 10h
.text:100023E0                 retn    4


и вторая в двух вариантах:

.text:1000248A                 call    Calls_1
.text:1000248F                 cmp     al, bl
.text:10002491                 jnz     short l_100024E1_exit
...
.text:100024E1 l_100024E1_exit:                        ; CODE XREF: IPCsend_rgb_image+171j
.text:100024E1                 pop     edi
.text:100024E2                 pop     esi
.text:100024E3                 pop     ebp
.text:100024E4                 pop     ebx
.text:100024E5                 add     esp, 10h
.text:100024E8                 retn    4


и:

.text:100024B8                 call    Send_Band
.text:100024BD                 test    eax, eax
.text:100024BF                 jz      short l_100024DF_exit_bl
...
.text:100024DF l_100024DF_exit_bl:                     ; CODE XREF: IPCsend_rgb_image+19Fj
.text:100024DF                 mov     al, bl
.text:100024E1 l_100024E1_exit:                        ; CODE XREF: IPCsend_rgb_image+171j
.text:100024E1                 pop     edi
.text:100024E2                 pop     esi
.text:100024E3                 pop     ebp
.text:100024E4                 pop     ebx
.text:100024E5                 add     esp, 10h
.text:100024E8                 retn    4


План дальнейших действий:
а) грубой силой забить условные джампы - может сработает;
б) проверить переменную bErrStatus - кто ее изменяет;
ц) проанализировать процедурку Calls_1 - какой результат в al она возвращает;
д) узнать содержимое bl в точке .text:100024DF.

К сожалению, пункт а) не помогает :). Приходится подключать логику. Смотрю ссылки на bErrStatus - ее изменяет уже встретившаяся нам процедура Calls_1. Прыгаю на Calls_1, изучаю. Какие-то левые вызовы, цикл... Цикл печати подготовленных данных? Вариантов возвращаемых в al значений всего три (!): 0, 1, и FF (-1), причем последняя и в bErrStatus единичку пишет:

.text:10002B72 l_10002B72_exit_FF:                     ; CODE XREF: Calls_1+50j
.text:10002B72                 mov     bErrStatus, 1
.text:10002B79                 or      al, 0FFh
.text:10002B7B                 pop     esi
.text:10002B7C                 add     esp, 20h
.text:10002B7F                 retn
.text:10002B7F Calls_1         endp


Похоже на правду - Calls_1, прерывая свой цикл, завершается со значением FF,
его ловит IPCsend_rgb_image, завершается с тем же FF, далее значение
возвращается в 16-разрядный драйвер LXBLPR.DRV, и там происходит следующее:

cseg05:01F5                 or      al, al
cseg05:01F7                 jg      short loc_E982  ; result>0
...
cseg05:01FC                 inc     al
...
cseg05:0204                 jz      short l_E979_money ; result=-1
...
cseg05:0209 l_E979_money:                           ; CODE XREF: SendRGBImage2+204j
cseg05:0209                 push    es              ; result=-1
cseg05:020A                 push    bx
cseg05:020B                 push    cs
cseg05:020C                 call    near ptr ShowMoneyMsg
cseg05:020F                 jmp     l_EC74_exit


Теперь попробую сделать так, чтобы процедура Calls_1 никогда не возвращала значение FF. Условный переход в LXBLIC9X.DLL

.text:10002B60                 jz      short l_10002B72_exit_FF

забиваю nop-ами, благо их всего две штуки и надо. Про FF забыто, вернуться может или 0 или 1, третьего не дано. Правлю драйвер. Пробую печатать злополучный плакат с баксом - печатается. Дело сделано. Можно пить пиво.


3. Мораль.

Что сказать напоследок? Я получил интересный опыт в "отыскивании хвостов" - с обычными программами работать как-то проще, (почти) всегда каталоги видно, в которых шарить надо - сразу диапазон поиска сужается. А здесь - россыпь 16- и 32-разрядных модулей драйвера посреди необьятного каталога операционной системы.

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

Опять же, код размазан по модулям - грузим строки из одного места, функции печати/проверки вызывает из второго, а проверяем и ругаемся в третьем. На проверку всех DLL-ок уходит лишнее время, да и внимание отвлекается.

Что еще добавить? Дебаггером не пользовался - на мой взгляд, в таких случаях полчаса, проведенные в IDA, и в отладчике, неравноценны. Да и попытайся еще драйвер "слови" отладчиком - я не пробовал.

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




Обсуждение статьи: Драйвер принтера Lexmark Z705 и водяные знаки >>>


Комментарии к статье: Драйвер принтера Lexmark Z705 и водяные знаки


Материалы находятся на сайте https://exelab.ru



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS