Давно ничего не писал в связи с отсутствием интересных идей, поэтому решил написать эту статью, чтобы развеять пустоту.
Люди, пишущие софт на продажу, любят накрывать свои продукты протекторами, однако редко собственноручно проверяют качество защиты. Они, конечно, по-своему правы, так как обычный человек вряд ли примется читать статьи, например, на wasm, чтобы убрать защиту с программы. Рассмотрим способы "обмана" защиты одной из программ для всеми любимых социалочек.
Программа называется LSSender, последние версии которой накрыты DotFix NiceProtect от небезызвестного GPcH. В сети существуют мануалы по снятию этого протектора, но это слегка занудно, и поэтому мы займемся более детальным изучением системы привязки.
Откроем программу в OllyDbg:
Как мы видим, программа действительно защищена протектором, однако, если продолжить выполнение программы, то после запуска мы увидим, что структура программы в памяти почти полностью восстановилась в читабельный вид. Также мне удалось найти более старую версию программы, которая не была защищена никакими протекторами, что позволило, в общем-то, целиком посмотреть структуру защиты с помощью Delphi RTTI eXtractor (DRX).
Как и ожидалось, автор не стал изобретать велосипед и воспользовался готовыми модулями для получения информации о компьютере и дальнейшей генерации серийного номера на основе неё. Это видно, во-первых, по данным, полученным с помощью DRX (DRX создала ассемблерные листинги модулей с "говорящими" названиями HDDInfo, LbCipher и т.д.),
во-вторых, по строковым ресурсам программы (сообщения об ошибках в модулях), которые позволяют найти сами модули в гугле. Эти же ресурсы "палят" ключ, который используется для шифрования данных при генерации серийного номера.
Вызов по адресу 00578590 соответствует вызову TLbRijndael.SetKey (очевидно путем сравнения тел методов из LbClass.pas и по адресу 00578590). Если посмотреть серийные номера, которые выдает программа при запуске на неактивированном оборудовании, то мы увидим, что это base64-строки размером 64 или 88 символов. То есть программа получает некоторые данные о компьютере, хэширует их и обрабатывает алгоритмом base64. Конечно, можно было бы подменить содержимое, возвращаемое функциями кодирования в Base64 или шифрования, но это неинтересно, поэтому будем разбираться дальше. Определим, какие данные использует программа для генерации серийного номера. Во-первых, это серийный номер HDD (модуль HDDInfo). В программе используется метод из модуля, который получает информацию о hdd, используя DeviceIoControl. Во-вторых, имя текущего пользователя системы (функция GetUserName).
Казалось бы, надежный метод, но не в случае с виртуальными машинами. Кто-то уже купил у автора привязку к виртуальной машине VMWare, так что у меня программа оказалась "активированной" после запуска. Также следует отметить, что при запуске никакой сетевой активности не наблюдается, так как все серийные номера содержатся внутри программы в открытом виде.
Что все это дает? Можно сделать, чтобы вызовы функций DeviceIoControl и GetUserName возвращали нужные нам данные (тем более есть, откуда сделать слепок данных), а можно просто вписать свой серийный номер в тело программы. Сделаем частично то и другое. Подменим данные, возвращаемые функциями, на данные с виртуальной машины и расширим лицензию до самой полной (которая стоит 250$). Выделываться с ассемблером и драйверами в этот раз не будем, а воспользуемся старой доброй Detours. Но для начала проанализируем вызовы DeviceIoControl и CreateFile (т.к. предыдущей функции нужен хендл) на виртуальной машине:
Как видно из лога, программа пытается получить серийные номера устройств PhysicalDrive0-7 и Scsi0-3. Но так как в моей виртуальной машине только один жесткий диск, то имеет смысл только чтение PhysicalDrive0. Функция DeviceIoControl вызывается с разными dwIoControlCode: 0х00074080, 0х002D1400, 0х000700A0). nOutBufferSize тоже варьируется. На основе анализа лога, а также содержимого lpOutBuffer можно составить свою функцию, которая всегда будет возвращать нужные данные. Но для начала опишем прототипы функций:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
BOOL (WINAPI *Real_GetUserName)(LPTSTR lpBuffer, LPDWORD lpnSize) = GetUserName; BOOL WINAPI MyGetUserName(LPTSTR lpBuffer, LPDWORD lpnSize); BOOL (WINAPI *Real_DeviceIoControl) ( HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped ) = DeviceIoControl; BOOL WINAPI MyDeviceIoControl ( HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped ); HANDLE (WINAPI *Real_CreateFile) ( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ) = CreateFile; HANDLE WINAPI MyCreateFile ( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ); |
Теперь опишем функцию MyDeviceIoControl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
BOOL WINAPI MyDeviceIoControl ( HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped ) { BOOL result; if(hDevice == hDrive) { if(dwIoControlCode == 0x00074080 && nOutBufferSize == 24) { ZeroMemory(lpOutBuffer, nOutBufferSize); *lpBytesReturned = 0; result = FALSE; } else if(dwIoControlCode == 0x002D1400 && nOutBufferSize == 10000) { CopyMemory(lpOutBuffer, code1, sizeof(code1)); *lpBytesReturned = sizeof(code1); result = TRUE; } else if(dwIoControlCode == 0x000700A0 && nOutBufferSize == 10000) { CopyMemory(lpOutBuffer, code2, sizeof(code2)); *lpBytesReturned = sizeof(code2); result = TRUE; } } else { result = Real_DeviceIoControl ( hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped ); } return result; } |
hDrive - это хендл после вызова CreateFileW("\\.\PhysicalDrive0",...
, code1 и code2 - содержимое буферов, которые возвращаются при запуске на виртуальной машине, вызов с dwIoControlCode равным 0х00074080 не срабатывает.
Для функции GetUserName все совсем просто:
1 2 3 4 5 6 7 8 |
BOOL WINAPI MyGetUserName(LPTSTR lpBuffer, LPDWORD lpnSize) { wchar_t uname[] = TEXT("Администратор"); CopyMemory(lpBuffer, uname, sizeof(uname)); *lpnSize = sizeof(uname); return TRUE; } |
Теперь сделаем свою функцию для CreateFileW. В ней нам необходимо возвращать хендл только при обращении к PhysicalDrive0 и сохранять его для дальнейшего использования в MyDeviceIoControl.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
HANDLE WINAPI MyCreateFile ( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ) { HANDLE temp = INVALID_HANDLE_VALUE; const wchar_t * p = NULL; BOOL save = 0; p = wcsstr(lpFileName, L"PhysicalDrive"); if(p != NULL) { if(*(p + 13) != '0') { return INVALID_HANDLE_VALUE; } save = 1; } p = wcsstr(lpFileName, L"Scsi"); if(p != NULL) { return INVALID_HANDLE_VALUE; } temp = Real_CreateFile ( lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile ); if(save) { hDrive = temp; } return temp; } |
И, наконец, DllMain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved) { (void)hinst; (void)reserved; if(dwReason == DLL_PROCESS_ATTACH) { DetourRestoreAfterWith(); DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(PVOID&)Real_GetUserName, MyGetUserName); DetourAttach(&(PVOID&)Real_DeviceIoControl, MyDeviceIoControl); DetourAttach(&(PVOID&)Real_CreateFile, MyCreateFile); DetourTransactionCommit(); } else if(dwReason == DLL_PROCESS_DETACH) { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourDetach(&(PVOID&)Real_GetUserName, MyGetUserName); DetourDetach(&(PVOID&)Real_DeviceIoControl, MyDeviceIoControl); DetourDetach(&(PVOID&)Real_CreateFile, MyCreateFile); DetourTransactionCommit(); } return TRUE; } |
Теперь у нас есть готовая dll, которую требуется загрузить в адресное пространство процесса до начала проверки серийного номера. Можно, конечно, написать лоадер, но мы просто поправим таблицу импорта с помощью CFF Explorer, благо, она не искажена протектором. Чтобы добавить нашу dll в таблицу импорта - создадим пустую экспортируемую функцию в ней
1 2 3 4 |
__declspec(dllexport) void __cdecl dummy(void) { return; } |
Теперь отредактируем таблицу импорта
После этого мы получим работоспособную стандартную версию, но нам ведь нужна версия за 250$ (если бы мы взяли данные, которые возвращают DeviceIoControl и GetUserName с компьютера, для которого была приобретена самая полная версия, то дальнейшие манипуляции не потребовались бы). Чтобы её получить, необходимо знать ключ, который генерировался для виртуальной машины, на которой я её запускал (данные в base64). Если знаем ключ, то просто открываем какой-нибудь hex-редактор и вписываем этот ключ поверх другого ключа, сразу после которого идет строка +fullplugin. Старый ключ на всякий случай лучше затереть.
Конечно, можно было не заморачиваться с dll, а сразу вписать ключ компьютера в тело программы. В любом случае, сразу после вписывания мы наконец-то получаем самую полную версию программы.
Исходный код dll: скачать
крутой
а где качнуть сам exe'шник ?
piar-soft.info/soft/LSS/LSS.rar
софт по ссылке выше заражен
Софт защищенный протекторами зачастую определяется как зараженный
Браво =)
Только софт я особо и не старался защитить.
Так как в софте вечно есть глюки, которые я всегда и правлю =)
Да и плюшки я добавляю по просьбе пользователей.
На это и идёт расчёт.
Тогда можно было и протектор не вешать, на MVI же не висит
Тут от мудаков помельче защита, которые только портят мнение о софте.
После которых софт лагает непонятно как.
А чеж ты с сайта уже удалил архивчик ;) ?
Как-раз таки от шаловливых ручек.
Потому как одно дело когда прога взломана и лежит в привате, а другое когда все туда лезут и ковыряют.
прогу непонятно откуда качать, ссылка которую давали не рабочая,
ещё с этой dll разбираться надо,
сделай так чтобы сразу можно было скачать архив с работающей программой, заранее спасибо)))
на фотке dx_laptop.jpg настоящий dx ?
А то! Теперь он будет источником картинок к постам )
да тоже интересно)) нeужто сам dx))
piar-soft.info/soft/LSS/LSS.rar
пароль ктото подскажет?
скомпилируйте dll пожалуйста и выложите на обменник
Практический смысл? Автор небось защиту уже поменял
Kaimi спасибо за повышение знаний :)
d_x что было на экране ? :)
Студия с сорцем третьего квеста
TI EBANUJ V JOPY PIDARAS ZASUNUL BU B JOPY TEBE KONTRABAS!!!!!!!!!!!!!!!!!!
пасаны если я оставлю номер кошелька скинете мне на него хотяб мильён деревянных? xD
Были бы эти деньги, лично у меня в электронной валюте не было суммы хотя бы > 100$ уже около года.
мда.. нищеброд((
Конечно. Я же не циничный спаммер как некоторые, да и какими-то сакральными знаниями не обладаю.
Хорошая работа Kiami! Был бы очень благодарен если ты выложил эксешник.
PS: Насчет выше написанных сообщений... Они просто сейчас завидуют, что некоторые получат программу бесплатно а некоторые должны заплатить по 50 у.е
piar-soft.info/soft/LSS/LSS.rar
Да и... На эксешнике пароль.
Пароль: 442ca31e85c7e36bfe3a0dca9
Толку, я же говорил, что защита изменилась.
O_o Как ты узнал пароль?
Да вот еще... Извините, но вашего серииника нет в базе! Обратитесь к разработчику за покупкои... Как исправить?
Подменить информацию о системе, если есть машина с лицензией. Или снять протектор и пропатчить нужные места в коде.
Для Вас ведь это не составит труда? :)
Как два пальца об асфальт?
Да. Как вы узнали пароль?
Составит, да и зачем, в очередной раз гоняться с автором? Ему то проще, скачал крякнутый протектор и в очередной раз сменил защиту.
Пароль...Владельцы лицензии сообщили.
Thank you for this awesome tutorials!