Phase 13: Tools & Protocols

विघटन और नियंत्रण प्रवाहः बाइट से ग्राफ तक आप जांच सकते हैं

एक डिमॉन्टेबल लिस्टिंग एक दावा है कि कौन से बाइट्स निर्देश हैं, और यह सबक डिकोडर और ग्राफ बनाता है जो दावा की जांच करता है।

Type: Build

Languages: Python, C, x86-64 assembly

Prerequisites: Phase 13 · 32 (ELF from scratch)

Time: ~150 minutes

सीखने के लक्ष्य

  • लेजर फिक्स्चर में x86-64 निर्देश उपसमूह को इंटेल SDM वॉल्यूम 2, अध्याय 2 में नियमों से डिकोड करें।
  • llvm-objdump -dहर एक क़ादिर की हर क़ादिर की हर एक क़ादिर की हर एक क़ादिर की हर एक क़ादिर की हर एक क़ादिर की हर एक क़ादिर की हर एक क़ादिर की हर एक क़ादिर की हर एक क़ादिर की हर एक क़ादिर की हर एक क़ादिर की हर एक क़ादिर की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़ादिरत की हर एक क़रार की हर एक क़रार की हर एक क़रार की हर एक क़रार की हर एक क़रार की हर क़रार की हर एक क़रार की हर क़रार से
  • एक ही बाइट्स पर एक रैखिक स्वीप और एक पुनरावर्ती अवतरण चलाएं और समझाएं कि प्रत्येक में गलत कहां है।
  • निर्देशों को बुनियादी ब्लॉक में काटकर टाइप किए गए किनारों के साथ नियंत्रण-प्रवाह ग्राफ बनाएं।
  • कूपर, हार्वे और केनेडी के पुनरावर्ती एल्गोरिथ्म के साथ तत्काल हावी करने वालों की गणना करें, और बैक किनारों से लूप खोजें।
  • उसके तालिका पर उस बिंदु पर स्थानांतरण प्रविष्टियों से अप्रत्यक्ष छलांग के लक्ष्य को पुनर्प्राप्त करें।

समस्या

आप संकलित किया ledger.cपर-O0और एक एजेंट से पूछा कि क्या gradeएजेंट एक डिमॉन्टेबलर बुलाया, सूची को पढ़ा, और जवाब दिया कि समारोह समाप्त होता हैjmpq *%raxइसके बाद उसने एक एकल के साथ एक छद्म कोड लिखाreturn 0. .

आप एक ही वस्तु खोलने मेंllvm-objdump -dऔर कूद के बाद चार ब्लॉक देखें कि स्टोर 4, 3, 2 और 1. सूची में वे सभी शुरू से ही था. सूची में कुछ भी नहीं बताता है कि निष्पादन उन्हें कैसे प्राप्त करता है, तो उपकरण उन्हें मृत कोड के रूप में माना जाता है.

एक सूची केवल एक प्रश्न का उत्तर देती हैः कौन सा निर्देश किस रूप से बाइट करता है। यह नहीं बताता है कि कौन से निर्देश चल रहे हैं, किस क्रम में, या कहां से। इनका उत्तर देने के लिए आपको तीन और संरचनाओं की आवश्यकता हैः बाइट्स जो नियंत्रण प्रवाह तक पहुंच सकते हैं, बुनियादी ब्लॉक, और उनके बीच के किनारे।

rea 6.5.0 का अपना कोई डिमॉन्टेजर नहीं है। गहन विश्लेषण के लिए, इसकी README आपको Ghidra, Hopper या IDA लाने के लिए कहती है। ऑफ़लाइन निरीक्षण के लिए, rea एक GNU objdump या एक Rizin को भी कॉल कर सकता है जिसे आप स्थापित करते हैं। इसका कॉल ग्राफ अनसुलझे लक्ष्यों पर रुकता हैः "कॉलर/कॉल में केवल अनसुलझे फ़ंक्शन होते हैं.... लक्ष्य रहित कम्प्यूटेड प्रवाह अज्ञात है। यह पाठ उस भाग को बनाता है जो उन तथ्यों को उत्पन्न करता है, ताकि आप उन्हें एक उपकरण द्वारा रिपोर्ट करते समय न्याय कर सकें।

निर्देश प्रारूप में प्रत्येक क्षेत्र के लिए एक नियम होता है

इंटेल एसडीएम वॉल्यूम 2, चित्रा 2-1 प्रत्येक निर्देश के बाइट क्रम देता है। पहले वैकल्पिक उपसर्ग आते हैं, फिर एक से तीन ओपकोड बाइट्स, फिर एक वैकल्पिक मॉडआरएम बाइट और एक वैकल्पिक एसआईबी बाइट। 0, 1, 2 या 4 बाइट का विस्थापन होता है, फिर 0, 1, 2 या 4 बाइट का तत्काल विस्थापन होता है। पूरा निर्देश अधिकतम 15 बाइट (SDM 2.3.11) है। एएमडी एपीएम वॉल्यूम 3, अध्याय 1, धारा 1.1 से 1.7 में कम पृष्ठों में एक ही प्रारूप का वर्णन किया गया है और यह एक अच्छा क्रॉस-चेक है।

प्रत्येक फ़ील्ड तय करता है कि अगला फ़ील्ड क्या है। इसलिए एक डिकोडर छोटे निर्णयों की एक श्रृंखला है, और प्रत्येक निर्णय के पीछे मैनुअल में एक वाक्य है।

पूर्वावलोकन चार समूहों में आते हैं

SDM 2.1.1 में पुराने उपसर्गों की सूची दी गई है।F0लॉक और दोहराव उपसर्ग F2और F3. समूह 2 में छह खंडों के ओवरराइड हैं (2Eहै %cs) और शाखा संकेत। समूह 3 है 66, ऑपरेन्ड आकार ओवरराइड। समूह 4 है67, पता आकार ओवरराइड.

मैनुअल कहता है कि प्रत्येक समूह से एक पूर्वावलोकन का उपयोग किया जा सकता है। संकलक अधिक उत्सर्जन करते हैं।ledger-x86_64-O0.oविलय में0xf2पढ़ता है 66 66 66 66 66 2e 0f 1f 84 00 00 00 00 00, 5 प्रतियां 66एक पंक्ति में।llvm-objdumpयह प्रिंट करता है nopw %cs:(%rax,%rax)और आप जो डिकोडर बनाते हैं वह भी ऐसा ही करता है. डिकोडर हर पूर्वसर्ग बाइट को एकत्र करता है जो वह देखता है और बाद में पूछता है कि कौन सा मायने रखता है।

66SSE निर्देशों के लिए यह एक अनिवार्य पूर्वावलोकन है जो opcode का चयन करता है, इसलिए 66 0f ef c0है pxor %xmm0, %xmm0और 16 बिट कुछ भी नहीं।F3 0f 6fहै movdqu. डिकोडर पहले अनिवार्य उपसर्ग और दूसरे ओपकोड बाइट की जोड़ी को देखता है. केवल जब उस जोड़ी में कोई प्रविष्टि नहीं होती है तो ऐसा होता है .66मतलब 16-बिट ओपेरांड।

REX चार बिट्स जोड़ता है और ऑपकोड के बगल में बैठना चाहिए

SDM 2.2.1 REX को एक बाइट से 404Fआकार के साथ 0100WRXB. .Wएक 64-बिट ऑपरेंड का चयन करता है। RModRM reg क्षेत्र का विस्तार करता है। XSIB सूचकांक क्षेत्र का विस्तार करता है। BModRM r/m फ़ील्ड, SIB बेस फ़ील्ड या ऑपकोड बाइट में रजिस्टर का विस्तार करता है (तालिका 2-4). REX को ऑपकोड या एस्केप बाइट से ठीक पहले आना चाहिए, और एक REX के बाद एक विरासत पूर्ववर्ती उपसर्ग की अनदेखी की जाती है।

48 89 e5बाइट 48क्या REX के साथ Wसेट। ऑप कोड 89है MOV r/m, r, और ModRM e5चुनता है %rspमें%rbp. REX हटा दें और वही बाइट्स हैं movl %esp, %ebp. .

REX बाइट रजिस्टरों के नाम भी बदलता है। किसी भी REX के साथ, 4 से 7 कोडिंग चुनें %spl,%bpl,%silऔर %dilइसके बजाय %ah,%ch,%dhऔर %bh. डिकोडर दो तालिकाओं को रखने के लिए है कि कारण है.

दो मानचित्रों में ओपकोड रहते हैं

एक बाइट ऑपकोड आम मामला है। 0Fदो बाइट नक्शे में भागता है, और 0F 38या 0F 3Aतीन बाइट नक्शे पर भागने के लिए जो फिक्स्चर कभी उपयोग नहीं करते हैं (SDM 2.1.2) । कुछ ऑपकोड ModRM reg फ़ील्ड को तीन और ऑपकोड बिट्स के रूप में उपयोग करते हैं, जो लिखा गया है /digitमैनुअल में।83 /7है cmpएक 8-बिट तत्काल के साथ, 83 /5है subऔर FF /4है अप्रत्यक्षjmp. .

में डिकोडरcode/main.pyचार फिक्स्चर ऑब्जेक्ट्स में ऑपकोड और कुछ पड़ोसी शामिल हैं। सूची आठ अंकगणितीय रूपों है, mov,lea,movslq,movzbl,imul, शिफ्टों,testऔर idiv. यह जारी है के साथ push,pop,call,ret, सोलह सशर्त कूद, बहु-बाइट nop, और पांच SSE2 ऑपकोड. किसी अन्य ऑपकोड बढ़ता हैDecodeErrorपता और कारण के साथ एक डिकोडर जो अनुमान है एक से भी बदतर है जो बंद हो जाता है.

ModRM और SIB ऑपरेन्ड चुनते हैं

SDM 2.1.3 ModRM को विभाजित करता है mod(बिट्स 7:6), reg(5:3), तथा r/m(2:0) mod = 11इसका मतलब है कि r/m एक रजिस्टर है। mod = 00,01और 10कोई विस्थापन, एक 8-बिट विस्थापन, या एक 32-बिट विस्थापन के बिना औसत स्मृति। r/m = 100इसका मतलब है कि एक SIB बाइट का अनुसरण करता है।scale(7:6), index(5:3), तथा base(2:0), और के एक सूचकांक100इसका अर्थ है कि कोई सूचकांक रजिस्टर नहीं है।

तालिका 2-2 में दो विशेष पंक्तियाँ 64-बिट मोड में हैं। mod = 00के साथr/m = 101नहीं है(%rbp). यह अगले निर्देश के सापेक्ष 32-बिट विस्थापन है (SDM 2.2.1.6) । mod = 00 के SIB आधार के साथ101कोई आधार रजिस्टर के बिना 32-बिट विस्थापन है।

ले लो8d 44 08 faसेgradeपर-O0. ओप कोड 8Dहै lea. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .44है 01 000 100: 8 बिट विस्थापन, reg %eax, और एक SIB के बाद. SIB 08है 00 001 000: स्केल 1, सूचकांक %rcx, आधार %rax. विस्थापन बाइट faएटी एंड टी पाठ है leal -0x6(%rax,%rcx), %eax, जो गणना करता है score / 10 - 6, स्विच सूचकांक।

विस्थापन है जहां लिंक संपादक लिखता है

48 8d 0d 00 00 00 00है leaq (%rip), %rcx. चार शून्य बाइट्स आरआईपी-संबंधी ऑपरेंड के विस्थापन क्षेत्र हैं. एक स्थानांतरित करने योग्य वस्तु में वे स्थान धारक हैं. प्रविष्टि में .rela.textविलय में0x69कहते हैंR_X86_64_PC32.rodata-0x4. पीएसएबीआई (तालिका 4.9) परिभाषित करता है R_X86_64_PC32जैसे S + A - P: प्रतीक पता प्लस परिशिष्ट शून्य क्षेत्र का पता।

प्रोसेसर अगले निर्देश के पते में फ़ील्ड जोड़ता है, इसलिए अंतिम लक्ष्य S + A + (end - P). . इसके लिए .leaक्षेत्र अंतिम चार बाइट्स है, तो end - P4 है और लक्ष्य है .rodata + (-4) + 4 =.rodata + 0x0-4 का परिशिष्ट उन चार बाइट्स को अग्रिम में भर देता है। फिक्स्चर में प्रत्येक आरआईपी-संबंधी ऑपरेंड में यह -4 अपने परिशिष्ट के अंदर होता है, इसलिए .rodata+0x10पर -O1 leaअर्थ.rodata+0x14. .

डिकोडर प्रत्येक निर्देश के भीतर विस्थापन क्षेत्र के ऑफसेट को रिकॉर्ड करता है। resolve_reference()यह एक फ़ंक्शन है जो घूमता हैleaq (%rip), %rcxमेज पर.rodata+0x0"।

एक अपवाद के साथ तत्काल अधिकतम 32 बिट्स हैं

SDM 2.2.1.5 कहता है कि 64 बिट मोड में तत्काल अधिकतम 32 बिट्स रहें और संकेत-विस्तारित हैं। अपवाद है MOV r64, imm64(B8+rके साथREX.W), जिसे एटी एंड टी सिंटैक्स कहता है movabsq. डिकोडर 8-बिट तत्काल के रूप में अंकगणित के लिए हस्ताक्षरित पढ़ता है (83 c1 faहै addl $-0x6, %ecx) और शिफ्ट गिनती और मिक्सल चयनकर्ता के लिए हस्ताक्षरित नहीं (pshufd $0xee) यह विभाजित क्या मेल खाता है llvm-objdumpफिंगरप्रिंट्स।

शाखाओं के निकट,pushऔर popएक 64-बिट ऑपरेंड पर डिफ़ॉल्ट और अनदेखा 66(SDM 2.2.1.7) यही कारण है कि डिकोडर उन्हें नाम देता है pushq,popq,callqऔर retqबिना एक ओपेरा आकार का पूर्वावलोकन पढ़ा।

एक निर्देश को हाथ से डिकोड करें

इससे पहले कि आप कोड पर भरोसा करें, डिकोड 48 69 c0 67 66 66 66सेgradeकागज पर।

  1. 48है 404F, तो यह REX के साथ है W = 1ओपेरा का आकार 64 बिट्स है।
  2. 69है IMUL r, r/m, imm32. .
  3. c0ModRM है 11 000 000: रजिस्टर फॉर्म, रेग%rax, r/m %rax. .
  4. अगले चार बाइट्स तत्काल, छोटे-एंडियन हैंः 0x66666667. .
  5. एटी एंड टी आदेश तत्काल है, स्रोत, गंतव्यः imulq $0x66666667, %rax, %rax. .

यह सात बाइट है, और llvm-objdumpएक ही पाठ प्रिंट करता है. निरंतर प्रतिपक्षीय है जो एक गुणा के साथ 10 से क्लिंग को विभाजित करने देता है और sarq $0x22जो आगे है, जो 34 से स्थानांतरित होता है. आप उस जोड़ी को फिर से सबक 34 में मिलेंगे, जहां लिफ्टर इसे वापस बदल देता है.score / 10. .

डिकोडर इन पांच चरणों में करता है Decoder.decode(),_State.modrm()और _State.finish(). उन्हें उस क्रम में पढ़ें और प्रत्येक शाखा को उसके डॉकस्ट्रिंग में नामित एसडीएम अनुभाग से मेल खाएं।

रैखिक स्वीप

श्वार्ज़, डेब्रे और एंड्रयूज ने "डिसांसमेंट ऑफ एक्जीक्यूटेबल कोड रिविज़िट" (WCRE 2002) के खंड 3.1 में रैखिक स्वीप का वर्णन किया है। कोड अनुभाग के पहले बाइट से शुरू करें, एक निर्देश को डिकोड करें, इसके बाद बाइट पर जारी रखें, और अंत में रुकें। objdumpइस तरह काम करता है, और यह भी करता है llvm-objdump. .

pythondef linear_sweep(code: bytes, start: int = 0, end: int | None = None) -> SweepResult:
    position = start
    while position < end:
        try:
            instruction = decode(code[:end], position)
        except DecodeError:
            undecodable.append((position, code[position]))
            position += 1
            continue
        instructions.append(instruction)
        position = instruction.end

विधि में एक विफलता मोड है, और कागज के चित्र 1 यह दिखाता है। डेटा बाइट को निर्देश के रूप में डिकोड किया जाता है, और डिकोडर तब कुछ समय के लिए वास्तविक निर्देश सीमाओं को याद करता है। x86 पर डिकोडर आमतौर पर कुछ बाइट्स के भीतर फिर से सिंक्रनाइज़ होता है, जो त्रुटि को देखना मुश्किल बनाता है। पता लगाने केवल एक अमान्य ओपकोड पर होता है, और पेपर का निष्कर्ष यह है कि ये एल्गोरिदम "चुपचाप विफल" होते हैं।

यह भी इस सबक के लिए एक प्रवचन है।llvm-objdump -dऔर हर एक फ़र्ज़ की हर एक फ़र्ज़ परllvm-objdumpयह स्वयं एक रैखिक स्वीप है।code/fixtures/*.objdump.txt. परीक्षण प्रत्येक पंक्ति को पता, बाइट और पाठ में विश्लेषण करता है, टिप्पणी के बाद छोड़ देता है #चार वस्तुओं में 86, 56, 84 और 11 निर्देश हैं, कुल में 237 हैं, और डिकोडर उन सभी से मेल खाता है।

पुनरावर्ती गिरावट

उसी पेपर के खंड 3.2 में दूसरी विधि को पुनरावर्ती पार करना कहा जाता है। एक ज्ञात प्रवेश बिंदु पर शुरू करें और नियंत्रण हस्तांतरण तक डिकोड करें। एक प्रत्यक्ष कूद के लिए, लक्ष्य पर जारी रखें। एक सशर्त शाखा के लिए, लक्ष्य और अगले निर्देश दोनों पर जारी रखें, और कॉल के लिए, इसके बाद जारी रखें, क्योंकि कॉल लौटाता है। एक वापसी के लिए, रोकें। यात्रा समाप्त होने के लिए दौरा किए गए पते का एक सेट रखें।

pythonif instruction.flow == "branch":
    worklist.append(instruction.target)
    address = instruction.end
elif instruction.flow == "indirect-jump":
    table = resolver(instruction, instructions) if resolver else None
    if table is None:
        unresolved.append(instruction)
        break

विफलता मोड विपरीत है। एक अप्रत्यक्ष कूद में अपने बाइट्स में कोई लक्ष्य नहीं है। चलने वहां रुकता है, और केवल कूद के माध्यम से पहुंचने योग्य प्रत्येक ब्लॉक अज्ञात रहता है। पेपर की आकृति 2 एक दूसरा खतरा दिखाता हैः एक लक्ष्य सेट जो निर्देश के मध्य में इंगित करने वाले स्थानांतरण से प्राप्त होता है।main.pyउस मामले को एक संघर्ष के रूप में रिकॉर्ड करता है और उस पथ को रोकता है, इसलिए ओवरलैप को चुपचाप दो बार डिकोड करने के बजाय दिखाई देता है।

एंड्रिस और अन्य ने 981 संकलक-जनित द्विआधारी पर नौ डिमॉन्टेबलर मापने (यूएसएनआईएक्स सिक्योरिटी 2016) । इनलाइन डेटा और ओवरलैपिंग कोड वास्तविक संकलक आउटपुट में साहित्य के अनुमान से कम दुर्लभ थे, और हार्ड आदिम कार्य सीमा वसूली थी। दोनों निष्कर्षों को ध्यान में रखें। अगले खंड में फंदा संकलक आउटपुट में दुर्लभ है। अप्रत्यक्ष कूद में gradeआम है।

एक ऐसी जगह जहां झाड़ना गलत है और उतरना सही है

code/fixtures/sweep-trap.sफंक्शन बॉडी के अंदर चार-बाइट तालिका के साथ 24 बाइट्स हाथ से लिखे गए असेंबली है। फ़ंक्शन तालिका को movzbl (%rcx,%rax), %eaxऔर उस पर कूदता है।

asmlookup:
        movslq  %edi, %rax
        leaq    .Ltable(%rip), %rcx
        movzbl  (%rcx,%rax), %eax
        jmp     .Ldone
.Ltable:
        .byte   0x31, 0xc0, 0x90, 0x48
.Ldone:
        addl    $1, %eax
        retq

इसे बनाओ clang -target x86_64-unknown-linux-gnu -c sweep-trap.sचार तालिका बाइट्स को चुना गया था ताकि वे तर्कसंगत निर्देशों के रूप में डिकोड करें।31 c0है xorl %eax, %eax. .90है nop. .48एक REX पूर्वावलोकन है, तो स्वीप इसे वास्तविक के तीन बाइट्स के साथ जोड़ता है addl $1, %eaxऔर छापें addq $0x1, %rax. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .llvm-objdumpआउटपुट ठीक से दिखाता हैः

text       e: eb 04                        	jmp	0x14 <lookup+0x14>
      10: 31 c0                        	xorl	%eax, %eax
      12: 90                           	nop
      13: 48 83 c0 01                  	addq	$0x1, %rax
      17: c3                           	retq

इस सूची में कुछ भी टूट नहीं लगता है. हर बाइट डिकोड, हर mnemonic असली है, और retqसूची अभी भी तीन तरीकों से गलत है.0xeलक्ष्य 0x14, और सूची में कोई भी निर्देश 0x14. . निर्देश जो कि पर चलती है .0x14एक 32-बिट है addl, और सूची एक 64 बिट दिखाता है addqचार तालिका बाइट्स कोड के रूप में दिखाई देते हैं।

lookupप्रतीक छह निर्देशों को डिक्रिप्ट करता है, कूद के बाद 0x14, और रिपोर्ट addl $0x1, %eaxयह चार टेबल बाइट्स में से कोई भी दावा नहीं करता है और पहले आठ पैडिंग बाइट्स में से कोई भी नहीं हैplus_one. .trap_report()में main.pyतुलना को छापता हैः

json"linearSweep": {
  "instructions": 9,
  "atJumpTarget": {"starts": "0x13", "text": "addq\t$0x1, %rax", "startsAtTarget": false},
  "claimedDataBytes": 4,
  "claimedPaddingBytes": 8
},
"recursiveDescent": {
  "instructions": 6,
  "atJumpTarget": "addl\t$0x1, %eax",
  "claimedDataBytes": 0,
  "claimedPaddingBytes": 0
}

परीक्षणtest_sweep_and_objdump_share_the_same_mistakeयह दावा करता है llvm-objdumpमुद्रित addq $0x1, %raxपर0x13. ओरेकल एक रैखिक स्वीप है और उसके अंधे स्थान को विरासत में देता है. ओरेकल के साथ समझौते डिकोडर की जांच करता है, और उतरता जांच करता है कि निर्देश चल रहे हैं.

मूलभूत ब्लॉक

एलन ने "नियंत्रण प्रवाह विश्लेषण" (1970) में मूल ब्लॉक को परिभाषित किया। यह निर्देशों का एक अनुक्रम है जो केवल अपने पहले निर्देश पर दर्ज किया जाता है और केवल अपने अंतिम में छोड़ दिया जाता है। यकदान और अन्य इसे "नो मोरो गोटोस" (एनडीएसएस 2015) के खंड II.ए.2 में डिकॉम्पिल करने के लिए फिर से लिखते हैं, जिसमें हेडर नोड फ़ंक्शन प्रविष्टि के रूप में है।

निर्माण डिकोड किए गए निर्देशों पर दो-पास स्कैन है, जो पते के अनुसार क्रमबद्ध है। पहला पास नेताओं को एकत्र करता है। एक नेता प्रविष्टि, शाखा या कूद लक्ष्य, टेबल लक्ष्य या किसी भी नियंत्रण हस्तांतरण के बाद निर्देश है। दूसरा पास प्रत्येक नेता पर और पते अनुक्रम में हर अंतराल पर एक नया ब्लॉक शुरू करता है।

pythonfor instruction in self.instructions:
    if instruction.address in leaders or current is None or current.end != instruction.address:
        current = Block(instruction.address, [])
        self.blocks[instruction.address] = current
    current.instructions.append(instruction)

प्रत्येक ब्लॉक के अंतिम निर्देश से किनारे आते हैं। एक वापसी का कोई उत्तराधिकारी नहीं है। एक कूदने के पास एक है, टैग किया गया है jumpएक सशर्त शाखा में दो हैं, टैग किए गए हैंtakenऔर fallthrough. एक ब्लॉक के अंत में एक सादा निर्देश अगले ब्लॉक पर पड़ता है, जो तब होता है जब अगला निर्देश शाखा लक्ष्य होता है। एक अप्रत्यक्ष कूदने में उसकी तालिका के लक्ष्य टैग किए जाते हैं।table, या कोई नहीं, और ब्लॉक में दर्ज किया गया है unresolved. .

एक ही स्कैन से दो और तथ्य सामने आते हैं एक ब्लॉक जो प्रविष्टि तक नहीं पहुंच सकता है, अछूता है, और स्वीप ओवर।gradeपर-O0एक ब्लॉक जिसका प्रारंभ पता किसी अन्य विधि के सूची के निर्देश के भीतर आता है, फंदे से ओवरलैप केस है।

कुल का नियंत्रण-प्रवाह ग्राफ

totalपर-O131 बाइट और 13 निर्देश हैः

text      40: 85 f6                        	testl	%esi, %esi
      42: 7e 18                        	jle	0x5c <total+0x1c>
      44: 89 f1                        	movl	%esi, %ecx
      46: 31 d2                        	xorl	%edx, %edx
      48: 31 c0                        	xorl	%eax, %eax
      4a: 66 0f 1f 44 00 00            	nopw	(%rax,%rax)
      50: 03 04 97                     	addl	(%rdi,%rdx,4), %eax
      53: 48 ff c2                     	incq	%rdx
      56: 48 39 d1                     	cmpq	%rdx, %rcx
      59: 75 f5                        	jne	0x50 <total+0x10>
      5b: c3                           	retq
      5c: 31 c0                        	xorl	%eax, %eax
      5e: c3                           	retq

नेता हैं0x40(प्रवेश), 0x44(पश्चात jle), 0x50(संयुक्त राष्ट्र संघ के लक्ष्य)jne), 0x5b(पश्चात jne), तथा 0x5c(संयुक्त राष्ट्र संघ के लक्ष्य)jle) पांच ब्लॉक, पांच किनारेः

FromToKindWhy
0x400x44fallthroughjle not taken, count is positive
0x400x5ctakencount is zero or negative, return 0
0x440x50fallthroughthe nopw is alignment inside the block, execution passes through it
0x500x50takenjne 0x50, the loop
0x500x5bfallthroughloop exit, return the sum

छह बाइट nopwपर0x4aयह पैडिंग है ताकि लूप में सिर पर 0x50यह 16 बाइट सीमा पर स्थित है. यह पहुंच योग्य है, तो यह ब्लॉक से संबंधित है0x44. एक बाइट nopपर0x5fअंतिम के बाद retqकार्य पर झाड़ना इसके लिए छठा ब्लॉक बनाता है, औरunreachable()सूचीबद्ध है।

-O2एक ही समारोह 140 बाइट्स है दो लूप के साथ।0x80प्रति पास के लिए आठ मान जोड़ता है paddd, और स्केलर शेष पर चलता है 0xc0CFG दो पीछे किनारों है,0x80 -> 0x80और 0xc0 -> 0xc0, और परीक्षण सूट दोनों की जांच करता है.

पुनरावर्ती एल्गोरिथ्म के साथ प्रभुत्वशाली

एक ब्लॉकdएक ब्लॉक पर हावी है bजब प्रवेश द्वार से bपार करता है d. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .bbजो निकटतम है b. यह अवधारणा प्रोसेर (1959) से एलन (1970) तक वापस जाती है, और यह संरचना है कि पाठ 34 को रखने की जरूरत है ifऔर whileसही कहा।

कूपर, हार्वे और केनेडी ने "ए सिंपल, फास्ट डोमिनेंस एल्गोरिथ्म" (2001) लिखा। वे तर्क देते हैं कि डेटा-फ्लो फॉर्मूलेशन, एक कॉम्पैक्ट प्रतिनिधित्व के साथ रिवर्स पोस्टऑर्डर में चलाया जाता है, लगभग एक हजार नोड्स के तहत ग्राफ पर लेंगावर-टार्जन (1979) को हराता है। उनके पेपर में कहा गया है कि इसका उद्देश्य "नई एल्गोरिथ्म पेश करना नहीं है। यह दिखाता है कि सरल एल्गोरिदम अभ्यास में जीत सकते हैं और अच्छा शिक्षण उपकरण हैं। एक फ़ंक्शन CFG में लगभग कभी भी एक हजार नोड्स नहीं होते हैं।

pythondef intersect(first: int, second: int) -> int:
    while first != second:
        while number[first] > number[second]:
            first = idom[first]
        while number[second] > number[first]:
            second = idom[second]
    return first

changed = True
while changed:
    changed = False
    for start in order[1:]:
        processed = [p for p in self.predecessors(start) if p in idom]
        candidate = processed[0]
        for other in processed[1:]:
            candidate = intersect(other, candidate)
        if idom.get(start) != candidate:
            idom[start] = candidate
            changed = True

numberप्रत्येक ब्लॉक की उल्टी पोस्टऑर्डर स्थिति रखता है, जिसमें प्रविष्टि 0 है intersectकागज पोस्ट ऑर्डर नंबरों के साथ तुलना लिखता है और<. इस कोड में रिवर्स पोस्ट ऑर्डर नंबर का उपयोग किया जाता है, इसलिए तुलना है >बाकी सब कुछ अखबार के छद्म कोड का पालन करता है।

के लिएtotalपर-O1परिणाम है idom(0x44) = 0x40,idom(0x50) = 0x44,idom(0x5b) = 0x50और idom(0x5c) = 0x40. किनारे 0x50 -> 0x50एक बैक-एज है क्योंकि इसका लक्ष्य इसके स्रोत पर हावी है, और एक बैक-एज एक लूप को चिह्नित करता है (एलेन 1970). back_edges()परीक्षण वापस करता है।test_dominators_on_a_hand_built_diamond_with_a_loopएक संयुक्त बिंदु के साथ 16 बाइट अनुक्रम पर एक ही कोड चलाता है, तो intersectदो पूर्ववर्ती देखता है।

-O0 पर ग्रेड में कूदने की तालिका

-O0क्लांग switchमें gradeस्थिति-निर्भर कूद तालिका की भाषा मेंः

text      5d: 83 e8 04                     	subl	$0x4, %eax
      60: 77 38                        	ja	0x9a <grade+0x6a>
      62: 48 8b 45 f0                  	movq	-0x10(%rbp), %rax
      66: 48 8d 0d 00 00 00 00         	leaq	(%rip), %rcx
		0000000000000069:  R_X86_64_PC32	.rodata-0x4
      6d: 48 63 04 81                  	movslq	(%rcx,%rax,4), %rax
      71: 48 01 c8                     	addq	%rcx, %rax
      74: ff e0                        	jmpq	*%rax

इसे चार तथ्यों के रूप में पढ़ें।subl $0x4इसके बाद jaसीमाओं की जांच है: 4 से ऊपर के सूचकांक डिफ़ॉल्ट मामले पर जाते हैं 0x9a, तो तालिका में पांच प्रविष्टियों है।leaqउसके स्थानांतरण के साथ तालिका का पता डालता है %rcx, और पिछले खंड में पता चला है कि पता है .rodata+0x0. . .movslqएक हस्ताक्षरित 32-बिट प्रविष्टि को 4 द्वारा स्केल करता है।addqतालिका आधार जोड़ता है, इसलिए प्रत्येक प्रविष्टि तालिका से उसके केस ब्लॉक की दूरी है।

वस्तु फ़ाइल में प्रत्येक तालिका बाइट शून्य है। लक्ष्य केवल स्थानान्तरण के रूप में मौजूद हैं .rela.rodata:

textRELOCATION RECORDS FOR [.rodata]:
OFFSET           TYPE                     VALUE
0000000000000000 R_X86_64_PC32            .text+0x91
0000000000000004 R_X86_64_PC32            .text+0x8c
0000000000000008 R_X86_64_PC32            .text+0x87
000000000000000c R_X86_64_PC32            .text+0x82
0000000000000010 R_X86_64_PC32            .text+0x86

आवेदन करेंS + A - Pप्रवेश करने के लिए kविलय में4k. भंडारित मूल्य है .text + A - (.rodata + 4k), और कोड जोड़ता है .rodata + 0तो लक्ष्य है .text + A - 4k. प्रविष्टि 0 है 0x91 - 0 = 0x91, प्रविष्टि 1 है 0x8c - 4 = 0x88, और प्रविष्टि 2 है 0x87 - 8 = 0x7f. प्रविष्टि 3 और 4 0x82 - 12 = 0x76और 0x86 - 16 = 0x76. मामले 6, 7, 8, 9 और 10score / 101, 2, 3, 4 और 4 को स्टोर करने वाले ब्लॉक पर जाएं। जो 9 और 10 के लिए साझा मामले सहित लाइन के लिए स्रोत लाइन सी से मेल खाता है।

recover_table()यह अंकगणित करता है।jump_table_resolver() से पीछे की ओर लहजे से मेल खाता हैjmpq. यह खोजता है addqजो रजिस्टर बना, movslqजो इसे लोड किया, leaqजो आधार और सीमा बनाती हैja. नीचे आने के बाद चार अलग-अलग लक्ष्यों को धक्का दिया जाता है और जारी रहता है। एक लिंक्ड निष्पादन योग्य में तालिका में अंतिम मान होते हैं और कोई स्थानांतरण नहीं होता है। उसी फ़ंक्शन ने फिर हस्ताक्षरित 32-बिट प्रविष्टियों को पढ़ा और तालिका का पता जोड़ा, और evidenceलक्ष्य को किस पथ से प्राप्त किया गया है, इसका रिकॉर्ड करना।

के लिए संख्याgradeपर-O0, से python3 code/main.py cfg code/fixtures/ledger-x86_64-O0.o grade:

MethodInstructionsBlocksEdgesBytesWhat is wrong
linear sweep3497118 code + 10 padding5 unreachable blocks, no edge into the four case blocks, padding decoded as nopw
recursive descent253282 code36 code bytes never reached, jmpq *%rax at 0x74 unresolved
descent with the table33811118 codenothing

टेबल के साथ प्रभुत्व वाला पेड़ पूरा हो गया है।idomप्रत्येक मामले ब्लॉक के 0x62, कूद के साथ ब्लॉक.idom0xa1यह प्रविष्टि है 0x30, क्योंकि डिफ़ॉल्ट पथ से 0x9aपहुँचता है 0xa1बिना गुजरने के0x62. .

-O1 पर ग्रेड करने के लिए ऑप्टिमाइज़र ने क्या किया

तुम देखोगेgradeएक स्विच है कि एक कूद तालिका में संकलित है के रूप में वर्णित है. बाइट कुछ अधिक विशिष्ट कहते हैं.-O1और -O2कोई अप्रत्यक्ष कूद नहीं है gradeबिल्कुल नहींः

text      2c: 83 f9 04                     	cmpl	$0x4, %ecx
      2f: 77 0c                        	ja	0x3d <grade+0x2d>
      31: 89 c8                        	movl	%ecx, %eax
      33: 48 8d 0d 00 00 00 00         	leaq	(%rip), %rcx
		0000000000000036:  R_X86_64_PC32	.rodata+0x10
      3a: 8b 04 81                     	movl	(%rcx,%rax,4), %eax
      3d: c3                           	retq

स्थानांतरण अनुपूरक0x10और सामान्य 4 टेबल पर डालता है .rodata+0x14. . लोड एक सादा है movl, और वहाँ बाइट्स हैं 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 04 00 00 00. संकलक ने पांच केस ब्लॉक को पांच रिटर्न मानों की एक तालिका के साथ बदल दिया है। एक स्थानांतरण, एक तालिका और सीमाओं की जांच है। नियंत्रण प्रवाह तीन ब्लॉक और तीन किनारों के साथ एक सशर्त शाखा है। find_value_tables()इसे एक के रूप में रिपोर्ट करता है value-tableप्रविष्टियों के साथ [1, 2, 3, 4, 4]और evidence: "bytes". .

grade_labelपर-O2एक कदम आगे बढ़ता है।labelsपांच स्ट्रिंग पॉइंटर की सरणी में पांच 32-बिट ऑफसेट की तालिका बन गई .rodata+0x0. प्रत्येक प्रविष्टि में एक R_X86_64_PC32स्ट्रिंग अनुभाग के खिलाफ स्थानांतरण, और फ़ंक्शन तालिका का पता वह प्रविष्टि जोड़ा लोड करता है। recover_table()उन पांच स्थानांतरणों और रिटर्न ऑफसेट को पढ़ता है 0, 2, 4, 6, 8में.rodata.str1.1, जो पकड़ F,D,C,Bऔर A. एक ही अंकगणित जो एक कूद तालिका को पुनर्प्राप्त करता है एक सापेक्ष डेटा तालिका को पुनर्प्राप्त करता है, क्योंकि दोनों हैं S + A - Pएक ज्ञात समय पर

इन दोनों बिल्ड्स से सीख यह है कि बाइट्स क्या दिखाते हैं उसका नाम दें। एक कूदने वाली तालिका नियंत्रण-प्रवाह ग्राफ को बदल देती है। एक मूल्य तालिका लोड को बदल देती है। दोनों को "स्विच" कहते हुए अंतर छिपाया जाता है जिसे पाठ 34 को पुनर्निर्माण करना है।

रिया और गिदरा आपको क्या देते हैं

rea 6.5.0 Ghidra 12.1.4 प्रदाता के साथ एक फ़ंक्शन के लिस्टिंग पाठ वापस करता है procedure_assemblyएक स्ट्रिंग के रूप में।inspect_native_instructionएक निर्देश के बाइट्स, लंबाई, डिकोडर पाठ, ऑपरेंड टोकन, टाइप किए गए संदर्भ और प्रवाह को लौटाता है, जिसमें वापसी p-कोड से वर्गीकृत होती है। xrefsGhidra के से क्रमबद्ध अद्वितीय स्रोत पते वापस करता है ReferenceManager.getReferencesTo. .analyze_functionCFG ले जाता है और bodyकार्य का। procedure_infoयह संगठन को समावेशी सीमाओं के रूप में देता है और चेतावनी देता है कि "गॅप और साझा पूंछों को स्वामित्व वाले बाइट्स के रूप में नहीं माना जाना चाहिए।

इन तथ्यों में से प्रत्येक Ghidra की पुनरावर्ती वंशज है और इसके विश्लेषकों, क्रमबद्ध.instruction_flow_evidence,function_referencesऔर jump_table_evidence. एक परिणाम इसलिए नियमों का नाम देता है जिसने इसे बनाया है। यह जो नहीं कर सकता है वह है अपना काम दिखाएं। जब गिदरा तालिका नहीं ढूंढता है, तो प्रवाह "अज्ञात" होता है, और कॉलर्स, कॉल और सीएफजी से केस ब्लॉक गायब होते हैं।

अब तुम काम कर रहे हो।python3 code/main.py cfgएक ही वस्तु पर और तुलना ब्लॉक शुरू, किनारे गिनती, और बरामद तालिका के साथ जो प्रदाता लौटाता है. जहां वे सहमत हैं आप दो स्वतंत्र व्युत्पन्न है. जहां वे अलग हैं आप एक विशिष्ट पता देखने के लिए है, जो कि है कि पाठ 35 अज्ञात के रूप में रिकॉर्ड करता है.

एक प्रोग्राम के रूप में डिकोडर पर एक नोट। कैपस्टोन 6.0.0-अल्फा10 (2026-07-21) फिक्स्ड आउट-ऑफ-बॉन्ड्स CVE-2026-55893 और CVE-2026-55894 और 5.0.9 (2026-05-28) बैकपोर्ट किए गए CVE फिक्स के तहत पढ़ता है और लिखता है। एक डिकोडर बाइट्स पढ़ता है जिसे उसने नहीं लिखा है। 15 बाइट सीमा, ट्रंकशन चेक इन _Cursor.byte(), और एक अज्ञात ओपीकोड सुरक्षा का अनुमान लगाने से इनकार main.pyइनपुट के लिए यह नहीं बनाया गया था.

इसे बनाओ

डेमो चलाएँः

bashcd phases/13-tools-and-protocols/33-disassembly-and-control-flow
python3 code/main.py

आउटपुट एक JSON रिपोर्ट है। प्रत्येक फिक्स्चर ऑब्जेक्ट के लिए यह ओरेकल समझौते देता है। प्रत्येक फ़ंक्शन के लिए यह तीन तरीकों के तहत निर्देश, ब्लॉक और किनारे गिनती देता है। यह अreachable ब्लॉक, अनसुलझा अप्रत्यक्ष कूद, बैक किनारे, तत्काल डोमिनटर्स और किसी भी तालिकाओं की सूची भी देता है। यह जाल तुलना के साथ समाप्त होता है।

अपेक्षित आकारः

json{
  "fixtures": {
    "ledger-x86_64-O0.o": {
      "oracle": "86/86 instructions agree with llvm-objdump",
      "functions": {
        "grade": {
          "range": "0x30-0xa6",
          "paddingBytes": 10,
          "linearSweep": {"instructions": 34, "blocks": 9, "edges": 7, "unreachableBlocks": ["0x76", "0x7f", "0x88", "0x91", "0xa6"], "...": "..."},
          "recursiveDescent": {"instructions": 25, "blocks": 3, "edges": 2, "unresolvedIndirectJumps": ["0x74"], "...": "..."},
          "recursiveDescentWithTables": {"instructions": 33, "blocks": 8, "edges": 11, "...": "..."},
          "tables": [{"kind": "jump-table", "table": ".rodata+0x0", "count": 5, "entries": ["0x91", "0x88", "0x7f", "0x76", "0x76"], "evidence": "relocation", "...": "..."}]
        }
      }
    }
  },
  "sweepTrap": {"...": "..."}
}

पढ़िए code/main.pyइस क्रम मेंः

  1. ElfObjectअनुभाग के शीर्षक, प्रतीक और .rela.*प्रविष्टियाँ, तथाsymbolize()मार्ग का नाम पता llvm-objdumpहै।
  2. Decoder.decode()prefixes और REX पढ़ता है, फिर एक-बाइट या दो-बाइट ऑपकोड नक्शे पर भेजता है।
  3. _State.modrm()ModRM, SIB और विस्थापन को पढ़ता है, और विस्थापन क्षेत्र ऑफसेट को रिकॉर्ड करता है।
  4. resolve_reference()लागू होता है S + A - Pविस्थापन या तत्काल क्षेत्र पर स्थानांतरण के लिए।
  5. linear_sweep()और recursive_descent()दो विधि हैं, जिनमें से प्रत्येक में अपनी विफलता दर्ज की गई है।
  6. recover_table()और jump_table_resolver()तालिका को उसके स्थानान्तरण या उसके बाइट्स के माध्यम से पढ़ें।
  7. ControlFlowGraphनेता, ब्लॉक और टाइप किए गए किनारे बनाता है, और dominators()यह पुनरावृत्ति एल्गोरिथ्म है।
  8. analyze_function()एक फ़ंक्शन पर प्रत्येक विधि चलाता है और compare_with_oracle()डिकोडर की जाँच करता है।

दो और आदेश मौजूद हैं।python3 code/main.py disasm FILE.oप्रत्येक निर्देश के बाद स्थानांतरण लक्ष्य के साथ एक सूची प्रिंट करता है जिसमें एक है। python3 code/main.py cfg FILE.o FUNCTIONएक फ़ंक्शन का विश्लेषण प्रिंट करता है।

इसका प्रयोग करें

एक नई वस्तु पर इस क्रम में तीन तरीकों का उपयोग करें।

  1. पहले ओरेकल तुलना चलाओ. एक डिकोडर जो सहमत नहीं है के साथllvm-objdumpएक साधारण निर्देश पर एक बग है, और हर बाद संरचना इसे विरासत में देता है.
  2. प्रत्येक फ़ंक्शन प्रतीक से पुनरावर्ती अवतरण चलाएँ और पढ़ें unresolved. प्रत्येक प्रविष्टि एक अप्रत्यक्ष कूद या कॉल है जिसका लक्ष्य आपके पास नहीं है.
  3. प्रत्येक अनसुलझा कूद के लिए, तालिका की बोली और उस पर इंगित करने वाले स्थान की तलाश करें। यदि बोली मेल नहीं खाती है, तो कूद से पहले बाइट्स पढ़ें और जो आप देखते हैं उसे लिखें। यह नोट पाठ 35 में पहला अज्ञात नोट है।
  4. साफ़ करने और नीचे आने वाले बाइट की तुलना बाइट से करें। बाइट्स जो केवल साफ़ करने के लिए दावा करते हैं वे पैडिंग या डेटा हैं। बाइट्स जो कोई भी दावा नहीं करते हैं वे अछूत हैं या किसी फ़ंक्शन से संबंधित हैं जिसे आपने नहीं पाया है।
  5. पुनर्प्राप्त तालिकाओं के साथ उतार से CFG का निर्माण करें और पीछे किनारों को पढ़ें। प्रत्येक एक लूप है कि पाठ 34 में बदल जाएगा।whileया for. .

ऑरकल पाठ फ़ाइलों को वस्तुओं के साथ रखें. जब आप एक नए संकलक संस्करण पर ले जाते हैं, उन्हें फिर से पकड़, क्योंकि एक बदल सूची एक बदल गया फिटिंग है.

इंटरैक्टिव लैब

लैब एः कागज पर डिकोड, फिर जांच

codeनिर्देशिका, खुला पायथनः

bashcd phases/13-tools-and-protocols/33-disassembly-and-control-flow/code
python3 -q

दौड़ें:

pythonfrom main import *
decode(bytes.fromhex("48 8d 0d 00 00 00 00"), 0).text
decode(bytes.fromhex("8d 44 08 fa"), 0).text
decode(bytes.fromhex("66 66 66 66 66 2e 0f 1f 84 00 00 00 00 00"), 0).length

प्रत्येक कॉल से पहले आप क्या उम्मीद करते हैं, लिखें।48पहले से और देखने के लिए ऑपरेन्ड आकार परिवर्तन.decode(bytes.fromhex("0f 0a"), 0)और पढ़ें DecodeError. .

लैब बीः एक ली पर स्थानांतरण

pythonelf = load_fixture("ledger-x86_64-O0.o")
code = elf.section(".text").data
lea = decode(code, 0x66)
lea.displacement_offset
resolve_reference(elf, lea)

संदर्भ में कहा गया है .rodata+0x0. पता बदलकर 0x116में grade_labelऔर संदर्भ को पढ़ें .data. फिर डिकोड callqपर0x10eऔर इसे हल. तत्काल क्षेत्र ले जाता हैR_X86_64_PLT32 grade-0x4, और हल लक्ष्य है 0x30, जो है grade. .

लैब सी: दो तरीकों के असहमति को देखने

pythontrap = load_fixture("sweep-trap.o")
text = trap.section(".text").data
[i.text for i in linear_sweep(text, 0, 0x20).instructions]
[i.text for i in recursive_descent(text, [0], 0x20).sorted_instructions()]

प्रत्येक सूची में निर्देशों की गणना करें और उस सूची को खोजें जो 0x13. फिर तालिका में अंतिम बाइट बदलें sweep-trap.sसे0x480xe8अब झाड़ू अब निगलता हैaddlऔर retqएक में callqकार्य कोई वापसी नहीं है लगता है।

लैब डी: हाथ से डोमिनटर्स

pythonanalysis = analyze_function(load_fixture("ledger-x86_64-O1.o"), "total")
analysis.cfg.edges
analysis.cfg.reverse_postorder()
analysis.cfg.dominators()
analysis.cfg.back_edges()

कागज पर पांच ब्लॉक खींचें और परिणाम को देखने से पहले प्रभुत्व गणना करें। फिर उसी चार पंक्तियों को चलाएं।gradeपर-O0और समझाएँ कि क्योंidom(0xa1)है 0x30और नहीं0x62. .

अभ्यास प्रयोगशाला

फिक्स्चर में एक अप्रत्यक्ष कॉल साइट जोड़ें और विश्लेषण का विस्तार करें।

आवश्यकताएँः

  • एक C फ़ंक्शन लिखें जो एक छोटे सरणी से चुने गए फ़ंक्शन पॉइंटर के माध्यम से कॉल करता है। इसे -O0और -O1और अपने साथ वस्तुओं को संलग्न llvm-objdumpपाठ.
  • दोनों वस्तुओं पर डिकोडर को ओरेकल के साथ मेल खाने दें। केवल तब ऑपकोड जोड़ें जब फिक्स्चर में किसी निर्देश को उनकी आवश्यकता हो।
  • विस्तार recursive_descent()तो एक अप्रत्यक्ष कॉल कॉल साइट रिकॉर्ड करता है callsके साथNoneलक्ष्य के रूप में और इसके बाद जारी है।
  • एक समाधान लिखें जो फ़ंक्शन पॉइंटर सरणी को उसके स्थानान्तरण के माध्यम से पढ़ता है और संभावित कॉलियों की रिपोर्ट करता है।
  • फ़ंक्शन रिपोर्ट में calllee सेट को के रूप में जोड़ेंindirectCallsप्रत्येक लक्ष्य के लिए सबूत प्रकार के साथ।
  • एक परीक्षण लिखें जो फंक्शन प्रतीक नहीं है कि एक लक्ष्य लौटाता है जब समाधान विफल रहता है।

सफलता का अर्थ हैpython3 code/main.py cfgयह साबित करने के लिए स्थानांतरण के साथ हर संभव कॉलली नाम, और Oracle तुलना अभी भी प्रत्येक वस्तु पर पारित करता है।

पाठ कलाकृतियाँ

इस सबक में शामिल है outputs/skill-disassembly-and-control-flow.md. इसका उपयोग किसी वस्तु और फ़ंक्शन नाम को एक चेक की गई सूची, ब्लॉक, टाइप किए गए किनारे, डोमिनटर्स और प्रत्येक अनसुलझे हस्तांतरण में बदलने के लिए करें. इसके लिए एक ओरेकल तुलना, दोनों विघटन विधियों, बाइट लेखांकन, पुनर्प्राप्त प्रत्येक तालिका के लिए स्थानांतरण सबूत, और अज्ञात रहने के विवरण की आवश्यकता होती है।

जाँचें

डेमो और निर्धारक सूट चलाएंः

bashcd phases/13-tools-and-protocols/33-disassembly-and-control-flow
python3 code/main.py
python3 code/tests/test_main.py

सत्यापन से साबित होना चाहिएः

  • डिकोडर सहमत है llvm-objdump -dचार फिक्स्चर ऑब्जेक्ट के सभी 237 निर्देशों पर
  • REX.W 64-बिट ऑपरेन्ड का चयन करता है और REX.R और REX.B रजिस्टर फ़ील्ड को बढ़ाता है
  • मॉडआरएम और एसआईबी एटी एंड टी मेमोरी ऑपरेंड, जिसमें स्केल, इंडेक्स और हस्ताक्षरित विस्थापन शामिल हैं, को रिडेंसर करते हैं
  • आरआईपी-संबंधी विस्थापन को निर्देश के अंदर इसके क्षेत्र के विपरीत के साथ दर्ज किया जाता है
  • दोहराए गए विरासत उपसर्ग और एक खंड ओवरराइड डिकोड जैसे कि ओरेकल
  • अनिवार्य66पूर्वावलोकन SSE ऑपकोड का चयन करता है और रजिस्टरों को 32-बिट छोड़ देता है
  • 8-बिट तत्काल अंकगणित के लिए संकेत-विस्तारित हैं और शिफ्ट गणना के लिए हस्ताक्षरित नहीं हैं
  • 15 बाइट से अधिक के लिए एक निर्देश, एक छोटा निर्देश, और एक अज्ञात opcode वृद्धि DecodeError
  • R_X86_64_PC32पर leaतालिका में हल करता है और R_X86_64_PLT32पर callकॉलली को हल करता है
  • स्पाइप फंदे की तालिका को कोड के रूप में डिक्रिप्ट करता है और इसमें कूदने के लक्ष्य पर कोई निर्देश नहीं होता है
  • नीचे कभी भी टेबल या पैडिंग का दावा नहीं करता है और पढ़ता है addl $0x1, %eaxलक्ष्य पर
  • llvm-objdumpजाल पर झाड़ू की तरह ही गलती करता है
  • निर्देश के अंदर एक प्रविष्टि को संघर्ष के रूप में दर्ज किया जाता है
  • अनसुलझा अप्रत्यक्ष कूदने से उतरना बंद हो जाता है और सूचीबद्ध किया जाता है
  • totalपर-O1पांच ब्लॉक, पांच टाइप किए गए किनारे, एक पीछे किनारे, और अपेक्षित तत्काल प्रभुत्व वाले
  • एक लूप के साथ हाथ से निर्मित हीरा dominators एल्गोरिथ्म परिभाषित देता है
  • totalपर-O2दो लूप है
  • gradeपर-O0से एक पांच प्रविष्टि कूद तालिका देता है .rela.rodataलक्ष्य के साथ 0x91, 0x88, 0x7f, 0x76, 0x76
  • मेज के बिना gradeपर-O0तीन ब्लॉक और एक अनसुलझा कूद है
  • gradeपर-O1और -O2एक मूल्य तालिका है [1, 2, 3, 4, 4]कोई अप्रत्यक्ष कूद के बिना
  • grade_labelपर-O2पांच स्थानांतरणों के माध्यम से एक सापेक्ष स्ट्रिंग तालिका पढ़ता है
  • इंटरैक्टिव आंकड़ा विश्लेषण द्वारा उत्पन्न किए गए समान डेटा को शामिल करता है

उत्पादन विफलता मोड

FailureWhat the weak tool reportsWhat the analysis must prove
Data inside .textA plausible listing with every byte decodedThe descent claims no data byte and the jump target starts an instruction
Padding after the last returnA nopw instruction at the end of the functionThe bytes are unreachable from the entry
Indirect jump through a table"jmp rax" and four dead blocksTable address from the lea relocation, entry count from the bounds check, targets from .rela.rodata
Switch turned into a value table"a jump table with a relocation"A movl load from .rodata+0x14, three blocks, no indirect transfer
Call with a zero displacementcallq 0x113, the next instructionR_X86_64_PLT32 grade-0x4 resolves to 0x30
Decoder guesses an unknown opcodeA mnemonic that looks rightDecodeError with the address, and the sweep records the byte
Instruction longer than 15 bytesA very long "instruction"The cursor raises before reading byte 16
Branch into the middle of an instructionTwo overlapping listingsA conflict record with both addresses
Loop reported as straight-line codeNo while in the pseudocodeA back edge whose target dominates its source
Oracle captured from a different compilerTests pass on stale textThe fixture README names the exact compiler and command

परिचालन नियम

एक ओरेकल के खिलाफ डिकोड करें, ज्ञात प्रविष्टियों से चलने नियंत्रण प्रवाह, और प्रत्येक बाइट को दावा, पैडिंग, डेटा या अछूता के रूप में खाता बनाएं। एक स्थानांतरण या एक तालिका से प्रत्येक अप्रत्यक्ष लक्ष्य को पुनर्प्राप्त करें जिसे आप इंगित कर सकते हैं। बाकी को अज्ञात के रूप में रिकॉर्ड करें, पते के साथ।

आगे पढ़ना

This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.

Browse the complete course catalog or open this lesson on GitHub.