سلام :)
این اولین نوشتهام توی ویرگوله (بقیه جاها هم البته چیزی ندارم) و امیدوارم بیشتر مطلب بذارم و به شدت انتقادها رو میپذیرم.
قراره توی این نوشته (بلاگ؟ متن؟) چند نوع Trojan attack که به نظرم جالب بودن و Supply chain attack رو توضیح بدم.
منبع اصلی رو میتونید از اینجا ببینید.

در جنگ تروجان یونانیها برای اینکه وارد شهر تروی شوند یک اسب چوبی بزرگ ساختند و به عنوان هدیه به تروی دادند. اما نکتهای که وجود داشت این بود که داخل این اسب تعدادی سرباز بود و وقتی اسب داخل شهر وارد شد سربازها از اسب بیرون آمدند و به شهر حمله کردند. طبق شواهد جنگ تروجان واقعی بوده اما اسب تروجان فقط یک داستان است.[1]
در حوزه برنامه نویسی و امنیت حمله تروجان یا اسب تروجان به کدی گفته میشود که به ظاهر سالم است اما امکان اسیب زنی، کنترل، جمع آوری دیتا و یا هر عمل مخرب دیگری را دارد.[2]
کد زیر را نگاه کنید:

این کد که به زبان c نوشته شده به نظر سالم است و انتظار داریم موقع اجرا داخل شرط (if) نرود؛ اما با اجرای کد میبینیم عبارت You are an admin نشان داده شده که یعنی داخل شرط رفته است. یه مثال جذاب از تروجان است که صرفا برای جلب توجه اینجا گذاشتمش ولی آخر توضیح میدم:) [3]
دقت کنید که حملههایی که در ادامه گفته میشود بیشتر جنبه تئوری و یادگیری دارند و در عمل ممکنه استفاده نشن. اما ایدههاشون و ترکیبشون با ایدههای دیگه استفاده میشود.
در این قسمت قصد داریم کامپایلر c ای بسازیم که source code اش هیچ مشکل امنیتیای نداشته باشد (واقعا هیچ مشکلی نداشته باشد) اما کامپایلر مشکل امنیتی دارد. در ۳ مرحله اینکار را انجام میدهیم.
سوال کلاسیکی وجود دارد که میگوید چطور یک برنامه بنویسیم که خودش را چاپ کند؟
به نظرم روی این سوال فکر کنید چون سوال قشنگیه (بدون فایل و این چیزا باید حل شه البته)
این لینک هم واسه داوری کدتونه اگه خواستید (البته یکم فرق داره سوالش) و راه حلش برای ++C هم در این لینک قرار دادم. (زیاد به کثیفی کد اهمیت ندید :) کد واسه یه دانشآموز ۱۷ ساله بودش)
راه حل:
پیچیدگی سوال اینجاس که فرض کنید بخواید رشته کد برنامه رو توی متغیر s بریزید. حالا مقدار s هم که توی کد هستش پس باید مقدار s هم داخل s باشه و این یه چرخه بینهایت است.
یعنی فرض کنید کد شما به این صورت باشد:

که A قسمت اول کدتان و B قسمت آخر کدتان هست. جای "؟" باید خود کد را گذاشت پس داریم:

حالا جای "؟" دوم چی باید قرار داد؟

یعنی کد این شکلی میشود: As="As=\"As=\"...?...\"B\"B"B و نمیشود همچین کدی زد. (قبل از " کارکتر \ برای escape کردن کارکتر آمده)
اما با یک کلک میشود درستش کرد
اگر دقت کنید رشته s را میشود بازگشتی تعریف کرد:
s = "As=\"?\"B"
که به جای ? باید خود s را گذاشت یعنی:
s = "As=\"" + s + "\"B"
فرض کنید در کد کارکتر $ نداریم رشتهی زیر را در نظر بگیرید:
As="As=\"$\"B"
در واقع به جای قسمت تکرار شونده که خود s است کارکتر $ را گذاشتیم و کافی است وقتی به $ رسیدیم کل s را چاپ کنیم. در واقع همچین کاری:

پس ما میتونیم کدی بزنیم که خودش را چاپ کند.
کامپایلر c با خود c نوشته شده. هرچند اولین کامپایلر c با زبان دیگری نوشته شده اما بعد از آن با خود c ادامه دادند. اما چطور این امکان دارد؟ فرض کنید کامپایلر c کارکتر v\ را پشتیبانی نمیکند و میخواهیم پشتیبانی از این کارکتر را اضافه کنیم.
در c یک رشته میتواند Escape character داشته باشد. که وقتی این کارکتر دیده میشود یعنی کارکتر بعدی باید جور دیگر تفسیر شود. در c این کارکتر \ است و مثلا وقتی مینویسیم t\ به معنا کارکتر تب (tab) است. فرض کنید قسمتی از سورس کد کامپایلر که قرار است یک رشته را تجزیه (parse) کند به این شکل باشد.

این تکه کد یک رشته را کارکتر به کارکتر میگیرد

و اگر آن کارکتر برابر \ نبود تفسیری که از کارکتر دارد خود کارکتر است.

در غیر این صورت کارکتر بعدی را میگیرد و وابسته به آن کارکتر، کارکتر متناظر با آن را باز میگرداند. برای مثال اگر n بود تفسیری که از کارکتر دارد n\ است.

حالا اگر بخواهیم v\ را اضافه کنیم باید این قسمت سورس کد کامپایر را اینگونه عوض کنیم:

کد اسکی کارکتر v\ برابر ۱۱ است. این کد میگوید اگر کارکتر بعد از \ برابر v بود کارکتر متناظر ۱۱ است.
حالا فرض کنید x کامپایلر اولیه ما است که v\ را پشتیبانی نمیکند. اگر سورس کد جدید را با x کامپایل کنیم به ما کامپایلر y را میدهد که از v\ پشتیبانی میکند. در سورس کد از خود کارکتر v\ استفاده نشده چون x آن را پشتیبانی نمیکند. حالا کد زیر را در نظر بگیرید:

تفاوت این کد با قبلی این است که به جای کد اسکی v\ خود کارکتر 'v\' را میدهد. این سورس کد را که v\ دارد میتوانیم با y کامپایل کنیم چون y از v\ پشتیبانی میکند. پس از این به بعد میتوانیم از v\ در کدها و حتی خود سورس کد کامپایلر c استفاده کنیم.

کد قسمت لاگین Unix با زبان c نوشته شده است. هدف این است که کامپایلری بسازیم که وقتی کد لاگین Unix با آن کامپایل شد بشود با رمز 1234 نیز وارد حساب کاربر شد.
فرض کنید قسمتی از کد کامپایلر که برنامه را به عنوان ورودی میگیرد این قسمت باشد:

در واقع s کدی است که قرار است کامپایل شود و در قسمت ... نیز این کد کامپایل میشود. در این قسمت کدی اضافه میکنیم که اگر s الگو (pattern) خاصی داشت که یعنی s کد لاگین Unix است کد (s) را جوری تغییر دهد که با 1234 بشود لاگین کرد.

اگر کد کامپایلر را اینگونه تغییر دهیم زمانی که کد لاگین Unix قرار است با کامپایلر خراب کامپایل شود میشود با 1234 نیز لاگین کرد. حالا مشکلی که وجود دارد این است که این کد خیلی واضح است و زود میفهمند.
مانند مرحله ۱ کدی میزنیم که اگر کامپایلر داشت سورس کد کامپایلر جدید را کامپایل میکرد کدی که لاگین Unix را خراب میکند و خود این کد که اینکار را قرار است بکند به سورس کد کامپایلر جدید اضافه کند.

در واقع سورس کد کامپایلر را اینگونه تغییر میدهیم:

همانطور که گفته شد این قسمتی است که کدی که قرار است کامپایل شود (s) را میگیرد و آن را کامپایل میکند. (در ...)
در شرط اول pattern1 الگو لاگین Unix است و اگر s لاگین Unix بود باگی به کد اضافه میکند که باعث شود با 1234 نیز بشود لاگین کرد.
در شرط دوم pattern2 الگو سورس کد خود c است و اگر s سورس کد کامپایلر بود هر دو if را به s اضافه میکند. (شبیه مرحله ۱)
فرض کنید کامپایر x با این سورس کد ساخته شده است. پس اگر کد لاگین Unix را با x کامپایل کنیم فایل اجرایی آن باگ دارد و با 1234 میشود لاگین کرد و اگر یک سورس کد کامپایلر c را به x بدهیم، حتی اگر سورس کد سالم باشد (یعنی این دو شرط را نداشته باشد) بازم این ۲ شرط را به آن اضافه میکند و سپس کامپایل میکند. که یعنی اگر سورس کد کامپایلر سالم را با x کامپایل کنیم حاصل باز این ۲ باگ را دارد. پس کافی است سورس کد را عوض کنیم، کامپایلری با آن بسازیم که باگ ۱ و ۲ را داشته باشد و این کد را از سورس کد پاک کنیم. از اونجایی که کامپایلر خراب شده هر سورس کد c ای که با آن کامپایل شود نیز خراب میکند و این ۲ باگ همیشه میمانند. (مگر اینکه با کامپایلر سالم سورس کد کامپایل شود)
در واقع این کامپایلر خراب همان اسب تروجان ما شده است.[4]

در این بخش میخواهیم بگویم چطور بهینه سازی میتواند باگ امنیتی ایجاد کند. برای کامپایل کردن کد c در میتوانید از gcc و برای بهینهسازی و کامپایل کردن از O2- استفاده کنید.
gcc file.c gcc file.c -O2
دستور دوم با بهینهسازی کامپایل میکند.
کد c زیر را در نظر بگیرید:

فرض کنید نمیخواهیم مقدار key در خارج از تابع crypt قابل دسترس باشد. برای همین در انتهای تابع مقدار key برابر صفر قرار داده شده است تا از حافظه رم پاک شود علت اینکار این است که در c مقدار متغیر در یک قسمتی از رم قرار میگیرد و وقتی متغیر حذف میشود (مثلا متغیر محلی بوده و از scope اش بیرون آمدهایم) مقدار آن همچنان در رم باقی مانده است. پس با اینکار وقتی crypt را صدا بزنیم بعد از آن دیگر به مقدار key دسترسی داریم.
اما لزوما اینطور نیست :)
یکی از روشهای بهینه سازی Dead code elimination است که یعنی کدی که تاثیری در خروجی برنامه ندارد را پاک میکند. خط آخر این تابع یعنی key = 0x0 چون دیگر بعد از آن از key استفاده نمیشود Dead code حساب میشود و بهینهساز این خط را موقع کامپایل پاک میکند! که یعنی مقدار key در قسمتی از رم باقیمانده است و با کمی جست و جو میشود آن را پیدا کرد.

کد بالا یک مثال از باگ امنیتیای است که بهینه ساز به وجود میآورد. در این کد یک آرایه از کارکتر به اسم key داریم و مثلا فرض کنید این key را از یک فایل دریافت میکند. در انتها نیز مقدار خانههای key را کارکتر ۰ قرار میدهد. (0\ برای مشخص کردن انتهای رشته است). با مصاحبه متغیرها و تابعهای موجود میشود فهمید اگر یک کارکتر جدید ایجاد کنیم و از آدرس آن ۷۱ بایت عقب بیایم به آدرس key میرسیم. پس خط زیر مقدار key را چاپ میکند:

کد را یک بار با O2- و یک بار بدون آن اجرا میکنیم:

مقدار key در فایل flag قرار دارد که برابر 123 است. w- برای این است که موقع کامپایل اخطار (warning) ندهد و در فایل test.in هم عدد ۳ قرار دارد (این عدد مهم نیست البته) بار اول بدون O2 کاماپیل و اجرای شده است. (a.out/. برای اجرای فایل کامپایل شده است) و حاصل رشته 0 بوده و یک بار با O2 و حاصل 123 بوده است. چون قسمت زیر که مقدار key را رشته ۰ میکند، توسط O2 حذف میشود:

در ادامه مثالی دیگر از بلاهایی که بهینهسازی سر کد میاره آورده شده
اما قبلش یه توضیح مختصر راجع به Timing attack باید گفته شود. فرض کنید شما قسمت لاگین یک سایت را اینگونه زدید که s رمزی است که کاربر وارد کرده و t رمز واقعی کاربر است. اگر s و t یکسان بودن یعنی کاربر رمز درست وارد کرده است.

مشکلی که وجود دارد این است که فرض کنید t برابر "aaaa" باشد. اگر s برابر "abcd" باشد ابتدا کارکتر اول s و t با هم مقایسه میشوند و چون یکسان هستند سپس کارکتر بعدی مقایسه میشود. که یعنی ۲ بار مقایسه کارکتر انجام شدهاست. اگر s برابر "aaab" باشد ۳ بار و اگر s برابر "qqqq" باشد صفر بار مقایسه انجام میشود. یعنی هرچقدر s پیشوند مشترک بزرگتری با t داشته باشد بیشتر طول میکشد. با حساب کردن زمان درخواست لاگین میشود رمز کاربر را پیدا کرد. هرچند در عمل به جای مقایسه s و t هش آنها را مقایسه میکنند و این نوع حمله جواب نمیدهد. اما نکتهای که وجود دارد این است که کد باگی داشت که باعث میشود کاربر خارج از کد اطلاعاتی از برنامه به دست بیاورد که نباید به دست بیاورد.
کد زیر را نگاه کنید:

قسمت سمت چپ کد اصلی و قسمت سمت راست کد بعد از بهینهسازی است. Side channel attack نوعی حمله است که فرد میتواند اطلاعاتی را از اجرای کد به دست آورد. (برای مثال Timing attack یک نوع Side channel attack است). کد سمت چپ (سورس کد) قسمت if و else تقریبا یک میزان طول میکشند. اما به خاطر بهینه سازی Common sub expression elimination داخل else یک عبارت ۳ بار آمده و کامپایلر مقدار عبارت را یک با حساب میکند و مثلا داخل tmp میریزد و در نهایت مقدار key را ۳ برابر tmp قرار میدهد. این باعث میشود داخل else سریعتر از داخل if باشد و کاربر با حساب کردن زمان درخواست بتواند بفهمد که کد داخل if رفته یا خیر. که این ممکن است اطلاعاتی باشد که کاربر نباید بفهمد (مثلا در اینجا میفهمد آیا عددی که داده برابر 0xC0DE بوده یا خیر. در عمل ممکن است اطلاعات مهمتری بفهمد که امنیت سیستم را به خطر بندازد.)
مثالهای دیگری نیز از مشکلات امنیتی که بهینهسازی ایجاد میکند در این مقاله میتوانید بخوانید. [5]

به صورت کلی برای اینکه نسخه جدید یک اپلیکیشن یا پکیج منتشر بشه اول مشارکتکنندگان (یا همون contributor خودمون :)) Pull Request تغییر خودشون رو به Version Control میدهند بعد از قبول شدن نگهدارنده پروژه (Maintainer) مراحل Build پروژه را انجام میدهد و در نهایت پکیج یا اپلیکیشن منتشر میشود. (بابت این همه کلمه انگلیسی معذرت میخوام اما معادل فارسیشون برای برنامه نویسا سختره :) چون همیشه انگلیسیشون رو دیدن)
در Supply chain attack یا حمله زنجیره تامین هدف آسیب زدن و نفوذ به زنجیره بالا است.
در شکل زیر انواع مختلف این نوع حمله را میتوانید ببینید:

در ادامه قسمت Create new package را با هم بررسی میکنیم و از آن دو مثال میبینیم.
سه پکیج NodeJS به نامهای getcookies، express-cookies و http-fetch-cookies توسط یک فرد اپلود شدند. پکیج getcookeis حاوی کدهای مخربی بود که میتوانست داخل سرور چند عملیات انجام بدهد. پکیج express-cookies و http-fetch-cookies پکیجهای سالمی بودن که صرفا هدفشون سختر پیدا شدن کد مخرب بود. برای همین از ساختار زنجیری ماژولها استفاده کردند به این صورت که http-fetch-cookies حاوی پکیج express-cookies بود و این پکیج نیز حاوی getcookies بود. در نهایت بعد از مدتی پکیج mailparser از http-fetch-cookies استفاده کرد و حمله را تکمیل کرد. اینکه چطوری http-fetch-cookies به لیست این واسبتگیهای (dependencies) در مراحل زنجیره تامین به mailparser اضافه شده است معلوم نیست. در پکیج getcookies قسمتی از کد وجود داشت که تقریبا به این شکل بوده:

در واقع اگر داخل header درخواست مقداری به فرمت gCOMMANDhDATAi بودش با توجه به COMMAND و DATA عملیاتی در سرور انجام میداد. (این قسمت که چه عملیاتی انجام میداد در شکل بالا نیست) و با توجه به کوتاه بودن و وجود پکیجهای http-fetch-cookies و express-cookies پیدا کردن این کد مخرب دشوار بوده. [6]
در این مدل حمله فرد پکیجی میسازد که اسم آن شبیه پکیجهای موجود باشد.
برای مثال پکیج pytz3 برای عملیاتهای مروبط به منطقه زمانی است. فردی پکیج مخربی به نام pytz3-dev ایجاد کرده که افراد ممکن است به اشتباه این پکیج را استفاده کنند.
توکنهای مربوط به نرمافزار Discord با فرمت sqlite ذخیره میشوند. این پکیج این فایلها را پیدا میکرد و توکن کاربر را به سرور خودش میفرستاد. کد مروبط به این قسمت را داخل یکی از فایلهای پکیج قرار داده بود (فایل __init__) که با import کردن پکیج اجرا میشود. کد در زیر میتوانید مشاهده کنید:

تعداد دانلود روزانه این پکیج نیز در تصویر زیر میتوانید مشاهده کنید: [7]

در این نوع حمله فرد بعد از خالی شدن یک اسم از آن استفاده میکند. برای مثال اگر فردی حساب خود را از گیتهاب پاک کند بقیه میتوانند حسابی با آن اسم بسازند و در نتیجه خود را جای آن فرد جا بزنند.
در نهایت Supply chain attack را با یک مثال تمام میکنیم.
یکی از هکهای پر سر و صدا، هک شدن نفر افزار مانیتورینگ Orion بود. این نرم افزار ۳۳۰۰۰ کاربر داشت از جمله پنتاگون، وزارت امنیت داخلی آمریکا، مایکروسافت، اینتل و کمپانی امنیت سایبری FireEye.
این حمله با اضافه کردن کد مخرب به فایل SolarWinds.Orion.Core.BusinessLayer.dll که باعث ایجاد یک درپشتی (Backdoor) در نرمافزار میشود انجام شد. با استفاده از این درپشتی هکر کد مخرب دیگری را به سرور میفرستاد و آن را اجرا میکرد. این کد مخرب در آپدیتی در اوایل ۲۰۲۰ منتظر شد که حدود ۱۸۰۰۰ از کاربر آن را دانلود کردند. در نهایت بعد از ۹ ماه این کد مخرب کشف شد.[8]

در انکودینگ (encoding) Unicode برای چپ به راست کردن، تعدادی کارکتر خاص وجود دارد که در جدول پایین آمدهاند:

برای مثال رشته زیر را نگاه کنید:

رشته اصلی به شکل RLI a b c PDI است. موقع نمایش به خاطر وجود کارکتر RLI، بین RLI تا PDI متناظر با آن، راست به چپ میشود. PDI برای بستن RLI یا LRI است (شبه پرانتزگذاری) و LRI نیز برای چپ به راست کردن است.
برای همین رشته RLI a b c PDI به شکل c b a نشان داده میشود.
این کارکترها که به آنها directorial override میگویم جهت متن را مشخص میکنند و فقط در خود متن هستند و موقع نمایش اکثر برنامهها آن را نشان نمیدهند. و صرفا ترتیب نشان دادن بقیه کارکترها را عوض میکنند.
همچنین RLI و LRI میتوانند تو در تو باشند مانند شکل زیر:

قسمت LRI a b c PDI به شکل a b c قرار میگیرد. قسمت LRI d e f PDI به شکل d e f و به خاطر RLI و PDI کلی این دو قسمت جا به جا میشوند.
کدی که در ابتدای مقاله بود را دوباره نگاه کنید:

این کدِ چیزی است که میبینید:

در واقع کل خط شرط داخل کامنت است و صرفا به خاطر کارکترهای directorial override چیزی که میبینیم این است که قسمت begin admins only قبل if آمده اما در حقیقت اینطور نیست.

در اکثر زبانها اگر کارکترهای directorial override در کد باشد خطا میدهند اما این کارکترها در رشتهها و کامنتها میتوانند قرار بگیرند. چون این قسمتها اجرا نمیشوند، خطا نیز نمیدهد.
در کد قبلی این کارکترها در کامنت قرار گرفته بودند در کد JavaScript زیر این کارکترها در رشته قرار گرفته اند:


در این کد در اصل accessLevel با user RLO LRI// Check if adminPDI LRI مقایسه میشود نه user.
از این تکنیک قبلا نیز استفاده میشود، مثلا در اسم فایل از این کارکترها استفاده میکردند تا فرمت فایل چیز اشتباهی به نظر بیاد. مثلا اسمی که کاربر میبیند CORP_INVOICE_08.14.2011_Pr.phylexe.doc بود و کاربر ممکن بود آن را باز کند چون فرمت doc برای Microsoft word است و مخرب نیست اما در اصل اسم فایل CORP_INVOICE_08.14.2011_Pr.phyldoc.exe بوده و یک فایل مخرب قابل اجرا است.
همچنین از این نوع حمله در NLP نیز استفاده میشود.

مثلا با استفاده از این کارکترها خروجی مترجم این است که به حساب ۴۳۲۱ پول واریز کند اما اصل متن این بوده که به حساب ۱۲۳۴ واریز کند.
یا برای مثال برای دور زدن سیستم شناسایی محتوای Toxic (واقعا معادل فارسی که مفهوم رو برسونه پیدا نکردم:)) میشود از کارکتر U+8 استفاده کرد که معادل Backspace است.
نوع دیگری از حمله که در آن از کارکترهای مشابه استفاده میکنند homoglyphs attack نام دارد. برای مثال paypaI.com خیلی شبیه paypaI.com است اما کارکتر آخری اولی L و کارکتر آخر سایت دوم i است و با این روش میشود فرد را به سایت اشتباهی فرستاد که به آن اعتماد دارد.
یا برای مثال کد زیر را نگاه کنید:

در این کد از شباهت کارکتر H با کارکتر یونانی Н استفاده شده است و وقتی تابع sayНello را صدا میزنیم تابع دوم صدا زده میشود نه تابع اول.
با استفاده از دو حملهای که الان گفته شد میشود کدی زد که با کاری که میکند تفاوت داشته باشد و برای مثال با فرستادن این کد به Supply chain یک محصول میشود باگ امنیتی به آن اضافه کرد و این باگ از چشم کسی که کد را میخواند پنهان است. در واقع این دو نوع حمله تروجان حساب میشوند چون کد به نظر سالمه و واقعا با چشم چیزی که دیده میشود سالم هست. اما در اصل اینطور نیست و رفتار کد با شکلی که دیده میشود متفاوت است.
در جدول زیر میتوانید آسیب پذیری نرمافزارها مختلف را برای این دو نوع حمله ببنید: [3]


منابع
[1]https://en.wikipedia.org/wiki/Trojan_Horse (2021/12/07)
[2]https://us.norton.com/internetsecurity-malware-what-is-a-trojan.html (2021/12/07)
[3]https://www.trojansource.codes/ (2021/12/07)
[4]K. Thompson, “Reflections on trusting trust,” Commun. ACM, vol. 27, no. 8, pp. 761–763, 1984. [Online]. Available: https: //doi.org/10.1145/358198.358210 (2021/12/07)
[5]L. Simon, D. Chisnall, and R. Anderson, “What you get is what you C: Controlling side effects in mainstream C compilers,” in 2018 IEEE European Symposium on Security and Privacy (EuroS&P), Apr. 2018, pp. 1–15. Available: https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=7163211 (2021/12/07)
[6] https://thenewstack.io/npm-attackers-sneak-a-backdoor-into-node-js-deployments-through-dependencies/ (2021/12/07)
[7] https://bertusk.medium.com/discord-token-stealer-discovered-in-pypi-repository-e65ed9c3de06 (2021/12/07)
[8]https://www.csoonline.com/article/3601508/solarwinds-supply-chain-attack-explained-why-organizations-were-not-prepared.html (2021/12/07)