مقالنا اليوم يتكلم حول طرق سيئه "للغايه" لكتابه الكود بحيث لن يفهم هذا الكود الا القليل ، وهناك بعض الأسباب لعمل مثل هذه البرامج منها مثلا التسليه Fun ، التحدي ، حمايه كود البرنامج ، الفوز بمسابقه ما (هناك مسابقه عالميه تقام كل سنه حول هذا النوع من البرامج International Obfuscated C Code Contest ) .
أغلب هذه البرامج تكون مكتوبه بسي ، أو بيرل ، ونادرا ما نجد سي++ ، وذلك لأن سي تحتوي بعضا من الأمور قد تم تلافيها في سي++ ، منها مثلا استدعاء الداله main من داخل البرنامج .
أحب أن أذكر أن الطرق الموجوده هنا ، لا ينصح باستخدامها أبدا ، فهي طرق سيئه جدا ، وكما ذكرت فقط الغرض منها هو التشويش ...
ولنأخذ مثال "فاتح للشهيه" قبل أن نبدأ بالطرق ، قم بقرائه المثال ، وفي حال فهمت المثال كاملا وعرفت المخرجات ، فسوف أجزم أنك أحد هؤلاء المجانين .
#include <stdio.h>
main(t, _, a) char *a;
{
return !0 < t ? t < 3 ? main(-79, -13, a + main(-87, 1 - _, main(-86, 0, a + 1) + a)) : 1,
t < _ ? main(t + 1, _, a) : 3,
main(-94, -27 + t, a) && t == 2 ? _ < 13 ? main(2, _ + 1, "%s %d %d\n") : 9 : 16
: t < 0
? t < -72
? main(
_, t,
"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l,+,/n{n+,/+#n+,/#\
;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l \
q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# \
){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' \
iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c \
;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# \
}'+}##(!!/")
: t < -50 ? _ == *a ? putchar(31 [a]) : main(-65, _, a + 1)
: main((*a == '/') + t, _, a + 1)
: 0 < t ? main(2, 2, "%s")
: *a == '/' ||
main(0,
main(-61, *a,
"!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m .vpbks,fxntdCeghiry"),
a + 1);
}
حاول أن تتخيل ما هي المخرجات قبل أن تقوم بتشغيله ، تخيل فقط !!
كيف تكتب برنامج مشوش ؟
هنا علينا أن نرجع الى بدايه تعلمنا للغه ونتذكر ما هي أصعب الأشياء التي واجهتنا في التعلم ، أذكر أنني جلست كم يوم حتى فهمت ما هو المؤشر Pointer ، مممم أيضا الأستدعاء الذاتي مربك أيضا ، أيضا المصفوفات وخاصه عندما نستخدم Pointer Arithmetic ، معامل الفاصله Comma مربك ايضا لان تنفيذه غريب بعض الشيء ، جميع المواضيع السابقه التي كنت تعتبرها صعبه ، هنا ستكون هي سلاحك الأول
أيضا المتغيرات ذات الشكل المتشابه مثلا الحرف O و الحرف o ( الأول كبير والثاني صغير ) و الرقم 0 ، أيضا الحرف i والحرف l والحرف L الرقم 1 ، هذه المتغيرات تسبب لي التشاؤم من البرنامج ، خاصه عندما كنت أقرأ برنامج أحد الطلاب ، وهذه أحد سطور أحد الطلاب :
char clear, ch, c, bag[100], bag1[10][10], bag2[100];
int n = 0, m, x, w = 0, aa = 0, z, l = 0, nn[50], mm[50], qw, az, i, j;
// other code
for (int fg = 1; fg <= az; fg += 2)
{
if (nn[fg - 1] == nn[fg])
{
bag2[fg - 1] = bag1[nn[fg - 1]][(mm[fg - 1] + 1) % 5];
bag2[fg] = bag1[nn[fg - 1]][(mm[fg] + 1) % 5];
}
else if (mm[fg - 1] == mm[fg])
{
bag2[fg - 1] = bag1[(nn[fg - 1] + 1) % 5][mm[fg - 1]];
bag2[fg] = bag1[(nn[fg] + 1) % 5][mm[fg]];
}
else
{
bag2[fg - 1] = bag1[nn[fg - 1]][mm[fg]];
bag2[fg] = bag1[nn[fg]][mm[fg - 1]];
}
}
يبدوا لي أن هذا الطالب يكتب برنامج Obfuscated بالفطره ، وأعتقد أنه سيفوز بمثل هذه المسابقات بكل سهوله :) .
نبدأ الأن بقطع من الكود ، ونحاول أن نغيرها من كود عادي الى مشوش ، وبعدها نأخذ مثال Hello World ومن ثم نحوله الى Hello Devil .
التلاعب بأسماء المتغيرات :
نأخذ مثال لبرنامج يطبع الحروف من A الى Z ، ونقوم ببعض التغيرات عليه في اسامي المتغيرات :
#include <stdio.h>
int main()
{
for (int i = 'A'; i <= 'Z'; i++)
printf("%c", i);
return 0;
}
الان قمنا بوضع جمله الطباعه في داله ، ومررنا لها الحرف الأول والأخير ، ومن ثم طبعنا الحروف :
#include <stdio.h>
void _(int O, int _O)
{
for (; O <= _O; O++)
printf("%c", O);
}
int main()
{
// you can change A with 65 in decimal OR with 0x41 in hex
// olse you can change Z to 90 in decimal or 0x5A in hex
_(0x41, 90);
return 0;
}
لاحظ استخدام الحروف المشابه O و _O ، أيضا ارسال الحرف A بقيمته في النظام Hex ، ويمكن أستخدام أي نظام أخر .
(طبعا عليك بازاله التعليق من البرنامج ، وهو أصلا موضوع لك أنت ، حيث البرنامج المفترض يشوش القارئ وليس يشرح للقارئ).
التلاعب بالمؤشرات والمصفوفات :
مراجعه سريعه للأمر ، تجدها في هذه السطور :
printf("\n\n%d\n", arr[1]); // print 20
printf("%d\n", *(arr + 1)); // print 20
printf("%d\n", *(1 + arr)); // print 20
printf("Address = %x\n", &arr[1]); // print Address for element 1
printf("Address = %x\n", arr + 1); // print Address for element 1
الأن كيف يمكن أن نتلاعب هنا ، كلنا نعرف أذا أردنا أن نرسل مصفوفه لداله نقوم بارسال عنوانها ، حسنا ، ممكن أن نضع هذا العنوان في متغير int أولا ، ونقوم بارسال هذا العدد (العنوان) ، الان من داخل المصفوفه نقوم بتحويل هذا العدد int الى مؤشر عن طرق الCast .
أنظر الى المثال القادم ، وهو لطباعه الأعداد من 1 الى 10 ، والذي يليه هو بعد التلاعب فيه قليلا :
#include <stdio.h>
void functionOne(int *array, int size)
{
for (int i = 0; i < size; i++)
printf("%d ", array[i]); // or *array++ or *(array+i)
}
int main()
{
int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
functionOne(array, 10);
return 0;
}
الان النسخه المعدله ، ولاحظ أننا في الداله main قمنا بأخذ عنوان المصفوفه ، وارساله الى الداله functionOne ، وهناك قمنا بعمل cast لهذا الرقم وحولناه الى مؤشر ، بعدها باستخدام pointer arithmetic قمنا بالوصول الى عناصر المصفوفه .
#include <stdio.h>
void functionOne(int address, int size)
{
int i = 0;
for (; i < size; i++)
printf("%d ", *((int *)address + i));
}
int main()
{
int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int address = (int)array; // (int) &array[0]
functionOne(address, 10);
return 0;
}
المعامل الشرطي الثلاثي Conditional Operator ومعامل الفاصله Comma Operator :
معامل الفاصله يعمل من اليسار الى اليمين ، ومن ثم يسند قيمه المتغير الموجود في الجهه اليمني الى المتغير اذا كان هناك. نأخذ مثال :
int a = 0;
int b = 3;
a = (b += 3, b - 1);
الان عند تنفيذ السطر الثالث ، نقوم بزياده b بمقدار 3 وتصبح 6 ، بعدها نطرح من b واحد وتصبح 5 ، وأخيرا نسند b الى a (اي تصبح قيمه a هي 5 ).
المعامل الشرطي هو بديل لجمله If :
if (<test_condition>)
{
<variable_name> = <value_if_true>;
}
else
{
<variable_name> = <value_if_false>;
}
وباستخدام Conditional Operator :
<variable_name> = (<test_condition>) ? (<value_if_true>) : (<value_if_false>);
في حال كان test_condition صحيح سوف ينفذ value_if_true والا value_if_false .
هذا المعامل له دور كبير جدا في هذه النوعيه من البرامج ، حيث يتم استبدال أي جمله شرطيه بهذا المعامل .
وفي حاله كانت هناك nested if ، يمكن استخدام المعامل الشرطي مع معامل الفاصله لكي يؤدي الغرض :
<variable_name> = (<test_condition>) ? (<expression_a1>, ..., <value_if_true>) : (<expression_b1>, ..., <value_if_false>);
مثال :
int a = 1;
int b = 2;
int c = 3;
if (a < 0)
{
b += 3;
a = b - 1;
}
else
{
c += b;
a = c + 2;
}
الان نحوله الى conditional operator و comma operator :
int a = 1;
int b, c;
a = (b = 2, c = 3, a < 0) ? (b += 3, b - 1) : (c += b, c + 2);
وهنا أولا سوف يسند 2 الى b ، بعدها 3 الى c ، بعدها يقارن هل a أصغر من 0 ، في حال كان ذلك ، يزيد على b ثلاثه ، ومن ثم يطرح واحد ، ويرجع القيمه ويسندها الى a . اما اذا كان a أكبر من 0 ، سوف يزيد على c مقدار b وهي 2 ، بعدها يزيد على c 2 ويسند الناتج الى a .
الأستدعاء الذاتي :
الجميع يعلم هذا الموضوع ، وهي داله تنادي نفسها في حال تحقق شرط ما ، وهنا أغلب الأحيان نرسل قيمه وهي التي تدل على نهائيه الأستدعاء ، أو نقوم بعمل متغير static لكن لا يجب أ ن تستخدم هذه الطريقه في حاله استدعيت الداله أكثر من مره .
نأخذ مثال لطباعه الأعداد من 1 - 10 ، وبعدها نحوله الى النداء الذاتي :
#include "stdio.h"
int main()
{
int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int i;
for (i = 0; i < 10; ++i)
{
printf("%d ", array[i]);
}
return 0;
}
وها هو البرنامج بعد التعديل :
#include "stdio.h"
void recursiveFunction(int array, int i, int stop)
{
printf("%d ", *((int *)array + i));
i++;
if (i < stop)
{
recursiveFunction(array, i, stop);
}
}
int main()
{
int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
recursiveFunction((int)array, 0, 10);
return 0;
}
الأن نأخذ مثال شامل لـ Hello World ، المثال هو :
#include "stdio.h"
int main()
{
printf("Hello World!");
return 0;
}
وبعدها سندخل هذا المثال غرفه العمليات ، للقيام بعمليه تجميل ، ليخرج بالشكل :
#include "stdio.h"
_(__, _0, O_, ___)
{
(O_ < _0) ? printf("%c", (O_ == 2) || (O_ == 3) || (O_ == 9) ? ___++, O_++,
108 : *((int *)__ + O_++ - ___)),
_(__, _0, O_, ___) : 0;
}
main()
{
int array[] = {72, 'e', 111, 040, 0127, 0x6F, 'r', 0x64, 041};
_(array, 12, 0, 0);
}
جميل أليس كذلك !
نبدا الأن .
الخطوه الأولى :
نعمل داله للطباعه ، نرسل اليها المصفوفه ، القيم التي في المصفوفه كتبناها بالنظام العشري .
void myFunction(int array[], int arraySize)
{
int i;
for (i = 0; i < arraySize; ++i)
{
printf("%c", array[i]);
}
}
int main()
{
int array[] = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33};
myFunction(array, 12);
return 0;
}
الخطوه الثانيه :
استخدمنا فكره ارسال متغير يحمل العنوان (التي شرحناها في الأعلى) وبعدها نقوم بعمل cast للمتغير ونحوله الى مؤشر :
#include "stdio.h"
void myFunction(int integer, int arraySize)
{
int i;
for (i = 0; i < arraySize; ++i)
{
printf("%c", *((int *)integer + i));
}
}
int main()
{
int array[] = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33}, integer = (int)array;
myFunction(integer, 12);
return 0;
}
الخطوه الثالثه :
غيرنا الداله وقمنا بعمل استدعاء ذاتي ، وغيرنا if الى Conditional Operator :
void myFunction(int integer, int arraySize, int i)
{
(i < arraySize) ? printf("%c", *((int *)integer + i++)), myFunction(integer, arraySize, i) : 0;
}
int main()
{
int array[] = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33};
myFunction((int)array, 12, 0);
return 0;
}
الخطوه الرابعه :
نتخلص من بعض الحروف المكرره في المصفوفه (وهو الحرف L) يتكرر ثلاث مرات 108 ، في الموقع 2 و 3 و 9 :
#include "stdio.h"
void myFunction(int integer, int arraySize, int i, int k)
{
(i < arraySize) ? (i == 2) || (i == 3) || (i == 9) ? k++, i++,
printf("%c", 108) : printf("%c", *((int *)integer + i++ - k)),
myFunction(integer, arraySize, i, k) : 0;
}
int main()
{
int array[] = {72, 101, 111, 32, 87, 111, 114, 100, 33};
myFunction((int)array, 12, 0, 0);
return 0;
}
الخطوه الخامسه :
نغير من ترتيب معامل الشرط في الداله :
void myFunction(int integer, int arraySize, int i, int k)
{
(i < arraySize) ? printf("%c", (i == 2) || (i == 3) || (i == 9) ? k++, i++,
108 : *((int *)integer + i++ - k)),
myFunction(integer, arraySize, i, k) : 0;
}
int main()
{
int array[] = {72, 101, 111, 32, 87, 111, 114, 100, 33};
myFunction((int)array, 12, 0, 0);
return 0;
}
الخطوه السادسه :
نغير من أسماء المتغيرات في الداله ، ودائما تغير اسماء المتغيرات يأتي في أخر مرحله حتى لا تضيع أنت وتشوش على نفسك اثناء كتابة البرنامج ، أيضا تغيير القيم في المصفوفه وجعلها بعده أنظمه Hex و octal وdecimal :
#include "stdio.h"
void myFunction(int __, int _0, int O_, int ___)
{
(O_ < _0) ? printf("%c", (O_ == 2) || (O_ == 3) || (O_ == 9) ? ___++, O_++,
108 : *((int *)__ + O_++ - ___)),
myFunction(__, _0, O_, ___) : 0;
}
int main()
{
int array[] = {72, 'e', 111, 040, 0127, 0x6F, 'r', 0x64, 041};
myFunction((int)array, 12, 0, 0);
return 0;
}
الخطوه السابعه :
تغير اسم الداله ، حذف القيمه الراجعه ، وسوف يعتبرها المترجم هي int ، أيضا حذف نوع المتغير المرسل في الداله وأيضا سيتعبرها int :
_(__, _0, O_, ___)
{
(O_ < _0) ? printf("%c", (O_ == 2) || (O_ == 3) || (O_ == 9) ? ___++, O_++,
108 : *((int *)__ + O_++ - ___)),
_(__, _0, O_, ___) : 0;
}
main()
{
int array[] = {72, 'e', 111, 040, 0127, 0x6F, 'r', 0x64, 041};
_(array, 12, 0, 0);
}
الخطوه الأخير :
نقوم بحذف المسافات بين الجمل ، وضغطها :
#include "stdio.h"
_(__, _0, O_, ___)
{
(O_ < _0) ? printf("%c", (O_ == 2) || (O_ == 3) || (O_ == 9) ? ___++, O_++,
108 : *((int *)__ + O_++ - ___)),
_(__, _0, O_, ___) : 0;
}
main()
{
int array[] = {72, 'e', 111, 040, 0127, 0x6F, 'r', 0x64, 041};
_(array, 12, 0, 0);
}
واوووو ...... من كان يصدق أن هذا البرنامج Hello World !
نقطه أخيره
البرنامج السابق هو أسهل برنامج مشوش ، نعم !! هناك بعض البرامج معقده جدا وخاصه عندما يتم استخدام جمل الهاش # أو Preprocessor Directive
هناك بعض البرامج ، يمكن أن تقوم بكشفها بسرعه مثل :
_(__, ___, ____)
{
___ / __ <= 1 ? _(__, ___ + 1, ____)
: !(___ % __) ? _(__, ___ + 1, 0)
: ___ % __ == ___ / __ && !____ ? (printf("%d\t", ___ / __), _(__, ___ + 1, 0))
: ___ % __ > 1 && ___ % __ < ___ / __ ? _(__, 1 + ___, ____ + !(___ / __ % (___ % __)))
: ___ < __ * __ ? _(__, ___ + 1, ____)
: 0;
}
main()
{
_(100, 0, 0);
}
وهو يطبع الأعداد الأوليه من 1 الى 100 ، وهنا أولا أستعن بخاصيه Find & Replace الموجوده في الDE أو في أي محرر نصوص ، وغير كل : _ بمثلا Function ، وبعدها سوف تصبح النتيجه :
Function(__, ___, ____)
{
___ / __ <= 1 ? Function(__, ___ + 1, ____)
: !(___ % __) ? Function(__, ___ + 1, 0)
: ___ % __ == ___ / __ && !____ ? (printf("%d\t", ___ / __), Function(__, ___ + 1, 0))
: ___ % __ > 1 && ___ % __ < ___ / __ ? Function(__, 1 + ___, ____ + !(___ / __ % (___ % __)))
: ___ < __ * __ ? Function(__, ___ + 1, ____)
: 0;
}
main()
{
Function(100, 0, 0);
}
الان غير كل : __ بمثلا varOne وقم بعمل replace All :
Function(varOne, ___, ____)
{
___ / varOne <= 1 ? Function(varOne, ___ + 1, ____)
: !(___ % varOne) ? Function(varOne, ___ + 1, 0)
: ___ % varOne == ___ / varOne && !____
? (printf("%d\t", ___ / varOne), Function(varOne, ___ + 1, 0))
: ___ % varOne > 1 && ___ % varOne < ___ / varOne
? Function(varOne, 1 + ___, ____ + !(___ / varOne % (___ % varOne)))
: ___ < varOne * varOne ? Function(varOne, ___ + 1, ____)
: 0;
}
main()
{
Function(100, 0, 0);
}
واستمر هكذا الى أن تنتهي من جميع أسماء المتغيرات العجيبه ، وبعدها قم بتنظيم البرنامج ، وهنا ستجد نفسك أمام الكود مباشره !
أخيرا ، لما لا يطرح كل منا كوده الـ Obfuscated ونشاهد من هو صاحب الكود الأكثر تشويشا ؟




