пишите нам 
Ладошки: КПК, Коммуникаторы, Смартфоны, Windows Mobile, Symbian, Palm OS PDA и гаджеты
Ладошки: КПК, Коммуникаторы, Смартфоны, Windows Mobile, Symbian, Palm OS PDA и гаджеты
Ладошки: КПК, Коммуникаторы, Смартфоны, Windows Mobile, Symbian, Palm OS PDA и гаджеты
Ладошки к Солнцу! Ладошки: КПК, Коммуникаторы, Смартфоны, Windows Mobile, Symbian, Palm OS PDA и гаджеты
 

Приветствуем на Ладошках!

Ладошки, у меня РАНЧИК РОДИЛСЯ! :-) ... Уважаемые давние поклонники и посетители Ладошек! Я запускаю коммьюнити-сайт, новый проект, а вы все, будучи https://www.facebook.com/run4iq Бег для интеллектуалов. Бег для интеллекта. Бег "за" интеллектом. Он сам не придёт ;-) Ранчик родился! Андрей AKA Andrew Nugged Ладошки служат как архив программ для Palm OS и Poclet PC / Windows Mobile и разрешённых книг с 15 окрября 2000 года.
 
Статьи на Ладошках: о КПК и гаджетах
список групп статейВы пишете статьи? Контактируйте с нами!

 
Платформа Palm OS: защита программного обеспечения


Автор/Источник: Aaron Ardiri
» 14.05.2003 13:34,
просмотров сегодня: 1, всего: 8816
статья размещена в группе: Программирование
оценка: 2.862, 65 голосов


Программное пиратство — это неавторизированное копирование программного обеспечения. Оно выражается в разных формах, однако, в основном, это нелегальное копирование и неавторизированное изменение программного обеспечения, для обхода регистрационной системы или регистрационных предупреждений. Когда пользователи покупают программное обеспечение, они контактируют с продавцом и становятся лицензированными пользователями программы. Эта лицензия не передает права на программу, однако дает право на ее использование. Этот документ осветит проблемы, о которых должны быть осведомлены авторы программного обеспечения для Palm OS платформы — это программное лицензирование, распространение ПО через веб-сайты, антивзломные технологии, и личный опыт разработчика, который ушел в подполье к пиратскому сообществу, чтобы изучать инструменты, используемые в пиратстве программного обеспечения.

1. Введение

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

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

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

2. Лицензирование программного обеспечения

Все программное обеспечение распространяется с определенными лицензиями, которые налагаются, когда пользователь получает копию программы. Существует множество видов лицензий, определенных в Free Software Foundation [3], но фактически это:

2.1 Free Software

Free Software — программы, которые идут с разрешением использования, копирования, распространения в том виде, в котором они распространялись, или в измененном виде, бесплатно или с оплатой. Наиболее важный фактор Free Software является то, что вместе с ним должны поставляться исходные коды программы. Смыслом Free Software является свобода, а не цена. GNU General Public License (GPL) является примером Free Software лицензии.

2.2 Freeware

Freeware — это программное обеспечение, в котором разработчик не просит оплаты лицензии, и программа может распространяться свободно в неизменяемом состоянии. Исходные коды в основном не поставляется, и продукт должен распространяться неизменным. Если исходные коды предоставляются, новые разработчики должны просить разрешения у автора на изменение и распространение измененного ПО.

2.3 Shareware

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

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

  • выключить действие набора функций
  • ограничить использование некоторых функций
  • «доверительная» система (предлагает полную версию без концепции регистрации)
  • напоминания о необходимости зарегистрировать ПО (диалоги, сообщение и т.п.) или
  • сделать вынужденные паузы при выполнении определенных задач (заставлять пользователя ждать)
Реализация этих методов может быть разной, а также существует множество других методов.

2.4 Commercial

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

3. Электронная дистрибуция программного обеспечения

Программное обеспечение для платформы Palm OS может распространяться либо в розницу, либо по сети Интернет через веб-сайт электронного распространения программных средств (ESD), на котором принимается оплата непосредственно по кредитным карточкам, при этом снимаются небольшие комиссионные, а выручка передается разработчикам проданных приложений.

3.1 Процесс покупки

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

Как правило, покупатель не получает программный продукт непосредственно в момент покупки. Разработчику посылается электронным письмом уведомление о покупке, после чего он должен лично связаться со своим покупателем и предоставить ему соответствующие регистрационные файлы и/или ключи. Это может привести к длительным задержкам, особенно, если речь идет об иностранных разработчиках, и поэтому такой способ приобретения программных продуктов является не очень удобным для покупателей...

3.2 Торговый знак RealTime Fulfillment

RealTime Fulfillment называется способность предоставить покупателям ПО на веб-сайтах незамедлительный доступ к зарегистрированной версии программы сразу же после ее приобретения. Это можно осуществить двумя способами: обеспечить возможность скачать полную версию, или же предоставить код разблокировки заблокированных ранее функциональных возможностей программы.

В первом квартале 2001 года, PalmGear H.Q. [5] позволил разработчикам генерировать коды разблокировки для пользователей во время оплаты счета при покупке программного продукта. jCode, система, предоставляемая PalmGear H.Q., позволяет разработчикам редактировать, компилировать и тестировать алгоритмы генерации ключей через веб-броузер без инсталляции средств разработки.

Введение такого сервиса позволяет разработчикам использовать для регистрации программ системы, основанные на ключах, предотвращая тем самым какие-либо задержки и необходимость ручной обработки заказов разработчиками. Демонстрационная версия данной системы находится на сайте http://www.ardiri.com/palm/jCode/.

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

4. Способы регистрации

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

4.1 Бит защиты от копирования базы данных

В платформе Palm OS реализована своя собственная форма защиты от копирования, которая защищает приложение или базу данных от передачи между устройствами в модуле запуска приложения. Приложение является по существу исполняемой базой данных ресурсов, и программист может установить определенные свойства приложения, используя простой вызов программного интерфейса приложения (API) [4].

Err DmSetDatabaseInfo(UInt16 cardNo, LocalID dbID, const Char* nameP, UInt16* attributesP, UInt16* versionP, UInt32* crDateP, UInt32* modDateP, UInt32* bckUpDateP, UInt32* modNumP, LocalID* appInfoIDP, LocalID* sortInfoIDP, UInt32* typeP, UInt32* creatorP);

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

{
	UInt16 card, attributes;
	LocalID dbID;
	SysCurrAppDatabase(&card, &dbID);
	if (dbID != NULL) {
		DmDatabaseInfo(card, dbID, NULL, &attributes, NULL, NULL, 
		NULL, NULL, NULL, NULL, NULL, NULL, NULL);
		attributes |= dmHdrAttrCopyPrevention;
		DmSetDatabaseInfo(card, dbID, NULL, &attributes, NULL, NULL,
		NULL, NULL, NULL, NULL, NULL, NULL, NULL);
	}
}

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

Эта идея упрощена и обеспечивает ограниченную защиту. Впрочем, многие утилиты управления файлами третьих сторон не проверяют этот атрибут — и передача базы данных (а также приложения) происходит достаточно легко. Также не представляет большого труда написать небольшое Palm OS приложение, которое устанавливает этот бит как «0» во всех базах данных и «обманывает» этот механизм защиты.

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

4.2 Серийный номер

Серийный номер — это номер или последовательность символов, одинаковая для всех зарегистрированных пользователей какой-то определенной части программного продукта. Когда пользователь вводит серийный номер, то активируются все возможности программы. Разработчик, как правило, создает для пользователей диалоговое окно, где им нужно ввести серийный номер или последовательность символов. Обработка вводимых данных выполняется способом, аналогичным нижеприведенному:

program.c:
#define SERIALNUMBER 12345
#define incorrectSerialAlert 1001
{
	codeEntered = StrAToI(strCode);
	if (codeEntered != SERIALNUMBER)
		FrmAlert(incorrectSerialAlert);
	else
		registered = true;
}

При дизассемблирование исходного кода, мы получим код схожий с следующим:

program.s:
...
2F03 MOVE.L D3,-(A7)
4E4FA0CE TRAP #15
DC.W sysTrapStrAToI
4FEF000C LEA 12(A7), A7
0C403039 CMPI.W #12345!$3039,D0
6708 BEQ L9
3F3C03E8 MOVE.W #1000!$03E8,-(A7)
4E4FA192 TRAP #15
DC.W sysTrapFrmAlert
7000 L9 MOVEQ #0,D0
...

Итак, серийный номер прямо перед Вами! В этой неразберихе ассемблеровского кода спрятано сравнение регистра D0 (который содержит результат запроса StrAToI) с постоянным значением. В данном случае, значением, с которым идет сравнение, является 0x3039 или 12345. Это в том случае, когда речь идет о простых числах, если в качестве сравнивающих последовательностей символов используется вызов StrCompare API [4], а последовательность символов, с которой сравнивают, содержит свой адрес, загружаемый при помощи команды LEA (к метке в коде) до того, как сделан вызов. Поиск кода для этой метки приведет Вас прямо к тому месту в памяти, где содержится необходимая последовательность символов.

Разработчик должен в достаточной мере доверять своим пользователям, чтобы быть уверенным в том, что они не раздают серийный номер своим друзьям или не распространяют его. «Добросовестный» пользователь может приобрести продукт и просто передать всю необходимую информацию другим людям.

4.3 Окно Nag

«Nag Screen» — это диалоговое окно, появляющееся, когда Вы запускаете приложение, и напоминающее Вам о необходимости зарегистрироваться. Его целью является постоянно докучать пользователю напоминаниями о покупке программного продукта, пока, наконец, пользователь не дойдет до той кондиции, что он сыт по горло этими напоминаниями.

Реализация окна Nag проста, если использовать следующий программный интерфейс приложения [4]:

UInt16 FrmAlert(UInt16 alertID);

Эти типы приложений очень уязвимы для взлома и модификации, поскольку внесение изменений — это достаточно простая задача даже для начинающих хакеров. Давайте рассмотрим следующий фрагмент кода:

program.c:
#define nagScreenAlert 1000
{
	FrmAlert(nagScreenAlert);
}
При дизассемблирование исходного кода, мы получим код схожий с следующим:
program.s:
...
3F3C03E8 MOVE.W #1000!$03E8,-(A7)
4E4FA192 TRAP #15
DC.W sysTrapFrmAlert
...

Простая замена вызова программного интерфейса приложения (API) — это все, что необходимо для того, чтоб избавиться от получения предупреждений с экрана. Взлом так же прост, как и замена вызова TRAP (0x4E4FA192) на последовательность команд NOP (0x4E714E71) — необходимы два оператора NOP, поскольку в TRAP используется 4 байта, тогда как NOP необходимо только два байта. Конечный код будет иметь следующий вид:

program.s:
...
3F3C03E8 MOVE.W #1000!$03E8,-(A7)
4E71 NOP
4E71 NOP
...

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

4.4 Системы генерации кода

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

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

Платформа Palm OS содержит встроенную псевдо-уникальную порцию информации. Каждый пользователь должен выполнить HotSync своего устройства для того, чтобы сделать резервные копии, обновить данные на своем устройстве или проинсталлировать программный продукт. Эта информация является псевдо-уникальной, поскольку во многих случаях два человека, находящихся поблизости друг от друга, не использовали бы одно и то же имя HotSync (если бы они использовали одно и то же имя, то они бы не смогли выполнить HotSync на одном и том же устройстве). Имя пользователя HotSync можно получить в приложении, используя следующий код [4].

#include 
{
	CharPtr username =
	(Char *)MemPtrNew(dlkUserNameBufSize * sizeof(Char));
	DlkGetSyncInfo(NULL,NULL,NULL,&username,NULL,NULL);
	...
	MemPtrFree(username);
}

Имя пользователя HotSync может вызвать ряд небольших проблем. Многие пользователи даже не имеют представление, что это такое, хотя и могут напечатать его правильно при покупке продукта. Необходимо быть внимательным с пунктуацией, заглавными буквами, пробелами и локализацией имени пользователя для HotSync. Самым гибким решением этой проблемы является трансляция двоичного представления в другую форму, более удобную для пользователя, как, например, это может быть сделано с такими средствами разработки, как RegCode [6].

В серии устройств Palm III (под операционную систему Palm OS 3.0) реализована другая форма уникальной идентификации — серийный номер перезаписываемого ПЗУ. В случаях, когда устройство содержало микросхему перезаписываемого ПЗУ, было возможно получить уникальный серийный номер для каждого пользователя, используя следующий код [4]:

{
	CharPtr serialNo;
	UInt16 serialLength, returnVal;
	returnVal =
	SysGetROMToken(0,sysROMTokenSnum,&serialNo,&serialLength);
	if ((!retval) && (serialNo) && ((UInt8)*serialNo != 0xff)) {
		...
	}
}

Однако не все устройствана платформе Palm OS (Handspring [2], IIIe, m100) содержат микросхемы с перезаписываемого ПЗУ. Они содержат так называемые схемы ПЗУ с масочным программированием, которые не содержат этого серийного номера и они не расширяемы. Кроме того, возникает проблема, когда пользователь повреждает или теряет свое устройство — при замене будет другой серийный номер перезаписываемого ПЗУ и потребуется перерегистрация пользователя или повторная покупка приложения. Компания Palm Inc. не несет обязательств предоставлять эту информацию для выпускаемых ею устройств и устройств своих лицензиатов.

В качестве альтернативного варианта, разработчик может предоставить пользователю возможность вручную ввести последовательность символов, которая используется для генерации значения ключа разблокирования. Поскольку ключ не привязан к какому-то конкретному устройству, то пользователь может использовать одно и тоже имя пользователя/комбинацию ключа на нескольких устройствах (включая устройства своих друзей). Более того, пользователь также может взламывать устройства (новую микросхему ПЗУ или имя пользователя на HotSync) и оставаться зарегистрированным, без необходимости проходить процесс новой регистрации (он ведь уже заплатил).

Как только получена пользовательская информация, разработчик пишет функцию, аналогичную данной:

UInt16 generateRegistrationCode(CharPtr username)
{
	UInt16 code, i;
	code = 0xCAFE;
	for (i=0; (i < dlkUserNameBufSize) && (username[i] != ''); i++) {
		code += (((username[i] & 0xAA) << 8) | (username[i] & 0x55));
		code = ((code << 1) | ((code & 0x8000) >> 15));
	}
	return code;
}

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

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

Еще одним подходом является использование обратного варианта: предоставить пользователю код, который затем используется для генерации строки. Далее сгенерированная строка сравнивается с набранной на устройстве. Этот метод несколько затрудняет создание генератора ключа, поскольку алгоритм преобразования последовательности символов в число отличается от преобразования числа в последовательность символов.

4.5 Счетчик

«Испытательный срок» — это широко используемый метод ограничения пользователя в использовании программы, который выражается предопределением количества запусков программы или предопределением периода времени ее использования. Широко распространенным примером счетчика является «30-дневный испытательный срок».

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

typedef struct
{
	UInt32 keyData; // the time the application was first started
	UInt8 keyValue; // the number of days remaining in the trial
} KeyType;

{
	KeyType *key = NULL;
	// load the key from somewhere
	...
	// a key exists, lets do our checking
	if (key != NULL) {
		UInt32 diff = TimGetSeconds() — key->keyData;
		// 30 days is over?
		if (diff >= 0x00278D00)
			 key->keyValue = 0; // 0x00278D00 = 30 days
		else
			key->keyValue = 30 — (diff / 0x00015180);
			// 0x00015180 = 1 day
	}
	// no key, create it.
	else {
		key = (KeyType *)MemPtrNew(sizeof(KeyType));
		key->keyData = TimGetSeconds();
		key->keyValue = 30;
	}
	// save the key somewhere
	...
	// clean up
	MemPtrFree(key);
}.

Приложение должно где-то хранить информацию, необходимую для определения оставшегося срока его бесплатного использования. Структура «KeyType» содержит всю соответствующую информацию, необходимую для того, чтобы приложение контролировало время своего использования. Данная структура хранит дату, когда приложение было запущено первый раз, а также количество оставшихся дней испытательного срока.

Где хранится данная информация?

Ответ на этот вопрос — на усмотрение разработчика. Информация может храниться в одном из следующих мест:

  • сохраняемые preferences
  • регистрационная база данных
  • ПО управления регистрацией (регистрационный сервер)
Платформа Palm OS содержит очень эффективную систему управления приложением. Так, например, когда удаляется приложение, то все связанные с ним настройки и базы данных тоже удаляются.

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

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

Не кажется ли вам, что, когда пользователь сталкивается с необходимостью удалять приложение и инсталлировать его заново, то это может являться для него барьером к совершению покупки?

Еще одним способом было бы добавление в Palm регистрационного сервера. Разработчик мог бы использовать эту область памяти для хранения необходимой информации, но опять-таки память на вес золота.

5. Анти-взломные технологии

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

5.1 Специальные версии

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

Данный подход является одним из лучших механизмов защиты Вашего программного продукта. Его преимущество состоят в том, что невозможно изменить демонстрационную версию так, чтоб она стала зарегистрированной, однако он не защищает приложение от передачи между пользователями (в том числе и зарегистрированной версии продукта). Точно так же как и демо-версию, зарегистрированную версию можно будет скачать в Интернете и использовать.

5.2 Проверка регистрации

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

Многие разработчики приложений выбирают простое решение. Можно написать всего лишь одну функцию, которая, на основе того, что известно приложению, возвращает значение true или false, указывая на статус регистрации

..program.c:
Boolean checkRegistered()
{
	Boolean result = false;
	// check user registration (using appropriate technique)
	...
	return result;
}
При дизассемблирование исходного кода, мы получим код схожий с следующим:
program.s:
4E560000 LINK A6,#0
...
57C0 SEQ D0
4400 NEG D0
4E5E UNLK A6
4E75 RTS

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

program.s:
4E560000 LINK A6,#0
...
7001 MOVEQ #1,D0
4E5E UNLK A6
4E75 RTS

Замена инструкции ассемблера перед командой UNLK инструкцией MOVEQ #1,D0 (0x7001) заставляет эту операцию всегда возвращать результат true. Это означает, что когда бы ни выполнялась проверка, приложение «считает», что оно зарегистрировано — и программа продолжает работать. Модульный принцип делает несложным достижение этой цели, поскольку проверка выполняется в одном месте.

Достаточно просто предотвратить это, используя атрибут inline, который указывает компилятору, что необходимо встроить код подпрограммы в функцию, вместо встраивания обычно вызова подпрограммы.

program.c:
inline Boolean checkRegistered()
{
	...
}

Таким образом, в ассемблерном виде, в месте каждого вызова функции checkRegister будет находиться копия этой функции, и для взлома программы необходимо будет сделать столько заплат, сколько раз вызывалась данная функция.

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

5.3 Эмулятор (POSE) Palm OS

Эмулятор Palm OS — это очень важная сервисная программа, обеспечивающая возможность незаконного использования и взлома программного продукта, без риска потери данных или повреждения устройства. При использовании эмулятора в сочетании с мощными средствами отладки программы, он демонстрирует превосходную отладку программного продукта. Без этих программных средств было бы очень утомительно и рискованно выполнять изменение программного продукта.

Определить запущена ли программа на эмуляторе можно, используя следующие два метода:

Boolean onPOSE()
{
	UInt32 value;
	Err err;
	// works on all versions of the Palm OS
	return (FtrGet('pose', 0, &value) == errNone);
}
или:
#include «HostControl.h» // distributed with POSE
Boolean onPOSE()
{
	// works only on versions of the Palm OS >= 3.0
	return (HostGetHostID() == hostIDPalmOSEmulator);
}

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

Хотя программа отладки под Palm может также быть подключена к реальному устройству для выполнения отладки, не прибегая к использованию эмулятора Palm OS.

5.4 Контрольная сумма кода

Первоначальной мерой защиты против изменения программы является способность обнаружения наличия изменений. Контрольная сумма, используемая обычно для проверки огромных кусков данных, переносимых между источниками, может также использоваться для определения, было ли внесено какое-либо изменение в приложение. Интерфейс прикладных программ (API) [4] выполняет операцию на системном уровне для реализации контрольной суммы на уровне CRC16.

{
	MemHandle codeH;
	void *codeP;
	UInt32 codeSize;
	UInt16 checkSum;
	// obtain a reference to the code0001.bin resource
	codeH = DmGet1Resource('code', 0x0001);
	codeSize = MemHandleSize(codeH)
	codeP = (void *)MemHandleLock(codeH);
	// determine the checksum of the code segment
	checkSum = Crc16CalcBlock(codeP,codeSize,0);
	MemHandleUnlock(codeH);
}

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

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

5.5 Обнаружение вставки

Еще один способ проверки двоичного кода приложения заключается в его анализе при помощи поиска различных известных кодов операции ассемблера, которые были вставлены в распространяемое приложение. Двумя наиболее мощными кодами операции ассемблера, используемых в изменении программ, являются NOP (0x4E71) и TRAP #8 (0x4E48). В большинстве случаев, данные коды операции не обнаруживаются в конечных версиях приложений, поскольку они используются, главным образом, как вспомогательные средства при разработке программных продуктов.

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

{
	MemHandle codeH;
	void *codeP;
	UInt32 codeSize;
	UInt8 patchStatus, i;
	patchStatus = 0;
	for (i=0; i> 1); // we are counting words
		codeP = (void *)MemHandleLock(codeH);
		// search for a patch (0x4e48 or 0x4e71)
		asm(«movem.l %%d0-%%d1/%%a0-%%a1, -(%%sp)» : : );
		asm(«move.l %0, %%a0» : : «g» (codeP));
		asm(«move.l %0, %%a1» : : «g» (&patchStatus));
		asm(«move.l %0, %%d0» : : «g» (codeSize-1));
		asm(»
		move.w (%%a0), %%d1
		eori.w #0xffff, %%d1
		cmpi.w #0xb18e, %%d1 | check if opcode is 0x4e71
		beq L01
		cmpi.w #0xb1b7, %%d1 | check if opcode is 0x4e48
		beq L02
		bra L03
		L01:
		ori.b #1, (%%a1) | bit one set if NOP
		bra L03
		L02:
		ori.b #2, (%%a1) | bit two set of TRAP #8
		L03:
		addq.l #2, %%a0
		dbra %%d0, L01
		« : : );
		asm(«movem.l (%%sp)+, %%d0-%%d1/%%a0-%%a1» : : );
		MemHandleUnlock(codeH);
	}
}

Вышеописанный код специально разработан для использования в среде разработки PRC-Tools (GNU gcc), однако задача написания кода, совместимого с CodeWarrior, не должна быть слишком объемной.

Ссылка на каждый сегмент кода получена, затем выполняется линейный поиск двух кодов операции: (0x4E71 и 0x4E48). XOR'ing с 0xFFFF необходим для того, чтобы предотвратить обнаружение двух кодов операции кодом обнаружения заплат.

У разработчика также есть возможность использовать этот механизм для подсчета определенного количества кодов операции в приложении. Всего лишь выполняя «обнаружение» и сохраняя его в результате булева выражения Boolean, и добыча у вас в руках — нужно лишь установить значение в false, и ничего лучшего приложению не надо.

Более продвинутым методом является ручная вставка предопределенного количества кодов операции в приложение и использование счетчика, как зависимости или смещения в Вашем приложении. При введении еще одного кода операции, число изменяется и может использоваться для предотвращения программы от самого запуска. Давайте рассмотрим следующий код:

void theCodeToExecuteIfNOTPatched()
{
	...
}
void theCodeToExecuteIfPatched()
{
	...
}

{
	void *functionList[] = {
		&theCodeToExecuteIfPatched,
		&theCodeToExecuteIfPatched,
		&theCodeToExecuteIfNOTPatched,
		&theCodeToExecuteIfPatched
	};

	void (*function)(void);
	// determine which function to execute
	function = (void *)functionList[opcodeCount % 4];
	function();
}

В этом примере код будет выполняться корректно только в том случае, если модуль кода операции 4 равен 3. Введение дополнительного кода операции приведет к изменению счета и вызову другой функции.

5.6 Зашифрованный код

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

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

{
	MemHandle codeH;
	UInt32 codeSize;
	UInt8 *codeP;
	// duplicate the encrypted resource into memory
	codeH = DmGet1Resource('data', 0x1111);
	codeSize = MemHandleSize(codeH)
	codeP = (UInt8 *)MemPtrNew(codeSize);
	MemMove(codeP, MemHandleLock(codeH), codeSize);.MemHandleUnlock(codeH);
	// dynamically modify the data stored in the codeP pointer
	// ...
	// execute the function
	{
		void (*myCode)(void); // the function specification
		myCode = (void *)codeP;
		myCode();
	}
	// clean up
	MemPtrFree(codeP);
}

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

Данный метод предоставляет разработчику уверенность в том, что простая декомпиляция приложения не обнаружит алгоритмы, используемые в приложении. Однако если использовать программу отладки (Palm Debugger), то можно получить дамп памяти во время работы программы. Этот дамп памяти может быть затем декомпилирован и просмотрен. Не имеет значения, насколько сильны Ваши методы кодирования — любопытный пользователь может всего лишь подождать завершения декодирования и затем просто выполнить этот дамп памяти.

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

5.7 Самомодифицирующийся код

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

{
	// turn off memory semaphore protection
	MemSemaphoreReserve(true);
	// modify memory inside the application (or anywhere else on device)
	// ...
	// turn on memory semaphore protection
	MemSemaphoreRelease(true);
}

Правильное использование данного метода может, несомненно, сильно озадачить нерадивого пользователя и потратить много его времени (что само по себе уже очень хорошо). Тем не менее, выполнение такого рода операции может быть очень опасно или привести к тому, что в Вашем приложении появятся проблемы при его использовании другими пользователями. Например, приложение будет выполняться только тогда, когда будет храниться в ОЗУ (RAM) — перемещение его во внешнюю память или в перезаписываемое ПЗУ (ROM) может стать причиной невыполнения операций записи в память.

5.8 Разбиение кода

Интересным суждением, бытующим в кругу взломщиков, является следующее: «если нет регистрационного кода — не стоит беспокоиться». Практически невыполнимой задачей для любого взломщика является вмешательство в Ваше приложение с целью сделать его зарегистрированным, если код, выполняющий операции и доступный только для зарегистрированных пользователей, не хранится в самом приложении. Данный метод реализован таким образом, каким выполнено кодирование сегментов кода. Добывается ссылка на адрес ячейки памяти, и затем она выполняется.

{
	DmOpenRef dbRef;
	// try and open the database
	dbRef = DmOpenDatabaseByTypeCreator('_key',appCreator,dmModeReadOnly);
	if (dbRef != NULL) {
		MemHandle recordH;
		// get a reference to the first record
		recordH = DmGetRecord(dbRef, 0);
		// execute the function
		{
			void (*myCode)(void); // the function specification
			myCode = (void *)MemHandleLock(recordH);
			myCode();
			MemHandleUnlock(recordH);
		}
	}
}

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

Демонстрационная версия является зарегистрированной версией. С точки зрения пользователей, они инсталлируют приложение для демонстрационных целей и используют программный продукт надлежащим образом. При покупке приложения, пользователю предоставляется дополнительный файл для инсталляции его на своем устройстве. Характер приложения таков, что при наличии этого файла приложение считается зарегистрированным, а если файл не найден — то незарегистрированным. Один пользователь может передавать приложение другим пользователям, даже если приложение и зарегистрировано, поскольку дополнительную базу данных передать нельзя. Используя дополнительные проверки пользовательского имени через HotSync, можно сделать базу данных псевдо-уникальной для каждого пользователя.

6. ПРИМЕР: Liberty — Эмулятор GameBoy для Palm OS

Liberty [7] — это первый эмулятор GameBoy для платформы Palm OS. Разработанный совместно с Michael Ethetton и Aaron Ardiri, эмулятор Liberty получил всеобщее признание за достижение того, что многие люди считали «невыполнимым». Поскольку эмулятор Liberty является коммерческим продуктом, описания того, как именно работает схема регистрации внутри, остается во власти фантазии читателя. Целью описания данного продукта является показать тот объем работ, который необходимо выполнить для реализации изощренной системы регистрации.

6.1 Дизайн

Первоначальная демонстрационная версия эмулятора Liberty позволяла пользователю эмулировать изображения игр GameBoy, размер которых не превышал 32 Кб. Пользователю были доступны главным образом изображения демонстрационных и бесплатных приложений. Тогда было известно более 50 изображений игр GameBoy, отвечающих данному требованию.

Получив большое количество негативных откликов пользователей относительно ограничений, наложенных на первую версию, последующие версии эмулятора Liberty предоставляли испытательный срок, ограничивающий количество выполнений приложения 30 разами ( поддерживались изображения игр GameBoy любого размера). Как только испытательный срок истекал, пользователь получал ограничения и мог работать с изображениями GameBoy размером в 32 Кб.

6.2 Реализация

Для того чтобы понять процесс реализации в приложении Liberty системы регистрации, важно понять структуру приложения. Эмулятор Liberty является мульти-сегментированным приложением вследствие своего размера, который упрощает реализацию системы регистрации. Всего приложение Liberty содержит семь сегментов кода.

code0000.bin — системный код сегмента
code0001.bin — главный код сегмента
code0002.bin — операции регистрации
code0003.bin — абстрактный уровень устройства
code0004.bin — операции эмуляции GameBoy 
code0005.bin — система справочной информации
code0006.bin — загрузчик системы регистрации
code0007.bin — ключ кодирования
Логическая схема регистрации приведена ниже:
LibertyRegistration()
{
	// tampering check
	perform a code scan, looking for NOP, TRAP #8
	if modified
		destroy code0006.bin code segment (self-terminate)
	endif
	// registration loader
	load the code0006.bin resource into the dynamic heap
	using code0002.bin as a key, decrypt it
	execute the decrypted code
	discard the memory used.
	// continue with the program
}

Для предотвращения взлома приложения ничего другого не остается, кроме как реализовать полный отказ приложения, когда кто-либо пытается сделать заплату (патч) двоичному коду, используя команду NOP или TRAP #8 (прерывание отладки). Если обнаруживается внесение изменения, то приложение самоликвидируется.

Сегмент кода code0006.bin закодирован на основе сегмента кода code0002.bin. Он содержит механизм для загрузки системы регистрации и зашифрован, чтобы уберечь от любопытных глаз то, что именно происходит. Что это дает? Дополнительную защиту в случае, если системы обнаружения заплат первого уровня успешно обойдены взломщиком.

registrationLoader()
{
	// sanity
	assume no demo, no registration
	// key resource search
	locate the registration key database
	if database found
		load the first record info the dynamic heap
		using code0007.bin and the HotSync username, decrypt it
		if demo database
			perform a 30 trial limit check
		end if
		store registered routine for later use
	end if
}.

Изменение ресурса code0002.bin приводит к некорректному декодированию загрузчиком регистрации. Программа загрузки регистрации отвечает за локализацию регистрационного ключа, определение версии (демонстрационная или зарегистрированная) и подготовку системы к использованию. Ее целью является обеспечение приложения дополнительным кодом, который недоступен в демонстрационной версии.

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

Демонстрационный ключ идентичен ключу полной версии, но он закодирован специальным пользовательским именем HotSync с шансом в 0.01% быть использованным обычным пользователем.

Незаконная дистрибуция приложения между пользователями не происходит, поскольку файл с ключом уникален для каждого пользователя. Это реализовано путем применения пользовательского имени HotSync для определения начала офсета для алгоритма кодирования, использующего сегмент кода code0007.bin в качестве своего ключа. Алгоритм кодирования использует команду XOR и динамично изменяет ключ после обработки каждого байта.

Рассмотрим нижеприведенный сегмент кода в приложении Liberty:

{
	// lock down the first 32K
	globals->emu.ptrPages[0] =
		(UInt8 *)MemHandleLock(DmGetResource(datType,0));
	globals->emu.ptrPages[1] =
		(UInt8 *)MemHandleLock(DmGetResource(datType,1));
        // load and lock the «remainding» rom chunks :))
	{
		GameAdjustmentType adjustType;
		// define the «adjustment»
		adjustType.adjustMode = gameLoadROM;
		adjustType.data.loadROM.pageCount = globals->emu.pageCount;
		adjustType.data.loadROM.ptrPages = globals->emu.ptrPages;
		// do it! :))
		RegisterAdjustGame(prefs, &adjustType);
	}
}

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

Потребовались дополнительные средства разработки для реализации процесса пост-компиляции ресурсов кода шифрования и отделения зарегистрированного кода во внешний файл базы данных.

Объем работ по дизайну и реализации данной системы может приблизительно измеряться одним месяцем.

7. Выводы

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

Время для взлома >= Времени, которое готов взломщик потрать И Раздражение доставляемое пользователю < Раздражение, которое выносит пользователь

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

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

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

Зачем они это делают? Ответ очень прост — потому что этого делать нельзя.

Я провел это исследование в области анти-взломов для того, чтобы удовлетворить мой личный интерес. Мне хотелось выяснить, как это делается — из чистого интереса. В процессе исследования я потратил много своего времени и усилий для того, чтобы расширить свои знания в этой области. Однако в то же самое время я добавил «горючего», которое необходимо пиратству для своего существования. Спрос порождает предложение. Главная задача — сделать все, чтобы честные пользователи оставались честными, — это самое важное.

Ссылки :

[1] Palm Computing — http://www.palm.com/
[2] Handspring — http://www.palm.com/
[3] Free Software Foundation — http://www.gnu.org/philosophy/categories.html Categories of Free and Non-Free Software
[4] Palm Computing — http://www.palmos.com/ PalmOS SDK 3.5 Reference
[5] PalmGear H.Q. — http://www.palmgear.com/
[6] Palm Creations — http://www.palmgear.com/software/showsoftware.cfm?prodID=2997 RegCode Development Kit
[7] Gambit Studios, LLC. — http://www.gambitstudios.com/

Перевод: Виктор Ронин, www.LADOSHKI.com

1   2   3   4   5
«хуже     ваша оценка     лучше»


Обсуждение
18.05.2003 10:31 - SYM

Довольно академичный и схематичный материал.
Даже если не вдаваться в технические тонкости (борьба брони и снаряда есть и здесь, и зашла она далеко), не освещены некоторые важные вопросы, в частности, неудобства, которые обретает пользователь по вине слишком изощренного автора программы. Пример. Расейский гений Палм-программирования А.Кляцкин (pZip, Pentix, etc) напихал в свой софт кучу антиотладочных трюков и проч. Отлично. Теперь зарегистрированные пользователи стоят перед проблемой — на PalmOS5 и её эмуляторе MAME это добро просто не запускается!


13.11.2003 10:02 - shub@

А как по мне — отличная толковая статья.
SYM пишет «не освещены некоторые важные вопросы» — скорее всего эта стать не призвана стать четким руководством к действию, а служит неким философским вступлением в теорию защиты программ под PalmOS.



Чтобы писать комментарии вам нужно
авторизоваться (войти) или зарегистрироваться


 
Регистрация товарного знака в Украине patent.km.ua.
Telephones, address and opening times for shops, post and banks in the UK
 
 

 

статьи
по этой теме
 
EWE — инструмент разработки Java-приложений для платформы PocketPC
04-02-2008, рейтинг(общ.): 3.4, просм.: 0/5815

Немножко о работе с сетями (Palm OS)
18-03-2003, рейтинг(общ.): 2.22, просм.: 0/4442

Установка цветов прорисовки (Palm OS)
15-01-2003, рейтинг(общ.): 2.35, просм.: 0/2760

Накладывающиеся Control’ы (Palm OS)
29-12-2002, рейтинг(общ.): 2.572, просм.: 0/2564

Создание Static Library (Palm OS)
28-12-2002, рейтинг(общ.): 2.9, просм.: 1/3222

Использование внеэкранного буфера при высоком разрешении (Palm OS)
11-12-2002, рейтинг(общ.): 2.226, просм.: 0/3331

Четыре метода перехвата нажатий на кнопки Палма (Palm OS)
28-11-2002, рейтинг(общ.): 2.653, просм.: 0/3599

Ссылки по программированию (Palm OS). Часть 3
21-11-2002, рейтинг(общ.): 1.812, просм.: 1/5558

Самоудаляющаяся программа (Palm OS)
21-11-2002, рейтинг(общ.): 2.364, просм.: 1/3034

Программирование под Palm OS: Урок 1. Знакомство с CodeWarrior и Constructor’ом
15-11-2002, рейтинг(общ.): 4.438, просм.: 0/4413

Недокументированные функции MemSemaphoreReserve и MemSemaphoreRelease (Palm OS)
14-11-2002, рейтинг(общ.): 2.749, просм.: 0/2531

Устройство и функционирование Shared Library (Palm OS)
12-11-2002, рейтинг(общ.): 5, просм.: 1/2635

Программирование под Palm OS: Урок 0. Что нужно для того, чтобы писать под Palm?
09-11-2002, рейтинг(общ.): 3.824, просм.: 0/3121

Коротко о вставке текста в Field’ы (Palm OS)
07-11-2002, рейтинг(общ.): 2.44, просм.: 0/2425

Ссылки по программированию (Palm OS). Часть 2
31-10-2002, рейтинг(общ.): 2.475, просм.: 1/5206

Работа с полями таблицы (Palm OS)
31-10-2002, рейтинг(общ.): 2.334, просм.: 0/2742

Работа с потоковыми файлами на Palm’е (File Streaming API) (Palm OS)
31-10-2002, рейтинг(общ.): 2.095, просм.: 0/2872

Хранение проекта в системе контроля версий (Palm OS)
29-10-2002, рейтинг(общ.): 2.111, просм.: 0/2884

О custom control’ах и их реализации в виде gadget’ов (Palm OS)
28-10-2002, рейтинг(общ.): 2.632, просм.: 0/3074

Быстрая работа с графикой (Palm OS)
25-10-2002, рейтинг(общ.): 2.167, просм.: 1/3361

а вы знаете, что есть:
- рейтинг-каталог сайтов Ладошек?

поддержите
Ладошки
 
Рейтинг Ладошек: КПК, мобильность, коммуникаторы, смартфоны, гаджеты, высокие технологии Рейтинг каталога сайтов Хмельницкого региона Поддержите Ладошки: Как поддержать сайт?
Использование материалов сайта разрешено только при наличии
гиперссылки на страницу Ладошек без блокировки индексации
реклама на сайте    Andrew Nugged © 2000-2015