حملهی درب پشتی از طریق کامپایلر یا Compiler backdoor attack یکی از حمله هایی است که هنوز به شکل رسمی روشی برای جلوگیری از آن معرفی نشده است
توی این پست روش کار این حمله رو توضیح میدم و سعی میکنم اون رو برای کامپایلر زبان C پیاده کنم.
معرفی
کن تامسون، برنامه نویس سیستم عامل یونیکس در سال 1984 در مراسم Turing Award داخل سخنرانیش حمله درب پشتی از طریق کامپایلر رو معرفی و در موردش صحبت میکنه.
در سال 2015 نرم افزار XcodeGhost که برای ساخت برنامه های شرکت اپل از اون استفاده میشده از همین تکنیک استفاده میکرده و گفته میشه که این اولین حمله در مقیاس بزرگ برای اپ استور اپل بوده و باعث شده بیش از 4000 برنامه داخل فروشگاه اپل آلوده بشه.
همانطور که از اسم حمله هم مشخصه، کامپایلر شما زمانی که میخواهد کد شما رو کمپایل کنه بدون سر و صدا یک در پشتی رو داخل کد های شما ایجاد میکنه
شما هم که از این درب پشتی خبر ندارید پس برنامه خودتون رو منتشر میکنید و این شروع ماجراست. .
چرا این حمله مهمه ؟
برای جواب دادن به این سوال نیاز هست که کمی عمیق تر به مشکل نگاه کنیم.
بذارید اینجوری شروع کنیم که اصلا از کجا میتونیم مطمئن باشیم که کامپایلری که الان ازش استفاده میکنیم توی برنامه هامون درب پشتی درست نمیکنه ؟
احتمالا جواب این هست که خب کد های مربوط به کامپایلر به شکل open source هست و اگر چنین مشکلی وجود داشته باشه حتما هستند کسایی که اون کد رو بخونن و متوجه چنین مشکلی بشن.
حالا سوال جدید این هست که اگر فرض کنیم کد مربوط به کامپایلر منبع باز باشه و کسی هم باشه که اون کد رو کنترل کنه و بخونه، کد مربوط به خود کامپایلر نیاز داره که توسط یک کامپایلر دیگه کامپایل بشه و ما چطوری میتونیم مطمئن بشیم که اون کامپایلر، درب پشتی رو داخل کامپلر اول ایجاد نمیکنه ؟
ممکنه بگید که خب میتونیم از یک disassembler استفاده کنیم و چک کنیم که فایل های اجرایی کامپایلر ما کد مخربی داخلشون نباشه.
اما این روش هم جوابگو نیست چون disassembler هم در نهایت یک برنامه است و ممکنه خود این برنامه هم رفتار بدی از خودش نشون بده و زمانی که میخواهد کد های مربوط به کامپایلر رو برای ما ایجاد کنه، کد های بد رو در داخل سورس کد مخفی کنه و نمایش نده.
ممکنه دوباره در جواب بگید که خب این روش خیلی سخته و ممکنه اصلا کسی سراغ عملی کردن این مسئله نره و در جواب باید بگم که ساخت این کامپایلر خیلی ساده تر از اون چیزی هست که فکر میکنید و در ادامه نشون میدم که چطور توی کمتر از ۱۰۰ خط کد این کامپایلر رو میشه ساخت.
ساخت کامپایلر
برای نشان دادن این حمله من از این ریپوزیتوری استفاده میکنم.
برای شروع بیاید نگاهی به فایل Login.cpp بندازیم. توی این فایل برنامه ی ساده ای هست که میگه اگر کاربر رمز ورود رو test123 بزنه میتونه وارد بشه
#include <iostream>
using namespace std;
int main() {
cout << "Enter password:" << endl;
string enteredPassword;
cin >> enteredPassword;
if(enteredPassword == "test123")
cout << "Successfully logged in as root" << endl;
else
cout << "Wrong password, try again." << endl;
}
حالا بیایید این برنامه رو با استفاده از کامپایلری که داریم کامپایل کنیم. پس داخل ترمینال کد زیر رو میزنیم
./Compiler Login.cpp -o Login
حالا اگر کد کامپایل شدهی برنامه رو اجرا کنیم و به جای رمز ورورد backdoor رو بزنیم میبینیم که برنامه این پسورد رو درست تشخیص میده و وارد میشه.
اما همونطور که داخل کد میبیندی ما فقط پسورد test123 رو قبول میکنیم.
احتمالا حدس میزنید که شاید مشکل از کامپایلری باشه که استفاده کردیم، پس بیاید نگاهی هم به سورس کد کامپایلر بندازیم.
#include <string>
#include <cstdlib>
using namespace std;
int main(int argc, char *argv[]) {
string allArgs = "";
for(int i=1; i<argc; i++)
allArgs += " " + string(argv[i]);
string shellCommand = "g++" + allArgs;
system(shellCommand.c_str());
}
همونطور که داخل کد هم مشخصه این کد از کامپایلر ++g استفاده میکنه و فایل ما رو کامپایل میکنه
توی این کد هم مشکلی نیست پس چطوری این درب پشتی به کد ما اضافه میشه.
قبل از اینکه در مورد اضافه شدن درب پشتی به کد حرف بزنم خوبه که با اصطلاح self-hosting آشنا بشیم
اگر دفت کرده باشید کد کامپایلر ما خودش به زبان C نوشته شده و فایل اجرایی کامپایلر ما میتونه فایل برنامه خودش رو هم کامپایل کنه.
مثلا داخل کد زیر من کد برنامه کامپایلرم رو با فایل اجرایی همون کامپایلر، کامپایل میکنم.
./Compiler Compiler.cpp -o newCompiler
این به این معنا هست که برای کامپایل کردن سورس کد ورژن جدید کامپایلرمون میتونیم از ورژن های قبلی خودش استفاده کنیم.
زبان های دیگه ای هم مثل C++ ، Java و یا python هم این ویژگی رو دارند
جلوتر در مورد اینکه این ویژگی چه تاثیری داره صحبت میکنم اما حالا بیایید در مورد اینکه درب پشتی چطور به کد اضافه میشه صحبت کنیم.
زمانی که شما فایل Login.cpp رو کامپایل میکنید اتفاق های زیر داخل کامپایلر میافته:
1- فایل Login.cpp داخل یک فایل موقت به نام LoginWithBackdoor.cpp کپی میشود.
2- فایل LoginWithBackdoor.cpp به اون شکلی که میخوایم اصلاح میشه. مثلا یک کلمه عبور جدید به اون اضافه میشه.
3- فایل LoginWithBackdoor.cpp به جای فایل Login.cpp کامپایل میشه
4- در نهایت فایل LoginWithBackdoor.cpp پاک میشه
اگر به فایل EvilCompiler.cpp نگاه کنیم میتونید این مراحل رو ببینید
اما کسی از این کامپایلر استفاده نمیکنه چون با خوندن سورس کد مربوط به EvilCompiler.cpp متوجه این مشکل میشه پس باید راهی پیدا کنیم که این مشکل رو هم حل کنه. برای حل این مشکل از self-hosting استفاده میکنیم.
مخفی کردن ساخت درب پشتی در کامپایلر
فرض کنید کسی برای اطمینان از درست عمل کردن کامپایلر، سورس کد های کامپایلر را دانلود کند و بخواهد خودش آن را کامپایل و از ان استفاده کند.
در این حالت اگر سورس کد EvilCompiler.cpp رو به کاربر بدیم ، کاربر متوجه حمله ما میشه و اگر سورس کد Compiler.cpp رو به کاربر بدیم دیگه درب پشتی به وجود نمیاد
برای رفع این مشکل باید از خاصیت self-hosting استفاده کنیم.
کاربر زمانی که میخواهد سورس کد کامپایلر رو کامپایل کنه نیاز به یک کامپایلر داره. ما ( در نقش طراح حمله ) میتونیم سورس کد Compiler.cpp رو به عنوان اولین کامپایلر ساخته شده برای این زبان به کاربر نشون بدیم ولی فایل اجرایی EvilCompiler.cpp به عنوان اولین کامپایلر به کاربر داده بشه. در نتیجه هر بار که کاربر سورس کد Compiler.cpp رو با این فایل اجرایی کامپایل کنه، همون روش اضافه کردن درب پشتی تکرار میشه و درب پشتی به کامپایلر اضافه میشه.
عکس زیر کل این روش رو به خوبی نشون میده :
استفاده از هش برای کشف درب پشتی
یکپارچه بودن اطلاعات یعنی کسی اطلاعات رو در حین انتقال تغییر نده و چیزی که توی مقصد تحویل میگیریم دقیقا همون چیزی باشه که در مبدا ارسال شده.
یکی از روش های مرسومی که این روز ها برای چک کردن بکپارچه بودن اطلاعات استفاده میشه هش هست. توی این روش فرسنتدهی اطلاعات، یک هش رو هم که هش اون اطلاعات هست در اختیار گیرنده قرار میده و با این روش گیرنده با هش کردن اطلاعات دریافتی و مقایسه کردن این دو تا هش با هم میتونه چک کنه آیا اطلاعات تغییر کردن یا نه.
مثلا وقتی که فایل ایزو اوبونتو رو دانلود میکنید هش اون هم همراهش به شما نشون داده میشه که میتونید با هشی که خودتون از فایل ایزو میگیرید مقایسه کنید.
اما باید حواسمون باشه که برنامه ای که هش رو برای ما محاسبه میکنه خودش ممکنه یک درب پشتی داشته باشه. اگر به نظرتون چنین چیزی خیلی عجیب، غیر ممکن و یا سخت هست کافیه به این فکر کنید که از gcc که معروف ترین کامپایلر برای زبان C هست برای کامپایل کردن خود gcc و sha256 استفاده میشه.
و اما درسی که ما میگیریم …
همانطور که دیدید حمله compiler backdoor attack حمله بسیار جالب و هوشمندانه ای محسوب میشه.
اما این حمله دو نکته مهم رو به ما یاد میده :
۱- به هیچ کدی که توسط شما نوشته و ایجاد نشده باشه نمیشه اعتماد کرد
2- هر چه به سمت لایه های پایین تر کامپیوتر ها و برنامه ها پیش میریم پیدا کردن باگ ها و درب های پشتی و .. سخت تر میشه
اگر توضیحات اضافه تری در مورد این حمله میخواید میتونید به منبع سر بزنید