خراب کردن قراردادهای هوشمند برای سرگرمی و سود واقعی

در این مطلب،‌‌ دانش همگانی شماری از پژوهش‌‌های انجام شده روی پلتفرم مایتریل (Mythril) مطرح می‌‌شوند. مایتریل یک پلتفرم آنالیز امنیتی برای قراردادهای هوشمند اتریوم است. در این مقاله‌‌ کنفرانس دانش همگانی، یک ابزار آنالیز امنیتی برای قراردادهای هوشمند اتریوم به نام مایتریل را معرفی می‌‌کنیم و نگاهی به لیزر – اتریوم بک‌‌اِند اجرای نمادین (symbolic execution backend LASER-Ethereum) آن خواهیم داشت. در بخش نخست مقاله، اجرای نمادین بایت‌‌کد اتریوم (Ethereum bytecode) در یک حالت نسبتا رسمی شرح داده می‌‌شود. در بخش دوم نمونه‌‌هایی از ماژول‌‌های شناسایی آسیب‌‌پذیری که تا کنون در مایتریل پیاده شده‌‌اند مورد بحث قرار می‌‌گیرند. این ماژول‌‌ها ترکیب پراگماتیکی (عمل‌‌گرایانه) از آنالیز ایستا، آنالیز نمادین و بررسی جریان کنترلی (CFC) را به کار می‌‌گیرد.

0 94

نخستین سالی که دانشجو بودم مجله‌‌ Phrack را پیدا کردم و به یک متن 1,746 خطی ASCII به نام « خراب کردن پشته برای سرگرمی و سود » برخوردم. کنکاش روی دفاتر ثبت توزیع شده و دنیای اتریوم مرا به یاد آن روزها می‌‌اندازد. بلاکچین‌‌ اتریوم از قرارداد هوشمند پشتیبانی می‌‌کند؛ نوعی برنامه‌‌ی شبه کامل تورینگ (quasi-Turing-complete) که روی یک ماشین مجازی مبتنی بر پشته (stack) اجرا می‌‌شود. با توجه به اینکه از سال 1996 چیز تازه‌‌ی زیادی یاد نگرفته‌‌ایم، بیشتر این قراردادها روی یک زبان برنامه‌‌نویسی توسعه داده شده و در نتیجه راه را برای انواع و اقسام باگ‌‌ها هموار کرده‌‌ است.

پژوهش حاضر به هیچ وجه بی‌‌سابقه و شگرف محسوب نمی‌‌شود ولی امید است بتواند به امن کردن اکوسیستم اتریوم حتی اندکی بیشتر کمک کند. من دسته‌‌کم امیدوارم خواندن این مقاله به اندازه‌‌ی نگارش آن برای شما لذت‌‌بخش باشد.

اگر در سه سال گذشته به طور کامل ارتباط خود را با دنیا قطع نکرده باشید، حتما متوجه صدای پای آمدن یک صنعت تازه شده‌‌اید. صنعتی که چرخ‌‌های « یادگیری ماشینی » را در مسیر پول به حرکت درآورده است. صنعتی به نام بلاکچین‌‌ (Blockchain).

اتریوم (Ethereum) یکی از موفق‌‌ترین نمونه‌‌های پیاده‌‌سازی این مفهوم است. برخلاف بیت کوین (Bitcoin) که امکانات محدودی را ارائه می‌‌دهد، اتریوم از یک ماشین مجازی کامل تورینگ (Turing-complete) برخوردار است. گذار وضعیت در شبکه‌‌ (مانند تغییر مانده حساب یک توکن خاص) به وسیله‌‌ی کدهایی مدیریت می‌‌شود که در ماشین مجازی اجرا می‌‌شوند و نام دیگر این ماشین مجازی قراردادهای هوشمند (smart contracts) است.

یک مثل باستانی در مورد امنیت هست که می‌‌گوید: « انعطاف‌‌پذیری فراوان با احتمال آسیب‌‌پذیری فراوان همراه است ». زبان برنامه نویسی سطح بالای اتریوم که سالیدیتی (Solidity) نام دارد از مفاهیمی برخوردار است که اغلب غیر – شهودی هستند و درک آن‌‌ها دشوار است. این مساله باعث خرابکاری‌‌های زیادی از سوی توسعه‌‌دهندگان شده است. یکی از نمونه‌‌های بزرگ این مساله باگ کیف پول مولتی‌‌سیگ توازن (Parity multisig wallet bug) است که باعث شد یک حمله‌‌کننده‌‌ی ناشناس 153,037 Eth (بیش از 30 میلیون دلار) بیرون بکشد.

افتضاح ماجرای The Parity نشان داد که خطاهای پیاده‌‌سازی می‌‌توانند ماه‌‌ها شناسایی نشده باقی بمانند؛ حتی هنگامی که قرارداد روی mainnet پیاده شده و کد منبع آن در دسترس همگان است. به طور دقیق نمی‌‌توان گفت چه گونه‌‌ آسیب‌‌پذیری‌‌هایی ممکن است در هزاران قرارداد پیاده‌‌ شده روی زنجیره پنهان مانده باشد و از سوی دیگر بسیاری از این قراردادها جعبه سیاه هستند، چراکه کد منبع آن‌‌ها روی اتراسکن (Etherscan) منتشر نشده است.

جای شگفتی نیست که این منبع آسیب‌‌پذیری با بازده پولی از چشم گروه‌‌های فعال در زمینه‌‌ی امنیت همچوین کلاه‌‌ سفید‌‌ها (white-hat) و کلاه سیاه‌‌ها (black-hat) دور نمانده است. ماجرا همان داستان قدیمی «خراب کردن پشته‌‌ها برای سرگرمی و سود»‌‌ است با این تفاوت که این بار پای سود واقعی در میان است (یادآوری می‌‌کنیم که نه تنها EVM دارای یک پشته است بلکه رجیستر هم ندارد، در نتیجه تقریبا هر دستوری از پشته استفاده می‌‌کند).

چند هفته پیش هنگامی که بررسی اتریوم را آغاز کردم، به چند ابزار مفید برای آنالیز قراردادها در مین‌‌نت برخوردم. پژوهشگران اتراسکن (Etherscan ) و ریمیکس‌‌ اَلو (remixallow) برای راحتی کندوکاو، قراردادها را در مرورگر وب جداسازی و باگ‌‌زدایی می‌‌کنند. دی‌‌کامپایلر پوروسیتی (Porosity decompiler) می‌‌تواند (تا حدی)‌‌ کد منبع را از هر بایت‌‌کد بازیابی کند. ترافل (Truffle) و testrpc کار کامپایل کردن و باگ‌‌زدایی کدهای سالیدیتی را آسان می‌‌سازند.

من همچنین در بررسی‌‌هایم متوجه شدم کارهای زیادی هست که نمی‌‌توانم آن‌‌ها را به شکل کارآمد انجام دهم. مهم‌‌ترین این کارها جستجوی بلاکچین‌‌ برای یافتن قراردادهای جالب و اسکریپت گرفتن از آنالیز ایستا / پویا در پایتون (Python) هستند (من هنوز به طور کامل سراغ انجام همه‌‌ی کارها در جاوا اسکریپت نرفته‌‌ام). از این رو، شروع کردم به نوشتن و گردآوری یک مجموعه از ماژول‌‌های پایتون و تهیه‌‌ی یک ابزار خط دستور. در نهایت به جایی رسیدم که دیگران می‌‌توانستند از آنچه ساخته‌‌ام استفاده کنند. خروجی کار مایتریل (Mythril) است. ابزاری برای دیس‌‌اسمبل (Disassemble) کردن اتریوم و کاوش و آنالیز بلاکچین‌‌. خلاصه‌‌ راهنمای این ابزار در ادامه آمده است.

نصب

ابتدا امیدوار بودم بتوانم PyEthApp را اجرا کنم به وضعیت در LevelDB آن دسترسی مستقیم پیدا کنم. متاسفانه گویا مدتی است PyEthApp دچار نبود نگهداری و توسعه شده و با مین‌‌نت اتریوم سینک نیست. بنابراین مایتریل باید به یک گره کاملا متصل گو – اتریوم (go-ethereum) دسترسی RPC داشته باشد. گو – اتریوم را نصب کرده و گره خود را به شکل زیر آغاز نمایید:

geth –rpc –rpcapi eth,debug –syncmode fast console 2>/dev/null $

توجه داشته باشید که مایتریل از APIهای باگ‌‌زدایی گو – اتریوم غیر استاندارد استفاده می‌‌کند، بنابراین مدتی باید با دیگر کلاینت‌‌های اتریوم کار کند و برخی کارکردهای آن در دسترس نخواهند بود.

خود مایتریل را می‌‌توانید از راه Pypi نصب کنید:

pip install mythril$

با این کار هم ماژول‌‌های پایتون و هم ابزار خط دستور myth  نصب می‌‌گردند.

ارزش‌‌دهی آغازین دیتابیس

با وجود مایتریل عملیات‌‌های جستجو مانند آنچه در وبلاگ میچ برنر (Mitch Brenner) افسانه‌‌ای آمده به جای چند روز ظرف چند دقیقه امکان‌‌پذیر خواهد شد. به این منظور یک نمای لحظه‌‌ای از قراردادهای پیاده شده در مین‌‌نت ایجاد می‌‌شود. برای ارزش‌‌دهی آغازی دیتابیس دستور زیر را اجرا کنید:

myth –init-db $

این فرآیند در مجموع قدری زمان‌‌بر است (در حقیقت چندان کارآمد نیست. امیدوارم در آینده شیوه‌‌ی پیاده‌‌سازی بهتری ارائه شود). اگر نمی‌‌خواهید تمام زنجیره را یک جا سینک کنید می‌‌توانید در هر زمان ctrl+c  را بزنید تا بار آینده که مایتریل را با فلگ –init-db  اجرا می‌‌کنید همگام‌‌سازی خود‌‌به‌‌خود ادامه پیدا کند.

کاربرد خط دستور

پس از آنکه چندین قرارداد در دیتابیس شما قرار گرفت می‌‌توانید دستورهای جستجو را اجرا کرده و به دنبال function signature و رشته‌‌های آپکد (opcode) بگردید. سینتکس مورد نیاز به این شکل است:

[func#[function signature#

[code#[opcodes#

برای نمونه، دستور زیر تمام قراردادهایی را که دارای تابعی به نام (changeMultisig(address هستند در خروجی می‌‌دهد:

 

این قابلیت جستجو از عبارت‌‌های ساده‌‌ی بولی پشتیبانی می‌‌کند. دستور زیر تمامی قراردادهایی که حاوی تابعی به نام (changeMultisig(address و رشته آپکد PUSH1 0x50, POP هستند را چاپ می‌‌کند:

“#myth –search “func#changeMultisig(address)# and code#PUSH1 0x50,POP$

دیس‌‌اسمبلر

دیس‌‌اسمبلر (disassembler) با یک فلگ -d فراخوانی می‌‌شود. این دیس‌‌اسمبلر تنها می‌‌تواند یک رشته بایت‌‌کد به وسیله‌‌ی آرگومان (argument) –c یا یک آدرس به وسیله‌‌ی -a ADDRESS را بپذیرد (با این کار کدهای قرارداد از گره اتریوم شما دانلود می‌‌شود).

مایتریل می‌‌کوشد به کمک یک فایل درونی مربوط به امضاء نام‌‌های توابع را مدیریت کند. سرچشمه‌‌ی این فایل دیتابیس امضای اتریوم است. اگر از مایتریل استفاده می‌‌کنید می‌‌توانید با این فایل آپدیت انجام دهید:

 

گراف فراخوانی

یکی از قابلیت‌‌های شگفت‌‌آور مایتریل تولید گراف فراخوانی (call graph) می‌‌باشد. افزودن آرگومان -g OUTPUT_FILE  باعث می‌‌شود مایتریل گراف را با فرمت HTML ذخیره کند.

myth -g ~/Desktop/graph.html -a 0x2d36cb89a977209703c1d6304f23198c22b7a498

CALL Gragh فایل به دست آمده را در یک مرورگر وب باز کنید تا گراف را مشاهده کنید. معمولا می‌‌توانید نمای کلی خوبی از مسیرهای اجرای دردسترس مشاهده کنید (خوشبختانه قراردادهای هوشمند آنقدرها پیچیده نیستند).

به کمک گراف فراخوانی و ردگیری اجرا می‌‌توان به تدریج یک قرارداد را مهندسی معکوس کرد و دسته‌‌کم من به خوبی توانستم آن را انجام دهم. البته عالی می‌‌شود اگر بتوان از یک ویراستار SVG با رابط کاربری گرافیکی برای نوشتن راهنما استفاده کرد (اگر می‌‌شناسید لطفا در کامنت‌‌ها به آن اشاره کنید).

یافتن ارجاعات متقابل

در بسیاری از موارد شناسایی قراردادهای دیگری که یک قرارداد به آن‌‌ها ارجاع داده می‌‌تواند مفید باشد. فرض کنید می‌‌خواهید قراردادهایی را جستجو کنید که در تابع فال‌‌بک خود از دستور DELEGATECALL  استفاده می‌‌کنند؛ همان گونه که در مورد باگ توازن (Parity Bug) شاهد بودیم. می‌‌توانید این کار را به وسیله‌‌ی آنالیز پویا انجام دهید. باید همه‌‌ی قراردادهای موجود در PyEthereum VM را بدون هرگونه ورودی اجرا کرده و ببینید آیا دستور DELEGATECALL اجرا شده یا نه. ریپو مایتریل دارای یک اسکرپیت نمونه است که نحوه‌‌ی انجام این کار را نشان می‌‌دهد. خروجی کار باید چیزی شبیه این باشد:

 

دستور DELEGATECALL  همان گونه که از نامش مشخص است، اجرا را به یک قرارداد دیگر محول می‌‌کند. پس طبیعتا شما به دنبال این هستید که ببینید چه قراردادی فراخوانی می‌‌شود. می‌‌توانید با آپشن –xrefs  آدرس قرارداد مرجع را چاپ کنید:

myth –xrefs 0x07459966443977122e639cbf7804c446  $

0x5b9e8728e316bbeb692d22daaab74f6cbf2c4691

به جای استفاده از این ابزار خط دستور می‌‌توانید به شکل برنامه‌‌وار ارجاع متقابل انجام دهید و با دنبال کردن آن قراردادهای مرجع را مورد آنالیزهای بیشتر قرار دهید (find-fallback-dcl.py یک نمونه از این کار را نیز دارد).

کاربرد پیشرفته

با وجود آنکه این ابزار خط دستور تمیز و درست کار می‌‌کند، تنها با کدهای دست‌‌ساز است که می‌‌توانید از همه‌‌ی توان مایتریل استفاده کنید. مایتریل افزون بر دیتابیس قرارداد، دیس‌‌اسمبلر و ماژول‌‌های ردگیری EVM دارای یک نسخه‌‌ی بهسازی شده از ethjsonrpc است که به شما امکان می‌‌دهد روی یک گره testrpc  ، کد پیاده و ردگیری نمایید. با در هم آمیختن همه‌‌ی این امکانات می‌‌توانید به یک آنالیز ایستا و پویای بسیار خوب داشته باشید.

جستجو

برای گشودن دیتابیس قرارداد از برنامه‌‌ی پایتون، تابع get_persistent_storage  را به کار ببرید. انجام این کار یک شی ContractStorage را به شما می‌‌دهد (دیتابیس به لحاظ پیش‌‌فرض در [your-home]/.mythril قرار دارد، ولی می‌‌توانید آن را در سازنده [constructor] لغو نمایید). برای آغاز جستجو، روش( search(expression, callback را فراخوانی کنید:

 

تابع بازفراخوانی (callback) به کار رفته در آرگومان دوم برای هر نتیجه‌‌ی جستجو فراخوانی می‌‌شود. این تابع آرگومان‌‌های زیر را دریافت می‌‌کند:

  • کلید هش، ویژه‌‌ی شناسایی قرارداد در دیتابیس مایتریل
  • شی ETHContract که حاوی قرارداد موجود است
  • لیست آدرس‌‌هایی که در آن قرارداد در بلاکچین‌‌ قرار دارد
  • لیست مانده حساب‌‌های هر قرارداد پیاده شده

 

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

اجرای ردگیری

فرض کنید می‌‌خواهید دیتابیس قرارداد را برای یافتن شرایط مربوط به باگ توازن اسکن کنید ولی نوع خاصی مورد نظر شما است. یک راه این است که دنبال هر تابعی بگردیم که در صورت اجرا شدن بی‌‌ آرگومان، با یک آدرس یا با لیستی از آدرس‌‌ها، آدرس شما را برای ذخیره با دستور SSTORE  می‌‌نویسد. البته این لزوما بدان معنا نیست که متغیر وضعیت مهمی مانند owner  یا owners را جای‌‌ نویسی می‌‌کنید بلکه اتفاقا از جمله رویکردهایی است که مایلید در آینده بیشتر روی آن تحقیق کنید.

در نمونه‌‌ی قبل دیدیم چگونه می‌‌توان کد را در PyEthereum VM ردگیری (traced) کرد. برای آنالیز پیشرفته‌‌تر که وضعیت (مانند اکانت‌‌های در دسترس، ذخیره قرارداد، فراخوانی سازنده و غیره) هم در آن لحاظ شده باشد بهتر است قرارداد را روی testrpc پیاده نماییم. من در محیط تست خود geth را روی پورت 8545 اجرا کردم و یک testrpc نمونه را روی پورت 8546 اجرا نمودم که به من اجازه می‌‌دهد به صورت آنی قراردادها را از شبکه‌‌ی واقعی به testrpc انتقال دهم. به منظور اجرای کد نمونه، testrpc را به این شکل آغاز می‌‌کنیم:

 

می‌‌خواهیم همه‌‌ی قراردادهای موجود در دیتابیس را بررسی کنیم، بنابراین یا از یک عبارت جستجو استفاده می‌‌کنیم که با همه‌‌ی قراردادها همخوانی داشته باشد یا به سادگی روی قراردادها تکرار انجام می‌‌دهیم:

 

 

این کار اشیای ETHContract را بازمی‌‌گرداند که هم کد قرارداد (contact.code) و هم کد تراکنش به وجود آورنده‌‌ی قرارداد (contract.creation_code) را ذخیره می‌‌سازد.

برای بازسازی قرارداد در زنجیره‌‌ی شخصی خود یا روی testrpc، به استفاده از کلاینت JSON RPC مایتریل، تراکنش ایجاد قرارداد را بازاجرا نمایید:

 

این کار ما را به یک رسید تراکنش می‌‌رساند که آدرس قرارداد در آن وجود دارد. توجه کنید که testrpc هر بار که یک تراکنش تازه دریافت نماید یک بلوک تازه استخراج (mine) می‌‌کند، بنابراین قرارداد شما در یک آن پیاده می‌‌شود.

با استفاده از کلاس Disassembly  (دیس‌‌اسمبلری) می‌‌توانید به لیست دستورالعمل‌‌ها، کد easm فرمت‌‌ شده، ارجاع‌‌های متقابل و توابع قرارداد دسترسی پیدا کنید. این کار نیازمند یک آرگومان سازنده (constructor argument) است؛ بایت‌‌کد قرارداد:

(disas = Disassembly(contract.code

شی دیس‌‌اسمبلری دارای دو لیست func_to_addr  و addr_to_func است که حاوی نگاشت میان نام‌‌های تابع و آدرس‌‌ها هستند. می‌‌توانید با انجام یک تکرار روی func_to_addr  ، امضای هر تابع را به دست آورید (توجه کنید که توابع ناشناس با لیبل[ UNK_[addressمشخص شده‌‌اند).

 

در اسکریپت نمونه، هر تابع در دسترس چندین بار با آرگومان‌‌های مختلف فراخوانی می‌‌شود (از جمله: بدون آرگومان، یک آدرس، لیستی از آدرس‌‌ها). من همه‌‌ی این‌‌ موارد را در اینجا دقیق شرح نمی‌‌دهم. لطفا کد را مشاهده کنید و ببینید انکود دیتای فراخوانی و ارسال تراکنش چگونه انجام می‌‌گیرد.

در نهایت، به منظور ردگیری اجرای یک فراخوانی تابع، از روش traceTransaction RPC استفاده نمایید:

 

این کار شما را به یک دیکشنری می‌‌رساند که تمامی دستورات اجرا شده و پشته در هر مرحله از اجرا در آن وجود دارد. ما تنها به دنبال دستورات SSTORE هستیم که آدرس مورد نظر ما را در جایگاه دوم تا بالای پشته دارند (یعنی آدرس حمله‌‌ کننده برای ذخیره نوشته شده است). می‌‌توانیم به شکل زیر در لیست دستورات جستجو کنیم:

 

گام بعدی می‌‌تواند آنالیز ایستا و پویای بیشتر باشد و هدف از آن مشخص کردن تاثیرات آدرس جای نویسی شده (overwritten address) یا روبرداری (dumping) یک گراف فراخوانی برای آنالیز دستی است.

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

TL;DR

مایتریل یک فریم‌‌ورک کاوش بلاکچین‌‌ و شناسایی باگ است. این ابزار امکان جستجو، دیس‌‌اسمبلری و آنالیز قراردادهای هوشمند را در بلاکچین‌‌ اتریوم فراهم می‌‌آورد. از آن برای دزدیدن پول یا ساختن برند از نقاط آسیب‌‌پذیر استفاده نکنید.

منبع: https://hackernoon.com/

شاید از این مطالب هم خوشتان بیاید.

ارسال پاسخ

آدرس ایمیل شما منتشر نخواهد شد.