رفتن به مطلب
جامعه‌ی برنامه‌نویسان مُدرن ایران
فرهاد شیری

تغییر کتابخانه استاندارد C استفاده شده در برنامه در زمان اجرا


پست های پیشنهاد شده

وقتی برای سیستم عامل لینوکس برنامه می نویسید باید خیلی عملگرا باشید و کدهایی که می نویسید انعطاف پذیر و قابلیت نگهداری بالایی داشته باشند. بنابراین تکنیکی که قصد معرفی اش را دارم نحوه تغییر کتابخانه استاندارد libc در زمان اجرا می باشد.

تصور کنید که چنین کدی دارید...

#include <stdio.h>
int main(void) {
    puts("Hello, world!");
    return 0;
}

همانطور که مشخص هست از تابع puts استفاده کردم که از کتابخانه های استاندارد c استفاده میکند، بنابراین اگر در آینده قصد داشته باشم که عملکرد تابع puts را تغییر بدم و به سورس اصلی هم دسترسی نداشته باشم، ویا در صورتی که به سورس هم دسترسی داشته باشم ولی امکان تغییر برنامه برای من میسر نباشد!

چاره کار این هست که یک کتابخانه بنویسم که در زمان اجرا بدون اینکه برنامه اصلی متوجه شود از تابع puts که در این کتابخانه نوشته ام استفاده کنم.

بنابراین کد زیر را می نویسم....

#include <stdio.h>
int puts( const char* str ) {
    return printf("We took control over your C library! \n");
}

و با دستورات زیر یک کتابخانه shared object کامپایل میکنم...

> gcc -o preload_launcher preload_launcher.c
> gcc -c -fPIC prelib.c
> gcc -o prelib.so -shared prelib.o

اکنون در صورتی که بخوام از رفتار پیش فرض تابع puts که در کتابخانه استاندارد هست استفاده کنم باید به روش زیر عمل کنم...

> export LD_PRELOAD=
> ./a.out
Hello, world!

همانطور که دید خروجی تابع با استفاده از کتابخانه استاندارد نمایش داده شد.

و اکنون در صورتی که بخوام برنامه از تابع puts من که در کتابخانه prelib.so نوشتم استفاده کنه به روش زیر عمل خواهم کرد...

> export LD_PRELOAD=$PWD/prelib.so
> ./a.out
We took control over your C library!

بنابراین همانطور که مشاهده میکنید خروجی تابع از کتابخانه جدید استفاده کرد.

در دستور بالا PWD$ به شاخه پیش فرض home/yourusername شما اشاره میکند در حقیقت متغیر سیستمی هست.

حالا تصور کنید که یک برنامه safety critical نوشتید و در سازمان مورد نظر هم نصب وراه اندازی شده و دوره های تست راهم پشت سر گذاشته ودر حال کار هست! قطعا درآینده نیاز دارید که عملکرد بخش هایی از این برنامه را تغییر دهید، و البته به راحتی نمی توانید که برنامه را تغییر بدید و مجددا دوره تست زمان اجرا بگیرید به علت های زیادی که در نرم افزارهای حیاتی وجود دارد، بنابراین تغییر کتابخانه های بهترین روش هست(البته حتما باید خود کتابخانه جدید کاملا به صورت کامل مراحل تست را انجام داده باشه)

ویا حتی تصور کنید که با یک نسخه قدیمی از لینوکس برنامه خودتون را اجرا کردید، قطعا میدونید که به روز کردن کتابخانه ها در لینوکس های قدیمی کار خیلی مشکلی هست و هر لحظه امکان داره که کرنل لینوکس از کار بیوفته! برای همین به راحتی نمی تونید کتابخانه ها را کپی کنید و به روز کنید علی الخصوص کتابخانه هایی مثل ldlinux.so , libc.so,libstdc++6.so

ولی در صورتی که به اسکریپت نویسی و دستورات لینوکس اشراف داشته باشید می توانید با همین دستورات مانند export , ld, ldconfig ,... به راحتی برنامه هایی که با کامپایلرهای به روز تر کامپایل میکنید را در کرنل لینوکس های قدیمی اجرا کنید.

 

 

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر
در 15 دقیقه قبل، فرهاد شیری گفته است :

وقتی برای سیستم عامل لینوکس برنامه می نویسید باید خیلی عملگرا باشید و کدهایی که می نویسید انعطاف پذیر و قابلیت نگهداری بالایی داشته باشند. بنابراین تکنیکی که قصد معرفی اش را دارم نحوه تغییر کتابخانه استاندارد libc در زمان اجرا می باشد.

تصور کنید که چنین کدی دارید...


#include <stdio.h>
int main(void) {
    puts("Hello, world!");
    return 0;
}

همانطور که مشخص هست از تابع puts استفاده کردم که از کتابخانه های استاندارد c استفاده میکند، بنابراین اگر در آینده قصد داشته باشم که عملکرد تابع puts را تغییر بدم و به سورس اصلی هم دسترسی نداشته باشم، ویا در صورتی که به سورس هم دسترسی داشته باشم ولی امکان تغییر برنامه برای من میسر نباشد!

چاره کار این هست که یک کتابخانه بنویسم که در زمان اجرا بدون اینکه برنامه اصلی متوجه شود از تابع puts که در این کتابخانه نوشته ام استفاده کنم.

بنابراین کد زیر را می نویسم....


#include <stdio.h>
int puts( const char* str ) {
    return printf("We took control over your C library! \n");
}

و با دستورات زیر یک کتابخانه shared object کامپایل میکنم...


> gcc -o preload_launcher preload_launcher.c
> gcc -c -fPIC prelib.c
> gcc -o prelib.so -shared prelib.o

اکنون در صورتی که بخوام از رفتار پیش فرض تابع puts که در کتابخانه استاندارد هست استفاده کنم باید به روش زیر عمل کنم...


> export LD_PRELOAD=
> ./a.out
Hello, world!

همانطور که دید خروجی تابع با استفاده از کتابخانه استاندارد نمایش داده شد.

و اکنون در صورتی که بخوام برنامه از تابع puts من که در کتابخانه prelib.so نوشتم استفاده کنه به روش زیر عمل خواهم کرد...


> export LD_PRELOAD=$PWD/prelib.so
> ./a.out
We took control over your C library!

بنابراین همانطور که مشاهده میکنید خروجی تابع از کتابخانه جدید استفاده کرد.

در دستور بالا PWD$ به شاخه پیش فرض home/yourusername شما اشاره میکند در حقیقت متغیر سیستمی هست.

حالا تصور کنید که یک برنامه safety critical نوشتید و در سازمان مورد نظر هم نصب وراه اندازی شده و دوره های تست راهم پشت سر گذاشته ودر حال کار هست! قطعا درآینده نیاز دارید که عملکرد بخش هایی از این برنامه را تغییر دهید، و البته به راحتی نمی توانید که برنامه را تغییر بدید و مجددا دوره تست زمان اجرا بگیرید به علت های زیادی که در نرم افزارهای حیاتی وجود دارد، بنابراین تغییر کتابخانه های بهترین روش هست(البته حتما باید خود کتابخانه جدید کاملا به صورت کامل مراحل تست را انجام داده باشه)

ویا حتی تصور کنید که با یک نسخه قدیمی از لینوکس برنامه خودتون را اجرا کردید، قطعا میدونید که به روز کردن کتابخانه ها در لینوکس های قدیمی کار خیلی مشکلی هست و هر لحظه امکان داره که کرنل لینوکس از کار بیوفته! برای همین به راحتی نمی تونید کتابخانه ها را کپی کنید و به روز کنید علی الخصوص کتابخانه هایی مثل ldlinux.so , libc.so,libstdc++6.so

ولی در صورتی که به اسکریپت نویسی و دستورات لینوکس اشراف داشته باشید می توانید با همین دستورات مانند export , ld, ldconfig ,... به راحتی برنامه هایی که با کامپایلرهای به روز تر کامپایل میکنید را در کرنل لینوکس های قدیمی اجرا کنید.

درود بر استاد بزرگ،

آقا به نظرم این نکته‌ای که اشاره کردی بسیار کاربردی می‌تونه باشه (مخصوصاً در کنترل و پایداری توسعه).

امکانش هست مسئله رو کمی کالبد شکافی کنیم؟ برای مثال اجرای چنین کاری در محیط توسعه‌ای مثل، کیوت‌کریتور، اِکس‌کد و یا ویژوال‌ استودیو چطور ممکنه؟ با توجه به اینکه به نسخه‌ی لینوکس اشاره کردی، اگر این تکنیک رو در محیط‌های توسعه معرفی کنی بسیار کاربردی‌تر خواهد بود.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر
در 12 ساعت قبل، کامبیز اسدزاده گفته است :

درود بر استاد بزرگ،

 

متشکرم!

در 12 ساعت قبل، کامبیز اسدزاده گفته است :

 آقا به نظرم این نکته‌ای که اشاره کردی بسیار کاربردی می‌تونه باشه (مخصوصاً در کنترل و پایداری توسعه).

 

دقیقا همینطوره! به نکته ای درستی اشاره کردید.

 خیلی وقتها به علت وسعت نرم افزار و طبعا هرچه نرم افزار stable بشه در محل نصب مشتری همیشه یکی از چالش های بزرگ این هست که بخواهید تغییری در نرم افزار ایجاد کنید و تضمین کنید که تمام تست های رگرسیون هم بدرستی انجام شده باشه! والبته به همین سادگی هم نیست که بتوان تمام بخش های نرم افزار را تست رگرسیون انجام داد، حتی در صورتی که نرم افزار شما به صورت Test Domain Driven طراحی شده باشد به هرحال کار پر زحمت و پر چالشی هست بنابراین اغلب شرکتهای بزرگ تولید کننده نرم افزار همیشه از تغییرات حساس و بزرگ واهمه دارند و کمتر سمت اش میرن. البته منظور من بیشتر نرم افزارهای کاربردی و حیاتی هستش!

در 12 ساعت قبل، کامبیز اسدزاده گفته است :

امکانش هست مسئله رو کمی کالبد شکافی کنیم؟ برای مثال اجرای چنین کاری در محیط توسعه‌ای مثل، کیوت‌کریتور، اِکس‌کد و یا ویژوال‌ استودیو چطور ممکنه؟ با توجه به اینکه به نسخه‌ی لینوکس اشاره کردی، اگر این تکنیک رو در محیط‌های توسعه معرفی کنی بسیار کاربردی‌تر خواهد بود.

بذارید سناریویی که برای خود من اتفاق افتاد را توضیح بدم! 

در یکی از لینوکس های ردهت که برای مشتری در سالهای قبل نصب کردیم، نیاز داشتیم که نرم افزارهای مشتری را به روز کنیم تا با برخی از سخت افزارهای جدیدتر شرکت بتونن ارتباط داشته باشند، ولی از آنجائیکه ردهت قدیمی بود تغییر در کتابخانه هایی که در پست اول هم اشاره کردم باعث از کار افتادن کرنل میشد بنابراین اومدم یک اسکریپت نوشتم که قبل از اینکه نرم افزار اصلی اجرا بشه مسیر بعضی از کتابخانه هایی که لازم داشتم را با استفاده از همین روش تغییر دادم و تونستم برنامه اصلی را اجرا کنم البته با استفاده از دستورات ld,ldconfig حتما باید لینک ها لازم را برای کتابخانه ها بسازید.

بنابراین استفاده از تکنیک preloader library در لینوکس مربوط به یک محیط توسعه خاص نیست، کافی است که در زمان کد نویسی اصول پایه ای برنامه نویسی FURPS را رعایت کنید.

پس اگر در کیوت هم که نرم افزار تولید میکنید، باید سعی کنید که به صورت ماژولار برنامه بنویسید و همیشه سعی داشته باشید که از کتابخانه های استاندارد زبان بیشترین بهره را ببرید و تا جایی که امکان داره هسته نرم افزار خودتون را با استانداردها توسعه بدید و از کتابخانه های غیر استاندارد در لایه منطق کمتر استفاده کنید تا وابستگی های کمتری داشته باشید و در زمان های لازم امکان استفاده از چنین تکنیک هایی را داشته باشید. البته به طور قطع به یقین امکان استفاده از چنین تکنیک هایی در لایه های دیگر نرم افزار هم وجود دارده! 

مثلا تصور کنید که یک کلاس خاص طراحی کرده باشید برای مدیریت ارتباطات بین لایه داده ها و لایه بیزینس نرم افزار تون، فرض کنید تا یک نسخه ای از برنامه شما ملزم به استفاده از پایگاه داده SQL SERVER بودید ولی در نسخ جدید نرم افزارتون نیاز دارید که فقط همین لایه ارتباط با پایگاه داده را توسعه بدید و به یک پایگاه داده دیگه مثل اوراکل متصل کنید و سایر قسمتهای دیگه برنامه به راحتی به کار خودشون ادامه بدهند خوب تحت این شرایط چند تا سناریو داریم...

1- آیا از اول امکانش هست که نیاز سنجی دقیقی داشته باشیم که تمامی این فیچرها را در نرم افزار عملیاتی کرده باشیم؟ خوب قطعا خیر تازه در صورتی هم که میشد کار عاقلانه ای نیست که در زمان حال فیچر ها را به نرم افزار تحمیل کنیم و ...

2- آیا امکانش هست که ما کلاس فوق را با تغییرات جدید به روز کنیم؟ شاید بله جواب این سوال باشه! ولی قطعا به نسبت بزرگی نرم افزار و پیچیدگی های اون درصد موفق بودن این کار خیلی کم میتونه باشه و ازهمه بدتر تست های رگرسیون هم زمان و هم انرژی وهم هزینه ای زیادی خواهد داشت پس بنابراین این کار هم شدنی نیست! تقریبا.

3- آیا امکانش هست که من فقط یک کتابخانه برای کاربر ارسال کنم و الباقی تنظیمات و نرم افزار اصلی بدون خللی به کارشون ادامه بدهند وتازه حلقه تست رگرسیون هم خیلی کوچکتر کنم فقط محدود به خود کتابخانه باشه؟ خوب قطعا با روضه هایی که تاحالا خوندم 😂 بله امکانش هست، خیلی راحت در آینده میام یک کتابخانه طراحی میکنم و روش های اتصالات جدید را هم به سیستم اضافه میکنم که هم بتونم به اس کیو ال سرور متصل باشم وهم به اوراکل (البته این پایگاه های داده فقط برای مثال بود)

حالا شاید بپرسید که در ویندوز هم امکان چنین چیزی وجود داره! خوب من شخصا چنین چیزی در ویندوز ندیدم که بتوان مسیر کتابخانه های استاندارد زبان را تغییر داد اونهم به این علت که فلسفه وجودی لینوکس و ویندوز خیلی باهم متفاوت هستند و اصولا ویندوز کمتر اجازه میده که مسیر های کتابخانه های سیستمی را تغییر داد چون قطعا با این کار خود ویندوز هم آسیب پذیر میشه همانطور که لینوکس هم می تونه آسیب ببینه! 

ولی معمولا در ویندوز از DLL ها برای توسعه نرم افزارها استفاده میشود که نحوه ساختن و استفاده از اونها در ویندوز با کتابخانه های Shared , Static کمی متفاوت تر هستش! و اغلب نیازهای نرم افزار را برآورده میکنند.

و در سیستم عامل مک او اس هم به علت اینکه unix base هستش قطعا نحوه استفاده از کتابخانه ها خیلی شبیه به لینوکس هستش البته اگر از clang استفاده کنید وبه زبان سی++ کد بزنید!

برای اطلاعات بیشترهم این مستند Using Dynamic Libraries in Mac OS را مطالعه کنید.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر
در 3 ساعت قبل، فرهاد شیری گفته است :

حالا شاید بپرسید که در ویندوز هم امکان چنین چیزی وجود داره! خوب من شخصا چنین چیزی در ویندوز ندیدم که بتوان مسیر کتابخانه های استاندارد زبان را تغییر داد اونهم به این علت که فلسفه وجودی لینوکس و ویندوز خیلی باهم متفاوت هستند و اصولا ویندوز کمتر اجازه میده که مسیر های کتابخانه های سیستمی را تغییر داد چون قطعا با این کار خود ویندوز هم آسیب پذیر میشه همانطور که لینوکس هم می تونه آسیب ببینه! 

ولی معمولا در ویندوز از DLL ها برای توسعه نرم افزارها استفاده میشود که نحوه ساختن و استفاده از اونها در ویندوز با کتابخانه های Shared , Static کمی متفاوت تر هستش! و اغلب نیازهای نرم افزار را برآورده میکنند.

و در سیستم عامل مک او اس هم به علت اینکه unix base هستش قطعا نحوه استفاده از کتابخانه ها خیلی شبیه به لینوکس هستش البته اگر از clang استفاده کنید وبه زبان سی++ کد بزنید!

برای اطلاعات بیشترهم این مستند Using Dynamic Libraries in Mac OS را مطالعه کنید.

با توجه به این نظری که داری، همونطور که فکرش رو می‌کردم پس بهتره پشتیبانی به صورت چند-سکویی تحت همون API‌های استاندارد پیاده سازی بشه. کاری که همین الآنش هم انجام میدم اینطور هست که برای مثال در ویندوز با توجه به رفتار کامپایلر و همچنین رفتار مشابهش در یونیکس و لینوکس کد‌ها، کتابخانه‌ها و اساس برنامه رو توسعه میدم.

متوجه شدم در کل قضیه از چه قراره! مثال و تجربه‌ی خوبی زدی دمت گرم 😋

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر
در 7 دقیقه قبل، کامبیز اسدزاده گفته است :

ا توجه به این نظری که داری، همونطور که فکرش رو می‌کردم پس بهتره پشتیبانی به صورت چند-سکویی تحت همون API‌های استاندارد پیاده سازی بشه. کاری که همین الآنش هم انجام میدم اینطور هست که برای مثال در ویندوز با توجه به رفتار کامپایلر و همچنین رفتار مشابهش در یونیکس و لینوکس کد‌ها، کتابخانه‌ها و اساس برنامه رو توسعه میدم.

بله منهم موافقم تجربه نشون داده این که می فرمائید درسته و در توسعه محصول خیلی کمک میکنه!

 

در 9 دقیقه قبل، کامبیز اسدزاده گفته است :

متوجه شدم در کل قضیه از چه قراره! مثال و تجربه‌ی خوبی زدی دمت گرم 😋

ارداتمندیم🌹

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

@کامبیز اسدزاده 

بله این یه روش به حساب میاد که در ویندوز هم قابل استفادس. به شکل های مختلف هم قابل استفادس.

یکی پچ کردن مستقیم dll اصلی هست، به صورتی که به مکان کود کیو پرش بزنید اونجا تمام رجیستر هارو پوش بکنید سپس کارتونو که انجام دادید تمام رجیستر ها پاپ شن و به جای اصلی برگردین.(البته باید مکانیزم aslr غیر فعال بشه وگرنه احتمال زیاد تو اجراهای بعدی برنامه دچار مشکل میشه)

راه دیگه هوک کردن با dll هست.

راه دیگه پابلیک هوک کردن api هست که به ندرت(توست ویروس های قدرتمند) استفاده میشه و رفتار api رو برای همه پردازه ها تغییر میده (نمونه بسیار معروفش هم کرم استاکسنت هست). که روش سخت و غیر استانداردیم هست.

البته خودم همشو تست نکردم!

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر
در 9 ساعت قبل، سید عمید قائم مقامی گفته است :

یکی پچ کردن مستقیم dll اصلی هست،

پچ کردن dll چه ربطی به preloader library linker می تونه داشته باشه؟

در 9 ساعت قبل، سید عمید قائم مقامی گفته است :

ه صورتی که به مکان کود کیو پرش بزنید اونجا تمام رجیستر هارو پوش بکنید سپس کارتونو که انجام دادید تمام رجیستر ها پاپ شن و به جای اصلی برگردین.

این مطلبی که دربالا بحث شده هیچ ربطی به تزریق استاتیک کد اسمبلی نداره! و یک سوال دیگه ؟ منظورتون از push کردن تمام رجیسترها چی؟ چطوری میخواهید رجیسترها را push کنید وبعدهم همه را pop  کنید! بنظرم همانطور که کل این مقاله را اشتباه متوجه شدید ظاهرا در برنامه نویسی اسمبلی و روش های پچ کردن و تزریق کد هم دچار اشکال هستید.

در 9 ساعت قبل، سید عمید قائم مقامی گفته است :

راه دیگه هوک کردن با dll هست.

راه دیگه پابلیک هوک کردن api هست که به ندرت(توست ویروس های قدرتمند) استفاده میشه و رفتار api رو برای همه پردازه ها تغییر میده (نمونه بسیار معروفش هم کرم استاکسنت هست). که روش سخت و غیر استانداردیم هست.

به نظرم دوست گرامی بهتره که هر مطلبی که مطالعه میکنیم را بیان نکنیم، مگه با دلیل و منطقی که بتونیم براش مثالی هم بزنیم.

اصلا فلسفه هوک کردن api های ویندوز چیزه دیگه ای هست که همانطور که قبلا گفتم هیچ ربطی به این مقاله نداره!

 

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

مهمان
ارسال پاسخ به این موضوع ...

×   شما در حال چسباندن محتوایی با قالب بندی هستید.   حذف قالب بندی

  تنها استفاده از ۷۵ اموجی مجاز می باشد.

×   لینک شما به صورت اتوماتیک جای گذاری شد.   نمایش به عنوان یک لینک به جای

×   محتوای قبلی شما بازگردانی شد.   پاک کردن محتوای ویرایشگر

×   شما مستقیما نمی توانید تصویر خود را قرار دهید. یا آن را اینجا بارگذاری کنید یا از یک URL قرار دهید.


  • کاربران آنلاین در این صفحه   0 کاربر

    هیچ کاربر عضوی،در حال مشاهده این صفحه نیست.

×
×
  • جدید...