روش طراحي قفل نرم افزاري

نويسنده: مهدي معاضدي

چکيده

با توجه به فزوني نرم افزار در سيستم هاي کامپيوتري از يک طرف و توانايي کنترل کپي هاي غيرمجاز ( از ديدگاه اقتصادي ان ) از طرفي ديگر دليلي محکم جهت بررسي اين شاخه از مهندسي نرم افزار مي باشد.
از انجا که متاسفانه قانون Copyright در تمام جهان بجز ايران و چند کشور ديگر اجرا مي گردد, بحث کنترل کپي هاي غيرمجاز حساس تر مي شود. در اين مقاله سعي بر اين است تا علاوه بر اشنايي با انواع قفل هاي نرم افزاري و اختلاف ان ها با قفل هاي سخت افزاري, بتوانيم به عنوان طراح يک قفل نرم افزاري از محصولات نرم افزاري خودمان حمايت کنيم. ضمنا علاوه بر اشنايي با مفاهيم فوق, نوشتن روتين هاي ضد ديباگ و همچنين نحوه کد کردن اطلاعات نيز لحاظ شده است.
لازم به ذکر است که اين مقاله حاصل تجربيات چند ساله و زحمات زيادي است که تقديم مي گردد.

کلمات کليدي

Key Lock, Hard Lock, Software Lock, قفل نرم افزاري، Tag Lock, Anti Debug, قفل سخت افزاري

مقدمه

از انجا که زمان زيادي تا سال 2000 باقي نمانده, اما هنوز در کشور ما ايران, نرم افزار جاي خود را به عنوان يک محصول صادراتي پيدا نکرده چرا که يکي از دلايل اصلي ان عدم توجه جدي به حمايت از توليد کنندگان نرم افزار مي باشد. به هر حال قصد اينجانب از ارايه مقاله, بررسي کم و کاستي هاي نرم افزار در ايران نيست بلکه طريقه حل مشکل موجود در اين بازار نابسامان مي باشد. با توجه به کپي هاي غيرمجازي که روزانه بصورت کاملا عادي و بدون اطلاع سازنده ان صورت مي گيرد, جلوگيري از اين عمل و کنترل جدي ان امري ضروري و واجب بنظر مي رسد. در ادامه مطلب به توضيح درباره قفل هاي نرم افزاري و سخت افزاري مي پردازيم.

اشنايي با قفل هاي نرم افزاري و سخت افزاري

تعريف قفل هاي نرم افزاري: به هر برنامه اي که کنترل کپي ان فقط از طريق نرم افزار و بدون نياز به سخت افزار اضافي قابل انجام باشد, گويند.
تعريف قفل هاي سخت افزاري: به هر برنامه اي که کنترل کپي ان از طريق سخت افزار اضافي قابل انجام باشد, گويند.
با توجه به تعاريف فوق مي توان به تفاوت قفل هاي سخت افزاري و نرم افزاري پي برد. قفل هاي سخت افزاري با توجه به اضافه کردن يک سخت افزار جديد به کامپيوتر ( اغلب از طريق ارتباط با پورت چاپگر ) برنامه خود را کنترل مي کنند. برنامه قبل از اجرا ابتدا با توجه به مراجعه به ادرس سخت افزار نصب شده ( اضافه شده با استفاده از دستور Port ) به سخت افزار مورد نظر خود مراجعه کرده و در صورت يافتن ان, تست هاي مختلف اعم از تست رمز, خواندن اطلاعات و ... مي تواند تصميم گيري نمايد. اما در قفل هاي نرم افزاري برنامه بدون نياز به سخت افزار اضافي و با کنترل رسانه ذخيره سازي مي تواند تصميم گيري کند. ضمنا لازم به توضيح است که هدف از طراحي قفل هاي نرم افزاري/ سخت افزاري اين نيست که هيچکس توانايي شکستن ( باز کردن ) انرا ندارد بلکه مقصود بالا بردن سطح کنترل کپي هاي غير مجاز تا حد ممکن مي باشد. ( چرا که مي دانيم اطلاعات همه در يک سطح نيست. )

طريقه استفاده از قفل نرم افزاري در برنامه مورد نظر

با توجه به نوع کاربرد برنامه ( کوچک وقابل کپي بر روي يک ديسکت, تحت شبکه و ... ) مي توانيم از انواع روش هايي که جهت حفاظت از نرم افزار در نظر داريم ( و متعاقبا توضيح داده خواهد شد ) استفاده کنيم. اما مساله قابل بحث اين است که چگونه از يک قفل منتخب استفاده نماييم؟
جواب اين سوال متغيير و وابسته به شرايط زير مي باشد:
الف: اعتقاد طراح نرم افزار به اينکه کاربر حتما بايد انرا خريداري نمايد تا از امکانات ان مطلع گردد.
در اين حالت قفل نرم افزاري در ابتداي شروع به کار برنامه کنترل مي گردد حتي طراح مي تواند در مواقع حساس نيز قفل را مجددا کنترل کند و يا در حالتي که طراح واقعا سخت گير باشد, مي تواند در زمان هاي مشخصي از وجود قفل اطمينان حاصل نمايد ( مثلا هر 4 ثانيه ). البته در اين حالت طراح بايد روشي را که جهت کنترل قفل استفاده مي کند, نيز در نظر بگيرد.
ب: اعتقاد طراح نرم افزار به اين که کاربر مي تواند از نرم افزار به عنوان نسخه نمايشي نيز استفاده کند.
طراح در اين حالت مي بايست در مکان هاي خاصي از برنامه, قفل را کنترل کند. مثلا در يک برنامه حسابداري مي توان تمام بخش هاي سيستم را ازاد گذاشته ( يعني برنامه نيازي به قفل نداشته باشد ) اما در صورتي که کاربر مايل به استفاده از امکانات گزارشگيري سيستم باشد, قفل نرم افزاري در خواست گردد. مزيت اين روش بر روش قبلي اين است که ديگر نياز به طراحي نسخه نمايشي جهت مشاهده کاربران وجود ندارد.

اشنايي با نحوه قفل گذاري بر روي يک برنامه

الف: طراح به سورس برنامه دسترسي دارد.
در اين حالت طراح پس از انتخاب روش قفل گذاري, کافيست انرا به زبان مورد نظر خود پياده سازي نموده و در برنامه خود بگنجاند. ( که مکان هاي قرار دادن قفل در عنوان قبلي توضيح داده شد. )
ب: طراح ( مجري پروژه ) به سورس برنامه دسترسي ندارد.
گاهي اوقات به يکسري برنامه هاي ارزشمندي برخورد مي کنيم که فاقد قفل هستند, بنابراين نياز به قفل گذاري وجود دارد. ( البته اين حالت بيشتر در کشور ما و چند کشور ديگر که در ان ها قانون Copyright معني ندارد, کاربرد دارد. ) جهت تزريق قفل به اين گونه برنامه ها, نياز به اشنايي کامل به ساختار فايل هاي اجرايي (EXE, COM, SYS, …) وجود دارد چرا که بايد برنامه اي طراحي کنيم تا همانند يک ويروس کامپيوتري به فايل اجرايي مشخصي بچسبد. البته جهت اينکار بهترين زبان برنامه نويسي, اسمبلي مي باشد. ( بدليل توانايي دخالت در روند اجراي برنامه )
البته در رابطه با نحوه نوشتن اين گونه برنامه ها, روش هاي زيادي وجود دارد که خود بحثي مجزا را مي طلبد و از حوصله اين مقاله خارج است.
ضمنا براي بالا بردن سطح امنيت برنامه لازم است تا يکسري کد هاي ضد ديباگ در برنامه گنجانده شوند. کدهاي ضد ديباگ, دستوراتي به زبان اسمبلي هستند که در حالت اجراي عادي برنامه, هيچ تغييري در روند اجراي نمي گذارند بلکه در صورتي که برنامه توسط ديباگرها اجرا گردد ( مورد ارزيابي قرار گيرد ) بتواند از اجراي ان جلوگيري نمايد. با اضافه کردن کد هاي ضد ديباگ به ابتداي برنامه ( يا قبل از کنترل قفل ) مي توان احتمال دستکاري در برنامه را پايين اورد. ( نحوه نوشتن کد هاي ضد ديباگ در پيوست A اورده شده است.)

اشنايي با روش هاي قفل گذاري و نحوه طراحي ان ها

همانطور که مي دانيد, سيستم عامل جهت هر ديسکت يک شماره سريال واحد (UNIQUE) اختصاص مي دهد, بطوريکه شماره سريال هر دو ديسکت با هم يکي نيستند. بنابراين همين خود يک راه تشخيص ديسکت کليد ( قفل ) مي باشد.
جهت استفاده از اين قفل مي بايست شماره سريال ديسکت را خوانده و سپس در داخل برنامه انرا کنترل نماييم. يک راه ساده جهت خواندن شماره سريال, اجراي دستور VOL بصورت شکل مقابل است:
VOL >>C:\DOS\LCK.TMP
بعد با باز کردن فايل LCK.TMP, مي توانيم به محتويات ان دسترسي پيدا کنيم. راه ديگر مراجعه به Boot Sector جهت کنترل قفل مي باشد.
ضريب اطمينان اين قفل در مورد ديسکت ها, 5%-2% بوده و در رابطه با هارد ديسک 60%-50% مي باشد. دليل اين اختلاف اين است که در حالت قفل ديسکتي با کپي Boot Sector, قفل بر روي ديسکت ديگر قرار خواهد گرفت اما در رابطه با هارد ديسک اينکار به سادگي انجام پذير نيست.
در اين نوع قفل نرم افزاري, برنامه قبل از اجرا ابتدا مشخصات سيستم را خوانده ( که اينکار از طريق مراجعه به بخش هاي خاصي از حافظه و يا مراجعه به اطلاعات BIOS انجام مي شود. ) سپس انرا با فايلي که قبلا توسط نويسنده نرم افزار بر روي کامپيوتر کپي گرديده, مقايسه مي کند و در صورت عدم برابري, اجراي برنامه پايان مي پذيرد.
اين نوع قفل هنوز هم در بسياري از برنامه ها استفاده مي گردد, اما نکته قابل ذکر اين است که جهت اطمينان بيشتر به قفل لازم است فايل حاوي مشخصات بصورت کد شده نوشته شده باشد تا امکان دستکاري ان توسط قفل شکنان به حد اقل ممکن برسد.
درصد اطمينان اين نوع قفل 75%-65% مي باشد.
اين نوع قفل فقط بر روي هارد ديسک قابل استفاده بوده و به اين صورت است که فايل اجرايي به موقعيت خود بر روي هارد حساس مي باشد چرا که قبل از اجرا ابتدا موقعيت خود را از روي سکتورهاي ROOT خوانده و سپس شماره کلاستر اشاره گر به خودش را بدست مي اورد, سپس انرا با شماره کلاستري که قبلا توسط برنامه نويس بر روي يکي از فايل هاي برنامه ( ممکن است بصورت کد شده باشد ) قرار داده شده, مقايسه کرده و در صورت برابر بودن اجرا مي شود. اين نوع قفل نسبت به قفل قبلي ( شماره 2 ) استفاده کمتري داشته چرا که در صورتيکه برنامه از روي بخشي از هارد به ناحيه ديگري انتقال يابد, اجرا نخواهد شد و اين از نظر کاربر بسيار ناپسند مي باشد. ( ضمنا امکان Defra, Scandisk, و ... نيز وجود ندارد چرا که شماره کلاستر اشاره گر به فايل تغيير خواهد کرد. )
ضريب اطمينان اين نوع قفل نيز 80%-70% مي باشد.
اين شيوه يکي از رايج ترين قفل هاي نرم افزاري است که هنوز هم بصورت جدي مورد استفاده قرار مي گيرد. برخي از دلايل اهميت ان عبارتند از:
- امکان استفاده از روش هاي متفاوت در اين روش
- راحتي و سرعت زياد به هنگام استفاده ان
- وجود ضريب اطمينان بالا و انعطاف پذيري زياد ان
- عدم وجود نرم افزار خاصي جهت باز کردن اين نوع از قفل ها
همان طور که مي دانيم سيستم عامل جهت دسترسي به اطلاعات يک ديسکت از فرمت خاصي ( 18 سکتور در هر تراک ) استفاده مي کند اما اگر يه تراک به صورت غير استاندارد فرمت شود, ( مثلا 19 سکتور در تراک ) سيستم عامل ديگر توانايي استفاده از سکتورهاي غيرمجاز را نخواهد داشت و بنابراين تمام نرم افزارهاي تحت سيستم عامل مزبور نيز از سکتورهاي مخفي استفاده نکرده, در نتيجه امکان کپي برداري از انها بسيار ضعيف است. مانيز از همين روش جهت طراحي قفل مورد نظر مان استفاده مي کنيم. بصورتيکه تراک اخر ديسک را بصورت يک سکتوري و با شماره 20 فرمت مي کنيم. سپس جهت کنترل ديسکت به سکتور فوق مراجعه کرده و در صورت وجود, کنترل برنامه را پي مي گيريم. البته غير از تغيير شماره سکتور مي توان از اندازه غير مجاز نيز استفاده کرد يعني بجاي اينکه سکتورها را بصورت 512 بايتي فرمت کنيم, از اندازه 1024, 2048 و ... استفاده مي کنيم. ( قفل نرم افزاري Copy Control که معروفترين در نوع خود مي باشد, از همين روش استفاده مي کند. )
اين قفل فقط جهت فلاپي ديسک قابل استفاده مي باشد و در صد اطمينان در اين روش حدود 95%-85% مي باشد.
اين روش قفل گذاري که قويترين قفل مي باشد, بصورت مخلوطي از روش هاي 1 و 4 مي باشد يعني ابتدا تراک خاصي را بصورت غير استاندرد فرمت کرده و سپس اطلاعات خاصي را درون ان قرار مي دهند ( شماره سريال فرضي ). اين قفل فقط جهت فلاپي ديسک قابل استفاده بوده و ضريب اطمينان ان حدود 98%-90% مي باشد.

پيوست A- روتين هاي ضد ديباگ Anti Debug Procedures

همان طور که توضيح داده شد, روتين هاي ضد ديباگ جت جلوگيري از اجراي برنامه هاي ديباگر و يا جلوگيري از ( حد اقل مشکل کردن کار ) دستکاري توسط قفل شکنان, استفاده مي شوند. در زير توضيحات چند روش موثر و مفيد, اورده شده است:

الف: غير فعال کردن وقفه ها

جهت جلوگيري از اجراي مرحله به مرحله ( Trace کردن ) برنامه, مي توان وقفه هاي کنترلر 8359 را غير فعال ساخت. ادرس اين کنترلر 21h بوده و IRQ هاي 7-0 را کنترل مي کند IRQ1 همان وقفه مربوط به صفحه کليد مي باشد. پس با غير فعال کردن اين وقفه مي توان صفحه کليد را غير فعال نمود.
طريقه استفاده:
CS:0100 E421 IN AL,21
CS:0102 0C02 OR AL,02
CS:0104 E621 OUT 21,AL

ب: تغيير بردار وقفه ها

يکي از روش هاي ساده و راحت جهت ضد ديباگ کردن برنامه ها, تغيير برداري است, که ديباگر از ان استفاده مي کند. (03 ) حتما بخاطر بسپاريد که در پايان برنامه دوباره ادرس بردار وقفه تغيير داده شده را بازيابي کنيد.
طريقه استفاده:
CS:0100 EB04 JMP 0106
CS:0102 0000 ADD [BX+SI],AL
CS:0104 0000 ADD [BX+SI],AL
CS:0106 31C0 XOR AX,AX
CS:0108 8EC0 MOV ES,AX
CS:010A 268B1E0C00 MOV BX,ES:[000C]
CS:010F 891E0201 MOV [0102],BX
CS:0113 268B1E0E00 MOV BX,ES:[000E]
CS:0118 891E0401 MOV [0104],BX
CS:011C 26C7064C000000 MOV Word Ptr ES:[000C],0000
CS:0123 26C7064E000000 MOV Word Ptr ES:[000E],0000

ج:گيج کردن ديباگر

اين راه يکي از قويترين تکنيک هاي ضد ديباگ بوده که در ان به وسط يک دستور, پرش مي شود و اينکار باعث قفل کردن ( Hang ) ديباگر خواهد شد.
طريقه استفاده:
CS:0100 E421 IN AL,21
CS:0102 B0FF MOV AL,FF
CS:0104 EB02 JMP 0108
CS:0106 C606E62100 MOV Byte Ptr [21E6],00
CS:010B CD20 INT 20

د: کنترل پرچم هاي CPU

اين روش در برابر ديباگرها بسيار مفيد مي باشد و به اين صورت است که ابتدا پرچم Trace از CPU را خاموش کرده و در بين برنامه انرا کنترل کنيم. در صورتيکه اين پرچم روشن شده باشد, مشخص است که ديباگر در پشت صحنه در حال اجراست.
طريقه استفاده:
CS:0100 9C PUSHF
CS:0101 58 POP AX
CS:0102 25FFFE AND AX,FEFF
CS:0105 50 PUSH AX
CS:0106 9D POPF
و در بين برنامه از دستورات ذيل استفاده کنيد:
CS:1523 9C PUSHF
CS:1524 58 POP AX
CS:1525 250001 AND AX,0100
CS:1528 7402 JZ 152C
CS:152A CD20 INT 20

ه: متوقف ساختن ديباگر

اين روش باعث متوقف شدن ديباگر مي شود که با اجراي دستور ساده INT 03 مي توان اين کار را انجام داد.
طريقه استفاده:
CS:0100 B96402 MOV CX,0264
CS:0103 BE1001 MOV SI,0110
CS:0106 AC LODSB
CS:0107 CC INT 3
CS:0108 98 CBW
CS:0109 01C3 ADD BX,AX
CS:010B E2F9 LOOP 0106

پيوست B- روش هاي کد کردن اطلاعات Data Coding Procedures

الف: افزودن يک عدد به کد هاي يک فايل

در اين روش جهت کد کردن يک فايل, ابتدا انرا خوانده و سپس يک مقدار خاص, مثلا 20 را به مقدار هر بايت فايل اضافه مي کنيم. اين يکي از ساده ترين روش ها بوده و نسبتا کارايي خوبي نيز دارد. جهت خارج کردن فايل از حالت کد شده ( Decode ) نيز, کافيست مقدار فوق را از تمام بايت هاي فايل کم کنيم.

ب: XOR کردن کل فايل

در اين روش نيز پس از خواندن کل فايل, تمام بايت هاي انرا با رشته کاراکتري يا عدد ثابت خاصي XOR کرده و سپس مقدار جديد را در فايل حاصل ضبط مي نماييم. جهت خارج کردن فايل از حالت کد شده, دقيقا عمل فوق را انجام مي دهيم.

پيوست C- ليست برنامه قفل گذار Pascal Source To Learn

در زير ليست دو برنامه نمونه, که شماره (1) جهت درست کردن ديسکت قفل و شماره (2) جهت تست ان طراحي شده, اورده شده است:
در اين برنامه ها از تراک 81 و سکتور 20 ( در حالت عادي هر ديسکت فقط 18 سکتور دارد ) جهت قفل برنامه استفاده شده و به اين صورت عمل مي کند که يک رشته را از کاربر دريافت کرده و در مکان فوق قرار مي دهد و سپس جهت تست رشته دريافتي در برنامه دوم انرا با اطلاعات موجود در ديسکت مقايسه مي کند و با دادن پيغام مناسبي انرا چاپ مي نمايد.
اين برنامه توسط Turbo Pascal 7.0 کامپايل و اجرا شده اند.
{

برنامه شماره(1)

اين برنامه جهت ساختن ديسکت قفل استفاده مي شود
}
program PROGRAM-1;
Uses Dos;
TYPE
DAT = String[40];
VAR
C : Registers;
FP : String[15];
PU : Array[1..512] of char;
Data: Dat;
I : Byte;
Key : Dat;
{******************************************}

Procedure ZUW;
Begin
Fp:=#81+#0+#20+#2;
c.d1 :=0;
c.dh :=0;
c.ch:=81;
c.c1:=20;
c.a1:=1;
end;

Procedure Write-Key(data:Dat);
Begin
c.ah:=5;
ZUW;
c.es:=Seg(fp[1]);
c.bx:=Ofs(fp[1]);
Intr(19,c);
c.ah:=5;
ZUW;
c.es:=Seg(fp[1]);
c.bx:=Ofs(fp[1]);
Intr(19,c);
For i:=1 to Length(data) do
Pu:=data;
Pu[i+1]:=#0;
c.ah:=3;
ZUW;
c.es:=Seg(Pu);
C.bx:=Ofs(Pu);
Intr(19.c);
end;

{=======================================}

begin
Writeln;
Writeln(' Program Number1 ');
Writeln(' This Program Used For Create The Key ');
Write(' Please Type Key Word : ');
Readln(Key);
Writeln;
Write(' Writing Key …');
Write-Key(Key);
Writeln(' OK .');
Writeln;
end.

{

برنامه شماره (2)

اين برنامه جهت کنترل ديسکت قفل استفاده مي شود
}

program PROGRAM-2;
Uses Dos;
Type
DAT = String[40];
Var
C : Registers;
FP : String[15];
PU : Array[1 ..512] of char;
Data: Dat;
I : Byte;
Key : Dat;

{********************************************}

Procedure ZUW;
Begin
Fp:=#81+#0+#20+#2;
c.d1:=0;
c.dh:=0;
c.ch:=81;
c.c1:=20;
c.a1:=1;
end;

{********************************************}

Function Read-Key(Key:Dat):Boolean;
Begin
c.ah:=2;
ZUW;
c.es:=Seg(pu);
c.bx:=Ofs(pu);
Intr(19.c);
c.ah:=2;
ZUW;
c.es:=Seg(pu);
c.bx:=Ofs(pu);
Intr(19,c);
I:=1;
data:=";
While pu<>#0 do
begin
Data :=data+pu[];
Inc(I);
end;
If data=Key then
Read-Key:=True
Else
Read-Key:=False;
end;

{***********************************************}

begin
Writeln;
Writeln(' Program Number2 ');
Writeln(' This Program Used For Check The Key ');
Write(' Please Type Key Word : ');
Readln(Key);
Writeln;
If Read-Key(Key)=False then
Writeln(' I am Sorry , Not Found .');
Else
Writeln(' Very Good , That Found .');
Writeln;
end.

منبع: www.iritn.com