ترفندی برای کم اثر کردن شکستن قفل میکروکنترلر

اگر می‌خواهید محصول خود را که مبنی بر میکروکنترلر است، به بازار یا به هر دلیلی به افرادی، ارائه دهید و در عین حال نگران کپی شدن آن و هدررفتن زحماتتان هستید. احتمالاً خواندن این مطلب برای شما خالی از لطف نباشد. در ادامه می‌خواهم برای شما از روشی صحبت کنم که حتی اگر کسی با شکستن قفل میکروکنترلر به کد هگز برنامه شما دسترسی پیدا کرد، نتواند به راحتی از آن استفاده کند. اگرچه توضیحاتی که در ادامه خواهید دید به طور خاص برای میکروکنترلر STM32 نوشته شده است، اما اصل روش برای سایر میکروکنترلرها نیز کاربردی است.

کافی است چند روز از آشنایی شما با برنامه نویسی -خاصه در اینجا نوع میکروکنترلری‌اش- گذشته باشد تا پی ببرید که کار چندان ساده‌ای هم نیست. این ساده نبودن باعث شده تا عده‌ای به این فکر بیفتند که چه کاری است این همه زحمت. زحمت برنامه‎نویسی اش ارزانی طراحان. ما بیاییم کپی کنیم و بفروشیم و حالش را ببریم.

ماجرا از آنجا شروع شد که قرار شد نسخه‌ای از یکی از بردهای شرکت برای نمونه، به یک شرکت خارجی داده شود تا فیدبک لازم گرفته شود. انصافاً شرکت برای به اینجا رساندن برد هزینه مادی و زمانی زیادی صرف کرده بود. برای من به شخصه تصور این موضوع بسیار دردناک می‌نمود که در آن ینگه دنیا سر ایرانی جماعت کلاه گذاشته شود و قرار باشد این زحمات دوستان و همکاران به باد فنا رود. از این رو در پس خاطراتم به این صرافت افتادم که راهکاری برای این موضوع سرچ کنم. بارها خوانده بودم که شکستن قفل میکروکنترلر، شکستن شاخ غول نیست. کافی است که به برادران کشور دوست و همسایه برد را به همراه حق الزحمه، ارائه دهید تا برایتان تا فیها خالدون مدار آمار دربیاورند. بگذریم که بعضی شنیده‌ها حاکی از این است کار از این ساده‌تر هم شده و لازم نیست بردتان را به آن سر دنیا بفرستید و … .

قبل از اینکه ادامه روش را توضیح دهم، لازم و واجب است تشکر کنم از شخصی که اصل این روش را از او آموختم. کسی که سال‌هاست برای افزایش بار علمی کسانی که در حوزه الکترونیک کار می‌کنند، تلاش می‌کند و هزینه می‌دهد. کسی که علاوه بر مهندسی، سال‎هاست معلمی می‌کند و بر گردن بسیاری از ماها حق دارد.

از این صفحه می‌تونید مقاله “استفاده از شماره شناسایی میکروکنترلر برای جلوگیری از کپی کردن برنامه” استاد کی نژاد رو دانلود کنید. خوبه که قبل از خوندن ادامه مطلب، نگاهی بهش بندازید.

برای سادگی کار از همان پیش فرض‌هایی استفاده می‌کنم که در مقاله ذکر شده.

بعضی از میکروکنترلرها به صورت داخلی حافظه EEPROM دارند. شرکت ST اما راه دیگری برای طراحان در نظر گرفته و آن هم استفاده از حافظه فلش خود میکروکنترلر است. -البته معدود سری‌هایی هم تولید شده‌اند که حاوی EEPROM داخلی‌اند- اگر فرصتی باقی بود، انشالله در پستی جداگانه در این خصوص مفصل توضیح خواهم داد.

با این حساب به جای EEPROM گفته شده در مقاله، ما از فلش میکروکنترلر استفاده خواهیم کرد. به این ترتیب باید بتوانیم به صورت مستقیم، آدرسی از حافظه فلش میکرو را مقدار دهی کنیم. این کار با استفاده از نرم افزار ST-LINK یا STM32CubeProgrammer امکان پذیر است.

ابتدا باید read out protection  و write protection حافظه فلش میکرو را غیرفعال کنیم. در این صورت بعد از پروگرام کردن میکروکنترلر، امکان مشاهده و تغییر حافظه فلش را به صورت مستقیم خواهیم داشت. حال باید آدرسی از حافظه فلش میکروکنترلر را در نظر بگیریم که تغییر آن موجب اختلال در عملکرد برنامه اصلی ما نشود.

قبل از اینکه کاری انجام دهید، لازم هست حتماً نگاهی به memory map میکروکنترلر نگاهی انداخته باشید. main flash memory ناحیه‌ای است که برنامه ما در آن قرار می‌گیرد و البته در اینجا قرار است علاوه بر این، قسمتی از آن را برای ذخیره کد اختصاصی تراشه – UID – استفاده کنیم. بطور کلی حافظه‌های فلش در میکروهای STM32 از بانک تشکیل شده‌اند. اغلب میکروها دارای یک بانک هستند، اما بعضی سری‌های رده بالا هستند که بیش از یک بانک حافظه دارند. بانک‌ها هم از واحدهای کوچکتری به نام sector تشکیل شده‌اند. این واحدها در بعضی سری‌ها خود از واحدهای باز کوچکتری به اسم page می‌توانند تشکیل شده‌ باشند. مثل همین میکرویی که قرار است در ادامه بر روی آن کار را پیش ببریم.

اگر به صفحه 47 رفرنس منول STM32F030F4P6 مراجعه کنید، – فایل پی دی اف DM00091010.pdf – خواهید دید که این میکروی ساده تنها از 4 sector تشکیل شده. هر سکتور هم به 4 page یک کیلوبایتی تقسیم می‌شود. هر چند که UID تنها 96 بیت جا می‌گیرد اما از آنجا که ما نمی‌توانیم عملیات پاک کردن فلش را برای کمتر از حداقل یک page انجام دهیم، لازم است تا فضایی به مراتب بیش از 96 بیت را – یک page – برای این کار اختصاص دهیم. پس باید مراقب بود تا حجم کد برنامه ما تا قبل از page نهایی به اتمام برسد. به عبارتی از 15 کیلوبایت بیشتر نشود.

حالا نوبت به بحث شیرین کدنویسی می‌رسد.

قبل از شروع حلقه اصلی برنامه یا هر جای دیگری که می‌خواهید صحت برنامه را چک کنید، باید کدهای چک کردن اصل بودن برنامه را قرار دهید یا به کمک یک تابع فراخوانی کنید. مثلاً من تابع زیر را برای اینکار در نظر گرفتم و در فایل main.c خودم اون رو ایجاد کرد. در نتیجه قبل از ورود به حلقه اصلی این تابع را فراخوانی کردم.

اولین کاری که باید بکنیم این است که مقدار خوانده شده در جایی از حافظه فلش را با رمزی که از پیش تعیین کرده‌ایم، چک کنیم. در اینجا همانند مقاله رمز را 0x50 قرار دادم و همانطور که از خط 5 مشخص است، تساوی آن را با مقداری که در صدبایت مانده به انتهای فلش، چک کرده‌ام.

اگر رمز صحیح باشد و ما وارد بلاک شرطمان شویم، پس از اینکه حافظه را از حالت قفل درآوردیم باید آخرین page را پاک کنیم. در اینصورت برای دفعات بعد لازم نیست وارد این بلاک شویم. البته کارمان هنوز در این بلاک تمام نشده و باید قبل از خارج شدن UID میکرو را بخوانیم و در آدرسی مجزا از رمز ذکر شده، این 96 بیت را ثبت کنیم. همانطور که مشاهده می‌کنید در تابع زیر من UID را در 200 بایت مانده به انتهای فلش، جایی که هنوز در page آخری قرار دارد، قرار دادم.

پس از قفل مجدد فلش به جهت اطمینان و جلوگیری از نوشتن ناخواسته بر روی آن و خارج شدن از این بلاک شرطی، نوبت به چک کردن UID با آدرس تعیین شده ما برای ثبت UID در حافظه فلش، می‌رسد. اگر این چک کردن جواب نداد، یعنی اینکه جایی از کار می‌لنگد و احتمالاً کسی به امیدی واهی قصد سرقت برنامه ما را داشته. از این رو ما هم او را آنقدر در (1)while نگه می‌داریم، تا بفهمد که یک من ماست چقدر کره دارد:)

void verify_unique_id(void)
{
	uint32_t unique_id[3] = {0, 0, 0};

	if(*(__IO uint32_t*)(FLASH_BANK1_END + 1 - 100) == 0x50)
	{
		FLASH_EraseInitTypeDef erase_page;
		uint32_t page_error = 0;


		erase_page.TypeErase   = FLASH_TYPEERASE_PAGES;
		erase_page.PageAddress = FLASH_BANK1_END + 1 - FLASH_PAGE_SIZE;
		erase_page.NbPages     = 1;

		/* Unlock the Flash Program Erase controller */
		HAL_FLASH_Unlock();

		HAL_FLASHEx_Erase(&erase_page, &page_error);

		unique_id[0] = *(__IO uint32_t*)(UID_BASE);
		unique_id[1] = *(__IO uint32_t*)(UID_BASE + 4);
		unique_id[2] = *(__IO uint32_t*)(UID_BASE + 8);

		HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_BANK1_END + 1 - 200, unique_id[0]);
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_BANK1_END + 1 - 200 + 4, unique_id[1]);
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_BANK1_END + 1 - 200 + 8, unique_id[2]);

		HAL_FLASH_Lock();
	}

	unique_id[0] = *(__IO uint32_t*)(UID_BASE);
	unique_id[1] = *(__IO uint32_t*)(UID_BASE + 4);
	unique_id[2] = *(__IO uint32_t*)(UID_BASE + 8);

	if(unique_id[0] != (*(__IO uint32_t*)(FLASH_BANK1_END + 1 - 200)) ||
			unique_id[1] != (*(__IO uint32_t*)(FLASH_BANK1_END + 1 - 200 + 4)) ||
			unique_id[2] != (*(__IO uint32_t*)(FLASH_BANK1_END + 1 - 200 + 8)) )
	{
		while(1);
	}

}

مقدار دهی به آدرسی خاص از حافظه فلش، بدون کدنویسی:

تا اینجا اغلب کاری که باید انجام دهید را توضیح دادم. اما هنوز یک مورد دیگر هم مانده که شاید سوال شما هم باشد.

چگونه رمزمان را در حافظه فلش ثبت کنیم تا عملیات ثبت UID انجام شود و بعداً خودمان در لوپ بی‌نهایت خودمان! گیر نکنیم؟

به عبارتی بر طبق مثال اینجا، قصد داریم پس از اینکه کدنویسی را انجام دادیم و میکروکنترلر را پروگرام کردیم، عدد 0X50 را در آدرس صد بایت مانده به انتهای فلش یعنی FLASH_BANK1_END + 1 – 100 = 0x8003F9C بریزیم.

برای اینکار نرم افزار STM32 ST-LINK Utility را نیاز داریم. اینکار را با نرم افزار جدید ST با نام CubeProgram هم می‌توان انجام داد.

پس از کانکت شدن موفقیت آمیز این برنامه با میکروکنترلر، ابتدا بایست هم Read و هم Write پروتکشن میکرو را برداشت. برای اینکار به target->option bytes بروید. ممکن است در میکروهای مختلف این قسمت کمی متفاوت باشد. برای میکرو مورد بررسی ما باید read out protection را بر روی level 0 قرار داد و همچنین در پایین صفحه، قسمت flash sectors protections تیک همه‌ی گزینه‌ها را با unselect all برداشت. پس از apply کردن، میکروکنترلر ما آماده است تا فلشش مقداردهی شود.

در صفحه اصلی نرم‌افزار در قسمت address شما می‌توانید آدرس مد نظرتان را وارد کنید. پس در اینجا ما 0x8003F9C  را میزنیم. در صورتی که data width هم روی 32 بیت قرار دارد، در اولین خونه 0x50 یا همان رمز خودتان را وارد کنید و اینتر را بزنید تا پروگرام شود.

پروگرام در آدرس خاصی از حافظه فلش
پروگرام در آدرس خاصی از حافظه فلش

اگر همه‌ی مراحل را به درستی طی کرده باشید پس از یک بار ریست باید برنامه شما به درستی کار کند وگرنه در آن لوپ کذایی باقی خواهد ماند.

پی‌نوشت 1:

بد نیست که آدرس 200 بایت مانده به انتهای فلش را -راستی مقدارش چیه؟- در همین نرم افزار بزنید تا هم UID میکروتون رو ببینید و هم درک بهتری از آنچه اتفاق افتاده پیدا کنید.

پی‌نوشت 2: (مهم)

دیفاین‌های بالا مثل FLASH_BANK1_END  برنامه را خواناتر و قابل حمل‌تر می‌کند. این تعاریف بطور پیشفرض در پروژه تولید شده توسط کیوب هستند. اما بهتر است که چکی هم با دیتاشیت انجام بدهید. متاسفانه در پروژه ایجاد شده برای من، این عدد اشتباه بود و لازم شد تا دیفاین را به مقدار صحیحش تغییر دهم. این مشکل احتمالاً ناشی از باگ برنامه است و ممکن است برای شما وجود نداشته باشد. اما لازم بود که به این نکته اشاره کنم.

 

خبرنامه هفتگیِ دیجیلاگیست

سلام

دوشنبه هر هفته، یک ایمیل از طرف دیجیلاگیست برای کسانی که آدرس ایمیل خودشون رو ثبت کردن ارسال میشه و توی اون در خصوص آخرین مطالب و اخبار دیجیلاگیست گزارش داده میشه.

اگه شما هم دوست داری این خبرنامه براتون ارسال بشه، تو کادر زیر آدرس ایمیل خودتون رو وارد کنید و دکمه "مشترک شوید!" را بزنید.

اشتراک شما ایجاد شد

1
Leave a Reply

1 Comment threads
0 Thread replies
1 Followers
 
Most reacted comment
Hottest comment thread
1 Comment authors
  Subscribe  
newest oldest most voted
Notify of
علی

استاد
پاینده باشی