Phase 13: Tools & Protocols

استعادة البيانات وأنواعها: الهياكل والسلسلات والكائنات C ++ و DWARF

طابعات مُفكّرa->balanceعندما يكون لديها طاولة النمط و*(int *)(param_1 + 4)عندما لا يفعل ذلك. هذا الدروس يُبني الجدول من الوصولات، يقرأ الجدول الحقيقي من DWARF 5، ويعرض أين يوافق الاثنان.

Type: Build

Languages: Python, TypeScript, C, C++, x86-64 assembly

Prerequisites: Phase 13 · 33 (disassembly and control flow), Phase 13 · 34 (lifting to pseudocode)

Time: ~140 minutes

أهداف التعلم

  • اقرأ وصول عضو البنية كمسجل أساسي بالإضافة إلى التنقل، ووصول الترتيب كخطوة أساسي بالإضافة إلى أوقات المؤشر، من قواعد التعامل مع نظام Intel SDM.
  • تطبيق قواعد التنظيم في النظام V يدوياً على struct accountويشرح كل بايت
  • قم ببناء مرسلة استرداد النوع التي تحول الوصولات في قائمة إلى تعويضات الحقل والعرض وأنواع، خطوة، وحجم، مع الأدلة لكل منها.
  • اكتب قراءة DWARF 5.debug_info،.debug_abbrev،.debug_str_offsetsو.debug_addrمن كائن قابل للتنقل، والتحقق منه منllvm-dwarfdump. . .
  • مقارنة التخطيط المُسترد مع التخطيط DWARF وسمي كل اختلاف كدليل مفقود أو وصول مُدمج أو نوع غير قابل للتثبت.
  • اقرأ إيتانيوم C ++ vtable، نوع المعلومات، وأسماءها المهزومة من عمليات التنقل، وحل مكالمة افتراضية إلى فتحة.

المشكلة

لقد قمت بتجميعهاaccount.cبدون-gو سألت عميل ماbumpلقد أرسل العميل الكائن إلى (غيدرا) 12.1.4 و حصل على هذا

c
void bump(long param_1,int param_2)
{
  *(int *)(param_1 + 4) = param_2 + *(int *)(param_1 + 4);
  return;
}

نفس المصدر الذي تم تجميعه مع-gيعطي هذا من نفس المتحلل:

c
int bump(account *a,int by)
{
  a->balance = by + a->balance;
  return a->balance;
}

التوجيهيات متطابقة في كلا الكائنات. الفرق هو جدول النوع. مع الجدول يعرف المتحلل أن المعلم الأول يشير إلى هيكل 32 بايت الذي يكون حقل في التداخل 4 هو 4 بايت intاسمهاbalanceبدونها ، المفكّر يعرف فقط أنّ شيء بعيد 8 بايت وصل إلى هنا%rdiوذلك 4 بايت في +4قراءة و كتابة

كل شيء في هذه الجدول لديه مصدر. التخفيض 4 يأتي من التخفيض بايت من addl 0x4(%rax), %ecx. الـ 4 من عرضlالإضافة، الاسم يأتي منDW_TAG_memberالدخول في .debug_infoهذه الدروس تبني كلا النصفين: مرسل يعيد ما تثبت التعليمات، وقارئ يفكّر ما سجّل المحامّل. الفجوة بينهما هي تكلفة اسم النموذج.

الوصول إلى عضو هو نزوح

تعريف إدارة إدارة الإنترنت الإنترنت (Intel SDM) الجدول 1، القسم 3.7.5 عنوان الفعالOffset = Base + (Index * Scale) + Displacement، مع مقياس 1، 2، 4 أو 8 وتحرك 8، 16 أو 32 بت. في القسم 3.7.5.1 يضيف نموذج وضع 64 بت RIP + Displacement. يستخدم وصول عضو البنية النموذج الأول دون مؤشر: سجل الأساس يحمل عنوان الكائن والتحرك هو تعويض العضو. يضيف وصول صفحة مصطلح المؤشر، والقياس أو مضاعفة منفصلة تحمل حجم العنصر.

خذbumpفي-O0. الإدراج منllvm-objdump -d -rيقرأ:

output
       4: 48 89 7d f8                  	movq	%rdi, -0x8(%rbp)
       e: 48 8b 45 f8                  	movq	-0x8(%rbp), %rax
      12: 03 48 04                     	addl	0x4(%rax), %ecx
      15: 89 48 04                     	movl	%ecx, 0x4(%rax)

فك الرمز03 48 04مع القواعد من الدروس 33. 03هوADD r32, r/m32. مودر ام48هو01 001 000: mod = 01يعني جهاز عمل ذاكرة مع تحرك 8 بتات، reg = 001هو%ecx،r/m = 000هو%rax. البايت التنقل هو 04. التعليمات تقرأ 4 بايت في %rax + 4و يضيفهم إلى%ecx- المُؤمنونlالإضافة التي llvm-objdumpالطابع هو حجم العرض، لذلك الحقل هو 4 بايت على عريض.%raxجاء من فتحة الإطار في-0x8(%rbp)، وذلك الفراغ قد تلقى%rdiثلاث حقائق تلي: المعلم الأول هو مؤشر، والجسم الذي يشير إليه لديه حقل 4 بايت في التداخل 4، و bumpيقرأ ويكتب هذا الحقل

لا شيء في هذه البايت تقولint،balanceأوstruct accountلا يوجد شيء يخبرنا كم حجم الكائن الباقي من الـ 28 بايت من الهيكل غير مرئيbump. . .

قواعد ترتيب النظام V

يقدم x86-64 psABI ، القسم 3.1.2 ، ثلاثة قواعد للمجمعات. "تفترض الهياكل والاتحادات تحسيس مكوناتها الأكثر صرامة. يتم تعيين كل عضو إلى أدنى تعادل متاح مع التوجه المناسب. حجم أي كائن هو دائمًا مضاعف من تحسيس الكائن". تشير الشكل 3.1 من نفس الوثيقة إلى أحجام التوجه والتحسيس: char1 و 1 ، short2 و 2 ، int4 و 4، double8 و 8، أي مؤشر 8 و 8.

تطبيق القواعد على الهياكلaccount.c:

c
struct account {
    char kind;
    int balance;
    short code;
    double rate;
    const char *label;
};
MemberSizeAlignmentLowest offset that fitsPadding before
kind1100
balance4443 (bytes 1 to 3)
code2280
rate88166 (bytes 10 to 15)
label88240

ينتهي العضو الأخير في 32 . التوجه الأكثر صرامة هو 8 ، و 32 هو مضاعف من 8 ، لذلك الحجم هو 32 دون ملعب ذيل. تسعة بايت من 32 ملعب ، و psABI يقول "محتويات أي ملعب غير محددة".

ترتيب الأعضاء يحدد حجمهم.double, pointer, int, short, charتعويضات الاكتفاء 0، 8، 16، 20، 22 وتنتهي عند 23، والتي تصل إلى 24 sysv_layout()فيcode/main.pyيطبق القواعد الثلاثة ويجلب كلا النتائج.ctypesيطبق الوحدة نفس القواعد من خلال اتفاقية المُجمّع المضيف، و ctypes.sizeof،ctypes.alignment، و .offsetمن كل حقل أعطوا 32، 8 و 0, 4, 8, 16, 24البرنامج نفسه يقوم بطبع النسخة الثالثة عندما تقوم ببناءها وتشغيلها على هذه الآلة:

output
sizeof 32 align 8
kind 0 balance 4 code 8 rate 16 label 24
sum 315 tag -3 projected 318.75 label_length 7

هذا البناء الأصلي هو arm64. يذكر Arm AAPCS64 نفس القواعد الإجمالية في قسم 5.10.1 ، ويقوم وثيقة Apple arm64 بحفاظ على هذه الأحجام المتعددة الخمسة ، لذلك تتوافق الأرقام على طول الهدفين. يتم تجميع كائنات x86-64 في الدروس مع -target x86_64-unknown-linux-gnuحتى قارئ ELF من الدروس 33 وقارئ DWARF أدناه يعمل على ذلك.

اقرأ بقية المخطوطة يدويا

bumpيرى حقل واحد.account.cانظر البقية، وكل منها يظهر نموذج تعليمات مختلفة.account-x86_64-O0-g.objdump.txt. . .

sum_balancesيُؤشر صفوف من الهياكل:

output
      51: 48 8b 45 f8                  	movq	-0x8(%rbp), %rax
      55: 48 63 4d ec                  	movslq	-0x14(%rbp), %rcx
      59: 48 c1 e1 05                  	shlq	$0x5, %rcx
      5d: 48 01 c8                     	addq	%rcx, %rax
      60: 8b 40 04                     	movl	0x4(%rax), %eax

%raxهوlist،%rcxهوiالتسجيل الممتد إلى 64 بت، و shlq $0x5مضاعفة ب 32 بعد addq،%raxهوlist + 32 * i، والحمل يقرأ 4 بايت في تعويض 4 من العنصر i. تعد الحلقة هي المكان الوحيد في هذا الكائن حيث يظهر حجم الهيكل كرقم. الحلقة التي تحتوي على هذه التعليمات تبدأ من 0x49إلىjmp 0x49في0x72. . .

tagيقرأ حقلين ضيقين:

output
      8c: 0f be 00                     	movsbl	(%rax), %eax
      98: 0f bf 48 08                  	movswl	0x8(%rax), %ecx

movsblتحميل بايت واحد في التداول 0 وتوسيعها، لذلك الحقل في 0 هو عدد كامل من 1 بايت. movswlتحميل بايتين في التداول 8 مع تمديد الإشارة، لذلك الحقل في 8 هو رقم كامل مؤلف من 2 بايت.-O1أول وصول يصبحcmpb $0x6c, (%rdi)، الذي لا يزال يقول 1 بايت عند تعويض 0 ولا يقول شيئا عن الشارة.

projectedيقرأ على العباء:

output
      cc: f2 0f 2a 40 04               	cvtsi2sdl	0x4(%rax), %xmm0
      d5: f2 0f 59 40 10               	mulsd	0x10(%rax), %xmm0

cvtsi2sdlتحويل عدد كامل من 32 بت من الذاكرة إلى مزدوج، مما يؤكد أن التفويض 4 يحمل عدد كامل من 4 بت. mulsdمضاعفة بمضاعفة من الذاكرة، لذلك تعويض 16 يحمل قيمة نقطة عائمة 8 بايت.

label_lengthيقرأ مؤشراً ثم يتابعه:

output
      f3: 48 8b 40 18                  	movq	0x18(%rax), %rax
      f7: 48 63 4d f4                  	movslq	-0xc(%rbp), %rcx
      fb: 80 3c 08 00                  	cmpb	$0x0, (%rax,%rcx)
  • نعمmovqيقرأ 8 بايت في التداول 24. بمفرده هذا هو عدد كامل من 8 بايت أو مؤشر.cmpbيستخدم القيمة المحملة كأساس لعملة الذاكرة. القيمة التي تستخدم كعنوان هي مؤشر.cmpbيقرأ 1 بايت في المؤشر %rcxمن ذلك، وفي-O1المؤشر متقدم مع leaq 0x1(%rcx), %rcxداخل الحلقة، لذلك العناصر المستهدفة إلى 1 بايت بعيدا.

لو وضعت الوظائف الخمسة معاً فإن الهيكل هو{ i8 at 0, i32 at 4, i16 at 8, f64 at 16, pointer at 24 }مع خطوة 32 . الفجوات في 1 إلى 3 و 10 إلى 15 هي بالضبط التدوية التي توقعها قواعد psABI. ما لا يقوله التعليمات: أسماء الأعضاء ، أن المؤشر هو const char *، النوع المعلن من البايت الموقّع عند 0 ، الذي يكتب المصدر char، وأن الوظائف الخمسة تتلقى نفس النوع.main، الذي يمر كائن إطار واحد إلى جميع الخمسة.-O0مواقع المكالمات تجعل ذلك مرئيًا: leaq -0x70(%rbp), %rdiيليها addq $0x20, %rdiقبل ذلكcallq bumpوaddq $0x40, %rdiقبل ذلكcallq tag. التكفيف 00x20و0x40هي ثلاثة عناصر من صف واحد، وال memcpyمن0x60البايتات التي تملأه هي 96 بايت، وهو ثلاثة عناصر من 32.

المرور

trace_function()فيcode/main.pyيمر إرشادات الوظيفة في ترتيب العنوان ويحافظ على حالة تجريدية صغيرة. حامل هو سجل 64 بت أو فتحة الإطار مثل -0x8(%rbp)كل حامل لديه واحد من ثلاثة قيمPointerهو الجذر زائد تعويض ثابت زائد مجموعة من الخطوات، حيث تسجل خطوة أن مضاعفة مجهولة من ذلك العدد من البايتات قد تم إضافة.Indexهو عدد كامل من القيمة غير المعروفة التي هي مضاعفة من مقياس. أي شيء آخر هو لا شيء.

البداية من ستة سجلات حجة النظام VPointer("param0", 0)من خلالPointer("param5", 0)ثم كل تعليمة تطبق قاعدة واحدة:

  • أmovqبين سجل 64 بت وفرق الإطار نسخ القيمة. كتابة 32 بت أو أصغر إلى سجل تجعلها Indexمع مقياس 1.
  • أleaqمع قاعدة المؤشر ينتج مؤشر مع إضافة التحول، وبالإضافة إلى سجل المؤشر يضيف scale * index_scaleإلى الخطواتleaqقاعدة من%rbpأو%rspاسم جسم الإطار، وهذا هو كيف mainيمرrows. . .
  • shlq $nعلىIndexيضاعف حجمها2^nوimulq $kيضاعف ب k. . .
  • addqمن المؤشر والمرجع يحافظ على المؤشر ويضيف مقياس المؤشر إلى الخطوات. addq $kإلى مؤشر يُحرك تعويضها ويُسجل أيضاً كإضافة إلى هذا السجل.
  • يجعلها المكالمة كل سجل مدفوع من المكالمة غير معروفة، و سجلات الحجج التي تحتوي على المؤشرات يتم تسجيلها كموقع المكالمة.
  • أي عملة ذاكرة تحتوي على سجل أساسي يشير إلى أن تكونAccessمع جذور المؤشر والعكس، وعرض ونوع من النموم، ودرجة المخزن.

الواسعة والنوع تأتي من الـ"منيمونيك" وحدهاaccess_width()خرائط b،w،lوqالإضافات إلى 1، 2، 4، و 8 بايت، movsblوmovswlلتحملات ضيقة موقعة، movzblإلى غير الموقعة،sdوssتعليمات SSE المتقدمة للطائرات العائمة بـ8 بايت و4 بايتcvtsi2sdlإلى مصدر كامل من 4 بايت وقع عليه، و16 بايت movapsالعائلة إلى المتجهleaqو nopالأوراق ليست إمكانية الوصول

python
if memory.base in FRAME_REGISTERS:
    return Pointer(f"frame({memory.base}{format_signed(memory.displacement) if memory.displacement else ''})", 0)
base = env.get(memory.base)
if not isinstance(base, Pointer):
    return Unknown
strides = set(base.strides)
if memory.index is not None:
    index_value = env.get(memory.index)
    strides.add((index_value.scale if isinstance(index_value, Index) else 1) * memory.scale)
return Pointer(base.root, base.offset + memory.displacement, frozenset(strides), base.origin)

تحميل من 8 بايت من خلال المؤشر ينتج مؤشر جديد يطلق عليه الاسم الجذري على الحقل الذي جاء منه ، مثل param0->0x18عندما تستخدم هذه القيمة لاحقاً كقاعدة أو تقدم معleaq، الحقل الذي جاء منه هو مسموح كمرشح مثبت. هذه هي القاعدة التي تحول تعويض 24 من "8 بايت" إلى "مرشح".

أربعة أنواع من الدليل

infer_layout()يدمج إمكانات وصول كل وظيفة الشاهد ويجمع الخطوة من أربعة مصادر، كل منها مع التعليم الذي أنتجته.

KindWhere it comes fromBuild that shows it
scalean Index with scale above 1 reached a pointer, from shlq $0x5 or imulq-O0, shlq $0x5, %rcx at 0x59
loop-stepa 64-bit increment of the base or index register inside a loop that also contains the access-O1, addq $0x20, %rdx at 0x24
offset-deltasame-width accesses through one base register inside one loop, at offsets a constant apart-O2, eight loads 32 apart
call-sitethe offsets at which one frame object is passed to the witness functions-O0, main passes rows, rows+0x20, rows+0x40

الحلقة هي عكسjmpأو فرع مشروط، ويعتبر زيادة فقط عندما يحتوي نطاق الحلقة على كل من زيادة والوصول.addl $0x1, %eaxالذي يتقدمiفي-O0، لأنه إضافة 32 بتة ولأن الوصول لا يمر من خلال %eaxالخطوة الأخيرة هي أكبر مقسم مشترك لكل قيمة وجدت. الحجم هو الخطوة عندما يكون الخطوة أكبر على الأقل من نهاية الحقل الأخير.

الحلقة المتجهة إلى -O2

في-O2الجهازات المتجهةsum_balances. جسم الحلقة من0x50إلى0xaaيقرأ ثمانية عناصر لكل مرسل:

output
      3b: 48 8d 87 e4 00 00 00         	leaq	0xe4(%rdi), %rax
      50: 66 0f 6e 50 80               	movd	-0x80(%rax), %xmm2
      55: 66 0f 6e 98 60 ff ff ff      	movd	-0xa0(%rax), %xmm3
      61: 66 0f 6e 90 40 ff ff ff      	movd	-0xc0(%rax), %xmm2
      69: 66 0f 6e a0 20 ff ff ff      	movd	-0xe0(%rax), %xmm4
      7d: 66 0f 6e 10                  	movd	(%rax), %xmm2
      81: 66 0f 6e 58 e0               	movd	-0x20(%rax), %xmm3
      8a: 66 0f 6e 50 c0               	movd	-0x40(%rax), %xmm2
      8f: 66 0f 6e 60 a0               	movd	-0x60(%rax), %xmm4
      a0: 48 05 00 01 00 00            	addq	$0x100, %rax
      aa: 75 a4                        	jne	0x50 <sum_balances+0x40>

%raxيبدأ فيlist + 0xe4. ثمانية عمليات نقل تحوي الحمولة إلى تعويضات0x04, 0x24, 0x44, 0x64, 0x84, 0xa4, 0xc4, 0xe4منlist، والتي هي4, 36, 68, 100, 132, 164, 196, 228. الزيادة هي0x100، وهو 256 . تمرير يسجل تعويضات كما يراه يبلغ ثمانية حقل وهيكل من 232 بايت على الأقل. القائمة صحيحة والنتائج خاطئة.

fold_unrolled()تطبق قاعدة واحدة. داخل حلقة واحدة، تأخذ وصولات ذات الاحجام من خلال سجل واحد أساسي. تعويضاتهم هي dبعيدا، هنا 32. إذا كانت خطوة الحلقة هي k * dوهناك بالضبطkالتداعيات المختلفة، وكانت الحلقة مفتوحةkفي بعض الأحيان، وكل وصول هو نفس عنصر الوصول في التداخل (offset - lowest) mod d- ها هي256 = 8 * 32مع ثمانية تعويضات، لذا كل ثمانية الحمولات ينحني على تعويض 4، والدقة دليل تسجيلoffset-delta 32،unrolled 32وloop-step 256. حلقة بقايا القياسية في0xe0يضيفscale 32منshlq $0x5, %rdxوloop-step 32منaddq $0x20, %rdxأكبر مقسم مشترك من جميع القيم الخمس هي 32

الصورة لديها زر يطفئ التطيع مع إيقافه-O2يظهر البناء سبعة حقل بعد البايت 32 و لا يوجد حجم. هذه الحالة هي ما يراه القارئ الذي يثق في التحول دون الحلقة.

DWARF 5 من البايت

  • نعم-gبناءaccount.cيحمل ثمانية أقسام إصلاح. .debug_infoهو 493 بايت، .debug_abbrev179, .debug_str230، .debug_str_offsets124، .debug_addr112، .debug_line312.debug_line_str12، و .debug_frame264 - إدخال قوائم معيار DWARF 5 .debug_str_offsets،.debug_addrو.debug_line_strكما هو جديد في النسخة 5 . القارئ في code/main.pyيحتاج الخمسة الأولى

الرأس الوحدة

يقدم القسم 7.5.1.1 من المعيار عنوان الوحدة الكاملة لـ"التجميع": unit_length،version،unit_type،address_sizeوdebug_abbrev_offsetأول 12 بايت من.debug_infoهي:

output
e9 01 00 00   unit_length = 0x1e9 = 489
05 00         version = 5
01            unit_type = DW_UT_compile
08            address_size = 8
00 00 00 00   debug_abbrev_offset = 0
  • نعمunit_typeالطول يحتسب البايتات بعد نفسه، لذلك الوحدة التالية ستبدأ في 4 + 489 = 493، نهاية القسمllvm-dwarfdump --debug-infoيطبخ نفس القيم الخمسة على مقالاتهCompile Unit:خط، والاختبار الأول يبحث عن هذا الخط ويقارن.

اختصارات

يقول القسم 2.1 أن كل إدخال معلومات التحليل "يتكون من علامة تحديد ومجموعة من الصفات"..debug_abbrev(القطع 7.5.3) الذي يسمي العلامة، يقول ما إذا كان الأطفال يتبعون، ويرسل كل صفة مع شكلها. هذا الكائن لديه 14 إعلانات.

output
[12] DW_TAG_structure_type  DW_CHILDREN_yes
     DW_AT_name              DW_FORM_strx1
     DW_AT_byte_size         DW_FORM_data1
     DW_AT_decl_file         DW_FORM_data1
     DW_AT_decl_line         DW_FORM_data1
[13] DW_TAG_member           DW_CHILDREN_no
     DW_AT_name              DW_FORM_strx1
     DW_AT_type              DW_FORM_ref4
     DW_AT_decl_file         DW_FORM_data1
     DW_AT_decl_line         DW_FORM_data1
     DW_AT_data_member_location  DW_FORM_data1

parse_abbreviations()يقرأ رمز ULEB128، علامة ULEB128، واحد الأطفال البايت، ثم صفة ونوع الأزواج حتى 0, 0زوج. في القسم 7.5.6 يقدم رمزية النموذج: data1هو بايت واحدref4هو تعويض 4 بايت بالنسبة لوحدة، strx1هو مؤشر بايت واحد في جدول تعويضات السلاسلsec_offsetوdata4هي 4 بايت، addrxهو مؤشر ULEB128 في جدول العناوين، exprlocهو طول ULEB128 و ذلك العديد من البايت، و flag_presentلا يوجد بها بايت على الإطلاق

فك الرقم من عضو الرصيد يدويا

إدخال الهيكل ل accountيبدأ في التكفيف0x195مع البايتات0c 15 20 00 03رمز الإختصار هو 12، الهيكلDW_AT_nameهو مؤشر السلاسل0x15،DW_AT_byte_sizeهو0x20= 32DW_AT_decl_fileهو 0 و DW_AT_decl_lineهو 3، وهو خط struct account {في المصدر

يبدأ الدخول الثاني للعضو في 0x1a3مع0d 10 88 01 00 00 00 05 04:

  1. 0dهو اختصار 13، عضو.
  2. 10هوDW_AT_nameكـstrx1: مؤشر السلاسل 16.
  3. 88 01 00 00هوDW_AT_typeكـref4: تعويضات 0x188من بداية الوحدة، وهو DW_TAG_base_typeالإدخال المسمىint. . .
  4. 00هوDW_AT_decl_fileو05هوDW_AT_decl_line: الخط 5 من account.c. . .
  5. 04هوDW_AT_data_member_location: التكفيف 4.

يقول القسم 5.7.6 أن إدخال عضو "يمكن أن يكون إما صفة DW_AT_data_member_location أو صفة DW_AT_data_bit_offset" وأن ثابت عدداً كاملا "هو التخفيض في البايتز من بداية الكيان المحتوى". هذا البايت هو نفس العدد الذي يتم تحويله في addl 0x4(%rax), %ecxوcompare_layouts()هو الوظيفة التي تضع الجانبين جنبا إلى جنب.

السلاسل والعناوين تذهب من خلال جدولين

مؤشر السلاسل 16 ليس تعويضًا لـ .debug_strالقارئ يأخذDW_AT_str_offsets_baseمن إدخال الوحدة، والذي هو 8، ويقرأ إدخال 4 بايت في 8 + 4 * 16في.debug_str_offsetsهذا الإدخال يحمل تعويضات"balance"في.debug_strنفس الإرشادات غير المباشرة تنطبق على العناوين.sum_balancesيبدأ بالبايت07 07: اختصار 7، وبرنامج فرعي، ثم DW_AT_low_pcكـaddrxالإندكس 7 الإدخال 8-byte في 8 + 8 * 7في.debug_addrهو0x30، عنوان الوظيفة في .textأول ستة إدخالات في هذه الجدول هي عناوين ستة سلسلة حرفية.

في كائن قابل للتنقل لا يوجد أي من الجداول تحتوي على قيمها النهائية..debug_str_offsetsهو صفر في الملف، و .rela.debug_str_offsetsيحتفظ 29 R_X86_64_32عمليات نقل ضد.debug_strمع التعويض الحقيقي في الإضافة. .debug_addrلديه 13 R_X86_64_64عمليات نقل ضد.textو.rodata.str1.1. ..debug_infoلديها أربع عمليات نقل خاصة بها: التكافؤ المختصر، DW_AT_str_offsets_baseكـ.debug_str_offsets+0x8،DW_AT_stmt_listوDW_AT_addr_baseكـ.debug_addr+0x8. .ElfObject.relocated()يطبقS + Aللأنواع 32 بت و 64 بت قبل أن يقوم القارئ بتحليل قسم. رمز قسم لديه قيمة 0 في ملف كائن، لذلك يحصل الحقل على الإضافة. llvm-dwarfdumpيظهر نفس القرار، ولهذا السبب يظهر إصدارها أسماء وعناوين مع كل مؤشر حل.

الأنواع والوظائف والمواقع

DwarfInfo.type_name()يلي DW_AT_typeالإشارات.labelنقاط عضو في DW_TAG_pointer_type، والتي تشير إلىDW_TAG_const_type، والتي تشير إلىcharالنوع الأساسي، لذلك الاسم يتكون من const char *. .type_size()العائداتDW_AT_byte_sizeللأنواع والهياكل الأساسية، 8 للنقاط، والعد مرات حجم العنصر للمصفوفة. type_kind()يقلل منDW_AT_encodingمن نوع أساسي إلى intمع علامة، أوfloat، و نوع المؤشر إلى pointer، وهو المفرد الذي تستخدمه المقارنة.

DwarfInfo.functions()يقرأ ستة DW_TAG_subprogramالإدخالاتbumpيذهب من0x0إلى0x21، وهوDW_AT_low_pcبالإضافة إلىDW_AT_high_pcطوله قاعدة الإطار هيDW_OP_reg6 RBP، وبرامجهاaلديه تعبير الموقع91 78: DW_OP_fbregمع قيمة SLEB128 0x78، وهو -8 . هذا هو فتحة الإطار-0x8(%rbp)حيث يتم تخزين الإدراجات%rdiفي العنوان0x4معلومات التحليل والإرشادات تصف نفس الفتحة من جانبي

مقارنة المخططين

compare_layouts()يمر أعضاء DWARF ويبحث عن حقل استنتاج في نفس التنازل.no-evidence. حقل من حجم مختلف هو size-mismatchعضو المؤشر الذي لم يتم إزالة الحقل من قبل هوpointer-unproven. نوع غير متفق ، مثل العباء حيث تستخدم التعليمات الرياضية للأعداد الكاملة ، هو kind-mismatch. الحقول المُسجلة التي تبدأ داخل عضو أو داخل التغطية هيunexpectedالحجم يقارن الخطوةDW_AT_byte_size. . .

أرقام البناء الثلاثة، منpython3 code/main.py:

BuildAccesses through the first parameterFieldsStride evidenceMembers matchedSize
-O0115scale 32, call-site 32, call-site 645 of 532, match
-O195loop-step 325 of 532, match
-O2175 after folding, 12 beforeoffset-delta 32, unrolled 32, loop-step 256, scale 32, loop-step 325 of 532, match

الفجوات المستنتاجة هي بايت 1 إلى 3 و 10 إلى 15 في كل بناء، وهذه هي بالضبط بايتات التشغيل من ترتيب DWARF. كل حقل المستنتاج يقع في مضاعف من حجمها الخاص، وهو ما يتطلب قواعد psABI، و natural_alignment_ok()سجل ذلك.

إضافة الوظائف واحدة في كل مرة وتغير الصورة.bumpوحدها التخطيط هو حقل واحد في التخفيف 4 ، والحجم غير معروف ، والحدود السفلى هو 8. sum_balancesيضيف الخطوة.tagيضيف التداعيات 0 و 8. projectedيضيف العائفة إلى 16.label_lengthيضيف المؤشر إلى 24 و يثبت أنه مؤشر. mainيضيف مواقع المكالمات في -O0ولا يضيف أي شيء إلى-O1و-O2لأن المجمع وضع كل مكالمة في خط.

ثلاثة أشياء لا تصل إليها، الأسماء ليست في التعليماتconstليس في التعليمات النوع البنتاlabelهو فقط "عنصراً 1 بايت بعيداً" عن leaq 0x1(%rcx), %rcxفي حلقة السلاسل، والتي تتوافق مع charو معunsigned charو_Boolفي-O1علامة البايت عند التداخل 0 غير معروفة، لأن cmpb $0x6c, (%rdi)تقارير الموافقة int8هناك وi8في-O0، حيثmovsblهذه هي الحقائق الدروس 35 سجلات كغير معروفة.

كائنات C ++

shapes.cppيحددShapeمع مدمر افتراضي، افتراضي خالص area()وطبيعة افتراضيةname()وRect، والذي ينبع منShapeو يضيف اثنينintأيه تيانيوم C++ ABI يحكم كيفية تشغيل هذه على x86-64 لينكس.

أسماء غير صحيحة

القسم 5.1.2 من القانون يقول "اسم يتم تدميرها من خلال التمهيئة_Z"الجهاز لديه 19 رمزاً من هذا القبيل._ZNK4Rect4areaEvيقرأ_Z، ثمNلسم مستقرKلـ وظيفة عضو const، 4Rectو4areaكأسماء مصدر ذات مسبوقة طويلة ،Eلإغلاق الاسم المُعشّر، وvلـ قائمة المعلمات الفارغةllvm-cxxfiltبصماتRect::area() const. ._Z8describeRK5Shapeiهوdescribe(Shape const&, int): Rهو مرجعKهو const،5Shapeهو الفصل،iهوint. الاسماء الخاصة في القسم 5.1.4 هي _ZTVلـ (فيتبول)_ZTIلـ "تايف انفو" و_ZTSللسلسلة الاسم Typinfo. المُبنيون والمُدمّرونC1،C2،D0،D1وD2، لذا_ZN4RectD0Evو_ZN4RectD2Evكلتا الطرازينRect::~Rect(): مدمر القضاء والمدمر الأساسي يشتركون في اسم المصدر.

Demanglerفيcode/main.pyتغطي هذه المجموعة الفرعية بالإضافة إلى رموز نوع البناء في القسم 5.1.5.2 و S_استبدال الفقرة 5.1.10 الاختبار ينفذ على جميع الأسماء 19 ويتم مقارنتها مع الأسماء التي تم القبض عليهاllvm-cxxfilt --no-strip-underscoreالنتائج: العلم مهم على macOS، حيث تسحب الأداة نقطة تأكيد واحدة مقدمة بشكل افتراضي ثم لا يتم التعرف عليها Z8describe.... . .

المعدات التي تم نقلها

يذكر القسم 2.5.2 من ABI مكونات الجدول الافتراضي بالترتيب: تعويضات الدعوة الافتراضي، تعويضات القاعدة الافتراضي، تعويض إلى الأعلى، مؤشر typeinfo، ومن ثم مؤشرات الوظيفة الافتراضية. نقطة العنوان، القيمة التي تحتوي عليها vptr للكائن، هي عنوان مؤشر الوظيفة الأول (قسم 2.6.2). Rectليس لديها قواعد افتراضية، لذلك يتم تعويض جدولها إلى أعلى، وتصفيف المعلومات، والعملات.

الرمز_ZTV4Rectهو 0x30 بايت، ستة فجوات 8 بايت. في الموضوع كل بايت هو صفر. المحتوى في .rela.data.rel.ro._ZTV4Rect:

output
0000000000000008 R_X86_64_64              _ZTI4Rect
0000000000000010 R_X86_64_64              _ZN4RectD2Ev
0000000000000018 R_X86_64_64              _ZN4RectD0Ev
0000000000000020 R_X86_64_64              _ZNK4Rect4areaEv
0000000000000028 R_X86_64_64              _ZNK4Rect4nameEv

سلايت 0 تحتفظ بالتعويض إلى الأعلى ، وهو 0 ولا يحتاج إلى إعادة التنقل. سلايت 1 هو نوع المعلومات. نقطة العنوان هي في +16من هناك، فتحة 0 هي مدمرة الكائن الكامل، فتحة 1 مدمرة الحذف، فتحة 2area، الفقرة 3name. .read_vtable()يقرأ حجم الرمز، وإعادة التنقل في نطاقه، والصفر عند التنازل 0. Shapeطاولة لديه نفس الشكل مع__cxa_pure_virtualفي فتحة 2، وهو وظيفة وقت تشغيل التي تصل مكالمة افتراضية نقية.

RTTI

القسم 2.9.4 يصف std::type_infoك vptr يليها مؤشر لسم النوع المختلف، ويرسل الفئات المشتقة. _ZTI4Rectلديه ثلاثة عمليات نقل: _ZTVN10__cxxabiv120__si_class_type_infoE+0x10،_ZTS4Rectو_ZTI5Shapeالأول هو القائمة__si_class_type_info، فئة تستخدمها ABI للنوع مع قاعدة عامة واحدة غير افتراضية عند التكفيف الصفر، و +0x10هو نقطة العنوان في هذه اللوحة. النقاط الثانية في السلسلة 4Rect، وهو اسم النوع المزعج ، وname()علىtype_infoيعود بالضبط هذا السلسلة. الثالث هو القاعدة. _ZTI5Shapeاستخدامات__class_type_infoو لا يوجد له مؤشر أساسيread_typeinfo()يعيد هذه الحقائق الثلاثة

المكالمة الافتراضية

describeفي-O0يقوم بمكالمة افتراضية واحدة:

output
       f: 48 8b 7d f8                  	movq	-0x8(%rbp), %rdi
      13: 48 8b 07                     	movq	(%rdi), %rax
      16: ff 50 10                     	callq	*0x10(%rax)

الأولmovqيُحمل مؤشر الكائن. يقرأ الثاني 8 بايت في تعويض 0 من الكائن، وهو vptr، لأن ABI يضعها أولاً في فئة ذات وظائف افتراضية.callqيمر0x10لقد مرّت (VPTR) ، و0x10 / 8هو فتحة 2.find_virtual_calls()يطابق النمط ويقدم تقارير لكل من المرشحين: Rect::area() constمن_ZTV4Rectو__cxa_pure_virtualمن_ZTV5Shapeأي واحد يدير يعتمد على الكائن، وهذا هو السبب في أن المكالمة غير مباشرة.

-تسجيلات DWARF نفس الفتحةDW_TAG_subprogramإدخالات areaفي كلا الفئتين تحملDW_AT_vtable_elem_location (DW_OP_constu 0x2)،nameيحمل فتحة 3 والمدمرون يحملون فتحة 0. RectهوDW_TAG_structure_type16 بايت مع DW_TAG_inheritanceالإدخالShapeفي الموازنة 0 والعضاء wفي الساعة الثامنةhفي الساعة 12Shapeهو 8 بايت مع عضو اصطناعي واحد _vptr$Shapeاثنين من مشتقات مستقلة يعطي فتحة 2 لarea: جدول الترحيل وإدخالات التحليل.

في-O1لا يوجد في هذا الكائن أي شيءmainالمبانيRectكالموقع المحلي، لذلك حل المحمول r.name()وdescribe(r, 2)في وقت تجميع وتخطيطها. لا شيء يأخذ عنوان الجدول، لذلك الجدول لا يتم إصدار. describeلا يزال يحتوي علىmovq (%rdi), %raxوcallq *0x10(%rax)، لأن ملامحها يمكن أن تكون أي Shape. الملف المفقود هو حقيقة حول ملف كائن واحد، والاختبار test_the_optimised_object_keeps_the_call_pattern_but_drops_the_vtablesيُحقق من كلا النصفين

تختلف ميكروسوفت ABI في كل من هذه التفاصيل.?وترجمة الاتفاقية المكالمة، لذلك void test(void)تم تجميعها كـ C++ __cdeclهو?test@@ZAXXZ. مقياس RTTI يقع في vfptr[-1]كـ_RTTICompleteObjectLocator، والرموز هي??_7لـ (vftable) و??_R4ويقول وثائق مايكروسوفت الخاصة أنه "لا توجد معايير لتسمية C++ بين بائع المجمعين".

كيفية توضيح الأدوات الأنواع التي تم استعادتها

كل سطر أسفل الذي يسمي خروجا تم إنتاجها على هذه الآلة و تم تسجيلها إلىcode/fixtures/tools/الصفوف للأدوات التي لا تثبت هنا تصف وثائقها وتقول ذلك.

ToolVersion, licenceWhere types liveOn our fixtures
Ghidra12.1.4, 2026-09-21, Apache-2.0Data Type Manager with built-in, user-defined, and derived types; .gdt archives; types used by a program are copied into itWith DWARF: /DWARF/account.c/account size=32 align=8 and a->balance = by + a->balance;. Without: *(int *)(param_1 + 4) with long param_1, tag(char *param_1), and *(char **)(param_1 + 0x18)
IDA Pro9.4, 2026-07-13, commercial; IDA Free is non-commercial with a cloud decompiler for x86 onlyLocal Types window, .til type libraries, DWARF and PDB import into a dwarf or pdb folderNot installed. Documentation only
Binary Ninja6.0, 2026-09-03, commercial with a Free editionType editing, header import and export, type archives, debug info from DWARF and PDB, type libraries, platform typesNot installed. Documentation only
radare26.2.4, 2026-10-05, LGPLv3ts, tsc, afvdts and tsc account printed nothing for the relocatable object with bin.dbginfo=true; pdf shows mov dword [rax + 4], ecx with source line comments from DWARF
rizin0.9.1, 2026-06-29, LGPL corets, tsc, afvdtsc account printed struct account { char kind; int balance; short code; double rate; const char *label; }; from DWARF; the build without -g lists only its bundled header types
lldblldb-1703.0.236.21image lookup -tbyte-size = 32 and the full compiler_type text from the ELF object
gdbnot installed hereptype /o prints "the offsets and sizes of fields in a struct" with holes markedDocumentation only
pyelftools0.33, public domainPython API over .debug_infoA 43-line script prints the same five members and unit version 5
llvm-dwarfdumpApple LLVM 17.0.0--debug-info, --debug-abbrev, --debug-str-offsetsThe oracle for every reader test

إن إصدار غيدرا بدون DWARF يظهر نفس القواعد في العمل. تم تعيين مفكّرها char *إلى مُعيار tagلأن الوظيفة تقارن بايت واحد من خلالها، وchar **إلى الميدان في0x18لأنlabel_lengthيُحذف القيمة المحملة ويقارن البايتات. هذه نفس القواعد الثنائية التي تطبق عليها الممر في هذا الدروس: عرض من التعليمات، مؤشر من الإشارة. تقول وثائق Ghidra لفك الكود p-code varnodes "لا يكون لزميا نوع البيانات المرتبطة بهم" وأن المفك "في النهاية يعطي نوع البيانات الرسمية" عن طريق الانتشار. خيار "إضافة مؤشرات ثابتة" يمتد نفس الانتشار إلى ثابتات. إضافة Ghidra 12.1 استعادة الحقول البيت إلى المتحلل، والتي لا تنفذ معدات هذا الدروس.

الطاولة المصنوعة يدويا هي النصف الآخر من القصة مدير نوع البيانات في Ghidra، الأنواع المحلية في IDA، أرشيف النوع من Binary Ninja، و rizin tdكل ما يسمح لك أن تكتب الهيكل من التخطيط المردد ومشاهدة إعادة تقديم المتحلل. المرور يخبرك ما الذي يجب أن تكتب وماذا الحقول التي تخمن.

rea 6.5.0 مع مزودها Ghidra يعيد نص المتحلل كسجل. كل شيء في صف Ghidra ينطبق عليه، والسجل يحمل أسماء النوع دون دليل الذي أنتجهم.python3 code/main.py inferفي نفس القائمة يعطيك هذا الدليل

أبحاث التأثيرات النوعية

الممر هنا هو نصف توليد القيود من نظام استنتاج النوع، المحدود إلى واحد من الهياكل التي تمت الوصول إليها من معايير.

  • قام TIE (Lee، Avgerinos، Brumley، NDSS 2011) بإعداد إعادة بناء النوع كإنتاج قيود وحل على IR ثنائي، وقاس الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحفاظ على الحما.
  • إضافة Retypd (Noonan, Loginov, Cok, PLDI 2016) إلى النماذج الفرعية والكثافة والأنواع المتراجعة، وأبلغ عن إعادة بناء المؤشر constتعليقات مع 98٪ استرجاع. تنفيذ مفتوح المصدر موجود تحت اسم GrammaTech.
  • تعامل OSPREY (Zhang وغيرها، IEEE S&P 2021) كل موقع ذاكرة كمقعد عشوائي وتستدعي الأنواع الأكثر احتمالا.
  • DIRTY (Chen وغيرها، USENIX Security 2022) و ReSym (Xie وغيرها، CCS 2024) إصدار مفصلات بعد العملية مع نماذج متعلمة، و ReSym تقرير دقة 72.9% على التخطيطات البنية الكاملة.
  • RecStruct (Chen وغيرها، USENIX Security 2026) يسترد ضربات مستقرة دون جهاز تحلل، مع تنفيذ رمزي غير حساس للتدفق والعثور على الاتحاد على فتحات كومة.

إنّه أول شيء يُغيب عنه هذه الدروس-O1،mainيحتفظrowsعلى كومة و الرمز المخطوط يقرأ0x24(%rsp)،0x40(%rsp)،0x48(%rsp)،0x30(%rsp)و0x38(%rsp)هذه التكافؤات المودول 32 هي 4، 0، 8، 16 و 24: نفس البنية، بدون أي معايير للزراعة من.

بناءها

أطلق التجربة:

bash
cd phases/13-tools-and-protocols/41-recovering-data-and-types
python3 code/main.py
npx tsx code/main.ts

إن المخرج هو تقرير JSON واحد مع خمسة أجزاء. dwarfيحتوي على عنوان الوحدة، والهياكل، والوظائف من -O0-أجسامoraclesيحتوي على ترتيب قاعدة psABI ،ctypesالتخطيط، التخطيط DWARF، التشغيل الأصلي، و agreeالعلمbuildsيحتوي على الوصولات، والتخطيط المستنير، والمقارنة لكل من الأشياء الثلاثة. cxxتحتوي على الأسماء المفصلة، كل من vtables، كل من سجلات typinfo، المكالمة الافتراضية، والمساحات DWARF. toolsيُدرج أداة النتائج التي تم التقاطها.

الشكل المتوقع:

json
{
  "dwarf": {"unit": {"unitLength": 489, "version": 5, "unitType": "DW_UT_compile", "addressSize": 8, "abbrevOffset": 0}, "...": "..."},
  "oracles": {"agree": true, "psabiRules": {"size": 32, "align": 8, "...": "..."}, "...": "..."},
  "builds": {
    "O0": {"layout": {"stride": 32, "size": 32, "gaps": [1, 2, 3, 10, 11, 12, 13, 14, 15], "...": "..."}, "comparison": {"matches": 5, "members": 5, "...": "..."}},
    "O1": {"...": "..."},
    "O2": {"...": "..."}
  },
  "cxx": {"virtualCalls": [{"slot": 2, "candidates": {"Shape": "__cxa_pure_virtual", "Rect": "_ZNK4Rect4areaEv"}, "...": "..."}], "...": "..."}
}

اقرأcode/main.pyفي هذا الترتيب:

  1. ElfObjectيقرأ القسمات والرموز والتحركات، وrelocated()يطبقS + Aإلى قسم التحليل
  2. parse_abbreviations()وDwarfInfo._parse_unit()اقرأ العنوان، و جدول الإختصار، والإدخالات، ثم حل strxوaddrxعبر الطاولتين
  3. DwarfInfo.struct_layouts()وfunctions()تحويل الإدخالات إلى StructLayoutوFunctionInfoالسجلات، مع type_name()،type_size()وtype_kind(). . .
  4. parse_listing()،parse_memory()وaccess_width()اقرأllvm-objdumpرسالة
  5. trace_function()هو آلة الحالة المجردة، وfold_unrolled()وinfer_layout()تحويل الوصول إلى تخطيط.
  6. compare_layouts()يُنتج صفوف الحالة، وsysv_layout()وctypes_layout()هي الـ (أوراكول) الثانيين الآخرين
  7. Demangler،read_vtable()،read_typeinfo()وfind_virtual_calls()تغطي كائن C ++

هناك أربعة أوامر أخرىpython3 code/main.py dwarf FILE.oيطبخ هيكل وظائف أي كائن قابل للانتقال من DWARF 5. python3 code/main.py infer LISTING.txt [FILE.o]يستخدم المرور على قائمة ويقارن مع كائن مع DWARFpython3 code/main.py demangle NAME...وpython3 code/main.py vtable FILE.oتغطي نصف C ++

استخدمها

على كائن جديد بنيته بنفسك

  1. القبضllvm-objdump -d -rويحفظها بجوار هذا الموضوع.
  2. أركضpython3 code/main.py inferفي القائمة. اقرأ خط الشهود أولاً: يذكر الوظائف التي استخدمت فيها المعلمة الأولى كأساس ذاكرة. إذا كانت الوظائف مفقودة، فإن معلمتها لم تصل إلى عمل ذاكرة، أو بلغت واحدة من خلال مسار لا تتبعه المرور.
  3. اقرأ كل حقل مع عدد الوصول واحد الوصول هو شاهد واحد سجل حقل مع شاهد واحد كما استنتج
  4. اقرأ دليل الخطواتcall-siteأوscaleدخول قوي واحدloop-stepإن الدخول بدون نوع آخر ضعيف، لأن حلقة غير مقمعة تشير إلى خطوة عدة عناصر.unrolledيبدو، تحقق من المثوب يدويا.
  5. إذا كان-gإنّه يُوجد، قم بتقارنة القراءة، وقرأ كل سطر غير موجودmatch.no-evidenceصف أسماء عضو لا عمل لمس.unexpectedأسماء الصف التي لم يتمكن المرور من إرسالها
  6. اكتب التخطيط في مدير النوع في أداتك كمنشأة، وتسجل أي حقل وأسماء وأنواع تخمينات.

أبقوا الأوراقل الثلاثة في متناولكم عندما تصل القواعدctypesو DWARF لا يوافقون على ترتيب كتبتموه، لقد وجدتم بنية معبأة، حقل بيت، أو هدف مع قواعد مختلفة.

المختبر التفاعلي

المختبر A: فك رمز إدخال الأعضاء

منcodeالإرشادات، مفتوحة Python:

bash
cd phases/13-tools-and-protocols/41-recovering-data-and-types/code
python3 -q

أركض

python
from main import *
elf = load_object("account-x86_64-O0-g.o")
dwarf = DwarfInfo(elf)
dwarf.header()
elf.section(".debug_info").data[0x1a3:0x1ac].hex(" ")
dwarf.dies[0x1a3].attributes
dwarf.type_name(dwarf.type_of(dwarf.dies[0x1a3]))

فكّر التسعة بايت على الورق مع اختصار 13 قبل أن تنظر إلى القاموس ثم قراءة القسم الخام دون نقلelf.section(".debug_str_offsets").data[8:16].hex()، والذي تم نقلهelf.relocated(".debug_str_offsets")[8:16].hex()و اشرح الفرق

المختبر ب: تتبع وظيفة واحدة

python
listing = load_listing("account-x86_64-O0-g.objdump.txt")
trace = trace_function("label_length", listing["label_length"])
[(hex(a.address), a.root, a.offset, a.width, a.kind) for a in trace.accesses]
trace.pointer_uses
trace_function("sum_balances", listing["sum_balances"]).loops

اكتب قبل تشغيله، أي وصول يثبت أن تعويض 24 هو مؤشر. ثم تشغيل نفس الخطوط على -O1إدراج العناوين والعثور على leaq 0x1(%rcx), %rcxفيtrace.increments. . .

المختبر C: طي حلقة متجهة

python
o2 = load_listing("account-x86_64-O2-g.objdump.txt")
trace = trace_function("sum_balances", o2["sum_balances"])
sorted({a.offset for a in trace.accesses})
folded, evidence = fold_unrolled(trace, trace.accesses)
sorted({a.offset for a in folded}), [(e.kind, e.value, e.text) for e in evidence]

ثمّ تهربanalyze_build("O2", fold=False)["comparison"]و أعدunexpectedصفوف. تغيير قاعدة التثبيت حتى يتطلب خطوة الحلقة أن تكون بالضبطk * dمعkعلى الأقل 2 و تأكد من أن -O1حلقة، مع وصول واحد وخطوة 32، يترك وحده.

المختبر D: المكتب والأسماء

python
shapes = load_object("shapes-x86_64-O0-g.o")
read_vtable(shapes, "_ZTV4Rect")
read_typeinfo(shapes, "_ZTI4Rect")
demangle("_ZNK4Rect4areaEv"), demangle("_Z8describeRK5Shapei")
find_virtual_calls(load_listing("shapes-x86_64-O0-g.objdump.txt"), [read_vtable(shapes, "_ZTV5Shape"), read_vtable(shapes, "_ZTV4Rect")])

احسب الفجوة منcallq *0x10(%rax)على الورق، ثم تأكيد ضدDwarfInfo(shapes).struct("Rect").virtual_methods- ثم تحميلshapes-x86_64-O1-g.oو حاولread_vtableاقرأ القصةElfErrorويشرح لماذا الرمز غائب

مختبر التدريب

استعادة الهيكل في mainفي-O1. . .

المتطلبات:

  • التمديدtrace_function()حتى منطقة فتحة الإطار يمكن أن تكون جذورا.movapsالمحلات التي تملأ0x0(%rsp)إلى0x5f(%rsp)من.data.rel.ro، وهو المُبداع البيطاني 96 من rows. . .
  • إعطاء الوصولات المضافة0x24(%rsp)،0x40(%rsp)،0x48(%rsp)،0x30(%rsp)و0x38(%rsp)إلى ذلك الجذر وتقديم تقرير تعويضاتهم modulo a متقدمة خطوة.
  • استنتاج الخطوة 32 من الحلقة في 0x120إلى0x12c، حيث%r14يبدأ في 4 ويتقدم في 0x20حتى0x64، و أظهر أن الحلقة مرتبطة0x64هو ثلاثة عناصر زائد تعويضات الحقل.
  • مقارنة النتيجة مع تصميم DWARF وصلت إلى خمسة تطابقات، وتسجيل أي أنواع لا يزال غير مثبتة.
  • إضافة اختبار يفشل عندما يتم إعطاء جذور الإطار إلى معايير.

النجاح يعنيpython3 code/main.py inferعلى-O1قوائم الإدراجات mainكشهد مع جذور الإطار، والمقارنة تظهر خمسة مطابقات منmainوحدها

درساً أساسياً

هذا الدروس يتضمنoutputs/skill-recovering-data-and-types.md. استخدمها لتحويل قائمة وإعداد إصلاح اخياري إلى تخطيط هيكل مع دليل لكل حقل ، خطوة مع مصادرها ، مقارنة مع DWARF ، وقائمة لما لم يُعرف. يتطلب جدول الوصول ، دليل الخطوة ، والتحقق من التثبيت ، والكتابة DWARF عندما يكون هناك واحد ، وإعلان الأسماء والمؤهلات التي لا يمكن أن تقدم أي تعليمات.

تحقق من ذلك

إشغال التجربة والسلسلة التحديدية:

bash
cd phases/13-tools-and-protocols/41-recovering-data-and-types
python3 code/main.py
npx tsx code/main.ts
python3 -m unittest discover -s code/tests -v

يجب أن تثبت التحقق:

  • رأس الوحدة، والـ 14 اختصار، والـ5 أعضاء التوازنات توافق على llvm-dwarfdump
  • الأسم يصل من خلال المنتقلين.debug_str_offsetsالطاولة ووحدة str_offsets_baseوaddr_baseهي 8
  • ستة وظائف تحمل النطاقاتDW_OP_reg6 RBPوDW_OP_fbregالمواقع التي يظهرها المجمع، وعناوينها المنخفضة تساوي إدراج الوظيفة يبدأ
  • كائن بدون.debug_infoيرفع، و إدارة ليب1280x78كـ -8
  • pyelftoolsوlldbطبع نفس التخطيط مثل القارئ
  • قواعد psABIctypes، DWARF، والسير الأصلي يعطي تعويضات 0, 4, 8, 16, 24و حجم 32، و إعادة ترتيب الهيكل هو 24 بايت
  • يقرأ المصفح المذكور 13 تعليمات في bump، المرفقاتR_X86_64_PLT32 bump-0x4إلى المكالمة0x194، ويقوم بالقفز للخلف في0x72
  • أوبراند الذاكرة، وعرضها، وأنواعها، والرسومات تشفير من نص AT&T والمنمونيك
  • bumpفي-O0يقرأ المؤشر مرة أخرى من فتحة الإطار ويقوم بثلاث وصولات في التداخل 4
  • خطوة تأتي منshlq $0x5في-O0، منaddq $0x20, %rdxفي-O1، ومن ثمانية عبء مطوية بخطوة من 256 في -O2
  • label_lengthيثبت المؤشر في المكافأة 24 عن طريق إزالتها
  • mainيمر كائن الإطار واحد عند التداعيات 0 0x20و0x40في-O0و لا يطلب شيئا سوىprintfفي-O1
  • كل بناء يطابق خمسة من خمسة أعضاء بحجم 32 وفرق تساوي ملابس DWARF
  • بدون طيّة-O2إعداد تقارير سبع حقل ما بعد 32 بايت ولا يوجد حجم
  • المُزعج يُعدّل كلّ 19 llvm-cxxfiltالسلاسل
  • كل من الملفات المتحركة، كل من سجلات المعلومات النموذجية، والكالم الافتراضي في 0x16قرروا في الحفرة 2 و DWARF توافق
  • الموقع-O1كائن C ++ يحافظ على نمط الدعوة وليس لديه أي vtable
  • تظهر صور غيدرا و ريزين و رادار2 الهيكل مع DWARF والعكسات الخام دونها
  • الرقم التفاعلي يضم نفس البيانات التي ينتجها التحليل

أساليب فشل الإنتاج

FailureWhat the weak tool reportsWhat the analysis must prove
Unrolled loopEight fields 32 bytes apart and a 232-byte structLoop step 256 equals 8 times the offset delta 32, so one field at offset 4
Stride read from one incrementsizeof 256 from addq $0x100The greatest common divisor of every stride source, with the scalar tail's addq $0x20
8-byte field called a pointervoid * at offset 24A later access uses the loaded value as a base, or the kind stays int64
Pointer called an integerlong at offset 24The dereference at 0xfb, recorded as pointer proof
Narrow comparechar with a signcmpb carries no sign; only movsbl or movzbl does
Five functions assumed to share a typeOne struct with no evidence of aliasingCall sites in main that pass one frame object, or an explicit analyst assertion
Strings read from .debug_str by indexGarbage names from a relocatable objectstr_offsets_base plus the relocated .debug_str_offsets entry
Addresses read as numberslow_pc of 0 for every functionaddrx through the relocated .debug_addr table
DWARF 4 objectA reader that misparses the headerVersion check on the second header field; 4 has no unit_type byte
Missing vtable"the class has no virtual functions"The -O1 object inlined every call; the -O0 object has the table and its relocations
Vtable slot read from the wrong baseOff-by-two slot numbersThe address point is 16 bytes past the symbol, after offset-to-top and typeinfo
Mangled name strippedZ8describeRK5Shapei left as is--no-strip-underscore on macOS, or a demangler that checks for _Z
Struct typed from one tool's guesschar *param_1 taken as the field typeThe width and the dereference, written as evidence, and the name marked as a guess

قاعدة العمليات

استعادة حقل من وصول وخطوة من حلقة، ورابط التعليمات لكل منها. انطلاقاً من الحلقات المفتوحة قبل أن تثق في تعويضات ما بعد الخطوة اقرأ DWARF من خلال اختصاراته وجداولها المتنقلة، أبداً عن طريق الترتيب إلى قسم سلسلة. مقارنة الحقل مع الحقل وكتب كل عضو بدون دليل، كل مؤشر غير مثبت، وكل اسم كما مجهول. في كائن C ++، قراءة vtable من إعادة التنظيمات الخاصة بها والفرق من الدعوة، ثم استخدام DWARF لتأكيد الفرق الذي تقرأ.

المزيد من القراءة

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.