مرکزی مواد پر جائیں

ایک کنٹریکٹ کی ریورس انجینئرنگ

evm
آپ کوڈز
ایڈوانسڈ
اوری پومرانٹز
۳۰ دسمبر، ۲۰۲۱
38 منٹ کا مطالعہ
صفحہ میں ترمیم کریں (opens in a new tab)

تعارف

بلاک چین پر کوئی راز نہیں ہوتے، جو کچھ بھی ہوتا ہے وہ مستقل، قابل تصدیق اور عوامی طور پر دستیاب ہوتا ہے۔ مثالی طور پر، کنٹریکٹس کا سورس کوڈ Etherscan پر شائع اور تصدیق شدہ ہونا چاہیے (opens in a new tab)۔ تاہم، ہمیشہ ایسا نہیں ہوتا (opens in a new tab)۔ اس مضمون میں آپ سیکھیں گے کہ سورس کوڈ کے بغیر کسی کنٹریکٹ کو دیکھ کر کنٹریکٹس کی ریورس انجینئرنگ کیسے کی جاتی ہے، 0x2510c039cc3b061d79e564b38836da87e31b342f (opens in a new tab)۔

ریورس کمپائلرز موجود ہیں، لیکن وہ ہمیشہ قابل استعمال نتائج (opens in a new tab) پیدا نہیں کرتے۔ اس مضمون میں آپ سیکھیں گے کہ دستی طور پر ریورس انجینئرنگ کیسے کی جائے اور آپ کوڈز (opens in a new tab) سے کنٹریکٹ کو کیسے سمجھا جائے، نیز ڈی کمپائلر کے نتائج کی تشریح کیسے کی جائے۔

اس مضمون کو سمجھنے کے قابل ہونے کے لیے آپ کو پہلے سے ہی EVM کی بنیادی باتوں کا علم ہونا چاہیے، اور کم از کم EVM اسمبلر سے کچھ حد تک واقف ہونا چاہیے۔ آپ ان موضوعات کے بارے میں یہاں پڑھ سکتے ہیں (opens in a new tab)۔

قابل عمل کوڈ تیار کریں

آپ کنٹریکٹ کے لیے Etherscan پر جا کر، کنٹریکٹ ٹیب پر کلک کر کے اور پھر Switch to Opcodes View پر کلک کر کے آپ کوڈز حاصل کر سکتے ہیں۔ آپ کو ایک ایسا ویو ملے گا جس میں ہر لائن پر ایک آپ کوڈ ہوگا۔

Opcode View from Etherscan

تاہم، jumps کو سمجھنے کے لیے، آپ کو یہ جاننے کی ضرورت ہے کہ کوڈ میں ہر آپ کوڈ کہاں واقع ہے۔ ایسا کرنے کا ایک طریقہ یہ ہے کہ ایک Google Spreadsheet کھولیں اور آپ کوڈز کو کالم C میں پیسٹ کریں۔ آپ اس پہلے سے تیار شدہ اسپریڈشیٹ کی کاپی بنا کر درج ذیل مراحل کو چھوڑ سکتے ہیں (opens in a new tab)۔

اگلا قدم کوڈ کے درست مقامات حاصل کرنا ہے تاکہ ہم jumps کو سمجھ سکیں۔ ہم آپ کوڈ کا سائز کالم B میں، اور مقام (ہیکسا ڈیسیمل میں) کالم A میں رکھیں گے۔ سیل B1 میں یہ فنکشن ٹائپ کریں اور پھر اسے کالم B کے باقی حصوں کے لیے کاپی اور پیسٹ کریں، جب تک کہ کوڈ ختم نہ ہو جائے۔ ایسا کرنے کے بعد آپ کالم B کو چھپا سکتے ہیں۔

=1+IF(REGEXMATCH(C1,"PUSH"),REGEXEXTRACT(C1,"PUSH(\d+)"),0)

سب سے پہلے یہ فنکشن خود آپ کوڈ کے لیے ایک بائٹ کا اضافہ کرتا ہے، اور پھر PUSH کو تلاش کرتا ہے۔ Push آپ کوڈز خاص ہوتے ہیں کیونکہ انہیں پش کی جانے والی ویلیو کے لیے اضافی بائٹس کی ضرورت ہوتی ہے۔ اگر آپ کوڈ PUSH ہے، تو ہم بائٹس کی تعداد نکالتے ہیں اور اسے شامل کرتے ہیں۔

A1 میں پہلا آف سیٹ، صفر رکھیں۔ پھر، A2 میں، یہ فنکشن رکھیں اور اسے دوبارہ کالم A کے باقی حصوں کے لیے کاپی اور پیسٹ کریں:

=dec2hex(hex2dec(A1)+B1)

ہمیں اس فنکشن کی ضرورت ہے تاکہ یہ ہمیں ہیکسا ڈیسیمل ویلیو دے سکے کیونکہ jumps سے پہلے پش کی جانے والی ویلیوز (JUMP اور JUMPI) ہمیں ہیکسا ڈیسیمل میں دی جاتی ہیں۔

انٹری پوائنٹ (0x00)

کنٹریکٹس ہمیشہ پہلی بائٹ سے ایگزیکیوٹ ہوتے ہیں۔ یہ کوڈ کا ابتدائی حصہ ہے:

آفسیٹآپ کوڈاسٹیک (آپ کوڈ کے بعد)
0PUSH1 0x800x80
2PUSH1 0x400x40, 0x80
4MSTOREخالی
5PUSH1 0x040x04
7CALLDATASIZECALLDATASIZE 0x04
8LTCALLDATASIZE<4
9PUSH2 0x005e0x5E CALLDATASIZE<4
CJUMPIخالی

یہ کوڈ دو کام کرتا ہے:

  1. میموری لوکیشنز 0x40-0x5F پر 0x80 کو 32 بائٹ ویلیو کے طور پر لکھیں (0x80 کو 0x5F میں اسٹور کیا جاتا ہے، اور 0x40-0x5E سب صفر ہیں)۔
  2. کال ڈیٹا کا سائز پڑھیں۔ عام طور پر ایتھریم کنٹریکٹ کا کال ڈیٹا اے بی آئی (ایپلیکیشن بائنری انٹرفیس) (opens in a new tab) کی پیروی کرتا ہے، جس کے لیے فنکشن سلیکٹر کے لیے کم از کم چار بائٹس درکار ہوتے ہیں۔ اگر کال ڈیٹا کا سائز چار سے کم ہے، تو 0x5E پر جمپ کریں۔

Flowchart for this portion

0x5E پر ہینڈلر (نان-اے بی آئی کال ڈیٹا کے لیے)

آفسیٹآپ کوڈ
5EJUMPDEST
5FCALLDATASIZE
60PUSH2 0x007c
63JUMPI

یہ اسنپٹ JUMPDEST کے ساتھ شروع ہوتا ہے۔ ای وی ایم (ایتھریم ورچوئل مشین) پروگرامز ایک ایکسیپشن تھرو کرتے ہیں اگر آپ کسی ایسے آپ کوڈ پر جمپ کرتے ہیں جو JUMPDEST نہیں ہے۔ پھر یہ CALLDATASIZE کو دیکھتا ہے، اور اگر یہ "درست" (یعنی صفر نہیں) ہے تو 0x7C پر جمپ کرتا ہے۔ ہم اس پر نیچے بات کریں گے۔

آفسیٹآپ کوڈاسٹیک (آپ کوڈ کے بعد)
64CALLVALUEکال کے ذریعے فراہم کردہ ۔ Solidity میں اسے msg.value کہا جاتا ہے
65PUSH1 0x066 CALLVALUE
67PUSH1 0x000 6 CALLVALUE
69DUP3CALLVALUE 0 6 CALLVALUE
6ADUP36 CALLVALUE 0 6 CALLVALUE
6BSLOADStorage[6] CALLVALUE 0 6 CALLVALUE

لہذا جب کوئی کال ڈیٹا نہیں ہوتا ہے تو ہم Storage[6] کی ویلیو پڑھتے ہیں۔ ہم ابھی تک نہیں جانتے کہ یہ ویلیو کیا ہے، لیکن ہم ان ٹرانزیکشنز کو تلاش کر سکتے ہیں جو کنٹریکٹ کو بغیر کسی کال ڈیٹا کے موصول ہوئی ہیں۔ وہ ٹرانزیکشنز جو بغیر کسی کال ڈیٹا (اور اس وجہ سے کوئی میتھڈ نہیں) کے صرف ETH کی منتقلی کرتی ہیں، ان کا Etherscan میں میتھڈ Transfer ہوتا ہے۔ درحقیقت، کنٹریکٹ کو موصول ہونے والی سب سے پہلی ٹرانزیکشن (opens in a new tab) ایک منتقلی ہے۔

اگر ہم اس ٹرانزیکشن میں دیکھیں اور Click to see More پر کلک کریں، تو ہم دیکھتے ہیں کہ کال ڈیٹا، جسے ان پٹ ڈیٹا کہا جاتا ہے، واقعی خالی ہے (0x)۔ یہ بھی نوٹ کریں کہ ویلیو 1.559 ETH ہے، جو بعد میں متعلقہ ہوگی۔

The call data is empty

اس کے بعد، State ٹیب پر کلک کریں اور اس کنٹریکٹ کو پھیلائیں جسے ہم ریورس انجینئر کر رہے ہیں (0x2510...)۔ آپ دیکھ سکتے ہیں کہ ٹرانزیکشن کے دوران Storage[6] تبدیل ہوا تھا، اور اگر آپ Hex کو Number میں تبدیل کرتے ہیں، تو آپ دیکھیں گے کہ یہ 1,559,000,000,000,000,000 ہو گیا، جو wei میں منتقل کی گئی ویلیو ہے (میں نے وضاحت کے لیے کوما شامل کیے ہیں)، جو اگلی کنٹریکٹ ویلیو کے مساوی ہے۔

Storage[6] میں تبدیلی

اگر ہم اسی عرصے کی دیگر Transfer ٹرانزیکشنز (opens in a new tab) کی وجہ سے ہونے والی حالت کی تبدیلیوں کو دیکھیں تو ہمیں معلوم ہوتا ہے کہ Storage[6] نے کچھ عرصے تک کنٹریکٹ کی ویلیو کو ٹریک کیا۔ فی الحال ہم اسے Value* کہیں گے۔ ایسٹرسک (*) ہمیں یاد دلاتا ہے کہ ہم ابھی تک نہیں جانتے کہ یہ ویری ایبل کیا کرتا ہے، لیکن یہ صرف کنٹریکٹ کی ویلیو کو ٹریک کرنے کے لیے نہیں ہو سکتا کیونکہ اسٹوریج استعمال کرنے کی کوئی ضرورت نہیں ہے، جو کہ بہت مہنگا ہے، جب آپ ADDRESS BALANCE کا استعمال کرتے ہوئے اپنے اکاؤنٹ کا بیلنس حاصل کر سکتے ہیں۔ پہلا آپ کوڈ کنٹریکٹ کا اپنا پتہ پش کرتا ہے۔ دوسرا اسٹیک کے اوپری حصے پر موجود پتہ پڑھتا ہے اور اسے اس پتے کے بیلنس سے بدل دیتا ہے۔

آفسیٹآپ کوڈاسٹیک
6CPUSH2 0x00750x75 Value* CALLVALUE 0 6 CALLVALUE
6FSWAP2CALLVALUE Value* 0x75 0 6 CALLVALUE
70SWAP1Value* CALLVALUE 0x75 0 6 CALLVALUE
71PUSH2 0x01a70x01A7 Value* CALLVALUE 0x75 0 6 CALLVALUE
74JUMP

ہم جمپ ڈیسٹینیشن پر اس کوڈ کو ٹریس کرنا جاری رکھیں گے۔

آفسیٹآپ کوڈاسٹیک
1A7JUMPDESTValue* CALLVALUE 0x75 0 6 CALLVALUE
1A8PUSH1 0x000x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1AADUP3CALLVALUE 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1ABNOT2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE

NOT بٹ وائز ہے، لہذا یہ کال ویلیو میں ہر بٹ کی ویلیو کو ریورس کر دیتا ہے۔

آفسیٹآپ کوڈاسٹیک
1ACDUP3Value* 2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1ADGTValue*>2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1AEISZEROValue*<=2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1AFPUSH2 0x01df0x01DF Value*<=2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1B2JUMPI

ہم جمپ کرتے ہیں اگر Value* 2^256-CALLVALUE-1 سے چھوٹا یا اس کے برابر ہے۔ یہ اوور فلو کو روکنے کے لیے لاجک کی طرح لگتا ہے۔ اور واقعی، ہم دیکھتے ہیں کہ چند بے معنی آپریشنز کے بعد (مثال کے طور پر، میموری میں لکھنا ڈیلیٹ ہونے والا ہے) آفسیٹ 0x01DE پر کنٹریکٹ ریورٹ ہو جاتا ہے اگر اوور فلو کا پتہ چلتا ہے، جو کہ ایک عام رویہ ہے۔

نوٹ کریں کہ اس طرح کا اوور فلو انتہائی ناممکن ہے، کیونکہ اس کے لیے کال ویلیو پلس Value* کو 2^256 wei کے برابر ہونا درکار ہوگا، جو تقریباً 10^59 ETH بنتا ہے۔ لکھتے وقت، کل ETH سپلائی دو سو ملین سے کم ہے (opens in a new tab)۔

آفسیٹآپ کوڈاسٹیک
1DFJUMPDEST0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1E0POPValue* CALLVALUE 0x75 0 6 CALLVALUE
1E1ADDValue*+CALLVALUE 0x75 0 6 CALLVALUE
1E2SWAP10x75 Value*+CALLVALUE 0 6 CALLVALUE
1E3JUMP

اگر ہم یہاں پہنچ گئے ہیں، تو Value* + CALLVALUE حاصل کریں اور آفسیٹ 0x75 پر جمپ کریں۔

آفسیٹآپ کوڈاسٹیک
75JUMPDESTValue*+CALLVALUE 0 6 CALLVALUE
76SWAP10 Value*+CALLVALUE 6 CALLVALUE
77SWAP26 Value*+CALLVALUE 0 CALLVALUE
78SSTORE0 CALLVALUE

اگر ہم یہاں پہنچتے ہیں (جس کے لیے کال ڈیٹا کا خالی ہونا ضروری ہے) تو ہم Value* میں کال ویلیو شامل کرتے ہیں۔ یہ اس بات سے مطابقت رکھتا ہے جو ہم کہتے ہیں کہ Transfer ٹرانزیکشنز کرتی ہیں۔

آفسیٹآپ کوڈ
79POP
7APOP
7BSTOP

آخر میں، اسٹیک کو صاف کریں (جو ضروری نہیں ہے) اور ٹرانزیکشن کے کامیاب اختتام کا سگنل دیں۔

مختصراً، یہاں ابتدائی کوڈ کے لیے ایک فلو چارٹ ہے۔

Entry point flowchart

0x7C پر ہینڈلر

میں نے جان بوجھ کر ہیڈنگ میں یہ نہیں لکھا کہ یہ ہینڈلر کیا کرتا ہے۔ مقصد آپ کو یہ سکھانا نہیں ہے کہ یہ مخصوص کنٹریکٹ کیسے کام کرتا ہے، بلکہ یہ سکھانا ہے کہ کنٹریکٹس کو ریورس انجینئر کیسے کیا جاتا ہے۔ آپ بھی اسی طرح سیکھیں گے جیسے میں نے سیکھا، یعنی کوڈ کی پیروی کر کے۔

ہم یہاں کئی جگہوں سے آتے ہیں:

  • اگر 1, 2, یا 3 بائٹس کا کال ڈیٹا موجود ہو (آفسیٹ 0x63 سے)
  • اگر میتھڈ کے دستخط نامعلوم ہوں (آفسیٹس 0x42 اور 0x5D سے)
آفسیٹآپ کوڈاسٹیک
7CJUMPDEST
7DPUSH1 0x000x00
7FPUSH2 0x009d0x9D 0x00
82PUSH1 0x030x03 0x9D 0x00
84SLOADStorage[3] 0x9D 0x00

یہ ایک اور اسٹوریج سیل ہے، جو مجھے کسی بھی ٹرانزیکشنز میں نہیں مل سکا اس لیے یہ جاننا مشکل ہے کہ اس کا کیا مطلب ہے۔ نیچے دیا گیا کوڈ اسے مزید واضح کر دے گا۔

آفسیٹآپ کوڈاسٹیک
85PUSH20 0xffffffffffffffffffffffffffffffffffffffff0xff....ff Storage[3] 0x9D 0x00
9AANDStorage[3]-as-address 0x9D 0x00

یہ آپ کوڈز اس ویلیو کو جو ہم Storage[3] سے پڑھتے ہیں، 160 بٹس تک مختصر کر دیتے ہیں، جو کہ ایک ایتھریم کے پتے کی لمبائی ہے۔

آفسیٹآپ کوڈاسٹیک
9BSWAP10x9D Storage[3]-as-address 0x00
9CJUMPStorage[3]-as-address 0x00

یہ جمپ غیر ضروری ہے، کیونکہ ہم اگلے آپ کوڈ پر جا رہے ہیں۔ یہ کوڈ گیس کے لحاظ سے اتنا موثر نہیں ہے جتنا اسے ہونا چاہیے تھا۔

آفسیٹآپ کوڈاسٹیک
9DJUMPDESTStorage[3]-as-address 0x00
9ESWAP10x00 Storage[3]-as-address
9FPOPStorage[3]-as-address
A0PUSH1 0x400x40 Storage[3]-as-address
A2MLOADMem[0x40] Storage[3]-as-address

کوڈ کے بالکل شروع میں ہم نے Mem[0x40] کو 0x80 پر سیٹ کیا تھا۔ اگر ہم بعد میں 0x40 کو تلاش کریں، تو ہم دیکھتے ہیں کہ ہم اسے تبدیل نہیں کرتے - لہذا ہم فرض کر سکتے ہیں کہ یہ 0x80 ہے۔

آفسیٹآپ کوڈاسٹیک
A3CALLDATASIZECALLDATASIZE 0x80 Storage[3]-as-address
A4PUSH1 0x000x00 CALLDATASIZE 0x80 Storage[3]-as-address
A6DUP30x80 0x00 CALLDATASIZE 0x80 Storage[3]-as-address
A7CALLDATACOPY0x80 Storage[3]-as-address

تمام کال ڈیٹا کو میموری میں کاپی کریں، جس کی شروعات 0x80 سے ہوتی ہے۔

آفسیٹآپ کوڈاسٹیک
A8PUSH1 0x000x00 0x80 Storage[3]-as-address
AADUP10x00 0x00 0x80 Storage[3]-as-address
ABCALLDATASIZECALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address
ACDUP40x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address
ADDUP6Storage[3]-as-address 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address
AEGASGAS Storage[3]-as-address 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address
AFDELEGATE_CALL

اب چیزیں بہت زیادہ واضح ہیں۔ یہ کنٹریکٹ ایک پراکسی (opens in a new tab) کے طور پر کام کر سکتا ہے، جو اصل کام کرنے کے لیے Storage[3] میں موجود پتے کو کال کرتا ہے۔ DELEGATE_CALL ایک الگ کنٹریکٹ کو کال کرتا ہے، لیکن اسی اسٹوریج میں رہتا ہے۔ اس کا مطلب یہ ہے کہ ڈیلیگیٹڈ کنٹریکٹ، جس کے لیے ہم ایک پراکسی ہیں، اسی اسٹوریج اسپیس تک رسائی حاصل کرتا ہے۔ کال کے لیے پیرامیٹرز یہ ہیں:

  • گیس: تمام باقی ماندہ گیس
  • کال کیا گیا پتہ: Storage[3]-as-address
  • کال ڈیٹا: CALLDATASIZE بائٹس جو 0x80 سے شروع ہوتی ہیں، جہاں ہم نے اصل کال ڈیٹا رکھا تھا
  • ریٹرن ڈیٹا: کوئی نہیں (0x00 - 0x00) ہم ریٹرن ڈیٹا دیگر ذرائع سے حاصل کریں گے (نیچے دیکھیں)
آفسیٹآپ کوڈاسٹیک
B0RETURNDATASIZERETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
B1DUP1RETURNDATASIZE RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
B2PUSH1 0x000x00 RETURNDATASIZE RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
B4DUP50x80 0x00 RETURNDATASIZE RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
B5RETURNDATACOPYRETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address

یہاں ہم تمام ریٹرن ڈیٹا کو میموری بفر میں کاپی کرتے ہیں جس کی شروعات 0x80 سے ہوتی ہے۔

آفسیٹآپ کوڈاسٹیک
B6DUP2(((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
B7DUP1(((call success/failure))) (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
B8ISZERO(((did the call fail))) (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
B9PUSH2 0x00c00xC0 (((did the call fail))) (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
BCJUMPI(((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
BDDUP2RETURNDATASIZE (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
BEDUP50x80 RETURNDATASIZE (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
BFRETURN

لہذا کال کے بعد ہم ریٹرن ڈیٹا کو بفر 0x80 - 0x80+RETURNDATASIZE میں کاپی کرتے ہیں، اور اگر کال کامیاب ہو جاتی ہے تو ہم بالکل اسی بفر کے ساتھ RETURN کرتے ہیں۔

DELEGATECALL ناکام ہو گیا

اگر ہم یہاں، 0xC0 پر پہنچتے ہیں، تو اس کا مطلب ہے کہ جس کنٹریکٹ کو ہم نے کال کیا تھا وہ ریورٹ ہو گیا۔ چونکہ ہم اس کنٹریکٹ کے لیے صرف ایک پراکسی ہیں، ہم وہی ڈیٹا واپس کرنا چاہتے ہیں اور ساتھ ہی ریورٹ بھی کرنا چاہتے ہیں۔

آفسیٹآپ کوڈاسٹیک
C0JUMPDEST(((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
C1DUP2RETURNDATASIZE (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
C2DUP50x80 RETURNDATASIZE (((call success/failure))) RETURNDATASIZE (((call success/failure))) 0x80 Storage[3]-as-address
C3REVERT

لہذا ہم اسی بفر کے ساتھ REVERT کرتے ہیں جسے ہم نے پہلے RETURN کے لیے استعمال کیا تھا: 0x80 - 0x80+RETURNDATASIZE

Call to proxy flowchart

ABI کالز

اگر کال ڈیٹا کا سائز چار بائٹس یا اس سے زیادہ ہے تو یہ ایک درست ABI کال ہو سکتی ہے۔

آفسیٹآپ کوڈاسٹیک
DPUSH1 0x000x00
FCALLDATALOAD(((کال ڈیٹا کا پہلا لفظ (256 bits))))
10PUSH1 0xe00xE0 (((کال ڈیٹا کا پہلا لفظ (256 bits))))
12SHR(((کال ڈیٹا کے پہلے 32 bits (4 bytes))))

Etherscan ہمیں بتاتا ہے کہ 1C ایک نامعلوم آپ کوڈ ہے، کیونکہ اسے Etherscan کے اس فیچر کو بنانے کے بعد شامل کیا گیا تھا (opens in a new tab) اور انہوں نے اسے اپ ڈیٹ نہیں کیا ہے۔ ایک اپ ٹو ڈیٹ آپ کوڈ ٹیبل (opens in a new tab) ہمیں دکھاتا ہے کہ یہ شفٹ رائٹ (shift right) ہے۔

آفسیٹآپ کوڈاسٹیک
13DUP1(((کال ڈیٹا کے پہلے 32 bits (4 bytes)))) (((کال ڈیٹا کے پہلے 32 bits (4 bytes))))
14PUSH4 0x3cd8045e0x3CD8045E (((کال ڈیٹا کے پہلے 32 bits (4 bytes)))) (((کال ڈیٹا کے پہلے 32 bits (4 bytes))))
19GT0x3CD8045E>first-32-bits-of-the-call-data (((کال ڈیٹا کے پہلے 32 bits (4 bytes))))
1APUSH2 0x00430x43 0x3CD8045E>first-32-bits-of-the-call-data (((کال ڈیٹا کے پہلے 32 bits (4 bytes))))
1DJUMPI(((کال ڈیٹا کے پہلے 32 bits (4 bytes))))

میتھڈ کے دستخط کے میچنگ ٹیسٹس کو اس طرح دو حصوں میں تقسیم کرنے سے اوسطاً آدھے ٹیسٹس کی بچت ہوتی ہے۔ وہ کوڈ جو اس کے فوراً بعد آتا ہے اور 0x43 میں موجود کوڈ اسی پیٹرن کی پیروی کرتے ہیں: کال ڈیٹا کے پہلے 32 bits کو DUP1 کریں، PUSH4 (((method signature> کریں، برابری چیک کرنے کے لیے EQ چلائیں، اور پھر اگر میتھڈ کے دستخط میچ کر جائیں تو JUMPI کریں۔ یہاں میتھڈ کے دستخط، ان کے پتے، اور اگر معلوم ہو تو متعلقہ میتھڈ کی تعریف (opens in a new tab) دی گئی ہے:

میتھڈمیتھڈ کے دستخطجمپ کرنے کے لیے آفسیٹ
splitter() (opens in a new tab)0x3cd8045e0x0103
???0x81e580d30x0138
currentWindow() (opens in a new tab)0xba0bafb40x0158
???0x1f1358230x00C4
merkleRoot() (opens in a new tab)0x2eb4a7ab0x00ED

اگر کوئی میچ نہیں ملتا، تو کوڈ 0x7C پر موجود پراکسی ہینڈلر پر جمپ کر جاتا ہے، اس امید کے ساتھ کہ جس کنٹریکٹ کے لیے ہم پراکسی ہیں اس میں کوئی میچ موجود ہو۔

ABI calls flowchart

splitter()

آفسیٹآپ کوڈاسٹیک
103JUMPDEST
104CALLVALUECALLVALUE
105DUP1CALLVALUE CALLVALUE
106ISZEROCALLVALUE==0 CALLVALUE
107PUSH2 0x010f0x010F CALLVALUE==0 CALLVALUE
10AJUMPICALLVALUE
10BPUSH1 0x000x00 CALLVALUE
10DDUP10x00 0x00 CALLVALUE
10EREVERT

یہ فنکشن سب سے پہلے یہ چیک کرتا ہے کہ کال نے کوئی ETH نہیں بھیجا ہے۔ یہ فنکشن payable (opens in a new tab) نہیں ہے۔ اگر کسی نے ہمیں ETH بھیجا ہے تو یہ یقیناً ایک غلطی ہوگی اور ہم REVERT کرنا چاہتے ہیں تاکہ وہ ETH ایسی جگہ نہ پھنس جائے جہاں سے وہ اسے واپس نہ لے سکیں۔

آفسیٹآپ کوڈاسٹیک
10FJUMPDEST
110POP
111PUSH1 0x030x03
113SLOAD(((Storage[3] یعنی وہ کنٹریکٹ جس کے لیے ہم پراکسی ہیں)))
114PUSH1 0x400x40 (((Storage[3] یعنی وہ کنٹریکٹ جس کے لیے ہم پراکسی ہیں)))
116MLOAD0x80 (((Storage[3] یعنی وہ کنٹریکٹ جس کے لیے ہم پراکسی ہیں)))
117PUSH20 0xffffffffffffffffffffffffffffffffffffffff0xFF...FF 0x80 (((Storage[3] یعنی وہ کنٹریکٹ جس کے لیے ہم پراکسی ہیں)))
12CSWAP10x80 0xFF...FF (((Storage[3] یعنی وہ کنٹریکٹ جس کے لیے ہم پراکسی ہیں)))
12DSWAP2(((Storage[3] یعنی وہ کنٹریکٹ جس کے لیے ہم پراکسی ہیں))) 0xFF...FF 0x80
12EANDProxyAddr 0x80
12FDUP20x80 ProxyAddr 0x80
130MSTORE0x80

اور اب 0x80 میں پراکسی کا پتہ موجود ہے۔

آفسیٹآپ کوڈاسٹیک
131PUSH1 0x200x20 0x80
133ADD0xA0
134PUSH2 0x00e40xE4 0xA0
137JUMP0xA0

E4 کوڈ

یہ پہلی بار ہے جب ہم ان لائنوں کو دیکھ رہے ہیں، لیکن یہ دیگر میتھڈز کے ساتھ شیئر کی گئی ہیں (نیچے دیکھیں)۔ لہذا ہم اسٹیک میں موجود ویلیو کو X کہیں گے، اور بس یہ یاد رکھیں کہ splitter() میں اس X کی ویلیو 0xA0 ہے۔

آفسیٹآپ کوڈاسٹیک
E4JUMPDESTX
E5PUSH1 0x400x40 X
E7MLOAD0x80 X
E8DUP10x80 0x80 X
E9SWAP2X 0x80 0x80
EASUBX-0x80 0x80
EBSWAP10x80 X-0x80
ECRETURN

لہذا یہ کوڈ اسٹیک (X) میں ایک میموری پوائنٹر وصول کرتا ہے، اور کنٹریکٹ کو ایک بفر کے ساتھ RETURN کرنے کا سبب بنتا ہے جو 0x80 - X ہے۔

splitter() کے معاملے میں، یہ وہ پتہ واپس کرتا ہے جس کے لیے ہم پراکسی ہیں۔ RETURN بفر کو 0x80-0x9F میں واپس کرتا ہے، جو وہ جگہ ہے جہاں ہم نے یہ ڈیٹا لکھا تھا (اوپر آفسیٹ 0x130

currentWindow()

0x158-0x163 آفسیٹس میں موجود کوڈ بالکل ویسا ہی ہے جیسا ہم نے splitter() میں 0x103-0x10E پر دیکھا تھا (سوائے JUMPI کی منزل کے)، اس لیے ہم جانتے ہیں کہ currentWindow() بھی payable نہیں ہے۔

آفسیٹآپ کوڈاسٹیک
164JUMPDEST
165POP
166PUSH2 0x00da0xDA
169PUSH1 0x010x01 0xDA
16BSLOADStorage[1] 0xDA
16CDUP20xDA Storage[1] 0xDA
16DJUMPStorage[1] 0xDA

DA کوڈ

یہ کوڈ دیگر میتھڈز کے ساتھ بھی شیئر کیا گیا ہے۔ اس لیے ہم اسٹیک میں موجود ویلیو کو Y کہیں گے، اور بس یہ یاد رکھیں کہ currentWindow() میں اس Y کی ویلیو Storage[1] ہے۔

آفسیٹآپ کوڈاسٹیک
DAJUMPDESTY 0xDA
DBPUSH1 0x400x40 Y 0xDA
DDMLOAD0x80 Y 0xDA
DESWAP1Y 0x80 0xDA
DFDUP20x80 Y 0x80 0xDA
E0MSTORE0x80 0xDA

Y کو 0x80-0x9F پر لکھیں۔

آفسیٹآپ کوڈاسٹیک
E1PUSH1 0x200x20 0x80 0xDA
E3ADD0xA0 0xDA

اور باقی کی وضاحت اوپر پہلے ہی کی جا چکی ہے۔ لہذا 0xDA پر جمپس اسٹیک کے اوپری حصے (Y) کو 0x80-0x9F پر لکھتے ہیں، اور وہ ویلیو واپس کرتے ہیں۔ currentWindow() کے معاملے میں، یہ Storage[1] واپس کرتا ہے۔

merkleRoot()

آف سیٹس 0xED-0xF8 میں موجود کوڈ بالکل ویسا ہی ہے جیسا ہم نے splitter() میں 0x103-0x10E پر دیکھا تھا (سوائے JUMPI منزل کے)، اس لیے ہم جانتے ہیں کہ merkleRoot() بھی payable نہیں ہے۔

آف سیٹآپ کوڈاسٹیک
F9JUMPDEST
FAPOP
FBPUSH2 0x00da0xDA
FEPUSH1 0x000x00 0xDA
100SLOADStorage[0] 0xDA
101DUP20xDA Storage[0] 0xDA
102JUMPStorage[0] 0xDA

جمپ کے بعد کیا ہوتا ہے، یہ ہم پہلے ہی جان چکے ہیں۔ لہذا merkleRoot() Storage[0] واپس کرتا ہے۔

0x81e580d3

آف سیٹس 0x138-0x143 میں موجود کوڈ بالکل ویسا ہی ہے جیسا ہم نے splitter() میں 0x103-0x10E میں دیکھا تھا (سوائے JUMPI منزل کے)، اس لیے ہم جانتے ہیں کہ یہ فنکشن بھی payable نہیں ہے۔

آف سیٹآپ کوڈاسٹیک
144JUMPDEST
145POP
146PUSH2 0x00da0xDA
149PUSH2 0x01530x0153 0xDA
14CCALLDATASIZECALLDATASIZE 0x0153 0xDA
14DPUSH1 0x040x04 CALLDATASIZE 0x0153 0xDA
14FPUSH2 0x018f0x018F 0x04 CALLDATASIZE 0x0153 0xDA
152JUMP0x04 CALLDATASIZE 0x0153 0xDA
18FJUMPDEST0x04 CALLDATASIZE 0x0153 0xDA
190PUSH1 0x000x00 0x04 CALLDATASIZE 0x0153 0xDA
192PUSH1 0x200x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA
194DUP30x04 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA
195DUP5CALLDATASIZE 0x04 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA
196SUBCALLDATASIZE-4 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA
197SLTCALLDATASIZE-4<32 0x00 0x04 CALLDATASIZE 0x0153 0xDA
198ISZEROCALLDATASIZE-4>=32 0x00 0x04 CALLDATASIZE 0x0153 0xDA
199PUSH2 0x01a00x01A0 CALLDATASIZE-4>=32 0x00 0x04 CALLDATASIZE 0x0153 0xDA
19CJUMPI0x00 0x04 CALLDATASIZE 0x0153 0xDA

ایسا لگتا ہے کہ یہ فنکشن کم از کم 32 بائٹس (ایک ورڈ) کا کال ڈیٹا لیتا ہے۔

آف سیٹآپ کوڈاسٹیک
19DDUP10x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA
19EDUP20x00 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA
19FREVERT

اگر اسے کال ڈیٹا نہیں ملتا ہے تو ٹرانزیکشن کسی بھی ریٹرن ڈیٹا کے بغیر ریورٹ ہو جاتی ہے۔

آئیے دیکھتے ہیں کہ اگر فنکشن کو اپنی ضرورت کا کال ڈیٹا مل جائے تو کیا ہوتا ہے۔

آف سیٹآپ کوڈاسٹیک
1A0JUMPDEST0x00 0x04 CALLDATASIZE 0x0153 0xDA
1A1POP0x04 CALLDATASIZE 0x0153 0xDA
1A2CALLDATALOADcalldataload(4) CALLDATASIZE 0x0153 0xDA

calldataload(4) میتھڈ کے دستخط کے بعد کال ڈیٹا کا پہلا ورڈ ہے۔

آف سیٹآپ کوڈاسٹیک
1A3SWAP20x0153 CALLDATASIZE calldataload(4) 0xDA
1A4SWAP1CALLDATASIZE 0x0153 calldataload(4) 0xDA
1A5POP0x0153 calldataload(4) 0xDA
1A6JUMPcalldataload(4) 0xDA
153JUMPDESTcalldataload(4) 0xDA
154PUSH2 0x016e0x016E calldataload(4) 0xDA
157JUMPcalldataload(4) 0xDA
16EJUMPDESTcalldataload(4) 0xDA
16FPUSH1 0x040x04 calldataload(4) 0xDA
171DUP2calldataload(4) 0x04 calldataload(4) 0xDA
172DUP20x04 calldataload(4) 0x04 calldataload(4) 0xDA
173SLOADStorage[4] calldataload(4) 0x04 calldataload(4) 0xDA
174DUP2calldataload(4) Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA
175LTcalldataload(4)<Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA
176PUSH2 0x017e0x017EC calldataload(4)<Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA
179JUMPIcalldataload(4) 0x04 calldataload(4) 0xDA

اگر پہلا ورڈ Storage[4] سے کم نہیں ہے، تو فنکشن ناکام ہو جاتا ہے۔ یہ کسی بھی ریٹرن ویلیو کے بغیر ریورٹ ہو جاتا ہے:

آف سیٹآپ کوڈاسٹیک
17APUSH1 0x000x00 ...
17CDUP10x00 0x00 ...
17DREVERT

اگر calldataload(4) کی قدر Storage[4] سے کم ہے، تو ہمیں یہ کوڈ ملتا ہے:

آف سیٹآپ کوڈاسٹیک
17EJUMPDESTcalldataload(4) 0x04 calldataload(4) 0xDA
17FPUSH1 0x000x00 calldataload(4) 0x04 calldataload(4) 0xDA
181SWAP20x04 calldataload(4) 0x00 calldataload(4) 0xDA
182DUP30x00 0x04 calldataload(4) 0x00 calldataload(4) 0xDA
183MSTOREcalldataload(4) 0x00 calldataload(4) 0xDA

اور میموری لوکیشنز 0x00-0x1F میں اب ڈیٹا 0x04 موجود ہے (0x00-0x1E تمام صفر ہیں، 0x1F چار ہے)

آف سیٹآپ کوڈاسٹیک
184PUSH1 0x200x20 calldataload(4) 0x00 calldataload(4) 0xDA
186SWAP1calldataload(4) 0x20 0x00 calldataload(4) 0xDA
187SWAP20x00 0x20 calldataload(4) calldataload(4) 0xDA
188SHA3(((SHA3 of 0x00-0x1F))) calldataload(4) calldataload(4) 0xDA
189ADD(((SHA3 of 0x00-0x1F)))+calldataload(4) calldataload(4) 0xDA
18ASLOADStorage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] calldataload(4) 0xDA

لہذا اسٹوریج میں ایک لوک اپ ٹیبل ہے، جو 0x000...0004 کے SHA3 سے شروع ہوتا ہے اور اس میں ہر جائز کال ڈیٹا ویلیو (Storage[4] سے کم ویلیو) کے لیے ایک اندراج موجود ہے۔

آف سیٹآپ کوڈاسٹیک
18BSWAP1calldataload(4) Storage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA
18CPOPStorage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA
18DDUP20xDA Storage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA
18EJUMPStorage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA

ہم پہلے ہی جانتے ہیں کہ آف سیٹ 0xDA پر موجود کوڈ کیا کرتا ہے، یہ کالر کو اسٹیک کی ٹاپ ویلیو واپس کرتا ہے۔ لہذا یہ فنکشن لوک اپ ٹیبل سے کالر کو ویلیو واپس کرتا ہے۔

0x1f135823

0xC4-0xCF آفسیٹس میں موجود کوڈ بالکل ویسا ہی ہے جیسا ہم نے splitter() میں 0x103-0x10E پر دیکھا تھا (سوائے JUMPI منزل کے)، اس لیے ہم جانتے ہیں کہ یہ فنکشن بھی payable نہیں ہے۔

آفسیٹآپ کوڈاسٹیک
D0JUMPDEST
D1POP
D2PUSH2 0x00da0xDA
D5PUSH1 0x060x06 0xDA
D7SLOADValue* 0xDA
D8DUP20xDA Value* 0xDA
D9JUMPValue* 0xDA

ہم پہلے ہی جانتے ہیں کہ آفسیٹ 0xDA پر موجود کوڈ کیا کرتا ہے، یہ کالر کو اسٹیک کی سب سے اوپر والی ویلیو واپس کرتا ہے۔ لہذا یہ فنکشن Value* واپس کرتا ہے۔

میتھڈ کا خلاصہ

کیا آپ کو لگتا ہے کہ آپ اس مقام پر کنٹریکٹ کو سمجھ گئے ہیں؟ مجھے تو نہیں لگتا۔ اب تک ہمارے پاس یہ میتھڈز ہیں:

میتھڈمطلب
Transferکال کے ذریعے فراہم کردہ ویلیو کو قبول کریں اور Value* میں اس رقم کا اضافہ کریں
splitter()Storage[3]، پراکسی پتہ واپس کریں
currentWindow()Storage[1] واپس کریں
merkleRoot()Storage[0] واپس کریں
0x81e580d3لک اپ ٹیبل سے ویلیو واپس کریں، بشرطیکہ پیرامیٹر Storage[4] سے کم ہو
0x1f135823Storage[6] واپس کریں، جسے Value* بھی کہا جاتا ہے

لیکن ہم جانتے ہیں کہ کوئی بھی دوسری فعالیت Storage[3] میں موجود کنٹریکٹ کے ذریعے فراہم کی جاتی ہے۔ شاید اگر ہمیں معلوم ہو جائے کہ وہ کنٹریکٹ کیا ہے تو اس سے ہمیں کوئی سراغ مل سکے۔ خوش قسمتی سے، یہ بلاک چین ہے اور کم از کم نظریاتی طور پر سب کچھ معلوم ہے۔ ہم نے کوئی ایسا میتھڈ نہیں دیکھا جو Storage[3] کو سیٹ کرتا ہو، اس لیے اسے کنسٹرکٹر کے ذریعے سیٹ کیا گیا ہوگا۔

کنسٹرکٹر

جب ہم کسی کنٹریکٹ کو دیکھتے ہیں (opens in a new tab) تو ہم اس ٹرانزیکشن کو بھی دیکھ سکتے ہیں جس نے اسے بنایا تھا۔

Click the create transaction

اگر ہم اس ٹرانزیکشن پر کلک کریں، اور پھر حالت ٹیب پر، تو ہم پیرامیٹرز کی ابتدائی قدریں دیکھ سکتے ہیں۔ خاص طور پر، ہم دیکھ سکتے ہیں کہ Storage[3] میں 0x2f81e57ff4f4d83b40a9f719fd892d8e806e0761 (opens in a new tab) موجود ہے۔ اس کنٹریکٹ میں لازمی طور پر وہ گمشدہ فعالیت موجود ہونی چاہیے۔ ہم اسے انہی ٹولز کا استعمال کرتے ہوئے سمجھ سکتے ہیں جو ہم نے اس کنٹریکٹ کے لیے استعمال کیے تھے جس کی ہم تحقیق کر رہے ہیں۔

پراکسی کنٹریکٹ

اوپر دیے گئے اصل کنٹریکٹ کے لیے ہم نے جو تکنیک استعمال کی تھیں، انہی کا استعمال کرتے ہوئے ہم دیکھ سکتے ہیں کہ کنٹریکٹ ریورٹ ہو جاتا ہے اگر:

  • کال کے ساتھ کوئی ETH منسلک ہو (0x05-0x0F)
  • کال ڈیٹا کا سائز چار سے کم ہو (0x10-0x19 اور 0xBE-0xC2)

اور یہ جن طریقوں (methods) کو سپورٹ کرتا ہے وہ یہ ہیں:

طریقہ (Method)طریقہ کے دستخط (Method signature)چھلانگ لگانے کے لیے آفسیٹ (Offset to jump into)
scaleAmountByPercentage(uint256,uint256) (opens in a new tab)0x8ffb5c970x0135
isClaimed(uint256,address) (opens in a new tab)0xd2ef07950x0151
claim(uint256,address,uint256,bytes32[]) (opens in a new tab)0x2e7ba6ef0x00F4
incrementWindow() (opens in a new tab)0x338b1d310x0110
???0x3f26479e0x0118
???0x1e7df9d30x00C3
currentWindow() (opens in a new tab)0xba0bafb40x0148
merkleRoot() (opens in a new tab)0x2eb4a7ab0x0107
???0x81e580d30x0122
???0x1f1358230x00D8

ہم نیچے والے چار طریقوں کو نظر انداز کر سکتے ہیں کیونکہ ہم کبھی ان تک نہیں پہنچ پائیں گے۔ ان کے دستخط کچھ اس طرح کے ہیں کہ ہمارا اصل کنٹریکٹ خود ہی ان کو سنبھال لیتا ہے (آپ تفصیلات دیکھنے کے لیے اوپر دیے گئے دستخطوں پر کلک کر سکتے ہیں)، اس لیے یہ لازمی طور پر اوور رائیڈ کیے گئے طریقے (methods that are overridden) (opens in a new tab) ہوں گے۔

باقی ماندہ طریقوں میں سے ایک claim(<params>) ہے، اور دوسرا isClaimed(<params>) ہے، اس لیے یہ ایک ایئر ڈراپ کنٹریکٹ لگتا ہے۔ باقی ماندہ کو ایک ایک آپ کوڈ کر کے دیکھنے کے بجائے، ہم ڈی کمپائلر آزما سکتے ہیں (opens in a new tab)، جو اس کنٹریکٹ کے تین فنکشنز کے لیے قابل استعمال نتائج پیدا کرتا ہے۔ باقی فنکشنز کی ریورس انجینئرنگ قاری کے لیے ایک مشق کے طور پر چھوڑی گئی ہے۔

scaleAmountByPercentage

اس فنکشن کے لیے ڈی کمپائلر ہمیں یہ دیتا ہے:

def unknown8ffb5c97(uint256 _param1, uint256 _param2) payable:
  require calldata.size - 4 >=64
  if _param1 and _param2 > -1 / _param1:
      revert with 0, 17
  return (_param1 * _param2 / 100 * 10^6)

پہلا require یہ جانچتا ہے کہ کال ڈیٹا میں، فنکشن کے دستخط کے چار بائٹس کے علاوہ، کم از کم 64 بائٹس موجود ہیں، جو دو پیرامیٹرز کے لیے کافی ہیں۔ اگر ایسا نہیں ہے تو ظاہر ہے کہ کچھ گڑبڑ ہے۔

if اسٹیٹمنٹ بظاہر یہ چیک کرتی ہے کہ _param1 صفر نہیں ہے، اور یہ کہ _param1 * _param2 منفی نہیں ہے۔ یہ غالباً ریپ اراؤنڈ (wrap around) کے معاملات کو روکنے کے لیے ہے۔

آخر میں، فنکشن ایک اسکیلڈ ویلیو (scaled value) واپس کرتا ہے۔

claim

ڈی کمپائلر جو کوڈ بناتا ہے وہ پیچیدہ ہے، اور اس کا سارا حصہ ہمارے لیے متعلقہ نہیں ہے۔ میں اس میں سے کچھ کو چھوڑنے جا رہا ہوں تاکہ ان لائنوں پر توجہ مرکوز کر سکوں جو میرے خیال میں مفید معلومات فراہم کرتی ہیں

def unknown2e7ba6ef(uint256 _param1, uint256 _param2, uint256 _param3, array _param4) payable:
  ...
  require _param2 == addr(_param2)
  ...
  if currentWindow <= _param1:
      revert with 0, 'cannot claim for a future window'

ہم یہاں دو اہم چیزیں دیکھتے ہیں:

  • _param2، اگرچہ اسے uint256 کے طور پر ڈکلیئر کیا گیا ہے، دراصل ایک پتہ ہے۔
  • _param1 وہ ونڈو ہے جس کا دعویٰ کیا جا رہا ہے، جسے currentWindow یا اس سے پہلے کا ہونا چاہیے۔
  ...
  if stor5[_claimWindow][addr(_claimFor)]:
      revert with 0, 'Account already claimed the given window'

تو اب ہم جانتے ہیں کہ Storage[5] ونڈوز اور پتوں کی ایک ایرے (array) ہے، اور یہ کہ آیا اس پتے نے اس ونڈو کے لیے انعام کا دعویٰ کیا ہے۔

ہم جانتے ہیں کہ unknown2eb4a7ab دراصل فنکشن merkleRoot() ہے، اس لیے یہ کوڈ ایسا لگتا ہے کہ یہ ایک مرکل ثبوت (opens in a new tab) کی تصدیق کر رہا ہے۔ اس کا مطلب ہے کہ _param4 ایک مرکل ثبوت ہے۔

  call addr(_param2) with:
     value unknown81e580d3[_param1] * _param3 / 100 * 10^6 wei
       gas 30000 wei

اس طرح ایک کنٹریکٹ اپنا ETH کسی دوسرے پتے (کنٹریکٹ یا بیرونی ملکیت والے) کو منتقل کرتا ہے۔ یہ اسے ایک ویلیو کے ساتھ کال کرتا ہے جو کہ منتقل کی جانے والی رقم ہوتی ہے۔ تو ایسا لگتا ہے کہ یہ ETH کا ایک ایئر ڈراپ ہے۔

  if not return_data.size:
      if not ext_call.success:
          require ext_code.size(stor2)
          call stor2.deposit() with:
             value unknown81e580d3[_param1] * _param3 / 100 * 10^6 wei

نیچے کی دو لائنیں ہمیں بتاتی ہیں کہ Storage[2] بھی ایک کنٹریکٹ ہے جسے ہم کال کرتے ہیں۔ اگر ہم کنسٹرکٹر ٹرانزیکشن کو دیکھیں (opens in a new tab) تو ہم دیکھتے ہیں کہ یہ کنٹریکٹ 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 (opens in a new tab) ہے، جو کہ ایک ریپڈ ایتھر (ڈبلیو ایتھ) کنٹریکٹ ہے جس کا سورس کوڈ Etherscan پر اپ لوڈ کیا گیا ہے (opens in a new tab)۔

تو ایسا لگتا ہے کہ کنٹریکٹ _param2 کو ETH بھیجنے کی کوشش کرتا ہے۔ اگر یہ ایسا کر سکتا ہے، تو بہت اچھا۔ اگر نہیں، تو یہ WETH (opens in a new tab) بھیجنے کی کوشش کرتا ہے۔ اگر _param2 ایک بیرونی ملکیت والا اکاؤنٹ (EOA) ہے تو یہ ہمیشہ ETH وصول کر سکتا ہے، لیکن کنٹریکٹس ETH وصول کرنے سے انکار کر سکتے ہیں۔ تاہم، WETH ایک ERC-20 ہے اور کنٹریکٹس اسے قبول کرنے سے انکار نہیں کر سکتے۔

  ...
  log 0xdbd5389f: addr(_param2), unknown81e580d3[_param1] * _param3 / 100 * 10^6, bool(ext_call.success)

فنکشن کے آخر میں ہم دیکھتے ہیں کہ ایک لاگ انٹری تیار کی جا رہی ہے۔ تیار کردہ لاگ انٹریز کو دیکھیں (opens in a new tab) اور اس ٹاپک پر فلٹر کریں جو 0xdbd5... سے شروع ہوتا ہے۔ اگر ہم ایسی انٹری تیار کرنے والی ٹرانزیکشنز میں سے کسی ایک پر کلک کریں (opens in a new tab) تو ہم دیکھتے ہیں کہ واقعی یہ ایک دعویٰ لگتا ہے - اکاؤنٹ نے اس کنٹریکٹ کو ایک پیغام بھیجا جس کی ہم ریورس انجینئرنگ کر رہے ہیں، اور بدلے میں ETH حاصل کیا۔

A claim transaction

1e7df9d3

یہ فنکشن اوپر دیے گئے claim سے بہت ملتا جلتا ہے۔ یہ بھی ایک مرکل ثبوت کی جانچ کرتا ہے، پہلے کو ETH منتقل کرنے کی کوشش کرتا ہے، اور اسی قسم کی لاگ انٹری تیار کرتا ہے۔

بنیادی فرق یہ ہے کہ پہلا پیرامیٹر، یعنی نکالنے کے لیے ونڈو، وہاں موجود نہیں ہے۔ اس کے بجائے، ان تمام ونڈوز پر ایک لوپ (loop) ہے جن کا دعویٰ کیا جا سکتا ہے۔

تو یہ claim کا ایک ایسا ویریئنٹ (variant) لگتا ہے جو تمام ونڈوز کا دعویٰ کرتا ہے۔

نتیجہ

اب تک آپ کو یہ جان لینا چاہیے کہ ان کنٹریکٹس کو کیسے سمجھا جائے جن کا سورس کوڈ دستیاب نہیں ہے، اس کے لیے یا تو آپ کوڈز کا استعمال کیا جاتا ہے یا (جب یہ کام کرے) ڈی کمپائلر کا۔ جیسا کہ اس مضمون کی طوالت سے ظاہر ہے، کسی کنٹریکٹ کی ریورس انجینئرنگ کوئی معمولی کام نہیں ہے، لیکن ایک ایسے سسٹم میں جہاں سیکیورٹی انتہائی ضروری ہو، یہ تصدیق کرنے کے قابل ہونا کہ کنٹریکٹس وعدے کے مطابق کام کرتے ہیں، ایک اہم مہارت ہے۔

میرے مزید کام کے لیے یہاں دیکھیں (opens in a new tab)۔

صفحہ کی آخری اپ ڈیٹ: ۳ اپریل، ۲۰۲۶