حمله بازگشتی در سالیدیتی زمانی اتفاق می افتد که یک قرارداد هوشمند در میانه اجرای یک تابع، کنترل جریان اجرای برنامه را به یک قرارداد دیگر واگذار کند. این واگذاری معمولاً از طریق فراخوانی تابع یا ارسال اتر به قرارداد دیگر انجام می شود.
اگر در طول اجرای یک تابع، هیچ فراخوانی به قرارداد دیگر یا ارسال اتر صورت نگیرد، کنترل برنامه همچنان در اختیار قرارداد فعلی باقی می ماند و حمله بازگشتی نمی تواند رخ دهد.
به مثال زیر توجه کنید:
1 2 3 4 |
function proxyVote(uint256 voteChoice) external { voteContract.vote(voteChoice); // hands control to voteContract alreadyVoted = true; } |
نکته مهم اینجاست که همیشه به سادگی نمی توان متوجه شد که در حال فراخوانی یک قرارداد دیگر هستید. برای مثال، کد زیر در ظاهر امن به نظر می رسد اما اگر داخل یک قرارداد ERC1155 استفاده شود، در برابر حمله بازگشتی آسیب پذیر خواهد بود:
1 2 3 4 |
function purchaseERC1155NFT() external { _mint(msg.sender, TOKEN_ID, 1, ""); erc20Token.transferFrom(msg.sender, address(this)); } |
mint
می تواند ناامن باشد؟ برای پاسخ به این سوال، بیایید نگاهی به پیاده سازی تابع _mint
در استاندارد ERC1155 از کتابخانه OpenZeppelin بیندازیم(اینجا):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function _mint( address to, uint256 id, uint256 amount, bytes memory data ) internal virtual { require(to != address(0), "ERC1155: mint to the zero address"); address operator = _msgSender(); uint256[] memory ids = _asSingletonArray(id); uint256[] memory amounts = _asSingletonArray(amount); _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); _balances[id][to] += amount; emit TransferSingle(operator, address(0), to, id, amount); _afterTokenTransfer(operator, address(0), to, ids, amounts, data); _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data); } |
کد سالیدیتی ERC1155
تابع _mint
در انتهای اجرای خود، تابعی به نام _doSafeTransferAcceptanceCheck
را فراخوانی می کند. حالا بیایید این تابع را مرحله به مرحله بررسی کنیم تا ببینیم چرا می تواند در معرض حمله بازگشتی (Reentrancy) قرار بگیرد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function _doSafeTransferAcceptanceCheck( address operator, address from, address to, uint256 id, uint256 amount, bytes memory data ) private { if (to.isContract()) { try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { if (response != IERC1155Receiver.onERC1155Received.selector) { revert("ERC1155: ERC1155Receiver rejected tokens"); } } catch Error(string memory reason) { revert(reason); } catch { revert("ERC1155: transfer to non-ERC1155Receiver implementer"); } } } |
کد سالیدیتی IERC1155Receiver
در پیاده سازی ERC1155، تابع _doSafeTransferAcceptanceCheck
در نهایت سعی می کند تابعی به نام onERC1155Received
را روی قرارداد گیرنده فراخوانی کند. این یعنی ما در این نقطه کنترل اجرا را به یک قرارداد خارجی واگذار کرده ایم—و دقیقاً همین واگذاری می تواند راه را برای حمله بازگشتی باز کند.
ابزاری به نام Slither می تواند به صورت خودکار فراخوانی های تابع های خارجی را شناسایی کند. بنابراین، توصیه می شود برای تحلیل امنیتی قراردادها از این ابزار استفاده کنید.
ممکن است این موضوع کمی گیج کننده به نظر برسد، اما اجازه بدهید نکته ای را روشن کنیم: قطعه کدی که تقریباً مشابه مثال قبل است
1 2 3 4 |
function purchaseERC1155NFT() external { _mint(msg.sender, AMOUNT); erc20Token.transferFrom(msg.sender, address(this)); } |
transferFrom
در استاندارد ERC20 نهفته است. در پیاده سازی این تابع در سالیدیتی، هیچ فراخوانی به توابع خارجی انجام نمی شود و بنابراین کنترلی به قرارداد دیگر واگذار نمی گردد. این تفاوت ساختاری، امنیت بیشتری در برابر حملات بازگشتی فراهم می کند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function _transfer( address from, address to, uint256 amount ) internal virtual { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); _beforeTokenTransfer(from, to, amount); uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { _balances[from] = fromBalance - amount; // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by // decrementing then incrementing. _balances[to] += amount; } emit Transfer(from, to, amount); _afterTokenTransfer(from, to, amount); } |
ERC721
safeTransferFrom
_safeMint
نکته گیجکننده این است که واژه “safe” در توابع safeTransferFrom
و _safeMint
به این معناست که بررسی میشود آیا آدرس گیرنده یک قرارداد هوشمند است یا خیر. اگر گیرنده یک قرارداد باشد، تابع onERC721Received
در آن قرارداد فراخوانی میشود.
اما توابع transferFrom
و _mint
چنین بررسی انجام نمیدهند، بنابراین هنگام استفاده از آنها نگرانی بابت حمله بازگشتی (Reentrancy) وجود ندارد.
البته این موضوع به این معنا نیست که نباید از safeTransferFrom
یا _safeMint
استفاده کنید؛ بلکه اگر از این توابع استفاده میکنید، باید از الگوی check-effects یا محافظهای ضد بازگشت (reentrancy guards) برای جلوگیری از حمله بازگشتی استفاده کنید.
در ادامه مثالی ساده از یک تابع mint را میبینید که در آن مهاجم میتواند همه NFTها را برای خود ضرب (mint) کند:
1 2 3 4 5 6 7 8 9 10 11 |
contract FooToken is ERC721 { function mint() external payable { require(msg.value == 0.1 ether); require(!alreadyMinted[msg.sender]); totalSupply++; _safeMint(msg.sender, totalSupply); alreadyMinted[msg.sender] = true; } } |
ERC1155
safeTransferFrom
_mint
safeBatchTransferFrom
_mintBatch
موضوع حتی پیچیدهتر از قبل میشود: در استاندارد ERC1155، تابع _mint
برخلاف _mint
در ERC721 رفتار میکند و بیشتر شبیه به _safeMint
در ERC721 عمل میکند.
به عبارت دیگر، در ERC1155 هیچ تابعی واقعاً “امن” (safe) نیست؛ چون همه توابع، بدون استثنا، با قرارداد گیرنده تعامل برقرار میکنند.
این موضوع بهخودیخود اشتباه طراحی محسوب نمیشود، اما الزاماً به این معناست که شما باید همیشه از الگوی “بررسی–تأثیر–تعامل” (check-effects-interactions) یا محافظ بازگشتی (reentrancy guard) استفاده کنید؛ چیزی که در هر صورت باید در تمام قراردادهای امن رعایت شود.
در ادامه مثالی از یک کد آسیبپذیر ERC1155 را میبینید:
1 2 3 4 5 6 7 8 9 10 11 |
contract FooToken is ERC1155 { function mint(uint256 tokenId) external payable { require(msg.value == 0.1 ether); require(!alreadyMinted[msg.sender]); totalSupplyForTokenId[tokenId]++; _mint(msg.sender, totalSupplyForTokenId[tokenId], 1, ""); alreadyMinted[msg.sender] = true; } } |
ERC223، ERC677، ERC777 و ERC1363
در اینجا نمیتوانیم همه نسخههای پیشنهادی از استاندارد ERC20 را پوشش دهیم.
این واقعیت که در استاندارد اصلی ERC20، توابع transfer
و transferFrom
باعث حمله بازگشتی (reentrancy) نمیشوند، یک مزیت امنیتی محسوب میشود.
اما همین ویژگی باعث مشکلاتی در تجربه کاربری (UX) میشود؛ چرا که قرارداد هوشمند گیرنده نمیتواند تشخیص دهد که توکنی برایش ارسال شده است.
استانداردهایی مثل ERC223، ERC677، ERC777 و ERC1363 برای رفع همین مشکل معرفی شدهاند؛ این استانداردها هنگام دریافت توکن، به قرارداد گیرنده اطلاع میدهند.
با این حال، باید هنگام تعامل با توکن های ناشناخته و غیرقابل اعتماد، محتاط بود. چرا که ممکن است این توکن ها در ظاهر شبیه ERC20 باشند، اما در واقع یکی از این نسخههای توسعه یافته باشند و بتوانند فراخوانی خارجی انجام دهند و حمله بازگشتی را فعال کنند.
در ERC777، فراخوانی خارجی به قرارداد گیرنده، پس از انتقال توکن ها و از طریق تابع _callTokensReceived
انجام میشود. پیادهسازی این بخش را میتوانید در این خط از مخزن OpenZeppelin مشاهده کنید: مشاهده کد در گیتهاب (خط ۴۹۹)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function _callTokensReceived( address operator, address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData, bool requireReceptionAck ) private { address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH); if (implementer != address(0)) { IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); } else if (requireReceptionAck) { require(to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient"); } } |
در اینجا، اگر گیرنده یک قرارداد باشد و پیادهساز tokensReceived
باشد، این تابع مستقیماً روی قرارداد گیرنده اجرا میشود—که همین باعث امکان بروز حمله بازگشتی در صورت نبود محافظ میشود.
در استاندارد ERC1363، برای بهبود تجربه کاربری، انتقال عادی (transfer
) دقیقاً مانند ERC20 رفتار میکند، بنابراین در حالت عادی ریسک حمله بازگشتی ندارد.
اما اگر بخواهیم قرارداد گیرنده را از دریافت توکن مطلع کنیم، باید از تابع transferAndCall
استفاده کنیم که همانند safeTransferFrom
در استانداردهای دیگر عمل میکند.
حملات بازگشتی در ERC777 در دنیای واقعی اتفاق افتادهاند و میتوانند منجر به خسارات فاجعهبار شوند. بنابراین، هنگام طراحی اپلیکیشنی که با توکن های ERC20 مختلف سروکار دارد، نباید بهطور پیشفرض فرض کنید که transfer
و transferFrom
همیشه ایمن و غیرقابل بازگشت هستند.
همیشه منطق امنیتی خود را بر اساس رفتار واقعی توکنها، نه فقط نام استاندارد آنها طراحی کنید.
ارسال اتر (Ether) در سالیدیتی
وقتی شما از دستور address.call("")
برای ارسال اتر استفاده میکنید، کنترل اجرای برنامه را به قرارداد گیرنده واگذار میکنید. همین واگذاری میتواند زمینه ساز حمله بازگشتی (reentrancy) شود.
به مثال کلاسیک زیر توجه کنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
contract FaultyBank { mapping(address => uint256) public balances; function deposit() external payable { balances[msg.sender] += msg.value; } function withdraw() external { msg.sender.call{value: balances[msg.sender]}(""); balances[msg.sender] = 0; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
contract RobTheBank { IFaultyBank private bank; constructor(IFaultyBank _bank) { bank = _bank; } function attack() payable { bank.deposit{value: 1 ether}() bank.withdraw(); } fallback() external payable { if (address(bank).balance >= 1 ether) { bank.withdraw(); // reenterancy attack here } } } |
balances[msg.sender]
بعد از ارسال اتر به صفر میرسد، مهاجم میتواند بارها ۱ اتر برداشت کند (و اتر سایر کاربران را سرقت کند)، تا زمانی که موجودی حساب بانک به کمتر از ۱ اتر برسد.
چگونه transfer
و send
جلوی حمله بازگشتی را میگیرند، و چرا نباید از آنها استفاده کنید؟
در حاشیه بحث امنیت، توابع transfer()
و send()
در ظاهر در برابر حمله بازگشتی ایمن هستند، با اینکه همچنان میتوانند تابعهای fallback
یا receive
را در قرارداد مقصد فعال کنند.
دلیل این ایمنی این است که این دو تابع، فقط ۲۳۰۰ واحد گس (gas) به قرارداد گیرنده ارسال میکنند، که برای اجرای یک حمله بازگشتی کافی نیست.
اما با وجود این مزیت امنیتی، استفاده از این توابع به عنوان یک الگوی طراحی، عموماً توصیه نمیشود.
فرض کنید یک قرارداد هوشمند دارید که باید وام دریافتی از یک قرارداد دیگر را بازپرداخت کند. اگر این بازپرداخت را با transfer
یا send
انجام دهید، قرارداد وامدهنده به دلیل کمبود گس نمیتواند متوجه شود که وام بازپرداخت شده است.
در واقع، پس از رخداد هک معروف DAO در سال ۲۰۱۶ که تقریباً برای کل اکوسیستم اتریوم فاجعهبار بود، طراحان سالیدیتی تصمیم گرفتند توابعی مثل transfer
و send
را ایجاد کنند تا از وقوع مجدد چنین حملاتی جلوگیری کنند.
نکته فنی مهم اینجاست:
وقتی از transfer
یا send
استفاده میکنید، فقط ۲۳۰۰ گس ارسال میشود. طبق قواعد EVM، زمانی که قرارداد گیرنده کمتر از ۲۳۰۰ گس دریافت کند، اجازه ندارد تغییری در متغیرهای ذخیرهشده ایجاد کند (یعنی نمیتواند حالت دائمی تغییر دهد).
به همین دلیل، قرارداد مهاجم حتی اگر تابع fallback
داشته باشد، نمیتواند وضعیت داخلی قرارداد قربانی را تغییر دهد.
مشکل اینجاست که بسیاری از قراردادها عمداً نیاز دارند هنگام دریافت اتر واکنش نشان دهند.
برای مثال، تصور کنید یک پلتفرم وام غیرمتمرکز دارید که باید هنگام دریافت اتر از وامگیرنده، وضعیت وام را به عنوان بازپرداختشده علامتگذاری کند. اما اگر وامگیرنده از transfer
برای بازپرداخت استفاده کند، قرارداد وامدهنده به دلیل محدودیت گس، نمیتواند هیچ تغییری در دادههایش اعمال کند.
ممکن است عجیب به نظر برسد که سالیدیتی توابعی دارد که نباید از آنها استفاده کرد، اما این موضوع بخشی از تکامل درک ما از بهترین شیوههای برنامه نویسی در بلاکچین است.
در ابتدا به نظر میرسید که محدود کردن گس برای جلوگیری از حمله بازگشتی، ایده خوبی است.
اما تجربه نشان داد که نمیتوان گس آینده را پیشبینی کرد و حتی مقدار گس مورد نیاز برای عملیات EVM ممکن است در نسخههای آینده تغییر کند. به همین دلیل، کدنویسی با گس ثابت (hardcoded gas) اکنون به عنوان یک الگوی نادرست شناخته میشود.
بازگشتپذیری بین توابع – بازگشتپذیری الزاماً نباید به همان تابع اولیه برگردد
در حملههای بازگشتی، بسیاری تصور میکنند که مهاجم باید حتماً به همان تابعی بازگردد که ابتدا آن را فراخوانی کرده است. اما در واقعیت، اگر قرارداد قربانی در زمان نامناسبی کنترل اجرا را به یک قرارداد خارجی واگذار کند، مهاجم میتواند به تابعی دیگر از همان قرارداد وارد شود که باز هم در برابر بازگشتپذیری آسیبپذیر است.
این الگو به نام بازگشتپذیری متقابل یا بازگشتپذیری بین توابع (Cross-function Reentrancy) شناخته میشود. برخی مهندسان از اصطلاح Trampoline یا بازگشت متقابل (Mutual Recursion) نیز استفاده میکنند.
در ادامه، مثالی از یک قرارداد آسیبپذیر به این نوع حمله را بررسی میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
contract CrossFunctionReentrancyVulnerable { // don't allow people to swap more than once every 24 hours mapping(address => uint256) public lastSwap; function swapAForB() { require(block.timestamp - lastSwap[msg.sender] >= 1 days); governanceTokenERC20.mint(msg.sender, AMOUNT); tokenAerc777.transferFrom(msg.sender, address(this)); tokenBerc777.transferFrom(address(this), msg.sender); lastSwap[msg.sender] = block.timestamp; } function swapBForA() { require(block.timestamp - lastSwap[msg.sender] >= 1 days); governanceTokenERC20.mint(msg.sender, AMOUNT); tokenBerc777.transferFrom(msg.sender, address(this)); tokenAerc777.transferFrom(address(this), msg.sender); lastSwap[msg.sender] = block.timestamp; } } |
در کد بالا، کاربران میتوانند توکن A را با B تعویض کنند (و بالعکس) و در ازای آن، توکنهای حاکمیتی دریافت کنند. با این حال، قرارداد سعی میکند با محدود کردن کاربران به انجام سواپ در هر ۲۴ ساعت، از ضرب شدن بیش از حد سریع توکنهای حاکمیتی جلوگیری کند.
توکنهای ERC777، همانطور که قبلاً اشاره شد، قابلیت بازگشتپذیری دارند. اما اجرای یک حمله بازگشتی ساده روی یکی از توابع کارآمد نخواهد بود، چون مهاجم در نهایت موجودی توکن A یا B خود را از دست میدهد.
با این حال، اگر مهاجم بارها توکن A را با B تعویض کند، میتواند تمام توکنهای حاکمیتی را برای خود ضرب کند.
در این مثال، ما توکن حاکمیتی را از نوع ERC20 قرار دادهایم، بنابراین مهاجم نمیتواند به همان تابع دوباره وارد شود. اما زمانی که transferFrom(address(this), msg.sender)
اجرا میشود، مهاجم پیش از آنکه مقدار lastSwap
بهروزرسانی شود، کنترل اجرا را در اختیار میگیرد.
بازگشتپذیری فقطخواندنی که با نام بازگشتپذیری بین قراردادی نیز شناخته میشود
بازگشتپذیری فقطخواندنی در سال ۲۰۲۲ وارد ذهن توسعهدهندگان شد، زمانی که یکی از سخنرانیهای کنفرانس Devcon اتریوم، آسیبپذیری را در پروژه Curve Finance شرح داد.
در واقع، بازگشتپذیری فقطخواندنی همان آسیبپذیری بازگشتپذیری بین قراردادی است، اما با نامی جدید.
این نوع حمله زمانی رخ میدهد که قرارداد Foo
به وضعیت (state) قرارداد Bar
متکی باشد، و Bar
در میانه تراکنش نتواند اطلاعات درستی از وضعیت خود ارائه دهد. در چنین شرایطی، مهاجم میتواند Foo
را فریب دهد.
مثال حمله در پروژه Curve:
در حملهای که به Curve نسبت داده میشود، خود Curve مستقیماً مورد سوءاستفاده قرار نگرفت، بلکه قراردادهایی که به وضعیت Curve وابسته بودند قربانی شدند. روند حمله به صورت زیر بود:
-
مهاجم مقداری اتر و توکنهای ERC20 را به Curve واریز میکند. Curve در ازای آن توکن نقدینگی (liquidity token) صادر میکند.
-
مهاجم با سوزاندن توکنهای نقدینگی، درخواست برداشت میدهد.
-
Curve ابتدا اتر را به مهاجم بازمیگرداند و سپس قرار است توکنهای ERC20 را بفرستد.
-
بهمحض ارسال اتر، مهاجم مجدداً کنترل اجرای برنامه را به دست میگیرد و در همین لحظه، در یک قرارداد دیگر معاملهای انجام میدهد.
-
این قرارداد دوم برای محاسبه قیمت، از Curve اطلاعات قیمت بین توکن نقدینگی، اتر و توکن ERC20 را میخواهد. اما چون توکنهای نقدینگی سوزانده شدهاند و اتر بازگشته، ولی ERC20ها هنوز منتقل نشدهاند، محاسبه قیمتها در این لحظه دچار خطا میشود.
-
تراکنش ادامه پیدا میکند و Curve در انتها ERC20ها را نیز ارسال میکند. حالا قیمتها به حالت عادی بازمیگردند—اما فایدهای ندارد، چون معاملهی ناعادلانه قبلاً انجام شده است.
این نوع حمله شباهت زیادی به حمله وام سریع (Flash Loan) دارد و در بسیاری از موارد، برای موفقیت به یک فلشلون نیاز دارد.
راهکارهای دفاعی در برابر بازگشتپذیری فقطخواندنی
برای مقابله با این نوع حمله دو راهکار کلیدی وجود دارد:
-
استفاده از قفل بازگشتی (reentrancy lock) برای توابع view یا public کردن آن
تابع نمایشی (view) که قیمت را گزارش میدهد، در لحظه برداشت جزئی نقدینگی در حالت نادرستی از وضعیت قرار دارد. پس صرافی میتواند اجازه استفاده از این تابع را در حین برداشت نقدینگی مسدود کند. -
اجازه بررسی عمومی وضعیت قفل بازگشتی
اگر قفل بازگشتی بهصورت عمومی قابل بررسی باشد، هر اپلیکیشنی که به دادههای آن تابع نمایشی متکی است، میتواند ابتدا بررسی کند که آیا برداشت نقدینگی در حال انجام است یا خیر.
در مثال Curve، اگر اتر ارسال شده اما توکنهای ERC20 هنوز منتقل نشدهاند، قفل بازگشتی همچنان فعال خواهد بود—چون تابع برداشت کامل اجرا نشده است.
نکته پایانی
این آسیبپذیری فقط در صورتی رخ میدهد که ترتیب ارسال داراییها بتواند اجرای توابع خارجی را فعال کند.
در مثال Curve، ارسال اتر پیش از ERC20 چنین شرایطی را فراهم کرد. اما همین سناریو ممکن است با استفاده از توکنهایی مثل ERC777 نیز تکرار شود، چرا که این توکنها نیز میتوانند باعث واگذاری کنترل به قرارداد گیرنده شوند.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۷ خرداد ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++C
- ADO.NET
- Adobe Flash
- Ajax
- AngularJS
- apache
- ARM
- Asp.Net
- ASP.NET MVC
- AVR
- Bootstrap
- CCNA
- CCNP
- CMD
- CSS
- Dreameaver
- EntityFramework
- HTML
- IOS
- jquery
- Linq
- Mysql
- Oracle
- PHP
- PHPMyAdmin
- Rational Rose
- silver light
- SQL Server
- Stimulsoft Reports
- Telerik
- UML
- VB.NET&VB6
- WPF
- Xml
- آموزش های پروژه محور
- اتوکد
- الگوریتم تقریبی
- امنیت
- اندروید
- اندروید استودیو
- بک ترک
- بیسیک فور اندروید
- پایتون
- جاوا
- جاوا اسکریپت
- جوملا
- دلفی
- دوره آموزش Go
- دوره های رایگان پیشنهادی
- زامارین
- سئو
- ساخت CMS
- سی شارپ
- شبکه و مجازی سازی
- طراحی الگوریتم
- طراحی بازی
- طراحی وب
- فتوشاپ
- فریم ورک codeigniter
- فلاتر
- کانستراکت
- کریستال ریپورت
- لاراول
- معماری کامپیوتر
- مهندسی اینترنت
- هوش مصنوعی
- یونیتی
- کتاب های آموزشی
- Android
- ASP.NET
- AVR
- LINQ
- php
- Workflow
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس