استعادة البيانات وأنواعها: الهياكل والسلسلات والكائنات 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 و حصل على هذا
void bump(long param_1,int param_2)
{
*(int *)(param_1 + 4) = param_2 + *(int *)(param_1 + 4);
return;
}نفس المصدر الذي تم تجميعه مع-gيعطي هذا من نفس المتحلل:
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يقرأ:
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:
struct account {
char kind;
int balance;
short code;
double rate;
const char *label;
};| Member | Size | Alignment | Lowest offset that fits | Padding before |
|---|---|---|---|---|
kind | 1 | 1 | 0 | 0 |
balance | 4 | 4 | 4 | 3 (bytes 1 to 3) |
code | 2 | 2 | 8 | 0 |
rate | 8 | 8 | 16 | 6 (bytes 10 to 15) |
label | 8 | 8 | 24 | 0 |
ينتهي العضو الأخير في 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البرنامج نفسه يقوم بطبع النسخة الثالثة عندما تقوم ببناءها وتشغيلها على هذه الآلة:
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يُؤشر صفوف من الهياكل:
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يقرأ حقلين ضيقين:
8c: 0f be 00 movsbl (%rax), %eax
98: 0f bf 48 08 movswl 0x8(%rax), %ecxmovsblتحميل بايت واحد في التداول 0 وتوسيعها، لذلك الحقل في 0 هو عدد كامل من 1 بايت. movswlتحميل بايتين في التداول 8 مع تمديد الإشارة، لذلك الحقل في 8 هو رقم كامل مؤلف من 2 بايت.-O1أول وصول يصبحcmpb $0x6c, (%rdi)، الذي لا يزال يقول 1 بايت عند تعويض 0 ولا يقول شيئا عن الشارة.
projectedيقرأ على العباء:
cc: f2 0f 2a 40 04 cvtsi2sdl 0x4(%rax), %xmm0
d5: f2 0f 59 40 10 mulsd 0x10(%rax), %xmm0cvtsi2sdlتحويل عدد كامل من 32 بت من الذاكرة إلى مزدوج، مما يؤكد أن التفويض 4 يحمل عدد كامل من 4 بت. mulsdمضاعفة بمضاعفة من الذاكرة، لذلك تعويض 16 يحمل قيمة نقطة عائمة 8 بايت.
label_lengthيقرأ مؤشراً ثم يتابعه:
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الأوراق ليست إمكانية الوصول
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()يدمج إمكانات وصول كل وظيفة الشاهد ويجمع الخطوة من أربعة مصادر، كل منها مع التعليم الذي أنتجته.
| Kind | Where it comes from | Build that shows it |
|---|---|---|
scale | an Index with scale above 1 reached a pointer, from shlq $0x5 or imulq | -O0, shlq $0x5, %rcx at 0x59 |
loop-step | a 64-bit increment of the base or index register inside a loop that also contains the access | -O1, addq $0x20, %rdx at 0x24 |
offset-delta | same-width accesses through one base register inside one loop, at offsets a constant apart | -O2, eight loads 32 apart |
call-site | the 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يقرأ ثمانية عناصر لكل مرسل:
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هي:
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 إعلانات.
[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_data1parse_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:
0dهو اختصار 13، عضو.10هوDW_AT_nameكـstrx1: مؤشر السلاسل 16.88 01 00 00هوDW_AT_typeكـref4: تعويضات0x188من بداية الوحدة، وهوDW_TAG_base_typeالإدخال المسمىint. . .00هوDW_AT_decl_fileو05هوDW_AT_decl_line: الخط 5 منaccount.c. . .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:
| Build | Accesses through the first parameter | Fields | Stride evidence | Members matched | Size |
|---|---|---|---|---|---|
-O0 | 11 | 5 | scale 32, call-site 32, call-site 64 | 5 of 5 | 32, match |
-O1 | 9 | 5 | loop-step 32 | 5 of 5 | 32, match |
-O2 | 17 | 5 after folding, 12 before | offset-delta 32, unrolled 32, loop-step 256, scale 32, loop-step 32 | 5 of 5 | 32, 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:
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يقوم بمكالمة افتراضية واحدة:
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/الصفوف للأدوات التي لا تثبت هنا تصف وثائقها وتقول ذلك.
| Tool | Version, licence | Where types live | On our fixtures |
|---|---|---|---|
| Ghidra | 12.1.4, 2026-09-21, Apache-2.0 | Data Type Manager with built-in, user-defined, and derived types; .gdt archives; types used by a program are copied into it | With 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 Pro | 9.4, 2026-07-13, commercial; IDA Free is non-commercial with a cloud decompiler for x86 only | Local Types window, .til type libraries, DWARF and PDB import into a dwarf or pdb folder | Not installed. Documentation only |
| Binary Ninja | 6.0, 2026-09-03, commercial with a Free edition | Type editing, header import and export, type archives, debug info from DWARF and PDB, type libraries, platform types | Not installed. Documentation only |
| radare2 | 6.2.4, 2026-10-05, LGPLv3 | ts, tsc, afvd | ts 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 |
| rizin | 0.9.1, 2026-06-29, LGPL core | ts, tsc, afvd | tsc 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 |
| lldb | lldb-1703.0.236.21 | image lookup -t | byte-size = 32 and the full compiler_type text from the ELF object |
| gdb | not installed here | ptype /o prints "the offsets and sizes of fields in a struct" with holes marked | Documentation only |
| pyelftools | 0.33, public domain | Python API over .debug_info | A 43-line script prints the same five members and unit version 5 |
| llvm-dwarfdump | Apple LLVM 17.0.0 | --debug-info, --debug-abbrev, --debug-str-offsets | The 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: نفس البنية، بدون أي معايير للزراعة من.
بناءها
أطلق التجربة:
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يُدرج أداة النتائج التي تم التقاطها.
الشكل المتوقع:
{
"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في هذا الترتيب:
ElfObjectيقرأ القسمات والرموز والتحركات، وrelocated()يطبقS + Aإلى قسم التحليلparse_abbreviations()وDwarfInfo._parse_unit()اقرأ العنوان، و جدول الإختصار، والإدخالات، ثم حلstrxوaddrxعبر الطاولتينDwarfInfo.struct_layouts()وfunctions()تحويل الإدخالات إلىStructLayoutوFunctionInfoالسجلات، معtype_name()،type_size()وtype_kind(). . .parse_listing()،parse_memory()وaccess_width()اقرأllvm-objdumpرسالةtrace_function()هو آلة الحالة المجردة، وfold_unrolled()وinfer_layout()تحويل الوصول إلى تخطيط.compare_layouts()يُنتج صفوف الحالة، وsysv_layout()وctypes_layout()هي الـ (أوراكول) الثانيين الآخرين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 ++
استخدمها
على كائن جديد بنيته بنفسك
- القبض
llvm-objdump -d -rويحفظها بجوار هذا الموضوع. - أركض
python3 code/main.py inferفي القائمة. اقرأ خط الشهود أولاً: يذكر الوظائف التي استخدمت فيها المعلمة الأولى كأساس ذاكرة. إذا كانت الوظائف مفقودة، فإن معلمتها لم تصل إلى عمل ذاكرة، أو بلغت واحدة من خلال مسار لا تتبعه المرور. - اقرأ كل حقل مع عدد الوصول واحد الوصول هو شاهد واحد سجل حقل مع شاهد واحد كما استنتج
- اقرأ دليل الخطوات
call-siteأوscaleدخول قوي واحدloop-stepإن الدخول بدون نوع آخر ضعيف، لأن حلقة غير مقمعة تشير إلى خطوة عدة عناصر.unrolledيبدو، تحقق من المثوب يدويا. - إذا كان
-gإنّه يُوجد، قم بتقارنة القراءة، وقرأ كل سطر غير موجودmatch.no-evidenceصف أسماء عضو لا عمل لمس.unexpectedأسماء الصف التي لم يتمكن المرور من إرسالها - اكتب التخطيط في مدير النوع في أداتك كمنشأة، وتسجل أي حقل وأسماء وأنواع تخمينات.
أبقوا الأوراقل الثلاثة في متناولكم عندما تصل القواعدctypesو DWARF لا يوافقون على ترتيب كتبتموه، لقد وجدتم بنية معبأة، حقل بيت، أو هدف مع قواعد مختلفة.
المختبر التفاعلي
المختبر A: فك رمز إدخال الأعضاء
منcodeالإرشادات، مفتوحة Python:
cd phases/13-tools-and-protocols/41-recovering-data-and-types/code
python3 -qأركض
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()و اشرح الفرق
المختبر ب: تتبع وظيفة واحدة
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: طي حلقة متجهة
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: المكتب والأسماء
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 عندما يكون هناك واحد ، وإعلان الأسماء والمؤهلات التي لا يمكن أن تقدم أي تعليمات.
تحقق من ذلك
إشغال التجربة والسلسلة التحديدية:
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طبع نفس التخطيط مثل القارئ- قواعد psABI
ctypes، 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يمر كائن الإطار واحد عند التداعيات 00x20و0x40في-O0و لا يطلب شيئا سوىprintfفي-O1- كل بناء يطابق خمسة من خمسة أعضاء بحجم 32 وفرق تساوي ملابس DWARF
- بدون طيّة
-O2إعداد تقارير سبع حقل ما بعد 32 بايت ولا يوجد حجم - المُزعج يُعدّل كلّ 19
llvm-cxxfiltالسلاسل - كل من الملفات المتحركة، كل من سجلات المعلومات النموذجية، والكالم الافتراضي في
0x16قرروا في الحفرة 2 و DWARF توافق - الموقع
-O1كائن C ++ يحافظ على نمط الدعوة وليس لديه أي vtable - تظهر صور غيدرا و ريزين و رادار2 الهيكل مع DWARF والعكسات الخام دونها
- الرقم التفاعلي يضم نفس البيانات التي ينتجها التحليل
أساليب فشل الإنتاج
| Failure | What the weak tool reports | What the analysis must prove |
|---|---|---|
| Unrolled loop | Eight fields 32 bytes apart and a 232-byte struct | Loop step 256 equals 8 times the offset delta 32, so one field at offset 4 |
| Stride read from one increment | sizeof 256 from addq $0x100 | The greatest common divisor of every stride source, with the scalar tail's addq $0x20 |
| 8-byte field called a pointer | void * at offset 24 | A later access uses the loaded value as a base, or the kind stays int64 |
| Pointer called an integer | long at offset 24 | The dereference at 0xfb, recorded as pointer proof |
| Narrow compare | char with a sign | cmpb carries no sign; only movsbl or movzbl does |
| Five functions assumed to share a type | One struct with no evidence of aliasing | Call sites in main that pass one frame object, or an explicit analyst assertion |
Strings read from .debug_str by index | Garbage names from a relocatable object | str_offsets_base plus the relocated .debug_str_offsets entry |
| Addresses read as numbers | low_pc of 0 for every function | addrx through the relocated .debug_addr table |
| DWARF 4 object | A reader that misparses the header | Version 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 base | Off-by-two slot numbers | The address point is 16 bytes past the symbol, after offset-to-top and typeinfo |
| Mangled name stripped | Z8describeRK5Shapei left as is | --no-strip-underscore on macOS, or a demangler that checks for _Z |
| Struct typed from one tool's guess | char *param_1 taken as the field type | The width and the dereference, written as evidence, and the name marked as a guess |
قاعدة العمليات
استعادة حقل من وصول وخطوة من حلقة، ورابط التعليمات لكل منها. انطلاقاً من الحلقات المفتوحة قبل أن تثق في تعويضات ما بعد الخطوة اقرأ DWARF من خلال اختصاراته وجداولها المتنقلة، أبداً عن طريق الترتيب إلى قسم سلسلة. مقارنة الحقل مع الحقل وكتب كل عضو بدون دليل، كل مؤشر غير مثبت، وكل اسم كما مجهول. في كائن C ++، قراءة vtable من إعادة التنظيمات الخاصة بها والفرق من الدعوة، ثم استخدام DWARF لتأكيد الفرق الذي تقرأ.
المزيد من القراءة
- System V Application Binary Interface, AMD64 Architecture Processor Supplement, section 3.1.2: Data Representation
- Procedure Call Standard for the Arm 64-bit Architecture (AAPCS64), section 5.10: Composite Types
- Writing ARM64 code for Apple platforms
- Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 1, section 3.7.5: Specifying an Offset
- DWARF Debugging Information Format Version 5, sections 2.1, 5.7.6, 7.5.1.1, 7.5.3, 7.5.6
- Itanium C++ ABI, sections 2.5 (Virtual Table Layout), 2.9 (Run-Time Type Information), 5.1 (External Names)
- Microsoft Learn: Decorated namesوName Decoration
- llvm-dwarfdump command guideوllvm-cxxfilt command guide
- Python ctypes: Structure layout, sizeof, alignment
- Ghidra Data Type Manager،Decompiler Concepts،Decompiler OptionsوGhidra 12.1.4 release
- IDA 9.1 user guide: Types،IDA 9.4 release postوIDA Free
- Binary Ninja user guide: TypesوBinary Ninja 6.0 Krypton
- radare2 6.2.4 releaseوrizin v0.9.1 release
- GDB manual: Examining the Symbol Table, ptype /o
- pyelftools on PyPI
- Lee, Avgerinos, Brumley, "TIE: Principled Reverse Engineering of Types in Binary Programs" (NDSS 2011)
- Noonan, Loginov, Cok, "Polymorphic Type Inference for Machine Code" (PLDI 2016)وthe retypd repository
- Zhang et al., "OSPREY: Recovery of Variable and Data Structure via Probabilistic Analysis for Stripped Binary" (IEEE S&P 2021)
- Chen et al., "Augmenting Decompiler Output with Learned Variable Names and Types" (USENIX Security 2022)
- Xie et al., "ReSym: Harnessing LLMs to Recover Variable and Data Structure Symbols from Stripped Binaries" (CCS 2024)
- Chen et al., "RecStruct" (USENIX Security 2026)
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.