الأسرار

الأسرار

تمرين تعلّمي

مقدمة

التلاعب بالبتات

يمكن استخدام كل بت في عدد صحيح لتخزين قيمة ثنائية. ولأن كثيرًا من الحالات تتضمن معلومات ثنائية، مثل صحيح أو خطأ، أو التضمين أو الاستبعاد، أو التشغيل أو الإطفاء، فإن التمثيل الثنائي لعدد صحيح من N بت يوفّر طريقة مدمجة لترميز الحالة الثنائية لعدد N من العناصر. وهذا يجعل القدرة على التلاعب بالبتات والبايْتات أمرًا أساسيًا في لغة التجميع. توفّر مجموعة تعليمات x86-64 تنويعًا واسعًا من تعليمات التلاعب بالبتات.

التلاعب ببت واحد

تعمل هذه التعليمات على بتات مفردة داخل طرف واحد.

وتأخذ جميعها طرفين، حيث يشير الطرف الثاني إلى فهرس البت الذي يتم العمل عليه في الطرف الأول. وتنسخ جميعها البت المحدد إلى علم الحمل (CF).

Name Description
bt ينسخ البت إلى CF دون تعديل أي طرف
bts ينسخ البت إلى CF ويضبطه في طرف الوجهة
btr ينسخ البت إلى CF ويصفّره في طرف الوجهة
btc ينسخ البت إلى CF ويكمّله (يقلبه) في طرف الوجهة

العمليات على مستوى البتات

تُنفَّذ العمليات على مستوى البتات على جميع بتات الطرف.

ولكل منها تعليمة تحمل الاسم نفسه للعملية التي تُنفَّذ على مستوى البتات:

Name Description
and القيمة 1 إذا كان البتان كلاهما 1
or القيمة 1 إذا كان أحد البتين على الأقل 1
xor القيمة 1 إذا اختلف البتان
not القيمة 1 إذا كان البت 0؛ و0 إذا كان البت 1

تأخذ معظمها طرفين، وتُجري عملية على مستوى البتات على كليهما، وتخزّن النتيجة في طرف الوجهة. والاستثناء هو not، الذي يأخذ طرف وجهة واحدًا فقط.

الأقنعة

عندما نفسّر الواحد والصفر على أنهما تضمين واستبعاد على الترتيب، يُسمّى العدد الصحيح قناع بتات (أو ببساطة قناع).

يقوم قناع البتات بـ"حجب" العناصر لأن الصفر في البت رقم i يستبعد العنصر رقم i، بينما الواحد يضمّه. ونستخدم أيضًا قناع البتات بشكل شائع لتضمين بتات معينة من عدد صحيح مع استبعاد غيرها.

على سبيل المثال، لتكن A عددًا صحيحًا تمثيله الثنائي هو:

index 7 6 5 4 3 2 1 0
bits 1 0 0 1 0 1 0 1

ولتكن M أيضًا عددًا صحيحًا تمثيله الثنائي هو:

index 7 6 5 4 3 2 1 0
bits 0 0 0 0 1 1 0 1

كلاهما عددان صحيحان من 8 بت. في هذه الحالة، يمكننا القول إن M يختار البتات 0 و2 و3 من A، ويستبعد الباقي.

تفيد تعليمات العمل على مستوى البتات التي ناقشناها سابقًا في التلاعب بالأعداد الصحيحة باستخدام الأقنعة. على سبيل المثال:

  • لتصفير البتات في A التي لا يختارها M، استخدم عملية AND على مستوى البتات: A AND M.
  • لضبط البتات في A التي يختارها M، استخدم عملية OR على مستوى البتات: A OR M.

التعليمة test

تُجري التعليمة test عملية AND على مستوى البتات بين الطرفين، وتضبط الأعلام وفقًا للنتيجة.

إذا كان A هو الطرف الأول وB الطرف الثاني:

flag set when
CF يُصفَّر دائمًا
ZF A AND B == 0
SF يُضبط بت الإشارة لـ A AND B
OF يُصفَّر دائمًا

تأخذ هذه التعليمة طرفين وتحدّث الأعلام، لكنها لا تعدّل طرفيها.

عمليات الإزاحة

تنقل هذه التعليمات البتات في طرف الوجهة بعدد من المواضع يحدّده الطرف الثاني. ويجب أن يكون الطرف الثاني عددًا ثابتًا (immediate) أو السجل cl (أدنى 8 بتات من rcx).

Name Description
shl/sal إزاحة البتات إلى اليسار
shr/sar إزاحة البتات إلى اليمين

لاحظ أن العدد في الطرف الثاني يُحجب إلى 5 بتات، أو 6 بتات مع طرف وجهة من 64 بت. وأي بت بعد ذلك يُتجاهل فعليًا. وهذا يعني أن أقصى إزاحة هي 31، أو 63 مع طرف من 64 بت.

shl / sal

تُجري كلٌّ من shl وsal العملية نفسها تمامًا، فواحدة منهما اسم بديل للأخرى.

كلما أُجريت إزاحة إلى اليسار، تُنقل البتات الأقرب إلى نهاية التسلسل بمقدار طول الإزاحة أولًا إلى CF ثم تُهمَل. وفي المقابل، يُضاف إلى البداية عدد من البتات المصفّرة الجديدة يساوي طول الإزاحة.

ولأن كل بت في العدد الصحيح يمثّل قوة للعدد 2، فإن الإزاحة إلى اليسار بمقدار n موضعًا تؤدي إلى ضرب العدد الصحيح في 2ⁿ.

shr / sar

هناك تعليمتان لنقل البتات إلى اليمين: shr وsar.

عند استخدام أي من التعليمتين، تُنقل البتات الأقرب إلى بداية التسلسل بمقدار طول الإزاحة أولًا إلى CF ثم تُهمَل. وفي المقابل، يُضاف إلى النهاية عدد من البتات الجديدة يساوي طول الإزاحة.

والفرق بينهما أن shr تنقل بتات 0 إلى الطرف الأيسر، بينما تنقل sar القيمة 1 إذا كان البت الأكثر أهمية مضبوطًا، و0 خلاف ذلك. وهذا يعني أن sar تحافظ على الإشارة عند إزاحة عدد صحيح موقّع.

ولأن كل بت في العدد الصحيح يمثّل قوة للعدد 2، فإن الإزاحة إلى اليمين بمقدار n موضعًا باستخدام shr تؤدي إلى قسمة غير موقّعة على 2ⁿ.

وبالمثل، فإن الإزاحة إلى اليمين بمقدار n موضعًا باستخدام sar تؤدي إلى قسمة موقّعة على 2ⁿ.

عمليات التدوير

تنقل هذه التعليمات البتات في طرف الوجهة بعدد من المواضع يحدّده الطرف الثاني. ويجب أن يكون الطرف الثاني عددًا ثابتًا (immediate) أو السجل cl (أدنى 8 بتات من rcx).

الفرق بين التدوير والإزاحة أن التدوير لا يهمل أي بتات ولا يضيف أيًا منها. فالبتات التي كانت ستُهمَل في الإزاحة تُنقل بدلًا من ذلك إلى الطرف المقابل. وهكذا تبقى جميع البتات، لكنها كلها تغيّر مواضعها.

Name Description
rol تدوير البتات إلى اليسار
ror تدوير البتات إلى اليمين

لاحظ أن العدد في الطرف الثاني يُحجب إلى 5 بتات، أو 6 بتات مع طرف وجهة من 64 بت. وأي بت بعد ذلك يُتجاهل فعليًا. وهذا يعني أن أقصى تدوير هو 31، أو 63 مع طرف من 64 بت.

تعليمات أخرى للتلاعب بالبتات

هناك تعليمات أخرى مفيدة للتلاعب بالبتات:

Name Description
popcnt يحصي عدد البتات المضبوطة
bsr يحصل على فهرس أكثر بتات مضبوطة أهمية. وإذا لم يكن أي بت مضبوطًا، تكون النتيجة غير معرّفة
bsf يحصل على فهرس أقل بتات مضبوطة أهمية. وإذا لم يكن أي بت مضبوطًا، تكون النتيجة غير معرّفة

تعمل هذه التعليمات جميعها مع طرفين من 16 بت أو 32 بت أو 64 بت. ولا يمكن استخدامها مع أطراف من 8 بت.

التعليمات

أرسل إليك صديقك للتو رسالة تحوي سرًّا مهمًّا. ولكي لا يسهّل على الآخرين قراءتها، شُفّرت الرسالة بإجراء سلسلة من التلاعبات بالبتات. وستحتاج إلى كتابة الطُرق للمساعدة في فك تشفير الرسالة.

Note

هذه هي تعليمات البت الواحد المذكورة في هذا المفهوم:

الاسم الوصف
bt ينسخ البت إلى CF دون تعديل أي معامل
bts ينسخ البت إلى CF ويضبطه في معامل الوجهة
btr ينسخ البت إلى CF ويصفّره في معامل الوجهة
btc ينسخ البت إلى CF ويكمّله (يقلبه) في معامل الوجهة

هذه هي التعليمات البتّية المذكورة في هذا المفهوم:

الاسم الوصف
and 1 إذا كان كلا البتين يساوي 1
or 1 إذا كان أحد البتين على الأقل يساوي 1
xor 1 إذا اختلف البتان
not 1 إذا كان البت 0؛ و0 إذا كان البت 1

هذه هي تعليمات الإزاحة المذكورة في هذا المفهوم:

الاسم الوصف
shl/sal يزيح البتات إلى اليسار
shr/sar يزيح البتات إلى اليمين

هذه هي تعليمات التدوير المذكورة في هذا المفهوم:

الاسم الوصف
rol يدوّر البتات إلى اليسار
ror يدوّر البتات إلى اليمين

هذه هي التعليمات المتنوعة المذكورة في هذا المفهوم:

الاسم الوصف
popcnt يعدّ عدد البتات المضبوطة
bsr يجلب فهرس أعلى بت مضبوط. إذا لم يُضبط أي بت، تكون النتيجة غير معرّفة
bsf يجلب فهرس أدنى بت مضبوط. إذا لم يُضبط أي بت، تكون النتيجة غير معرّفة

1. استخراج القناع

الرسالة مشفّرة داخل عدد صحيح من 16 بت. غير أن أعلى 8 بتات منها ليست في الحقيقة جزءًا من الرسالة، بل قناعًا يجب استخدامه في فك التشفير.

نفّذ الدالة extract_higher_bits التي تأخذ عددًا صحيحًا من 16 بت وتُرجع أعلى 8 بتات منه.

extract_higher_bits(0b1010010011000101)
// => 0b10100100

2. استخراج الرسالة

لا تكفي القدرة على استخراج القناع، بل عليك أيضًا عزل الرسالة.

نفّذ الدالة extract_lower_bits التي تأخذ عددًا صحيحًا من 16 بت وتُرجع أدنى 8 بتات منه.

extract_lower_bits(0b1010010011000101);
// => 0b11000101

3. استخراج البتات الزائدة

هناك بتات مضبوطة في كلٍّ من الرسالة والقناع. وهذه معلومة مهمة جدًّا سيُستعان بها لاحقًا.

نفّذ الدالة extract_redundant_bits التي تأخذ عددًا صحيحًا من 16 بت يرمّز كلا من الرسالة والقناع، وتُرجع عددًا صحيحًا من 8 بتات تكون فيه البتات الزائدة وحدها مضبوطة. يجب ضبط البت في العدد المُرجَع على 1 حيث يكون أيضًا 1 في كلٍّ من الرسالة والقناع. أما بقية البتات فيجب تصفيرها.

extract_redundant_bits(0b1010010011000101);
// => 0b10000100

4. ضبط جميع بتات الرسالة

بعد ذلك، هناك بعض البتات التي يجب ضبطها على 1 في الرسالة، وفقًا للقناع.

نفّذ الدالة set_message_bits التي تأخذ عددًا صحيحًا من 16 بت يرمّز كلا من الرسالة والقناع، وتُرجع نتيجة ضبط بتات الرسالة على 1. يجب ضبط البت في الرسالة على 1 حيث يكون البت في القناع 1. أما بقية البتات فيجب إبقاؤها دون تغيير، بحيث تبقى مضبوطة إن كانت مضبوطة من قبل، ومصفّرة إن كانت مصفّرة من قبل.

set_message_bits(0b1010010011000101);
// => 0b11100101

5. تدوير المفتاح الخاص

هناك قطعة من الأحجية غير ظاهرة صراحةً في الرسالة: العدد 0b1011001100111100 المكوّن من 16 بت. هذا العدد هو مفتاحك الخاص المشترك، وعليك استخدامه للمساعدة في فك تشفير الرسالة.

وللقيام بذلك، عليك أولًا تدوير بتات مفتاحك الخاص إلى اليسار بعدد معيّن من المواضع. وهذا العدد من المواضع يساوي عدد البتات الزائدة المضبوطة في كلٍّ من الرسالة والقناع.

نفّذ الدالة rotate_private_key التي تأخذ عددًا صحيحًا من 16 بت يرمّز كلا من الرسالة والقناع، وتُرجع نتيجة تدوير مفتاحك الخاص. هذه النتيجة عدد صحيح من 16 بت.

rotate_private_key(0b1010010011000101);
// => 0b1100110011110010
Note

يدعم NASM (The Netwide Assembler، وهو المجمّع المستخدم في هذا المسار) كتابة الثوابت بالصيغة الثنائية مع بادئة 0b. كما يدعم استخدام الشرطة السفلية (_) فاصلًا داخل الثابت، لتحسين القراءة:

PRIVATE_KEY equ 0b1011_0011_0011_1100

6. تنسيق المفتاح الخاص

لكي يُستخدم في فك التشفير، يجب تنسيق مفتاحك الخاص لعزل البتات ذات الصلة.

لتنسيق المفتاح الخاص تنسيقًا كاملًا، عليك:

  • تدويره.
  • عزل الجزء الأدنى المكوّن من 8 بتات من المفتاح الخاص المُدوَّر، وهو القيمة الأساسية.
  • عزل الجزء الأعلى المكوّن من 8 بتات من المفتاح الخاص المُدوَّر، وهو قناع يُطبَّق على القيمة الأساسية.
  • قلب البتات في القيمة الأساسية التي تكون أيضًا مضبوطة في القناع.
  • قلب جميع البتات في النتيجة.

البت المقلوب يساوي 1 إذا كان 0، و0 إذا كان 1.

نفّذ الدالة format_private_key التي تأخذ عددًا صحيحًا من 16 بت يرمّز كلا من الرسالة والقناع، وتُرجع مفتاحًا خاصًّا منسّقًا بالكامل من 8 بتات.

format_private_key(0b1010010011000101);
// => 0b11000001

7. إنهاء فك التشفير

بمجرد أن تصبح لديك الرسالة بجميع بتاتها ذات الصلة مضبوطة والمفتاح الخاص المنسّق، يحين وقت جمعهما معًا للحصول على الرسالة الناتجة.

الرسالة الناتجة عدد صحيح من 16 بت، يتكوّن مما يلي:

  • أعلى 8 بتات تُملأ بالمفتاح الخاص المنسّق.
  • أدنى 8 بتات تُملأ بالرسالة، بعد ضبط جميع البتات ذات الصلة.

نفّذ الدالة decrypt_message التي تأخذ عددًا صحيحًا من 16 بت يرمّز كلا من الرسالة والقناع، وتُرجع عددًا صحيحًا من 16 بت مع فك تشفير الرسالة بالكامل.

ينبغي لهذه الدالة أن تستفيد من المفتاح الخاص المنسّق الذي تولّده عبر format_private_key، وكذلك من الرسالة بجميع بتاتها ذات الصلة المضبوطة عبر set_message_bits.

decrypt_message(0b1010010011000101);
// => 0b1100000111100101
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
x86-64 Assembly Exercism

مستعد لبدء الأسرار؟

سجّل في Exercism لتتعلّم وتتقن x86-64 Assembly عبر 22 مفهومًا130 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.