Print This Post LSSender, detours и боянный сплайсинг

Вторник, 3. Май 2011
Раздел: C/C++, Windows, Социальные сети, автор:


Давно ничего не писал в связи с отсутствием интересных идей, поэтому решил написать эту статью, чтобы развеять пустоту.
Люди, пишущие софт на продажу, любят накрывать свои продукты протекторами, однако редко собственноручно проверяют качество защиты. Они, конечно, по-своему правы, так как обычный человек вряд ли примется читать статьи, например, на 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 можно составить свою функцию, которая всегда будет возвращать нужные данные. Но для начала опишем прототипы функций:

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

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 все совсем просто:

BOOL WINAPI MyGetUserName(LPTSTR lpBuffer, LPDWORD lpnSize)
{
	wchar_t uname[] = TEXT("Администратор");
	CopyMemory(lpBuffer, uname, sizeof(uname));
	*lpnSize = sizeof(uname);
 
	return TRUE;
}

Теперь сделаем свою функцию для CreateFileW. В ней нам необходимо возвращать хендл только при обращении к PhysicalDrive0 и сохранять его для дальнейшего использования в MyDeviceIoControl.

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

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 в таблицу импорта - создадим пустую экспортируемую функцию в ней

__declspec(dllexport) void __cdecl dummy(void)
{
	return;
}

Теперь отредактируем таблицу импорта

После этого мы получим работоспособную стандартную версию, но нам ведь нужна версия за 250$ (если бы мы взяли данные, которые возвращают DeviceIoControl и GetUserName с компьютера, для которого была приобретена самая полная версия, то дальнейшие манипуляции не потребовались бы). Чтобы её получить, необходимо знать ключ, который генерировался для виртуальной машины, на которой я её запускал (данные в base64). Если знаем ключ, то просто открываем какой-нибудь hex-редактор и вписываем этот ключ поверх другого ключа, сразу после которого идет строка +fullplugin. Старый ключ на всякий случай лучше затереть.

Конечно, можно было не заморачиваться с dll, а сразу вписать ключ компьютера в тело программы. В любом случае, сразу после вписывания мы наконец-то получаем самую полную версию программы.

Исходный код dll: скачать

Также рекомендую почитать

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


Получать обновления на почту:     

Метки: , , , , .

Комментариев: 31 к “LSSender, detours и боянный сплайсинг”


  1. vV :

    а где качнуть сам exe'шник ?

    [Ответить]

    Jenki:

    piar-soft.info/soft/LSS/LSS.rar

    [Ответить]

    bertolai:

    софт по ссылке выше заражен

    [Ответить]

    Kaimi:

    Софт защищенный протекторами зачастую определяется как зараженный


  2. ЛеНтЯй :

    Браво =)
    Только софт я особо и не старался защитить.
    Так как в софте вечно есть глюки, которые я всегда и правлю =)
    Да и плюшки я добавляю по просьбе пользователей.
    На это и идёт расчёт.

    [Ответить]

    Kaimi:

    Тогда можно было и протектор не вешать, на MVI же не висит

    [Ответить]

    ЛеНтЯй:

    Тут от мудаков помельче защита, которые только портят мнение о софте.
    После которых софт лагает непонятно как.

    [Ответить]

    ApocX:

    А чеж ты с сайта уже удалил архивчик ;) ?

    ЛеНтЯй:

    Как-раз таки от шаловливых ручек.
    Потому как одно дело когда прога взломана и лежит в привате, а другое когда все туда лезут и ковыряют.


  3. ya :

    прогу непонятно откуда качать, ссылка которую давали не рабочая,
    ещё с этой dll разбираться надо,
    сделай так чтобы сразу можно было скачать архив с работающей программой, заранее спасибо)))

    [Ответить]


  4. ya :

    на фотке dx_laptop.jpg настоящий dx ?

    [Ответить]

    Kaimi:

    А то! Теперь он будет источником картинок к постам )

    [Ответить]


  5. ya :

    да тоже интересно)) нeужто сам dx))

    [Ответить]


  6. grom :

    piar-soft.info/soft/LSS/LSS.rar

    пароль ктото подскажет?

    [Ответить]


  7. ya :

    скомпилируйте dll пожалуйста и выложите на обменник

    [Ответить]

    Kaimi:

    Практический смысл? Автор небось защиту уже поменял

    [Ответить]


  8. Neo :

    Kaimi спасибо за повышение знаний :)
    d_x что было на экране ? :)

    [Ответить]

    Kaimi:

    Студия с сорцем третьего квеста

    [Ответить]


  9. KAIMI PIDARAS :

    TI EBANUJ V JOPY PIDARAS ZASUNUL BU B JOPY TEBE KONTRABAS!!!!!!!!!!!!!!!!!!

    [Ответить]


  10. romkin :

    пасаны если я оставлю номер кошелька скинете мне на него хотяб мильён деревянных? xD

    [Ответить]

    Kaimi:

    Были бы эти деньги, лично у меня в электронной валюте не было суммы хотя бы > 100$ уже около года.

    [Ответить]

    qweqwe:

    мда.. нищеброд((

    [Ответить]

    Kaimi:

    Конечно. Я же не циничный спаммер как некоторые, да и какими-то сакральными знаниями не обладаю.


  11. axzhel :

    Хорошая работа Kiami! Был бы очень благодарен если ты выложил эксешник.

    PS: Насчет выше написанных сообщений... Они просто сейчас завидуют, что некоторые получат программу бесплатно а некоторые должны заплатить по 50 у.е

    [Ответить]


  12. axzhel :

    piar-soft.info/soft/LSS/LSS.rar

    Да и... На эксешнике пароль.

    [Ответить]

    Kaimi:

    Пароль: 442ca31e85c7e36bfe3a0dca9
    Толку, я же говорил, что защита изменилась.

    [Ответить]

    axzhel:

    O_o Как ты узнал пароль?

    Да вот еще... Извините, но вашего серииника нет в базе! Обратитесь к разработчику за покупкои... Как исправить?

    [Ответить]

    Kaimi:

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


  13. axzhel :

    Для Вас ведь это не составит труда? :)
    Как два пальца об асфальт?

    Да. Как вы узнали пароль?

    [Ответить]

    Kaimi:

    Составит, да и зачем, в очередной раз гоняться с автором? Ему то проще, скачал крякнутый протектор и в очередной раз сменил защиту.
    Пароль...Владельцы лицензии сообщили.

    [Ответить]


Оставьте ваш комментарий