شرح Static Linking و Dynamic Linking و Function Hooking في C
السلام عليكم ورحمة الله وبركاته
--------------------
الحمد لله على جميع نِعمه علينا ما علمنا منها وما لم نعلم حمداً والصلاة والسلام على نبينا محمد اشرف الخلق والمرسلين, اما بعد :-
--------------------
# الفرق بين Static Linking و Dynamic Linking :-
قديما كانت البرامج عبارة عن statically linked وقد تتسائل ماذا يعني هذا ؟ هذا يعني أن كل المكتبات كانت compiled (أي يتم تحميلها في الملف التنفيذي) مع الملف التنفيذي وهذا مايفسر حجمها الكبير أي في الواقع هي لايتم استدعاءها كما نعرف نحن بل تكون compailed مع الملف التنفيذي, كانت الميزة المذكوره هي أنها تتخلص من مشاكل استدعاء المكاتب وعيوبها كانت حجمها الكبير وتخيل معي لو قام المطور بتحديث المكتبة ف أنت تحتاج إلى تغيير البرنامج كاملا !!!!
لكن الآن الغالبية العضمى تكون البرامج dynamically linked وماذا يعني هذا ؟ يعني اننا تخلصنا وبشكل كبير من مشاكل البرامج التي كانت statically linked حيث يقوم dynamic linker وهي عبارة عن أداة بتحميل وربط المكتبات اللازمة في الملف التنفيذي لكن ماذا اختلف ؟ اختلف ياعزيزي ان مهمه dynamic linker تتم وقت تشغيل البرنامج (run time) وليس compile time.
والآن سوف نطبق لنرى ماهي برامج dynamically linked و statically linked, لدينا هذا الكود :
#include <stdio.h>
void main() {
printf("1337r00t\n");
}
نرى في الكود استدعاء لملف header ألا وهو stdio.h و هو عباره عن C Standard Library أي بمعنى انه أساسي مثلا مكتبة ctypes او re في Python, لنقوم الآن بجعل البرنامح الذي كتبناه dynamically linked ببساطه عن طريق الأمر المتعارف عليه في compilation للغة C عن طريق gcc compiler :
gcc root.c -o root
سيقوم gcc compiler تلقائيا بجعل البرنامج dynamically linked وتقدر تعرف هالشيء عن طريق أمر readelf
https://linux.die.net/man/1/readelf *
abo@rakan $: readelf -h root
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2\'s complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
يهمك type انت لكي تتعرف ما إذا كان برنامج dynamically linked (لو كان type هو DYN فهذا يعني ان البرنامج dynamically linked) او statically linked
وإذا أردت معرفة ماهي dynamic libraries المستدعاه استخدم امر ldd
abo@rakan $: ldd root
linux-gate.so.1 (0xb7f49000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7d5c000)
/lib/ld-linux.so.2 (0xb7f4b000)
نستطيع رؤية system calls المستدعاه في البرنامج و arguments الذي تم ادخالها مع القيم الممرره فيها في system calls عن طريق اداة او أمر strace
http://man7.org/linux/man-pages/man1/strace.1.html
يهمنا نرى openat()
abo@rakan $: strace ./root
execve("./root", ["./root"], 0xbfd19880 /* 21 vars */) = 0
brk(NULL) = 0x950000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fb0000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=19114, ...}) = 0
mmap2(NULL, 19114, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fab000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300\254\1\0004\0\0\0"..., 512) = 512
سوف نرى :
openat(AT_FDCWD, "/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_LARGEFILE|O_CLOEXEC)
يستدعي C Standard Library بشكل dynamically.
الآن مع الجزئية التطبيقيه مع Static Linking وكيف يكون البرنامج statically linked وكيف نتعرف عليه ببساطه كالآتي (نفس الكود السابق) :-
gcc -static root.c -o root
نفذ الآن أمر ldd وكالآتي
abo@rakan $: ldd root
not a dynamic executable
وهكذا تعرفنا على ان البرنامج statically linked ولو نفذت أمر readelf ستلاحظ ان type هو EXEC (Executable file) ولو طبقت أمر strace سوف تلاحظ انه قام بتحميل المكتبات في البرنامج نفسه بشكل statically.
انتهينا اللآن من شرح جزئية Static Linking و Dynamic Linking والفرق بينهم.
--------------------
# شرح متغير LD_PRELOAD :-
هو ببساطه environment variable لديه القدرة على تغيير سلوك او تغيير عمل دوال او symbols في البرنامج قبل استدعاء بقية المكتبات عن طريق تمرير اسم ملف shared object (.so) الذي تريد استدعاءه قبل بقية المكتبات في متغير LD_PRELOAD, أي بالمعنى الأصح تستطيع اعادة كتابة دالة puts التي تعتبر c standard function بطريقتك الخاصة وهذا ماسوف نشرحه عمليا في النقطة القادمة .
---------------------
# شرح Function Hooking عن طريق LD_PRELOAD :-
لنرجع قليلا إلى الوراء في الجزئية العملية لبرنامج dynamically linked عندما نفذنا امر strace وظهر لنا
abo@rakan $: strace ./root
execve("./root", ["./root"], 0xbfd19880 /* 21 vars */) = 0
brk(NULL) = 0x950000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fb0000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=19114, ...}) = 0
mmap2(NULL, 19114, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fab000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300\254\1\0004\0\0\0"..., 512) = 512
ستلاحظ كما قلنا قام بأستدعاء C Standard Library في السطر الحادي عشر لكن مايثبت كلامنا سابقا في نقطة (# شرح متغير LD_PRELOAD :-) في السطر السادس:
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
حيث قام بإستدعاء قيمة متغير LD_PRELOAD لكن لم يجدها لكن كما ترى ان LD_PRELOAD يتم استدعاءه قبل كل المكتبات, والآن سوف نغير سلوك دالة puts وهي تعتبر دالة أساسية في لغة C وسوف نقوم بكتابة الكود التالي اولا :-
#include <stdio.h>
void main() {
puts("Hi");
}
من الطبيعي عندما نقوم بعمل compilation له وتشغيله سوف يطبع Hi لكن سوف نغير سلوك او عمل دالة puts بحيث تطبع لنا مثلا Hooked بدون تغيير كود root.c عن طريق LD_PRELOAD وهذا ماسنقوم به الآن سوف نكتب shared object :-
#include <stdio.h>
int puts(const char *str) {
printf("Hooked");
return 0;
}
نقوم بعمل compile له:-
gcc root.c -o root.so -fPIC -shared -ldl
سوف يسلمنا ملف shared object وسوف نقوم بتمريره إلى LD_PRELOAD environment variable وسوف تلاحظ انه طبعا Hooked بدلا من Hi
# Before using LD_PRELOAD
abo@rakan $: ./root
Hi
# After using LD_PRELOAD
abo@rakan $: LD_PRELOAD="./root.so" ./root
Hooked
وهذا هو Function Hooking في Linux (C) Run time :)
ملحوظات :
- من الممكن يتم استخدام هالتقنيات في تخطي Anti-debugging وأيضا مشهورة في CTFs Challenges
- للحماية من هذه trick يجب أن تعرف ان loader سوف يتجاهل LD_PRELOAD لو كانت ruid != euid :)
- تستطيع تطبقيها حتى على الأوامر مثلا على أمر ls
LD_PRELOAD="./test.so" /bin/ls
------------------------
والسلام خير ختام
التعليقات (0)
لايوجد لديك حساب في عالم البرمجة؟
تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !