وصف Linux لاستدعاءات نظام kernel. Man syscalls (2): مكالمات نظام Linux

وصف Linux لاستدعاءات نظام kernel. Man syscalls (2): مكالمات نظام Linux

في أغلب الأحيان، يتم تعريف رمز استدعاء النظام بالرقم __NR_xxx، في /usr/include/asm/unistd.h، يمكن العثور عليها في مصدر الرمزنواة لينكس في الوظائف sys_xxx(). (يمكن العثور على جدول الاتصال لـ i386 في /usr/src/linux/arch/i386/kernel/entry.S.) هناك العديد من الاستثناءات لهذه القاعدة، ويرجع ذلك أساسًا إلى حقيقة أن معظم استدعاءات النظام القديمة يتم استبدالها باستدعاءات جديدة، دون أي نظام. على منصات محاكاة أنظمة التشغيل الخاصة مثل parisc، وsparc، وsparc64، وalpha، هناك العديد من استدعاءات النظام الإضافية؛ يحتوي mips64 أيضًا على مجموعة كاملة من مكالمات النظام 32 بت.

مع مرور الوقت، حدثت تغييرات في واجهة بعض مكالمات النظام، إذا لزم الأمر. أحد أسباب هذه التغييرات هو الحاجة إلى زيادة حجم الهياكل أو القيم العددية التي تم تمريرها إلى استدعاء النظام. وبسبب هذه التغييرات، ظهرت في بعض البنيات (أي i386 الأقدم 32 بت) مجموعات مختلفة من استدعاءات النظام المماثلة (على سبيل المثال، اقتطاع(2 و اقتطاع64(2))، والتي تؤدي نفس المهام، ولكنها تختلف في حجم وسيطاتها. (كما ذكرنا، هذا لا يؤثر على التطبيقات: تقوم وظائف غلاف glibc ببعض الأعمال لتشغيل استدعاء النظام الصحيح، وهذا يضمن توافق ABI مع الثنائيات الأقدم.) أمثلة على استدعاءات النظام التي لها إصدارات متعددة:

*يوجد حاليًا ثلاثة إصدارات مختلفة القانون الأساسي(2): sys_stat() (مكان __NR_oldstat), sys_newstat() (مكان __NR_stat) و sys_stat64() (مكان __NR_stat64)، ويستخدم هذا الأخير في هذه اللحظة. حالة مماثلة مع lstat(2 و com.fstat(2). * تعريف بالمثل __NR_oldolduname, __NR_oldunameو __NR_unameللمكالمات sys_olduname(), sys_uname() و sys_newuname(). * ظهرت في لينكس 2.0 نسخة جديدة vm86(٢) جديد و نسخة قديمةتسمى الإجراءات النووية sys_vm86old() و sys_vm86(). * لينكس 2.4 لديه نسخة جديدة com.getrlimit(2) تسمى الإصدارات الجديدة والقديمة من الإجراءات النووية sys_old_getrlimit() (مكان __NR_getrlimit) و sys_getrlimit() (مكان __NR_ugetrlimit). * في Linux 2.4، تمت زيادة حجم حقول معرف المستخدم والمجموعة من 16 إلى 32 بت. تمت إضافة العديد من استدعاءات النظام لدعم هذا التغيير (على سبيل المثال: chown32(2), getuid32(2), getgroups32(2), setresuid32(2))، حذف المكالمات السابقة بنفس الأسماء، ولكن بدون اللاحقة "32". * أضاف Linux 2.4 دعمًا للوصول إلى الملفات الكبيرة (التي لا تتناسب أحجامها وإزاحاتها مع 32 بت) في التطبيقات ذات بنيات 32 بت. يتطلب هذا إجراء تغييرات على استدعاءات النظام التي تعمل مع أحجام الملفات والإزاحات. تمت إضافة مكالمات النظام التالية: fcntl64(2), getdents64(2), stat64(2), statfs64(2), اقتطاع64(2) ونظائرها، التي تتعامل مع واصفات الملفات أو الروابط الرمزية. تقوم استدعاءات النظام هذه بإزالة استدعاءات النظام القديمة، والتي تم تسميتها أيضًا، باستثناء استدعاءات "stat"، ولكنها لا تحتوي على اللاحقة "64".

في الأنظمة الأساسية الأحدث التي تتمتع فقط بإمكانية الوصول إلى الملفات 64 بت وUID/GID 32 بت (مثل alpha وia64 وs390x وx86-64)، يوجد إصدار واحد فقط من استدعاءات النظام للوصول إلى UID/GID والوصول إلى الملفات. على الأنظمة الأساسية (عادةً الأنظمة الأساسية 32 بت) حيث تتوفر مكالمات *64 و*32، تكون الإصدارات الأخرى قديمة.

* التحديات رت_سيج*تمت إضافته إلى kernel 2.2 لدعم الإشارات الإضافية في الوقت الفعلي (انظر kernel 2.2). الإشارة(7)). تحل استدعاءات النظام هذه محل استدعاءات النظام القديمة التي تحمل نفس الأسماء، ولكن بدون البادئة "rt_". * في مكالمات النظام يختار(2 و mmap(2) تم استخدام خمس وسيطات أو أكثر، مما تسبب في حدوث مشكلات في تحديد كيفية تمرير الوسائط على i386. ونتيجة لذلك، بينما في البنى الأخرى يدعو sys_select() و sys_mmap() مباراة __NR_selectو __NR_mmap، على i386 أنها تتوافق old_select() و old_mmap() (الإجراءات التي تستخدم مؤشرًا لكتلة من الوسائط). حاليا لم تعد هناك مشكلة في تمرير أكثر من خمس وسيطات، وهناك __NR__newselect، وهو ما يتوافق تمامًا sys_select()، ونفس الوضع مع __NR_mmap2.

فلاديمير ميشكوف

اعتراض مكالمات النظام في نظام التشغيل Linux

في السنوات الأخيرة، احتل نظام التشغيل Linux مكانة رائدة كمنصة خادم، متفوقًا على العديد من التطورات التجارية. ومع ذلك، قضايا الحماية نظم المعلومات، المبني على أساس نظام التشغيل هذا، لا يتوقف عن كونه ذا صلة. موجود عدد كبير من الوسائل التقنية، سواء البرامج أو الأجهزة، مما يساعد على ضمان أمان النظام. هذه هي أدوات تشفير البيانات و ازدحام انترنت، التفريق بين حقوق الوصول إلى مصادر المعلومات، حماية بريد إلكترونيخوادم الويب، الحماية من الفيروسات، إلخ. القائمة، كما تفهم، طويلة جدًا. ندعوك في هذه المقالة إلى النظر في آلية الحماية التي تعتمد على اعتراض مكالمات نظام التشغيل. أنظمة لينكس. تسمح لك هذه الآلية بالتحكم في تشغيل أي تطبيق وبالتالي منع الإجراءات التدميرية المحتملة التي قد يقوم بها.

مكالمات النظام

لنبدأ بالتعريف. استدعاءات النظام هي مجموعة من الوظائف التي يتم تنفيذها في نواة نظام التشغيل. تتم ترجمة أي طلب تطبيق مستخدم في النهاية إلى استدعاء نظام ينفذ الإجراء المطلوب. توجد قائمة كاملة باستدعاءات نظام Linux في الملف /usr/include/asm/unistd.h. دعونا نلقي نظرة على الآلية العامة لتنفيذ مكالمات النظام مع مثال. اسمح لمصدر التطبيق الخاص بك باستدعاء الدالة create() لإنشاء ملف جديد. يقوم المترجم، بعد أن واجه استدعاء لهذه الوظيفة، بتحويلها إلى رمز التجميع، وتحميل رقم استدعاء النظام المطابق لهذه الوظيفة ومعلماتها في سجلات المعالج ثم استدعاء المقاطعة 0x80. يتم تحميل القيم التالية في سجلات المعالج:

  • لتسجيل EAX– رقم استدعاء النظام. لذلك، في حالتنا، سيكون رقم استدعاء النظام هو 8 (انظر __NR_creat)؛
  • إلى سجل EBX- المعلمة الأولى للوظيفة (لإنشاء هذا هو مؤشر إلى سطر يحتوي على اسم الملف الذي يتم إنشاؤه)؛
  • إلى سجل ECX- المعلمة الثانية (حقوق الوصول إلى الملف).

يتم تحميل المعلمة الثالثة في سجل EDX؛ لإجراء مكالمة نظام في نظام التشغيل Linux، استخدم وظيفة system_call، المحددة في الملف /usr/src/liux/arch/i386/kernel/entry.S. هذه الوظيفة هي نقطة الدخول لجميع مكالمات النظام. تستجيب النواة للمقاطعة 0x80 عن طريق استدعاء الدالة system_call، والتي تعد في الأساس معالجًا للمقاطعة 0x80.

للتأكد من أننا على المسار الصحيح، دعونا نكتب جزءًا صغيرًا من الاختبار بلغة التجميع. سنرى فيه ما تتحول إليه وظيفة create() بعد التجميع. دعنا نسمي الملف test.S. وهنا محتواه:

Globl_start

نص

يبدأ:

نقوم بتحميل رقم استدعاء النظام في سجل EAX:

موفل $8،٪ eax

في سجل EBX – المعلمة الأولى، مؤشر إلى السطر الذي يحمل اسم الملف:

movl $filename، %ebx

في سجل ECX - المعلمة الثانية، حقوق الوصول:

نقل $0، %ecx

استدعاء المقاطعة:

كثافة العمليات $0x80

نخرج من البرنامج. للقيام بذلك، استدعاء وظيفة الخروج (0):

movl $1، %eax movl $0، %ebx int $0x80

في مقطع البيانات، نشير إلى اسم الملف الذي سيتم إنشاؤه:

بيانات

اسم الملف: سلسلة "file.txt"

تجميع:

اختبار دول مجلس التعاون الخليجي -c

اختبار ld -s -o test.o

سيظهر اختبار الملف القابل للتنفيذ في الدليل الحالي. من خلال تشغيله سوف نقوم بإنشاء ملف جديداسمه file.txt.

الآن دعنا نعود إلى النظر في آلية استدعاء النظام. لذا، فإن النواة تستدعي معالج المقاطعة 0x80 - وظيفة system_call. يضع System_call نسخًا من السجلات التي تحتوي على معلمات الاستدعاء على المكدس باستخدام الماكرو SAVE_ALL ويستدعي وظيفة النظام المطلوبة باستخدام أمر الاستدعاء. يوجد جدول المؤشرات لوظائف kernel التي تنفذ استدعاءات النظام في مصفوفة sys_call_table (راجع الملف Arch/i386/kernel/entry.S). يعد رقم استدعاء النظام الموجود في سجل EAX بمثابة فهرس في هذه المصفوفة. وبالتالي، إذا تم العثور على القيمة 8 في EAX، فسيتم استدعاء وظيفة kernel sys_creat(). لماذا هناك حاجة إلى الماكرو SAVE_ALL؟ الشرح هنا بسيط جداً. نظرًا لأن جميع وظائف نظام kernel تقريبًا مكتوبة بلغة C، فإنها تبحث عن معلماتها على المكدس. ويتم دفع المعلمات إلى المكدس باستخدام الماكرو SAVE_ALL! يتم تخزين القيمة المرجعة لاستدعاء النظام في سجل EAX.

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

وحدة النواة القابلة للتحميل

وحدة kernel القابلة للتحميل (دعنا نشير إليها LKM - وحدة Kernel القابلة للتحميل) هي كود برنامج يتم تنفيذه في مساحة kernel. الميزة الرئيسية لـ LKM هي القدرة على التحميل والتفريغ ديناميكيًا دون الحاجة إلى إعادة تشغيل النظام بأكمله أو إعادة ترجمة النواة.

يتكون كل LKM من وظيفتين رئيسيتين (الحد الأدنى):

  • وظيفة تهيئة الوحدة. يتم استدعاؤه عند تحميل LKM في الذاكرة:

int_module(باطل) (...)

  • وظيفة تفريغ الوحدة:

وحدة تنظيف باطلة (باطلة) (...)

فيما يلي مثال لوحدة بسيطة:

#تعريف الوحدة النمطية

#يشمل

int_module(باطل)

printk("مرحبا بالعالم");

العودة 0؛

وحدة تنظيف باطلة (باطلة)

برينتك("وداعا");

ترجمة وتحميل الوحدة. يتم تحميل الوحدة في الذاكرة باستخدام الأمر insmod:

دول مجلس التعاون الخليجي -c -O3 helloworld.c

insmod helloworld.o

توجد معلومات حول جميع الوحدات المحملة حاليًا في النظام في الملف /proc/modules. للتحقق من تحميل الوحدة، أدخل الأمر cat /proc/modules أو lsmod. يقوم الأمر rmmod بتفريغ الوحدة النمطية:

rmmod helloworld

خوارزمية اعتراض مكالمات النظام

لتنفيذ وحدة تعترض استدعاء النظام، من الضروري تحديد خوارزمية الاعتراض. الخوارزمية هي على النحو التالي:

  • حفظ مؤشر الاستدعاء الأصلي (الأصلي) بحيث يمكن استعادته؛
  • إنشاء وظيفة تنفذ استدعاء نظام جديد؛
  • في جدول مكالمات النظام sys_call_table، استبدل المكالمات، على سبيل المثال. إعداد مؤشر مناسب لاستدعاء النظام الجديد؛
  • عند الانتهاء من العمل (عند تفريغ الوحدة)، قم باستعادة استدعاء النظام الأصلي باستخدام المؤشر المحفوظ مسبقًا.

يتيح لك التتبع معرفة مكالمات النظام التي يتم استخدامها عند تشغيل تطبيق المستخدم. ومن خلال التتبع، يمكنك تحديد استدعاء النظام الذي يجب اعتراضه للتحكم في التطبيق. سيتم مناقشة مثال على استخدام برنامج التتبع أدناه.

لدينا الآن معلومات كافية لبدء دراسة أمثلة لتطبيقات الوحدات النمطية التي تعترض مكالمات النظام.

أمثلة على اعتراض مكالمات النظام

حظر إنشاء الدليل

عند إنشاء دليل، يتم استدعاء وظيفة kernel sys_mkdir. المعلمة عبارة عن سلسلة تحتوي على الاسم الدليل الذي يتم إنشاؤه. دعونا نلقي نظرة على الكود الذي يعترض استدعاء النظام المقابل.

#يشمل

#يشمل

#يشمل

تصدير جدول مكالمات النظام:

الفراغ الخارجي *sys_call_table;

لنحدد مؤشرًا لحفظ استدعاء النظام الأصلي:

int (*orig_mkdir)(const char *path);

لنقم بإنشاء مكالمة نظام خاصة بنا. مكالمتنا لا تفعل شيئًا، فقط تُرجع قيمة فارغة:

int own_mkdir(const char *path)

العودة 0؛

أثناء تهيئة الوحدة، نحفظ مؤشرًا للاستدعاء الأصلي ونستبدل استدعاء النظام:

int_module()

orig_mkdir=sys_call_table;

sys_call_table=own_mkdir; العودة 0؛

عند التفريغ، نقوم باستعادة المكالمة الأصلية:

باطلة cleanup_module ()

Sys_call_table=orig_mkdir;

سنقوم بحفظ الكود في الملف sys_mkdir_call.c. للحصول على وحدة نمطية للكائن، قم بإنشاء ملف Makefile بالمحتوى التالي:

CC = دول مجلس التعاون الخليجي

CFLAGS = -O3 -Wall -fomit-frame-pointer

sys_mkdir_call.o: sys_mkdir_call.c

$(CC) -c $(CFLAGS) $(MODFLAGS) sys_mkdir_call.c

استخدم الأمر make لإنشاء وحدة kernel. بعد تنزيله، سنحاول إنشاء دليل باستخدام الأمر mkdir. كما ترون، لا يحدث شيء. الأمر لا يعمل. لاستعادة وظائفه، ما عليك سوى إلغاء تحميل الوحدة.

منع قراءة ملف

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

#يشمل

#يشمل

#يشمل

#يشمل

#يشمل

#يشمل

#يشمل

الفراغ الخارجي *sys_call_table;

مؤشر لحفظ استدعاء النظام الأصلي:

int (*orig_open)(const char *pathname, int flag, int mode);

المعلمة الأولى للوظيفة المفتوحة هي اسم الملف المراد فتحه. يجب أن يقوم استدعاء النظام الجديد بمقارنة هذه المعلمة باسم الملف الذي نريد حمايته. إذا تطابقت الأسماء، سيتم محاكاة خطأ في فتح الملف. تبدو مكالمة النظام الجديدة لدينا كما يلي:

int own_open(const char *pathname, int flag, int mode)

لنضع اسم الملف الذي سيتم فتحه هنا:

شار *kernel_path;

اسم الملف الذي نريد حمايته:

إخفاء الحرف = "test.txt"

لنخصص الذاكرة وننسخ اسم الملف الذي سيتم فتحه هناك:

kernel_path=(char *)kmalloc(255,GFP_KERNEL);

Copy_from_user(kernel_path, pathname, 255);

فلنقارن:

إذا (strstr(kernel_path,(char *)&hide) != NULL) (

نقوم بتحرير الذاكرة وإرجاع رمز خطأ إذا كانت الأسماء متطابقة:

kfree(kernel_path);

عودة -ENOENT؛

آخر(

إذا كانت الأسماء غير متطابقة، فإننا نستدعي استدعاء النظام الأصلي لتنفيذ الإجراء القياسي لفتح ملف:

kfree(kernel_path);

إرجاع orig_open(pathname, flag, mode);

int_module()

orig_open=sys_call_table;

sys_call_table=own_open;

العودة 0؛

باطلة cleanup_module ()

sys_call_table=orig_open;

لنحفظ الكود في الملف sys_open_call.c وننشئ ملف Makefile للحصول على وحدة الكائن:

CC = دول مجلس التعاون الخليجي

CFLAGS = -O2 -Wall -fomit-frame-pointer

MODFLAGS = -D__KERNEL__ -DMODULE -I/usr/src/linux/include

sys_open_call.o: sys_open_call.c

$(CC) -c $(CFLAGS) $(MODFLAGS) sys_open_call.c

في الدليل الحالي، قم بإنشاء ملف يسمى test.txt، ثم قم بتحميل الوحدة وأدخل الأمر cat test.txt. سيقوم النظام بالإبلاغ عن عدم وجود ملف بهذا الاسم.

بصراحة، من السهل تجاوز هذه الحماية. يكفي استخدام الأمر mv لإعادة تسمية الملف ثم قراءة محتوياته.

إخفاء إدخال ملف في الدليل

دعونا نحدد استدعاء النظام المسؤول عن قراءة محتويات الدليل. للقيام بذلك، دعونا نكتب جزءًا اختباريًا آخر يقرأ الدليل الحالي:

/* الملف dir.c*/

#يشمل

#يشمل

انت مين()

دير * د؛

البنية المباشرة *dp;

د = opendir(".");

موانئ دبي = قراءة(د);

العودة 0؛

دعنا نحصل على الوحدة القابلة للتنفيذ:

دول مجلس التعاون الخليجي -o dir dir.c

وتتبعه:

ستراس ./دير

دعونا ننتبه إلى السطر قبل الأخير:

getdents(6, /* 4 إدخالات*/, 3933) = 72;

تتم قراءة محتويات الدليل بواسطة وظيفة getdents. يتم تخزين النتيجة كقائمة بنيات من النوع struct dirent. المعلمة الثانية لهذه الوظيفة هي مؤشر لهذه القائمة. ترجع الدالة طول كافة الإدخالات في الدليل. في مثالنا، حددت وظيفة getdents وجود أربعة إدخالات في الدليل الحالي - "."، ".." والملفين لدينا، الوحدة القابلة للتنفيذ والنص المصدر. طول كافة الإدخالات في الدليل هو 72 بايت. يتم تخزين المعلومات حول كل سجل، كما قلنا من قبل، في البنية المباشرة. يهمنا مجالان من هذا الهيكل:

  • d_reclen- حجم السجل؛
  • d_name- اسم الملف.

لإخفاء إدخال حول ملف (بمعنى آخر، جعله غير مرئي)، تحتاج إلى اعتراض استدعاء النظام sys_getdents، والعثور على الإدخال المقابل في قائمة الهياكل المستلمة، وحذفه. دعونا نلقي نظرة على الكود الذي ينفذ هذه العملية (مؤلف الكود الأصلي هو Michal Zalewski):

الفراغ الخارجي *sys_call_table;

int (*orig_getdents)(u_int, struct dirent *, u_int);

دعونا نحدد استدعاء نظامنا.

int own_getdents(u_int fd, struct dirent *dirp, u_int count)

int غير الموقعة tmp، n؛

كثافة العمليات؛

سيتم عرض الغرض من المتغيرات أدناه. بالإضافة إلى ذلك، نحن بحاجة إلى الهياكل:

البنية المباشرة *dirp2, *dirp3;

اسم الملف الذي نريد إخفاءه:

شار إخفاء = "our.file"؛

لنحدد طول الإدخالات في الدليل:

tmp=(*orig_getdents)(fd,dirp,count);

إذا (تمب> 0)(

لنخصص ذاكرة للبنية في مساحة kernel وننسخ محتويات الدليل إليها:

dirp2=(struct dirent *)kmalloc(tmp,GFP_KERNEL);

сopy_from_user(dirp2,dirp,tmp);

لنستخدم البنية الثانية ونحفظ طول الإدخالات في الدليل:

dirp3=dirp2;

t=tmp;

لنبدأ بالبحث عن ملفنا:

بينما(ر>0) (

نقرأ طول الإدخال الأول ونحدد طول الإدخالات المتبقية في الدليل:

n=dirp3->d_reclen;

t-=n;

نتحقق مما إذا كان اسم الملف من الإدخال الحالي يطابق الاسم الذي نبحث عنه:

إذا (strstr((char *)&(dirp3->d_name),(char *)&hide) != NULL) (

إذا كان الأمر كذلك، فاستبدل الإدخال واحسب الطول الجديد للإدخالات في الدليل:

memcpy(dirp3,(char *)dirp3+dirp3->d_reclen,t);

tmp-=n;

نضع المؤشر على الإدخال التالي ونواصل البحث:

dirp3=(struct dirent *)((char *)dirp3+dirp3->d_reclen);

نعيد النتيجة ونحرر الذاكرة:

Copy_to_user(dirp,dirp2,tmp);

kfree(dirp2);

إرجاع طول الإدخالات في الدليل:

عودة تمب؛

وظائف تهيئة الوحدة النمطية وتفريغها لها نموذج قياسي:

int_module(باطل)

orig_getdents=sys_call_table;

sys_call_table=own_getdents;

العودة 0؛

باطلة cleanup_module ()

sys_call_table=orig_getdents;

لنحفظ النص المصدر في الملف sys_call_getd.c وننشئ ملف Makefile بالمحتوى التالي:

CC = دول مجلس التعاون الخليجي

الوحدة النمطية = sys_call_getd.o

CFLAGS = -O3 -الجدار

لينكس = /usr/src/linux

MODFLAGS = -D__KERNEL__ -DMODULE -I$(LINUX)/تشمل

sys_call_getd.o: sys_call_getd.c $(CC) -c

$(CFLAGS) $(MODFLAGS) sys_call_getd.c

في الدليل الحالي، قم بإنشاء ملف Our.file وقم بتحميل الوحدة. يختفي الملف، وهو ما يجب إثباته.

كما تفهم، ليس من الممكن النظر في مثال لاعتراض كل استدعاء للنظام في مقال واحد. لذلك أنصح المهتمين بهذا الموضوع بزيارة المواقع:

هناك يمكنك العثور على أمثلة أكثر تعقيدًا وإثارة للاهتمام لاعتراض مكالمات النظام. يرجى الكتابة عن جميع التعليقات والاقتراحات في منتدى المجلة.

عند إعداد المقال تم استخدام مواد من الموقع

هذه المادة عبارة عن تعديل للمقال الذي يحمل نفس الاسم بقلم فلاديمير ميشكوف المنشور في مجلة "System Administrator"

هذه المادة عبارة عن نسخة من مقالات فلاديمير ميشكوف من مجلة "مسؤول النظام". يمكن العثور على هذه المقالات باستخدام الروابط أدناه. تم أيضًا تغيير بعض الأمثلة على الأكواد المصدرية للبرامج - وتحسينها ووضعها في صيغتها النهائية. (تم تعديل المثال 4.2 بشكل كبير، حيث كان علينا اعتراض مكالمة نظام مختلفة قليلاً) عناوين URL: http://www.samag.ru/img/uploaded/p.pdf http://www.samag.ru/img/uploaded /a3.pdf

هل لديك أسئلة؟ ثم هنا تذهب: [البريد الإلكتروني محمي]

  • 2. وحدة النواة القابلة للتحميل
  • 4. أمثلة على اعتراض مكالمات النظام بناءً على LKM
    • 4.1 حظر إنشاء الدليل

1. منظر عام لبنية Linux

تتيح لنا الرؤية الأكثر عمومية رؤية نموذج من مستويين للنظام. نواة<=>بروغ في الوسط (يسار) هو جوهر النظام. تتفاعل النواة مباشرة مع أجهزة الكمبيوتر، مما يؤدي إلى عزل البرامج التطبيقية عن الميزات المعمارية. تحتوي النواة على مجموعة من الخدمات المقدمة للبرامج التطبيقية. تتضمن خدمات النواة عمليات الإدخال/الإخراج (فتح الملفات وقراءتها وكتابتها وإدارتها)، وإنشاء العمليات وإدارتها، ومزامنتها، والتواصل بين العمليات. تطلب جميع التطبيقات خدمات kernel من خلال مكالمات النظام.

يتكون المستوى الثاني من التطبيقات أو المهام، سواء تلك الخاصة بالنظام، والتي تحدد وظائف النظام، أو التطبيقات، التي توفر واجهة مستخدم Linux. ومع ذلك، على الرغم من عدم التجانس الخارجي للتطبيقات، فإن مخططات التفاعل مع النواة هي نفسها.

يحدث التفاعل مع النواة من خلال واجهة استدعاء النظام القياسية. تمثل واجهة استدعاء النظام مجموعة من خدمات kernel وتحدد تنسيق طلبات الخدمة. تطلب العملية خدمة من خلال استدعاء النظام لإجراء محدد في kernel، يشبه في المظهر استدعاء دالة المكتبة العادية. تقوم النواة، نيابة عن العملية، بتنفيذ الطلب وإرجاع البيانات الضرورية إلى العملية.

في المثال أعلاه، يقوم البرنامج بفتح ملف، وقراءة البيانات منه، ثم إغلاق الملف. في هذه الحالة، يتم إجراء عملية فتح (فتح) وقراءة (قراءة) وإغلاق (إغلاق) ملف بواسطة النواة بناءً على طلب المهمة، ويتم فتح (2) وقراءة (2) وإغلاق (2) ) الوظائف هي مكالمات النظام.

/* المصدر 1.0 */ #include main () ( int fd; char buf; /* افتح الملف - احصل على رابط (واصف الملف) fd */ fd = open("file1",O_RDONLY); /* اقرأ 80 حرفًا في المخزن المؤقت buf */ read( fd, buf , sizeof(buf)); /* أغلق الملف */ Close(fd); /* EOF */ القائمة الكاملة لاستدعاءات نظام التشغيل Linux موجودة في الملف /usr/include/asm/unistd.h. دعونا الآن نلقي نظرة على آلية تنفيذ استدعاءات النظام في هذا المثال. يقوم المترجم، بعد أن واجه وظيفة open() لفتح ملف، بتحويله إلى رمز التجميع، وتحميل رقم استدعاء النظام المطابق لهذه الوظيفة ومعلماتها في سجلات المعالج ثم استدعاء المقاطعة 0x80. يتم تحميل القيم التالية في سجلات المعالج:

  • في سجل EAX - رقم مكالمة النظام. لذلك، في حالتنا، رقم استدعاء النظام هو 5 (انظر __NR_open).
  • في سجل EBX - المعلمة الأولى للوظيفة (لـ open() - هذا مؤشر إلى سلسلة تحتوي على اسم الملف الذي سيتم فتحه.
  • إلى سجل ECX - المعلمة الثانية (حقوق الوصول إلى الملف)
يتم تحميل المعلمة الثالثة في سجل EDX؛ لإجراء استدعاء نظام في نظام التشغيل Linux، استخدم وظيفة system_call، التي تم تعريفها (اعتمادًا على البنية، في هذه الحالة i386) في الملف /usr/src/linux/arch/i386/kernel/entry.S. هذه الوظيفة هي نقطة الدخول لجميع مكالمات النظام. تستجيب النواة للمقاطعة 0x80 عن طريق استدعاء الدالة system_call، والتي تعد في الأساس معالجًا للمقاطعة 0x80.

للتأكد من أننا نسير على الطريق الصحيح، دعونا نلقي نظرة على كود الدالة open() في مكتبة نظام libc:

# gdb -q /lib/libc.so.6 (gdb) disas open تفريغ كود المجمّع للوظيفة المفتوحة: 0x000c8080 : اتصل بالرقم 0x1082be< __i686.get_pc_thunk.cx >0x000c8085 : أضف $0x6423b,%ecx 0x000c808b : كمبل $0x0.0x1a84(%ecx) 0x000c8092 :جين 0xc80b1 0x000c8094 : ادفع %ebx 0x000c8095 : الحركة 0x10(%esp,1),%edx 0x000c8099 : mov 0xc(%esp,1),%ecx 0x000c809d : mov 0x8(%esp,1),%ebx 0x000c80a1 : الحركة $0x5,%eax 0x000c80a6 : int $0x80... كما ليس من الصعب ملاحظة ذلك في الأسطر الأخيرة، يتم نقل المعلمات إلى سجلات EDX وECX وEBX، ويتم وضع رقم استدعاء النظام في آخر سجل EAX، وهو ما يعادل، كما نعلم بالفعل ، 5.

الآن دعنا نعود إلى النظر في آلية استدعاء النظام. لذلك، تستدعي النواة معالج المقاطعة 0x80 - وظيفة system_call. يضع System_call نسخًا من السجلات التي تحتوي على معلمات الاستدعاء على المكدس باستخدام الماكرو SAVE_ALL ويستدعي وظيفة النظام المطلوبة باستخدام أمر الاستدعاء. يوجد جدول المؤشرات لوظائف kernel التي تنفذ استدعاءات النظام في مصفوفة sys_call_table (راجع الملف Arch/i386/kernel/entry.S). يعد رقم استدعاء النظام الموجود في سجل EAX بمثابة فهرس في هذه المصفوفة. وبالتالي، إذا كانت EAX تحتوي على القيمة 5، فسيتم استدعاء دالة kernel sys_open(). لماذا هناك حاجة إلى الماكرو SAVE_ALL؟ الشرح هنا بسيط جداً. نظرًا لأن جميع وظائف نظام kernel تقريبًا مكتوبة بلغة C، فإنها تبحث عن معلماتها على المكدس. ويتم دفع المعلمات إلى المكدس باستخدام SAVE_ALL! يتم تخزين القيمة المرجعة لاستدعاء النظام في سجل EAX.

الآن دعونا نتعرف على كيفية اعتراض مكالمة النظام. ستساعدنا آلية وحدات kernel القابلة للتحميل في ذلك.

2. وحدة النواة القابلة للتحميل

وحدة Kernel القابلة للتحميل (الاختصار الشائع LKM - وحدة Kernel القابلة للتحميل) هي رمز برنامج يتم تنفيذه في مساحة kernel. الميزة الرئيسية لـ LKM هي القدرة على التحميل والتفريغ ديناميكيًا دون الحاجة إلى إعادة تشغيل النظام بأكمله أو إعادة ترجمة النواة.

يتكون كل LKM من وظيفتين رئيسيتين (الحد الأدنى):

  • وظيفة تهيئة الوحدة. يتم استدعاؤه عند تحميل LKM في الذاكرة: int init_module(void) ( ... )
  • وظيفة تفريغ الوحدة النمطية: void cleanup_module(void) (...)
فيما يلي مثال لوحدة بسيطة: /* المصدر 2.0 */ #include int init_module(void) ( printk("Hello World\n"); return 0; ) void cleanup_module(void) ( printk("Bye\n"); ) /* EOF */ ترجمة وتحميل الوحدة. يتم تحميل الوحدة في الذاكرة باستخدام الأمر insmod، وعرض الوحدات المحملة باستخدام الأمر lsmod: # gcc -c -DMODULE -I/usr/src/linux/include/ src-2.0.c # insmod src-2.0.o تحذير: تحميل src-2.0 .o سوف يلوث النواة: لا يوجد ترخيص تم تحميل الوحدة src-2.0، مع تحذيرات # dmesg | tail -n 1 Hello World # lsmod | grep src src-2.0 336 0 (غير مستخدم) # rmmod src-2.0 # dmesg | الذيل -ن 1 وداعا

3. خوارزمية اعتراض مكالمات النظام على أساس LKM

لتنفيذ وحدة تعترض استدعاء النظام، من الضروري تحديد خوارزمية الاعتراض. الخوارزمية هي على النحو التالي:
  • احفظ مؤشرًا على الاستدعاء الأصلي (الأصلي) حتى يمكن استعادته
  • إنشاء وظيفة تنفذ استدعاء نظام جديد
  • في جدول مكالمات النظام sys_call_table، استبدل المكالمات، أي قم بإعداد مؤشر مطابق لاستدعاء نظام جديد
  • عند الانتهاء من العمل (عند تفريغ الوحدة)، قم باستعادة استدعاء النظام الأصلي باستخدام المؤشر المحفوظ مسبقًا
يتيح لك التتبع معرفة مكالمات النظام المستخدمة أثناء تشغيل تطبيق المستخدم. ومن خلال التتبع، يمكنك تحديد استدعاء النظام الذي يجب اعتراضه للتحكم في التطبيق. # ltrace -S ./src-1.0 ... open("file1", 0, 01 SYS_open("file1", 0, 01) = 3<... open resumed>) = 3 قراءة(3، SYS_read(3, "123\n", 80) = 4<... read resumed>"123\n"، 80) = 4 إغلاق(3 SYS_Close(3) = 0<... close resumed>) = 0... الآن لدينا معلومات كافية لبدء دراسة أمثلة لتطبيقات الوحدات التي تعترض مكالمات النظام.

4. أمثلة على اعتراض مكالمات النظام بناءً على LKM

4.1 حظر إنشاء الدليل

عند إنشاء دليل، يتم استدعاء وظيفة kernel sys_mkdir. المعلمة عبارة عن سلسلة تحتوي على اسم الدليل الذي سيتم إنشاؤه. دعونا نلقي نظرة على الكود الذي يعترض استدعاء النظام المقابل. /* المصدر 4.1 */ #include #يشمل #يشمل /* تصدير جدول استدعاء النظام */ extern void *sys_call_table; /* تحديد مؤشر لحفظ المكالمة الأصلية */ int (*orig_mkdir)(const char *path); /* لنقم بإنشاء استدعاء النظام الخاص بنا. مكالمتنا لا تفعل شيئًا، فقط تُرجع قيمة فارغة */ int own_mkdir(const char *path) ( return 0; ) /* أثناء تهيئة الوحدة، نحفظ مؤشرًا للمكالمة الأصلية ونستبدل استدعاء النظام */ int init_module(void ) ( orig_mkdir =sys_call_table; sys_call_table=own_mkdir; printk("sys_mkdir استبدال\n"); return(0); /* عند التفريغ, استعادة المكالمة الأصلية */ void cleanup_module(void) ( sys_call_table=orig_mkdir; printk("sys_mkdir" تم نقله للخلف\n "); ) /* EOF */ للحصول على وحدة الكائن، قم بتشغيل الأمر التالي وإجراء سلسلة من التجارب على النظام: # gcc -c -DMODULE -I/usr/src/linux/include/ src-3.1.c # dmesg | تم استبدال tail -n 1 sys_mkdir # mkdir test # ls -ald test ls: test: لا يوجد مثل هذا الملف أو الدليل # rmmod src-3.1 # dmesg | tail -n 1 sys_mkdir انتقل للخلف # mkdir test # ls -ald test drwxr-xr-x 2 root root 4096 2003-12-23 03:46 test كما ترون، الأمر "mkdir" لا يعمل، أو بالأحرى لا شيء يحدث. لاستعادة وظائف النظام، ما عليك سوى إلغاء تحميل الوحدة. وهو ما تم عمله أعلاه.

4.2 إخفاء إدخال ملف في الدليل

دعونا نحدد استدعاء النظام المسؤول عن قراءة محتويات الدليل. للقيام بذلك، دعونا نكتب جزءًا اختباريًا آخر يقرأ الدليل الحالي: /* المصدر 4.2.1 */ #include #يشمل int main() ( DIR *d; struct dirent *dp; d = opendir("."); dp = readdir(d); return 0; ) /* EOF */ احصل على الملف القابل للتنفيذ وتتبعه: # gcc -o src-3.2.1 src-3.2.1.c # ltrace -S ./src-3.2.1 ... opendir("." SYS_open("."، 100352، 010005141300) = 3 SYS_fstat64(3، 0xbffff79c، 0x4014c2c0، 3، 0xbffff874) = 0 SYS_fcntl64(3، 2، 1، 1، 0x4014c2c0) = 0 SYS_ برك (فارغ) = 0x080495f4 SYS_brk(0x0806a5f4) ) = 0x0806a5f4 SYS_brk(NULL) = 0x0806a5f4 SYS_brk(0x0806b000) = 0x0806b000<... opendir resumed>) = 0x08049648 ريدير(0x08049648 SYS_getdents64(3, 0x08049678, 4096, 0x40014400, 0x4014c2c0) = 528<... readdir resumed>) = 0x08049678... انتبه للسطر الأخير. تتم قراءة محتويات الدليل بواسطة وظيفة getdents64 (يمكن الحصول على getdents في حبات أخرى). يتم حفظ النتيجة كقائمة بنيات من النوع struct dirent، وتقوم الوظيفة نفسها بإرجاع طول جميع الإدخالات في الدليل. نحن مهتمون بمجالين من هذا الهيكل:
  • d_reclen - حجم السجل
  • d_name - اسم الملف
من أجل إخفاء سجل ملف حول ملف (بمعنى آخر، جعله غير مرئي)، تحتاج إلى اعتراض استدعاء النظام sys_getdents64، والعثور على السجل المقابل في قائمة الهياكل المستلمة وحذفه. دعونا نلقي نظرة على الكود الذي ينفذ هذه العملية (author الكود الأصلي- ميشال زاليفسكي): /* المصدر 4.2.2 */ #include #يشمل #يشمل #يشمل #يشمل #يشمل #يشمل #يشمل الفراغ الخارجي *sys_call_table; int (*orig_getdents)(u_int fd, struct dirent *dirp, u_int count); /* تحديد استدعاء نظامنا */ int own_getdents(u_int fd, struct dirent *dirp, u_int count) ( unsigned int tmp, n; int t; struct dirent64 ( int d_ino1,d_ino2; int d_off1,d_off2; unsigned short d_reclen; unsigned char d_type; char d_name ) *dirp2, *dirp3; /* اسم الملف الذي نريد إخفاءه */ char إخفاء = "file1"; (fd,dirp ,count); if (tmp>0) ( /* تخصيص الذاكرة للبنية في مساحة kernel ونسخ محتويات الدليل إليها */ dirp2 = (struct dirent64 *)kmalloc(tmp,GFP_KERNEL); Copy_from_user(dirp2,dirp,tmp) /* استخدم البنية الثانية واحفظ طول الإدخالات في الدليل */ dirp3 = t = tmp /* لنبدأ بالبحث عن ملفنا */ while (t>0) ( / * اقرأ طول الإدخال الأول وحدد الطول المتبقي للإدخالات في الدليل */ n = dirp3->d_reclen t -= n; /* تحقق مما إذا كان اسم الملف من الإدخال الحالي يطابق الاسم الذي تم البحث عنه */ if (strstr((char *)&(dirp3->d_name), (char * )&hide) != NULL) ( /* إذا كانت هذه هي الحالة، فقم بالكتابة فوق الإدخال وحساب الطول الجديد للإدخالات في الدليل * / memcpy(dirp3, (char *)dirp3+dirp3->d_reclen, t); تمة -= ن; ) /* ضع المؤشر على السجل التالي واستمر في البحث */ dirp3 = (struct dirent64 *)((char *)dirp3+dirp3->d_reclen); ) /* إرجاع النتيجة وتحرير الذاكرة */ Copy_to_user(dirp,dirp2,tmp); kfree(dirp2); ) /* إرجاع طول الإدخالات في الدليل */ return tmp; ) /* وظائف تهيئة الوحدة النمطية وتفريغها لها نموذج قياسي */ int init_module(void) ( orig_getdents = sys_call_table; sys_call_table=own_getdents; return 0; ) void cleanup_module() ( sys_call_table=orig_getdents; ) /* EOF */ Getting قمنا بتجميع هذا الكود، دعونا نلاحظ كيف يختفي "file1"، وهو ما نحتاج إلى إثباته.

5. طريقة الوصول المباشر إلى مساحة عنوان kernel /dev/kmem

دعونا نفكر أولاً من الناحية النظرية في كيفية تنفيذ الاعتراض باستخدام الوصول المباشر إلى مساحة عنوان kernel، ثم سننتقل إلى التنفيذ العملي.

يتم توفير الوصول المباشر إلى مساحة عنوان kernel بواسطة ملف الجهاز /dev/kmem. يعرض هذا الملف كافة مساحة العنوان الظاهرية المتوفرة، بما في ذلك قسم المبادلة. للعمل مع ملف kmem، يتم استخدام وظائف النظام القياسية - open()، read()، write(). من خلال فتح /dev/kmem بالطريقة القياسية، يمكننا الوصول إلى أي عنوان في النظام، وتعيينه كإزاحة في هذا الملف. هذه الطريقةتم تطويره بواسطة سيلفيو سيزار.

يتم الوصول إلى وظائف النظام عن طريق تحميل معلمات الوظيفة في سجلات المعالج ثم استدعاء مقاطعة البرنامج 0x80. معالج هذه المقاطعة، وظيفة system_call، يدفع معلمات الاستدعاء إلى المكدس، ويسترد عنوان وظيفة النظام المستدعى من جدول sys_call_table وينقل التحكم إلى هذا العنوان.

نأخذ الوصول الكاملإلى مساحة عنوان kernel، يمكننا الحصول على محتويات جدول استدعاء النظام بالكامل، أي: عناوين كافة وظائف النظام. ومن خلال تغيير عنوان أي استدعاء للنظام، فإننا بذلك نعترضه. ولكن لهذا تحتاج إلى معرفة عنوان الجدول، أو بمعنى آخر الإزاحة في الملف /dev/kmem الذي يوجد به هذا الجدول.

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

في الوضع الحقيقي، عند تسجيل المقاطعة، يصل المعالج إلى جدول متجه المقاطعة، والذي يقع دائمًا في بداية الذاكرة ويحتوي على عناوين مكونة من كلمتين لبرامج معالجة المقاطعة. في الوضع المحمي، يكون التناظري لجدول متجه المقاطعة هو جدول واصف المقاطعة (IDT، جدول واصف المقاطعة)، الموجود في نظام تشغيل الوضع المحمي. لكي يتمكن المعالج من الوصول إلى هذا الجدول، يجب تحميل عنوانه في سجل IDTR (سجل جدول واصف المقاطعة). يحتوي جدول IDT على واصفات معالجات المقاطعة، والتي تتضمن، على وجه الخصوص، عناوينها. وتسمى هذه الواصفات البوابات. يقوم المعالج، بعد تسجيل المقاطعة، باسترداد البوابة من IDT باستخدام رقمها، ويحدد عنوان المعالج وينقل التحكم إليه.

لحساب عنوان وظيفة system_call من جدول IDT، من الضروري استخراج بوابة المقاطعة int $0x80، ومنه عنوان المعالج المقابل، أي. عنوان الدالة system_call. في وظيفة system_call، يتم الوصول إلى جدول system_call_table باستخدام أمر الاتصال<адрес_таблицы>(،٪ إياكس، 4). بعد العثور على رمز التشغيل (التوقيع) لهذا الأمر في الملف /dev/kmem، سنجد أيضًا عنوان جدول استدعاء النظام.

لتحديد كود التشغيل، سنستخدم مصحح الأخطاء ونقوم بتفكيك وظيفة system_call:

# gdb -q /usr/src/linux/vmlinux (gdb) disas system_call تفريغ كود المجمع للوظيفة system_call: 0xc0194cbc : ادفع %eax 0xc0194cbd : كلد 0xc0194cbe : ادفع %es 0xc0194cbf : اضغط %ds 0xc0194cc0 : اضغط %eax 0xc0194cc1 : ادفع %ebp 0xc0194cc2 : ادفع %edi 0xc0194cc3 : اضغط %esi 0xc0194cc4 : ادفع %edx 0xc0194cc5 : ادفع %ecx 0xc0194cc6 : ادفع %ebx 0xc0194cc7 : الحركة $0x18,%edx 0xc0194ccc : الحركة %edx،%ds 0xc0194cce : الحركة %edx,%es 0xc0194cd0 : الحركة $0xffffe000,%ebx 0xc0194cd5 : و %esp,%ebx 0xc0194cd7 : testb $0x2.0x18(%ebx) 0xc0194cdb :جيني 0xc0194d3c 0xc0194cdd : cmp $0x10e,%eax 0xc0194ce2 :jae0xc0194d69 0xc0194ce8 : اتصل بـ *0xc02cbb0c(,%eax,4) 0xc0194cef : الحركة %eax,0x18(%esp,1) 0xc0194cf3 : nop نهاية تفريغ المجمع. السطر "call *0xc02cbb0c(,%eax,4)" هو استدعاء للجدول sys_call_table. القيمة 0xc02cbb0c هي عنوان الجدول (على الأرجح ستكون أرقامك مختلفة). دعنا نحصل على كود التشغيل لهذا الأمر: (gdb) x/xw system_call+44 0xc0194ce8 : 0x0c8514ff لقد وجدنا كود التشغيل الخاص بالأمر للوصول إلى جدول sys_call_table. وهو يساوي \xff\x14\x85. البايتات الأربع التالية هي عنوان الجدول. يمكنك التحقق من ذلك عن طريق إدخال الأمر: (gdb) x/xw system_call+44+3 0xc0194ceb : 0xc02cbb0c وبالتالي، عند العثور على التسلسل \xff\x14\x85 في الملف /dev/kmem وقراءة البايتات الأربع التالية، نحصل على عنوان جدول استدعاء النظام sys_call_table. وبمعرفة عنوانه يمكننا الحصول على محتويات هذا الجدول (عناوين جميع وظائف النظام) وتغيير عنوان أي استدعاء للنظام عن طريق اعتراضه.

دعونا نلقي نظرة على الكود الكاذب الذي ينفذ عملية الاعتراض:

Readaddr(old_syscall, scr + SYS_CALL*4, 4); writeaddr(new_syscall, scr + SYS_CALL*4, 4); تقوم وظيفة readaddr بقراءة عنوان استدعاء النظام من جدول استدعاء النظام وتخزينه في المتغير old_syscall. يستغرق كل إدخال في جدول sys_call_table 4 بايت. يقع العنوان المطلوب في الإزاحة sct + SYS_CALL*4 في الملف /dev/kmem (هنا sct هو عنوان جدول sys_call_table، وSYS_CALL هو الرقم التسلسلي لاستدعاء النظام). تقوم وظيفة writeaddr بالكتابة فوق عنوان استدعاء النظام SYS_CALL بعنوان وظيفة new_syscall، وستتم خدمة جميع المكالمات إلى استدعاء النظام SYS_CALL بواسطة هذه الوظيفة.

يبدو أن كل شيء بسيط وتم تحقيق الهدف. ومع ذلك، دعونا نتذكر أننا نعمل في مساحة عنوان المستخدم. إذا وضعنا وظيفة نظام جديدة في مساحة العنوان هذه، فعندما نستدعي هذه الوظيفة، سنتلقى رسالة خطأ لطيفة. ومن هنا الاستنتاج - يجب وضع استدعاء النظام الجديد في مساحة عنوان kernel. للقيام بذلك، تحتاج إلى: الحصول على كتلة ذاكرة في مساحة kernel، وإجراء مكالمة نظام جديدة في هذه الكتلة.

يمكنك تخصيص الذاكرة في مساحة kernel باستخدام وظيفة kmalloc. ولكن من المستحيل استدعاء وظيفة النواة مباشرة من مساحة عنوان المستخدم، لذلك سوف نستخدم الخوارزمية التالية:

  • بمعرفة عنوان الجدول sys_call_table، نحصل على عنوان بعض مكالمات النظام (على سبيل المثال، sys_mkdir)
  • نحدد وظيفة تستدعي وظيفة kmalloc. تقوم هذه الوظيفة بإرجاع مؤشر إلى كتلة من الذاكرة في مساحة عنوان kernel. دعنا نسمي هذه الوظيفة get_kmalloc
  • حفظ البايتات N الأولى من استدعاء النظام sys_mkdir، حيث N هو حجم الدالة get_kmalloc
  • قم بالكتابة فوق أول N بايت من استدعاء sys_mkdir باستخدام الدالة get_kmalloc
  • نقوم باستدعاء استدعاء النظام sys_mkdir، وبالتالي تشغيل وظيفة get_kmalloc
  • استعادة البايتات N الأولى لاستدعاء النظام sys_mkdir
ونتيجة لذلك، سيكون لدينا تحت تصرفنا كتلة من الذاكرة الموجودة في مساحة النواة.

ولكن للتنفيذ من هذه الخوارزميةنحن بحاجة إلى عنوان الدالة kmalloc. هناك عدة طرق للعثور عليه. أبسطها هو قراءة هذا العنوان من ملف System.map أو تحديده باستخدام مصحح أخطاء gdb (طباعة &kmalloc). إذا تم تمكين دعم الوحدة النمطية في النواة، فيمكن تحديد عنوان kmalloc باستخدام وظيفة get_kernel_syms(). سيتم مناقشة هذا الخيار بشكل أكبر. إذا لم يكن هناك دعم لوحدات kernel، فسيتعين البحث عن عنوان وظيفة kmalloc بواسطة كود تشغيل أمر استدعاء kmalloc - على غرار ما تم إجراؤه لجدول sys_call_table.

تأخذ وظيفة kmalloc معلمتين: حجم الذاكرة المطلوبة ومحدد GFP. للبحث عن كود التشغيل، سنستخدم مصحح الأخطاء ونقوم بتفكيك أي وظيفة kernel تحتوي على استدعاء للدالة kmalloc.

# gdb -q /usr/src/linux/vmlinux (gdb) disas inter_module_register تفريغ كود المجمع للوظيفة inter_module_register: 0xc01a57b4 : ادفع %ebp 0xc01a57b5 : ادفع %edi 0xc01a57b6 : ادفع %esi 0xc01a57b7 : ادفع %ebx 0xc01a57b8 : فرعي $0x10,%esp 0xc01a57bb : mov 0x24(%esp,1),%ebx 0xc01a57bf : الحركة 0x28(%esp,1),%esi 0xc01a57c3 : الحركة 0x2c(%esp,1),%ebp 0xc01a57c7 : الحركة $0x1f0,0x4(%esp,1) 0xc01a57cf : الحركة $0x14,(%esp,1) 0xc01a57d6 : اتصل بالرقم 0xc01bea2a ... لا يهم ما تفعله الوظيفة، الشيء الرئيسي فيها هو ما نحتاجه - استدعاء وظيفة kmalloc. انتبه إلى الأسطر الأخيرة. أولاً، يتم تحميل المعلمات على المكدس (يشير سجل esp إلى أعلى المكدس)، متبوعًا باستدعاء دالة. يتم تحميل محدد GFP على المكدس أولاً ($0x1f0,0x4(%esp,1). بالنسبة لإصدارات kernel 2.4.9 والإصدارات الأحدث، هذه القيمة هي 0x1f0. فلنبحث عن كود التشغيل لهذا الأمر: (gdb) x/xw inter_module_register +19 0xc01a57c7 : 0x042444c7 إذا وجدنا رمز التشغيل هذا، فيمكننا حساب عنوان وظيفة kmalloc. للوهلة الأولى، عنوان هذه الوظيفة هو وسيطة لتعليمات الاستدعاء، ولكن هذا ليس صحيحا تماما. على عكس وظيفة system_call، لا تحتوي التعليمات هنا على عنوان kmalloc، ولكن الإزاحة الخاصة به بالنسبة إلى العنوان الحالي. دعونا نتحقق من ذلك عن طريق تحديد كود التشغيل لاستدعاء الأمر 0xc01bea2a: (gdb) x/xw inter_module_register+34 0xc01a57d6 : 0x01924fe8 البايت الأول هو e8 - وهذا هو رمز التشغيل لتعليمات الاتصال. لنجد قيمة وسيطة هذا الأمر: (gdb) x/xw inter_module_register+35 0xc01a57d7 : 0x0001924f الآن إذا أضفنا العنوان الحالي 0xc01a57d6، وإزاحة 0x0001924f و5 بايت للأمر، فسنحصل على العنوان المطلوب لوظيفة kmalloc - 0xc01bea2a.

وبهذا تنتهي الحسابات النظرية، وباستخدام الطريقة المذكورة أعلاه، سنعترض استدعاء النظام sys_mkdir.

6. مثال على الاعتراض باستخدام /dev/kmem

/* المصدر 6.0 */ #include #يشمل #يشمل #يشمل #يشمل #يشمل #يشمل #يشمل /* رقم استدعاء النظام للاعتراض */ #define _SYS_MKDIR_ 39 #define KMEM_FILE "/dev/kmem" #define MAX_SYMS 4096 /* وصف تنسيق تسجيل IDTR */ struct ( حد قصير غير موقع؛ قاعدة int غير موقعة؛ ) __attribute__ (( معبأة)) ) idtr; /* وصف تنسيق بوابة مقاطعة جدول IDT */ struct ( unsigned short off1; unsigned short sel; unsigned char none, flags; unsigned short off2; ) __attribute__ ((packed)) idt; /* وصف بنية وظيفة get_kmalloc */ struct kma_struc ( ulong (*kmalloc) (uint, int); // - عنوان وظيفة kmalloc int size; // - حجم الذاكرة لتخصيص إشارات int; // - علامة للنوى > 2.4.9 = 0x1f0 (GFP) ulong mem ) __attribute__ ((معبأة)) kmalloc; /* دالة تقوم فقط بتخصيص كتلة من الذاكرة في مساحة عنوان kernel */ int get_kmalloc(struct kma_struc *k) ( k->mem = k->kmalloc(k->size, k->flags); return 0 ) /* دالة تُرجع عنوان الوظيفة (المطلوبة للبحث kmalloc) */ ulong get_sym(char *n) ( struct kernel_sym tab; int numsyms; int i; numsyms = get_kernel_syms(NULL); if (numsyms > MAX_SYMS ||< 0) return 0; get_kernel_syms(tab); for (i = 0; i < numsyms; i++) { if (!strncmp(n, tab[i].name, strlen(n))) return tab[i].value; } return 0; } /* Наша новая системная функция, ничего не делает;) */ int new_mkdir(const char *path) { return 0; } /* Читает из /dev/kmem с offset size данных в buf */ static inline int rkm(int fd, uint offset, void *buf, uint size) { if (lseek(fd, offset, 0) != offset){ printf("lseek err\n"); return 0; } if (read(fd, buf, size) != size) return 0; return size; } /* Аналогично, но только пишет в /dev/kmem */ static inline int wkm(int fd, uint offset, void *buf, uint size) { if (lseek(fd, offset, 0) != offset) return 0; if (write(fd, buf, size) != size) return 0; return size; } /* Читает из /dev/kmem данные размером 4 байта */ static inline int rkml(int fd, uint offset, ulong *buf) { return rkm(fd, offset, buf, sizeof(ulong)); } /* Аналогично, но только пишет */ static inline int wkml(int fd, uint offset, ulong buf) { return wkm(fd, offset, &buf, sizeof(ulong)); } /* Функция для получения адреса sys_call_table */ ulong get_sct(int kmem) { ulong sys_call_off; // - адрес обработчика // прерывания int $0x80 (функция system_call) char *p; char sc_asm; asm("sidt %0" : "=m" (idtr)); if (!rkm(kmem, idtr.base+(8*0x80), &idt, sizeof(idt))) return 0; sys_call_off = (idt.off2 << 16) | idt.off1; if (!rkm(kmem, sys_call_off, &sc_asm, 128)) return 0; p = (char *)memmem(sc_asm, 128, "\xff\x14\x85", 3) + 3; printf("call for sys_call_table at %08x\n",p); if (p) return *(ulong *)p; return 0; } /* Функция для определения адреса функции kmalloc */ ulong get_kma(ulong pgoff) { uint i; unsigned char buf, *p, *p1; int kmemz; ulong ret; ret = get_sym("kmalloc"); if (ret) { printf("\nZer gut!\n"); return ret; } kmemz = open("/dev/kmem", O_RDONLY); if (kmemz < 0) return 0; for (i = pgoff+0x100000; i < (pgoff + 0x1000000); i += 0x10000){ if (!rkm(kmemz, i, buf, sizeof(buf))) return 0; p1=(char *)memmem(buf,sizeof(buf),"\x68\xf0\x01\x00",4); if(p1) { p=(char *)memmem(p1+4,sizeof(buf),"\xe8",1)+1; if (p) { close(kmemz); return *(unsigned long *)p+i+(p-buf)+4; } } } close(kmemz); return 0; } int main() { int kmem; // !! - пустые, нужно подставить ulong get_kmalloc_size; // - размер функции get_kmalloc !! ulong get_kmalloc_addr; // - адрес функции get_kmalloc !! ulong new_mkdir_size; // - размер функции-перехватчика!! ulong new_mkdir_addr; // - адрес функции-перехватчика!! ulong sys_mkdir_addr; // - адрес системного вызова sys_mkdir ulong page_offset; // - нижняя граница адресного // пространства ядра ulong sct; // - адрес таблицы sys_call_table ulong kma; // - адрес функции kmalloc unsigned char tmp; kmem = open(KMEM_FILE, O_RDWR, 0); if (kmem < 0) return 0; sct = get_sct(kmem); page_offset = sct & 0xF0000000; kma = get_kma(page_offset); printf("OK\n" "page_offset\t\t:\t0x%08x\n" "sys_call_table\t:\t0x%08x\n" "kmalloc()\t\t:\t0x%08x\n", page_offset,sct,kma); /* Найдем адрес sys_mkdir */ if (!rkml(kmem, sct+(_SYS_MKDIR_*4), &sys_mkdir_addr)) { printf("Cannot get addr of %d syscall\n", _SYS_MKDIR_); perror("er: "); return 1; } /* Сохраним первые N байт вызова sys_mkdir */ if (!rkm(kmem, sys_mkdir_addr, tmp, get_kmalloc_size)) { printf("Cannot save old %d syscall!\n", _SYS_MKDIR_); return 1; } /* Перепишем первые N байт, функцией get_kmalloc */ if (!wkm(kmem, sys_mkdir_addr,(void *)get_kmalloc_addr, get_kmalloc_size)) { printf("Can"t overwrite our syscall %d!\n",_SYS_MKDIR_); return 1; } kmalloc.kmalloc = (void *) kma; //- адрес функции kmalloc kmalloc.size = new_mkdir_size; //- размер запращевоемой // памяти (размер функции-перехватчика new_mkdir) kmalloc.flags = 0x1f0; //- спецификатор GFP /* Выполним сис. вызов sys_mkdir, тем самым выполним нашу функцию get_kmalloc */ mkdir((char *)&kmalloc,0); /* Востановим оригинальный вызов sys_mkdir */ if (!wkm(kmem, sys_mkdir_addr, tmp, get_kmalloc_size)) { printf("Can"t restore syscall %d !\n",_SYS_MKDIR_); return 1; } if (kmalloc.mem < page_offset) { printf("Allocated memory is too low (%08x < %08x)\n", kmalloc.mem, page_offset); return 1; } /* Оторбразим результаты */ printf("sys_mkdir_addr\t\t:\t0x%08x\n" "get_kmalloc_size\t:\t0x%08x (%d bytes)\n\n" "our kmem region\t\t:\t0x%08x\n" "size of our kmem\t:\t0x%08x (%d bytes)\n\n", sys_mkdir_addr, get_kmalloc_size, get_kmalloc_size, kmalloc.mem, kmalloc.size, kmalloc.size); /* Разместим в пространстве ядра наш новый сис. вызво */ if(!wkm(kmem, kmalloc.mem, (void *)new_mkdir_addr, new_mkdir_size)) { printf("Unable to locate new system call !\n"); return 1; } /* Перепишем таблицу sys_call_table на наш новый вызов */ if(!wkml(kmem, sct+(_SYS_MKDIR_*4), kmalloc.mem)) { printf("Eh ..."); return 1; } return 1; } /* EOF */ Скомпилируем полученый код и определим адреса и размеры функций get_kmalloc и new_mkdir. Запускать полученое творение рано! Для вычисления адресов и размеров воспользуемся утилитой objdump: # gcc -o src-6.0 src-6.0.c # objdump -x ./src-6.0 >dump لنفتح ملف التفريغ ونبحث عن البيانات التي تهمنا: 080485a4 g F .text 00000032 get_kmalloc 080486b1 g F .text 0000000a new_mkdir الآن دعنا ندخل هذه القيم في برنامجنا: ulong get_kmalloc_size=0x32; ulong get_kmalloc_addr=0x080485a4 ; ulong new_mkdir_size=0x0a; ulong new_mkdir_addr=0x080486b1; الآن دعونا إعادة ترجمة البرنامج. من خلال تشغيله، سنعترض استدعاء النظام sys_mkdir. ستتم الآن خدمة كافة الاستدعاءات لاستدعاء sys_mkdir بواسطة الدالة new_mkdir.

نهاية الورقة/EOP

تم اختبار وظائف الكود من جميع الأقسام على النواة 2.4.22. عند إعداد التقرير تم استخدام مواد من الموقع
مكالمات النظام

حتى الآن، كان على جميع البرامج التي قمنا بإنشائها أن تستخدم آليات kernel محددة جيدًا لتسجيل ملفات /proc وبرامج تشغيل الأجهزة. يعد هذا أمرًا رائعًا إذا كنت تريد القيام بشيء تم توفيره بالفعل بواسطة مبرمجي kernel، مثل كتابة برنامج تشغيل الجهاز. ولكن ماذا لو كنت تريد أن تفعل شيئًا غير عادي، أو تغير سلوك النظام بطريقة ما؟

هذا هو المكان الذي تصبح فيه برمجة kernel خطيرة. عند كتابة المثال أدناه، قمت بتدمير استدعاء النظام المفتوح. وهذا يعني أنني لم أتمكن من فتح أي ملفات، ولم أتمكن من تشغيل أي برامج، ولم أتمكن من إيقاف تشغيل النظام باستخدام أمر إيقاف التشغيل. لا بد لي من إيقاف الطاقة لإيقافه. ولحسن الحظ، لم يتم إتلاف أي ملفات. ولضمان عدم فقدان الملفات أيضًا، يرجى إجراء المزامنة قبل إصدار أوامر insmod وrmmod.

نسيان ملفات /proc وملفات الجهاز. إنها مجرد تفاصيل صغيرة. إن عملية الاتصال الحقيقية بالنواة، والتي تستخدمها جميع العمليات، هي استدعاءات النظام. عندما تطلب عملية ما خدمة من النواة (مثل فتح ملف، أو بدء عملية جديدة، أو طلب المزيد من الذاكرة)، يتم استخدام هذه الآلية. إذا كنت تريد تغيير سلوك النواة بطرق مثيرة للاهتمام، فهذا هو المكان المناسب لك. بالمناسبة، إذا كنت تريد معرفة استدعاءات النظام التي يستخدمها البرنامج، قم بتشغيل: strace .

بشكل عام، العملية غير قادرة على الوصول إلى النواة. لا يمكنه الوصول إلى ذاكرة kernel ولا يمكنه استدعاء وظائف kernel. تحدد أجهزة وحدة المعالجة المركزية هذه الحالة (هناك سبب يطلق عليها "الوضع المحمي"). تعد مكالمات النظام استثناءً لهذه القاعدة العامة موقع محدد مسبقًا في النواة (بالطبع، تتم قراءته بواسطة عمليات المستخدم ولكن لا تتم الكتابة فوقه بواسطتها). ضمن وحدات المعالجة المركزية Intel، يتم تحقيق ذلك عن طريق المقاطعة 0x80. يعرف الجهاز أنه بمجرد وصولك إلى هذا الموقع، فلن تعد قيد التشغيل بدلاً من ذلك، فإنك تعمل كنواة نظام التشغيل، وبالتالي يُسمح لك بفعل ما تريد.

الموقع في النواة الذي يمكن أن تتصل به العملية يسمى system_call. يتحقق الإجراء الموجود من رقم استدعاء النظام، الذي يخبر النواة بما تريده العملية بالضبط. ثم يبحث بعد ذلك عن جدول استدعاء النظام (sys_call_table) للعثور على عنوان وظيفة kernel المراد الاتصال بها. يتم بعد ذلك استدعاء الوظيفة المطلوبة، وبعد أن تقوم بإرجاع قيمة، يتم إجراء العديد من عمليات فحص النظام. يتم بعد ذلك إرجاع النتيجة مرة أخرى إلى العملية (أو إلى عملية أخرى إذا انتهت العملية). إذا كنت تريد رؤية الكود الذي يفعل كل هذا، فهو موجود مصدر الملفقوس/< architecture >/kernel/entry.S، بعد السطر ENTRY(system_call) .

لذا، إذا أردنا تغيير كيفية عمل بعض استدعاءات النظام، فإن أول شيء يتعين علينا القيام به هو كتابة وظيفتنا الخاصة للقيام بالشيء المناسب (عادةً عن طريق إضافة القليل من التعليمات البرمجية الخاصة بنا، ثم استدعاء الوظيفة الأصلية)، ثم قم بتغيير المؤشر إلى sys_call_table للإشارة إلى وظيفتنا. نظرًا لأنه قد يتم حذفنا لاحقًا ولا نريد ترك النظام في حالة غير متناسقة، فمن المهم لـ cleanup_module استعادة الجدول إلى حالته الأصلية.

رمز المصدر الموضح هنا هو مثال على هذه الوحدة. نريد "التجسس" على بعض المستخدمين وإرسال رسالة عبر printk في أي وقت هذا المستخدميفتح الملف. نستبدل مكالمة النظام التي تفتح ملفًا بمكالمتنا وظيفة خاصةدعا لدينا_sys_open. تتحقق هذه الوظيفة من uid (معرف المستخدم) للعملية الحالية، وإذا كان مساويا لـ uid الذي نتجسس عليه، فستستدعي printk لعرض اسم الملف الذي سيتم فتحه. ثم يستدعي وظيفة الفتح الأصلية بنفس المعلمات، ويفتح الملف بالفعل.

تقوم الدالة init_module بتغيير الموقع المقابل في sys_call_table وتخزن المؤشر الأصلي في متغير. تستخدم وظيفة cleanup_module هذا المتغير لاستعادة كل شيء إلى وضعه الطبيعي. يعد هذا الأسلوب خطيرًا بسبب إمكانية قيام وحدتين بتعديل نفس استدعاء النظام. تخيل أن لدينا وحدتين، A وB. لنستدعي استدعاء النظام المفتوح للوحدة A A_open ونستدعي نفس الاستدعاء للوحدة B B_open. الآن بعد أن تم استبدال syscall المدرج في kernel بـ A_open، والذي سوف يستدعي sys_open الأصلي عندما يقوم بما يجب عليه القيام به. بعد ذلك، سيتم إدراج B في النواة، وسيستبدل استدعاء النظام بـ B_open، والذي سوف يستدعي ما يعتقد أنه استدعاء النظام الأصلي، ولكنه في الواقع A_open.

الآن، إذا تمت إزالة B أولاً، فسيكون كل شيء على ما يرام: سيستعيد ببساطة استدعاء النظام على A_open الذي يستدعي النسخة الأصلية. ومع ذلك، إذا تمت إزالة A ثم تمت إزالة B، فسوف ينهار النظام. ستؤدي إزالة A إلى استعادة استدعاء النظام إلى sys_open الأصلي، مما يؤدي إلى قطع B خارج الحلقة. وبعد ذلك، عند إزالة B، فإنه سيعيد استدعاء النظام إلى ما يعتقد أنه الأصلي وسيتم توجيه الاستدعاء فعليًا إلى A_open، الذي لم يعد موجودًا في الذاكرة. للوهلة الأولى، يبدو أننا نستطيع حل هذه المشكلة تحديدًا عن طريق التحقق مما إذا كان استدعاء النظام يساوي دالتنا المفتوحة، وإذا كان الأمر كذلك، فلا نغير قيمة هذا الاستدعاء (بحيث لا يغير B استدعاء النظام عند حذفه )، ولكن هذا من شأنه أن يسبب مشكلة أخرى أسوأ. عند إزالة A، فإنه يرى أن استدعاء النظام قد تم تغييره إلى B_open بحيث لم يعد يشير إلى A_open، لذلك لن يستعيد المؤشر إلى sys_open قبل إزالته من الذاكرة. لسوء الحظ، سيظل B_open يحاول استدعاء A_open، الذي لم يعد موجودًا في الذاكرة، لذلك حتى بدون إزالة B، سيظل النظام يتعطل.

أرى طريقتين لمنع هذه المشكلة. أولاً: استعادة الوصول إلى القيمة الأصلية لـ sys_open. لسوء الحظ، sys_open ليس جزءًا من جدول kernel في /proc/ksyms، لذلك لا يمكننا الوصول إليه. الحل الآخر هو استخدام عداد مرجعي لمنع إلغاء تحميل الوحدة. يعد هذا أمرًا جيدًا للوحدات النمطية العادية، ولكنه سيئ للوحدات "التعليمية".

/* syscall.c * * نموذج "سرقة" استدعاء النظام */ /* حقوق الطبع والنشر (C) 1998-99 بواسطة Ori Pomerantz */ /* ملفات الرأس الضرورية */ /* المعيار في وحدات kernel */ #include /* نحن نقوم بعمل النواة */ #include /* وحدة على وجه التحديد */ /* التعامل مع CONFIG_MODVERSIONS */ #if CONFIG_MODVERSIONS==1 #define MODVERSIONS #include #endif #تشمل /* قائمة استدعاءات النظام */ /* بالنسبة للبنية (العملية) الحالية، نحتاج إلى * هذا لمعرفة من هو المستخدم الحالي. */ #يشمل /* في 2.2.3 /usr/include/linux/version.h يتضمن ماكرو * لهذا، ولكن 2.0.35 لا يفعل ذلك - لذلك أقوم بإضافته * هنا إذا لزم الأمر */ #ifndef KERNEL_VERSION #define KERNEL_VERSION(a ,b,c) ((a)*65536+(b)*256+(c)) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) #include #endif /* جدول استدعاء النظام (جدول الوظائف). نحن * فقط نعرّف هذا على أنه خارجي، وسوف تملأه النواة لنا عندما نكون insmod"ed */ extern void *sys_call_table; /* UID الذي نريد التجسس عليه - سيتم ملؤه من * سطر الأوامر */ int uid #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) MODULE_PARM(uid, "i"); #endif /* مؤشر لاستدعاء النظام الأصلي * نحتفظ بهذا، بدلاً من استدعاء الوظيفة الأصلية. (sys_open)، لأن شخصًا آخر ربما قام * باستبدال استدعاء النظام الذي أمامنا. لاحظ أن هذا * ليس آمنًا بنسبة 100%، لأنه إذا تم استبدال وحدة أخرى * بـ sys_open أمامنا، فعند إدراجنا * سنستدعي وظيفة في تلك الوحدة - وقد تتم إزالتها قبل أن نقوم بذلك * * سبب آخر لذلك هو أننا لا نستطيع الحصول على sys_open. * إنه متغير ثابت، لذلك لا يتم تصديره. */ asmlinkage int (*original_call)(const char *, int, int); /* لسبب ما، في 2.2.3 current->uid أعطاني * صفر، ليس معرف المستخدم الحقيقي، لقد حاولت العثور على الخطأ الذي حدث، لكنني لم أتمكن من القيام بذلك في وقت قصير، وأنا كسول - لذا سأستخدم استدعاء النظام للحصول على معرف المستخدم *. الطريقة التي ستتم بها العملية. * * لسبب ما، اختفت هذه المشكلة بعد أن قمت بإعادة ترجمة النواة. */ asmlinkage int (*getuid_call)(); /* الوظيفة التي سنستبدل بها sys_open (الوظيفة * التي يتم استدعاؤها عند استدعاء استدعاء النظام المفتوح) بها. * للعثور على النموذج الأولي الدقيق، مع عدد ونوع * الوسائط، نجد الوظيفة الأصلية أولاً * (it" الصورة في خس/open.c). * * من الناحية النظرية، يعني هذا أننا مرتبطون بالإصدار * الحالي من النواة. ومن الناحية العملية، لا تتغير استدعاءات النظام * تقريبًا (قد يؤدي ذلك إلى تدمير * ويتطلب إعادة ترجمة البرامج، نظرًا لأن استدعاءات النظام * الواجهة بين النواة والعمليات *). */ asmlinkage intour_sys_open(const char *filename, int flags, int mode) ( int i = 0; char ch; /* تحقق مما إذا كان هذا هو المستخدم الذي نتجسس عليه. */ if (uid == getuid_call()) ( /* getuid_call هو استدعاء نظام getuid، * الذي يعطي معرف المستخدم الذي * قام بتشغيل العملية التي استدعت النظام * المكالمة التي تلقيناها */ /* الإبلاغ عن الملف، إذا كان ذلك مناسبًا */ printk("تم فتح الملف بواسطة %d:"، uid do ( #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) get_user(ch, filename+i); #else ch = get_user(filename+ i) ); #endif i++; printk("%c, ch); while (ch != 0); printk("\n"); return original_call(filename, flags, mode); الوحدة النمطية - استبدل استدعاء النظام */ int init_module() ( /* تحذير - لقد فات الأوان لذلك الآن، ولكن ربما * في المرة القادمة. .. */ printk("أنا" خطير. أتمنى أن تكون قد قمت بذلك "); printk("sync before you insmod"ed me.\n"); printk("نظيري، cleanup_module()، متساوي"); printk("أكثر خطورة. إذا\n"); printk("أنت تقدر قيمة الخاص بك نظام الملفات، سوف "); printk("be \"sync; rmmod\" \n"); printk("عند إزالة هذه الوحدة.\n"); /* احتفظ بمؤشر إلى الوظيفة الأصلية في * original_call، و ثم استبدل استدعاء النظام * في جدول استدعاء النظام بـour_sys_open */ original_call = sys_call_table[__NR_open]; .*/ printk("التجسس على UID:%d\n", uid); /* الحصول على استدعاء النظام لـ getuid */ getuid_call = sys_call_table[__NR_getuid]; return 0; يعود استدعاء النظام إلى الوضع الطبيعي */ if (sys_call_table[__NR_open] !=our_sys_open) ( printk("شخص آخر لعب أيضًا مع "); printk("open system call\n "); printk("قد يتم ترك النظام in "); printk("حالة غير مستقرة.\n"); ) sys_call_table[__NR_open] = original_call; )