PDA

View Full Version : اجرای یک متد یا تابع از داخل یک کتابخونه



oxygenws
پنج شنبه 15 آذر 1386, 09:42 صبح
سلام،

توی لینوکس چطوری میشه یه تابع یا متد یک کلاس رو از داخل یک کتابخونه (so) رو از طریق خط فرمان صدا زد؟

شاید یه چیزی شبیه rundll32 توی ویندوز.

ممنون.

illegalyasync
جمعه 16 آذر 1386, 13:34 عصر
dtrace مال اینکار نیست اما میشه چنین کاری رو باهاش کرد....

Bayazee
جمعه 16 آذر 1386, 22:44 عصر
سلام
من تو اینجور موارد از یک اسکریپت ساده استفاده می کنم . پایتون روش بسیار آسونی در اختیار قرار می ده .


from ctypes import *
lib = CDLL('someLib.so')
lib.Func(param)

ماژول ctypes امکانات بسیار زیاد و جالبی داره . توی ویندوز ، لینوکس ، مک و ... هم قابل استفاده هست . بخوبی با انواع کتابخانه کار می کنه .
اینم یک ویژگی جالب دیگه !


>>> from ctypes.util import find_library
>>> find_library('spread')
'/usr/local/lib/libspread.dylib'

این مثال آخری از این سایت بود که یک توضیح بسیار جالب و مفید و مختصر داده :
http://trizpug.ninemoreminutes.com/ctypes.html

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

oxygenws
جمعه 16 آذر 1386, 22:54 عصر
ممنون. نه من می خوام اگه امکانش باشه با خط فرمان این کار رو انجام بدم.
نمی شه راه مستقیمی نباشه. (البته چیزی که دنبالشم هم زیاد مستقیم نیست!!)

rasool_brn
شنبه 17 آذر 1386, 10:07 صبح
ببینید این مقاله کمکتون می کنه :

شل کد نویسی در لینوکس
شل کد ، در حقیقت قطعه کدی هست که به صورت غیر استاندارد نوشته ، در حافظه تزریق شده و اجرا میشه . وقتی ما با یک زبان سطح بالا مثل C برنامه می نویسیم و اون رو به زبان ماشین ترجمه می کنیم ، جزییات کار ، مثل مدیریت حافظه و آدرس دهی ، توسط کامپایلر مدیریت می شه . اما در شل کد نویسی ، انجام این کارها به عهده ما هست و این ، کار رو جذاب می کنه . روش کار معمولاً به این شکل هست که کاری رو که باید شل کد انجام بده ، به صورت یک برنامه به زبان C نوشته و بعد اون رو با یک دیباگر آنالیز می کنیم تا بفهمیم که کد نوشته شده ، هنگام نزدیک شدن به زبان ماشین به چه شکل اجرا میشه . اما من این روش رو نمی پسندم . چون آنالیز دقیق کدهای اسمبلی ، خیلی کسالت آوره . نکته مهم اینه که ما در شل کد ، فقط به قسمتی از کد احتیاج داریم که یک تابع رو به همراه آرگومانهاش فرا خوانی و اجرا می کنه و اگر اصول انجام این کار رو بلد باشیم ، نوشتن شل کد بسیار راحت خواهد بود .

خب ، اولین نکته این هست که بدونیم تابع مورد نظر ما ، چه ساختار دستوری داره . برای اینکار از دستور man در لینوکس استفاده می کنیم . یکی از تابع های پر کاربرد ، تابع execve هست که بوسیله اون می تونیم دستورات مختلف رو اجرا کنیم . بنابرین در کنسول تایپ می کنیم :

man execve

و جواب می گیریم :

http://www.imenafza.net/pics/shellcode1/execven.jpg



اگر چه به نظر می رسه که این دستور گرامر سختی داشته باشه ، اما در واقع بسیار راحت تر از اونی هست که فکر کنید . ( مخصوصاً وقتی به اسمبلی اون رو می نویسیم )
قبل از شروع نوشتن شل کد مورد نظر خودمون به اسمبلی ، 4 تا نکته مهم رو باید در نظر داشته باشیم :

اول اینکه همونطور که قطعاً می دونید ، شل کد ما نباید دارای Null Byte باشه . یعنی نباید صفر در رشته تولید شده نهایی وجود داشته باشه .

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

سوم اینکه همونطور که مطمئن هستم می دونید ، حافظه همیشه رو به پایین رشد پیدا می کنه و به قول معروف از سیستم LIFO استفاده می کنه . یعنی آخرین ورودی ، اولین خروجی هست . پس وقتی یک تابع می خواد اجرا بشه و احتیاج به دریافت اولین آرگومانش داره ، این آرگومان باید بالای حافظه باشه تا به عنوان اولین آرگومان قابل دسترس باشه ، پس باید آخر از همه وارد حافظه شده باشه . این مسئله باعث می شه که ما مجبور باشیم آرگومانها رو به صورت بر عکس وارد حافظه کنیم تا به صورت درست قابل دسترس باشند .

و نکته آخر اینکه شل کد باید تا اونجا که میشه ، کوتاه باشه .

اما راهکار های این 4 نکته :

برای حل مشکل Null Byte معمولاً از دستور XOR در اسمبلی استفاده میشه . اعمال این دستور بر روی یک متغیر یا رجیستر باعث میشه که مقدار اون صفر بشه ، بدون اینکه از عدد صفر استفاده شده باشه .

برای حل مشکل آدرس دهی 2 راه وجود داره . راه اول استفاده از حقه فراخوانی و پرش هست که به Call and Jump trick معروف هست . در این روش ابتدا یک پرش کوتاه به یک قطعه کد دیگه انجام می شه و بعد اون قطعه کد جدید ، کد اصلی شل کد رو فراخوانی میکنه . به این ترتیب مشکل آدرس دهی حل میشه . این روش بدین شکل اعمال میشه :


jmp short callit
doit:
[ shellcode start] ….
.
.
.
[shellcode end]

callit:
call doit
dbهمونطور که می بینید ، ابتدا یک پرش به callit انجام شده و بعد callit قطعه کد doit رو که کد شل کد در اون قرار داره رو فراخوانی کرده . مشکل این روش اینه که شل کدهای ایجاد شده به این روش ، بزرگتر از شل کدهای روش دوم هستند .
اما روش دوم برای حل مشکل آدرس دهی ، استفاده از ورود مستقیم آرگومانها به حافظه هست . یعنی خود شل کد ، هنگام تزریق شدن به حافظه ، آرگومانهای مورد نیازش رو هم مستقیماً با دستور push وارد حافظه می کنه . این روش شل کدهای کوچکتری تولید می کنه ، ولی مشکلش فقط در این هست که باید در وارد کردن رشته ها به عنوان آرگومان ، دقت لازم بشه که رشته ها به درستی وارد حافظه بشند . چون باید به hex تبدیل شده و به صورت بر عکس وارد حافظه بشند .

در مورد کوتاه بودن شل کد هم ، من توصیه می کنم که دستوراتی که در مثالها آورده میشه رو به همون صورت یاد بگیرید و خودتون رو درگیر یادگیری جزییات اسمبلی نکنید .

خب ، حالا فرض کنید قصد داریم یک شل کد لوکال بنویسیم که یک خط فرمان در اختیار ما قرار بده . بنبابرین یک نگاه دوباره به دستور execve می کنیم تا شکل صحیح ورود آرگومانها و اشاره گر ها به اون آرگومانها رو متوجه بشیم . ما باید به وسیله تابع execve ، دستور bin/sh / رو در لینوکس اجرا کنیم . اگر مستقیماً در خط فرمان bin/sh / رو اجرا کنیم ، به شل می رسیم و احتیاج به آرگومانی نداریم . اما در تابع execve احتیاج به 2 تا آرگومان هست . در حقیقت تابع execve به این شکل اجرا می شود :


execve( pointer to /bin/sh0 , { pointer to /bin/sh0 , 0 } , 0) دقت کنید که باید به انتهای رشته bin/sh / یک صفر اضافه بشه تا انتهای این رشته مشخص بشه و باید مواظب این صفر بود که ما رو دچار Null Byte نکنه . و همینطور 2 تا صفر دیگه وجود داره . ( یعنی 3 تا صفر داریم که باید مراقبشون باشیم )

خب ، ابتدا این شل کد رو به روش قراردهی مستقیم آرگومانها در حافظه می نویسیم . به عنوان یک اصل کلی ابتدا صفر های مورد نظر رو ، با اجرای دستور زیر آماده می کنیم :

توجه مهم : ما در تمام مثال ها و تمرینات از intel syntax استفاده می کنیم .


xor eax,eax با اجرای این دستور مقدار eax برابر صفر می شه و ما می تونیم از اون در جایی که احتیاج به صفر داریم استفاده کنیم .
حالا با اجرای دستور cdq ، مقدار رجیستر edx رو هم برابر صفر قرار میدیم . این به عنوان آرگومان سوم تابع execve که صفر هست در نظر گرفته میشه .

حالا رشتهbin/sh0 / رو وارد حافظه می کنیم تا بتونیم در آرگومان بعدی به اون اشاره کنیم . اینجا یک نکته وجود داره و اون هم اینه که ما نمی تونیم صفر آخر رشته bin/sh0 / رو به همراه رشته وارد کنیم ، چون دچار null byte میشیم . پس مجبوریم که از رجیستر eax که قبلاً اون رو تبدیل به صفر کردیم استفاده کنیم . در این صورت باید eax رو به صورت جداگانه با یک خط دستور جدا وارد حافظه کنیم و این باعث میشه که طول رشتهbin/sh / هفت بایت بشه که خودش تولید مشکل می کنه و باعث ایجاد یک null byte دیگه می شه . علتش هم اینه که فضایی که اختصاص داده می شه باید 8 بایت باشه و اون یه بایت که زیاد میاد ، با صفر پر می شه . برای رفع این مشکل هم ما رشتهbin/sh / رو به صورتbin//sh / وارد می کنیم که تاثیری در اجرای دستور نداره . این رو می تونید از خط فرمان لینوکس هم تست کنید .

بنابرین تمام این مراحل به شکل زیر نوشته میشه :


push eax
push long 0x68732f2f
push long 0x6e69622f حتماً متوجه شدید که ما این رشته رو به صورت/ hs//nib وارد حافظه کردیم . یعنی بر عکسش کردیم .
حالا باید یک اشاره گر به این رشته بسازیم که بتونیم در آرگومان دوم ، به اون اشاره کنیم . برای اینکار مقدار رجیستر esp رو که به حافظه اشاره می کنه رو در رجیستر ebx قرار میدیم . یعنی :


mov ebx , esp حالا باید آرگومان دوم تابع execve رو در حافظه قرار بدیم و یک اشاره گر به اون درست کنیم که با توجه به روش بالا ، کار راحتی هست .

اول صفر رو در پشته قرار میدیم ( eax ) ، بعد اشاره گر به رشته دستورbin/sh / رو (ebx ) و در نهایت یک اشاره گر به کل این آرایه رو در ecx قرار میدیم . به شکل زیر :

push eax
push ebx
mov ecx , esp حالا نوبت آرگومان اول تابع هست . یعنی اشاره گر به رشته bin/sh / . خب ما قبلاً این اشاره گر رو درست کردیم (ebx) ، و چون آخرین ورودی ما به حافظه push ebx بود ، دیگه احتیاجی به وارد کردن این اشاره گر به حافظه نداریم .

حالا حافظه آماده اجرا شدن هست . باید یک وقفه انجام بدیم و از cpu بخوایم که تابع رو اجرا کنه . برای اینکار از System Call ها باید استفاده کنیم .

سیستم عامل لینوکس چون کد باز هست و برنامه نویسان در سراسر دنیا بر روی هسته ی اون کار می کنند ، برای اجرای توابع مورد نظر ، از یک سری system call ثابت استفاده می کنه که در فایلusr/include/asm/unistd.h / قابل دیدن هست . برای هر تابع یک شماره خاص به عنوان شماره syscall در نظر گرفته شده که باید برای اجرای توابع از اون شماره ها استفاده کنیم . در هنگام ساخت شل کدهای remote هم از این syscall ها استفاده می کنیم .
بد نیست که این اشاره رو اینجا داشته باشم که عمده تفاوت نحوه شل کد نویسی در لینوکس و ویندوز به مسئله syscall ها مربوط می شه . ویندوز به علت ماهیت تجاری خودش اطلاعات مربوط به فراخوانی توابع رو به صورت مستقیم و قابل دسترس برای همه قرار نداده و آدرس توابع داخلی هسته سیستم عامل خودش رو در نسخه های متفاوت و سرویس پک های مختلف ، تغییر میده که اینکار ، شل کد نویسی رو در ویندوز جذاب تر کرده که در هنگام تدریس شل کد نویسی در ویندوز مفصلاً به اون می پردازیم .

خب ، بر میگردیم به شل کد خودمون . حالا شماره syscall مربوط به تابع execve رو در که عدد 11 هست و به hex میشه 0x0b رو در رجیستر al قرار میدیم و با ایجاد یک وقفه ، cpu رو مجبور به اجرای تابع execve با آرگومانهای تنظیم شده می کنیم . به شکل زیر :


mov al , 0x0b
int 0x80 حالا باید کدهای اسمبلی رو در کنار هم قرار بدیم و نتیجه رو تست کنیم . برای این کار در لینوکس یک editor باز کرده و کدهای ذیل رو وارد می کنیم .

BITS 32
xor eax,eax
cdq
push eax
push long 0x68732f2f
push long 0x6e69622f
mov ebx , esp
push eax
puch ebx
mov ecx , esp
mov al , 0x0b
int 0x80 حالا این فایل رو تحت یک نام مثل shellcode1.s ذخیره می کنیم . سپس از خط فرمان لینوکس تایپ می کنیم :



nasm – o shellcode1 shellcode1.s

در این مرحله ما احتیاج به برنامه ای داریم که بتونیم شل کد خودمون رو ببینیم و تست کنیم . برای اینکار از برنامه
s-proc استفاده می کنیم که سورس اون پیوست این مقاله هست و باید اون رو کامپایل کنید .
برای اینکه شل کد رو ببینیم در خط فرمان به این شکل عمل می کنیم :



http://www.imenafza.net/pics/shellcode1/shellcode1-1n.jpg



برای اجرای شل کد هم تایپ می کنیم :


http://www.imenafza.net/pics/shellcode1/shellcode1-2n.jpg


اگر مثال را بدرستی انجام داده باشید ، باید بتونید تمرین زیر رو حل کنید .

شل کدی بنویسید که یک کاربر در گروه root در سیستم ایجاد نماید .
راهنمایی :
ابتدا مسیر فایل useradd رو پیدا کنید و به عنوان رشته از مسیر کامل اون استفاده کنید . برای ساخت اشاره گر به آرگومانها هم از رجیستر های عمومی مثل eax , ebx , ecx ,esi و ... استفاده کنید .

منبع (http://www.imenafza.net/showthread.php?t=10)

oxygenws
شنبه 17 آذر 1386, 12:02 عصر
ممنونم :)
نه، این هم برنامه نویسی شد که :(

Inprise
شنبه 17 آذر 1386, 12:33 عصر
نه . طبیعی است که فقط به کمک خط فرمان نمیتونی چنین کاری رو انجام بدی

oxygenws
شنبه 17 آذر 1386, 12:38 عصر
نه . طبیعی است که فقط به کمک خط فرمان نمیتونی چنین کاری رو انجام بدی

مگه rundll32 کار مشابهی رو انجام نمی ده؟
فرض کنید یک کتابخونهء ساده، که فقط یک عمل خاص رو انجام میده و تموم میشه. (مسلما هر کتابخونه ای نمی شه این شکلی باشه)

یا مثلا همونطور که دوستمون گفت، توی پیتون هم همچین چیزی هست. اما همون خطوط سادهء داخل پیتون، با کمک چند فرمان بیاد توی ترمینال.

- این کار از نظر منطقی مشکلی داره؟
- rundll32 همین کار رو نمی تونه انجام بده؟

Inprise
شنبه 17 آذر 1386, 12:54 عصر
rundll32 هم چنین کاری رو انجام نمیده . با اینکه بی ربط هست اما : این برنامه فقط توابع خاصی از سیستم عامل که دارای پروتو تایپ کاملا مشخصی هستن رو فراخوانی و اجرا میکنه .

برای اینکه بتونی از تابعی داخل یه کتابخانه استفاده کنی نمیتونی از روی دیسک صداش کنی . تابع باید در حافظه باشه و تو باید آرگومانها و بازگشتی هاش رو بدونی . کتابخانه باید Map و Relocate بشه و Import هاش Resolve‌شده باشه که هر کدوم از اینها یه نیازمندی هست . اگه میتونی اسکریپت اجرا کنی طبیعتا میتونی با زبون موردت علاقت -- مثلا همون پایتون -- چنین کاری رو انجام بدی اما نه با خط فرمان چون هیچ برنامه ای نمیتونه از قبل حدس بزنه که پروتوتایپ توابع تو در so. ات چطور هست . ممکنه خودت بتونی کدی بنویسی که نام کتابخانه رو بگیره و آرگومانها و ... و بعد اول Import هاش رو ترجمه کنه و نهایتا تابع مورد نظر رو اجرا کنه اما حتی چنین برنامه فرضی هم نمیتونه General باشه چون خیلی وقتها بعضی از توابع نیاز به وجود یه سری پوینتر یا استراکت از قبل دارن که باید اونها هم ایجاد بشه که نیاز به اجرای توابع دیگری هست و ...الخ . در کل این مسئله به دلیل وجود فاکتورهای متعدد جوری نیست که با یه دستور از خط فرمان عملی باشه

oxygenws
شنبه 17 آذر 1386, 13:23 عصر
ممنونم :)

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

اگر فرض کنیم بشه، هیچ راهی برای فراخوانی اونها از طریق خط فرمان نداریم؟! یا اینکه برنامه ای باشه که این کار رو انجام بده. (مثلا بیاد اسم کتابخونه، سپس اسم متد و مقادیر ورودی رو بگیره و خروجی تابع رو بفرسته به خروجی ترمینال (مسلما به شرطی که خروجی تابع یک «شی» یا ... نباشه))

Inprise
شنبه 17 آذر 1386, 13:48 عصر
نه هیچ راه کلی ای وجود نداره . باید خودت بر اساس شرایط کدت رو بنویسی .

هیچ وقت مسئله به این سادگی که الان مثال میزنم نیست اما در ساده ترین حالت : فرض میکنیم که library.so یک تابع اکسپورت شده داره بنام sum که دو تا int میگیره و یک int برمیگردونه . برای اینکه کسی بتونه از sum استفاده کنه :

الف. باید library.so در حافظه باشه یعنی یکی dlopen اش کرده باشه . چه با سی چه با پایتون یا هر چیز دیگری . بهر حال سیستم کال سیستم عامل باید صدا زده بشه . اگر یک برنامه یا اسکریپت اینکار رو انجام بده این کتابخانه در فضای آدرسی اون پروسه ، یا فضای آدرسی مترجم اون اسکریپت Load خواهد شد و یک اشاره گر به عنوان هندل خود کتابخانه که در واقع Base Address اش در حافظه هم هست برمیگردونه .
ب. وقتی این کتابخانه به حافظه فراخوانی میشه قسمت Import Table اش خونده میشه . موقع لینک شدن این کتابخونه بر اساس کامپایلر و لینکر و توابعی که مورد استفاده قرار گرفتن یه سری کتابخانه و API دیگه بهش لینک شدن که توابع اونها مورد استفاده قرار گرفته و اول باید آدرسشون مشخص بشه . بنابراین اونها هم یا باید از قبل Load شده باشن و این بار برای استفاده در این پروسه Map بشن یا اگر Load نشدن اونها هم Load بشن و آدرس محل اون توابع جایگزین بشه ( so یا در کل هر کدوم از باینریهات رو ldd کن که ببینی به چیا وابسته است )

ج. یه سری روش برای دسترسی به هندل کتابخانه ها در فضای آدرسی بقیه پروسه ها هم هست که اینجا برای اختصار ازشون میگذرم
د. بعدش باید یک اشاره گر به محل sum وجود داشته باشه . پس sum باید dlsym بشه که هندلش برگرده و مقدار هندل در واقع محل قرار گرفتن sum هم هست .
ه. حالا با استفاده از این اشاره گر میشه با sum کار کرد . یعنی با این "پیش فرض" که تو میدونی باید دو تا int بهش بدی و یک int ازش بگیری تازه الان این تابع قابل استفاده است . اگر آرگومانها پیچیده تر باشه ، یا تابع خودش اشاره گری به تابع دیگری باشه ، یا شیء ای رو برگردونه سناریو خیلی پیچیده تر میشه

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

Inprise
شنبه 17 آذر 1386, 13:52 عصر
dtrace مال اینکار نیست اما میشه چنین کاری رو باهاش کرد....

تو با dtrace میتونی اسکریپت بنویسی که با فرض علم و اطلاع از پروتوتایپ تابع با مفروضاتی که در دو پست قبلی گفتم تابعی صدا زده بشه . اما چه سود ؟ اگر قرار است که کد نوشته بشه طبیعتا با هر زبان و کامپایلری میشه اینکار رو انجام داد و اگر هدف ننوشتن کد باشه نشدنی بودنش همیشه یه جور هست .

oxygenws
شنبه 17 آذر 1386, 14:02 عصر
ممنون به خاطر توضیحاتت :)

راستش در موارد الف و ب و د، خوب همون کامندی که صدا می زنم، خودش می تونه همین کارا رو انجام بده، یعنی اون کتابخونه ای که آدرس دادم و متعلقاتش رو بیاره تو حافظه و لینکش رو تولید کنه.

در مورد ه هم، آره، مسلما می دونم که ورودی ها و خروجی (ها) چی هستند. در مورد «شی» و «آرایه» و غیره هم تا حدودی می تونه serialize کردن یا احیانا JSON مفید و قابل استفاده باشه.

صورت مسئله چیز خاصی نیست، دقیقا همین که می خواستم یک تابع از یک کتابخونه رو از خط فرمان صدا بزنم، مثلا فرض کنید برنامهء imagick از کتابخونه های خودش استفاده می کنه و با خط فرمان میشه هر کاری که اون توایع انجام می دن رو انجام داد. البته می دونم که این خیلی فرق داره، اما من می خوام یه همچین کاری رو برای کتابخونه هایی که CLI براشون نیست انجام بدم.

باز هم ممنونم. :)

Inprise
شنبه 17 آذر 1386, 14:14 عصر
اما من می خوام یه همچین کاری رو برای کتابخونه هایی که CLI براشون نیست انجام بدم.

مثلا ؟

oxygenws
شنبه 17 آذر 1386, 14:31 عصر
مثلا ؟
مثلا GD یا purple یا ...