
نص برمجي لـ radare2 لتحليل جدول gopclntab لتسهيل الهندسة العكسية لثنائيات Go.
ملاحظة: تم تنفيذ هذه الميزة مباشرة في نواة radare2 باسم gopc analysis plugin
محلل gopclntab لـ Go يعتمد على radare2 لاستعادة رموز الدوال من ملفات Go الثنائية، بما في ذلك تلك التي تم تجريدها بالكامل. يدعم ملفات ELF و Mach-O و PE عبر إصدارات Go 1.2 و 1.16 و 1.18 و 1.20+.
كل ملف ثنائي لـ Go 1.2+ يحتوي على منطقة بيانات تسمى gopclntab (جدول خطوط عداد البرنامج) التي تستخدمها بيئة تشغيل Go لتتبعات المكدس ورسائل الذعر وجمع القمامة ودعم المصحح. إنها واحدة من أكثر مصادر معلومات الرموز قيمة في ملفات Go الثنائية.
يقرأ r2gopclntabParser هذه المنطقة عبر radare2، ويحلل الهياكل الخاصة بالإصدار، ثم يطبع قائمة الدوال المستعادة (مع العناوين وملفات المصدر وأرقام الأسطر) أو يطبق الأسماء المستعادة مرة أخرى في جلسة radare2 المفتوحة كتعريفات دوال وعلامات وتعليقات.
| التبعية | الحد الأدنى للإصدار |
|---|---|
| Python 3 | 3.8+ |
| radare2 | 5.0+ (تم الاختبار على 6.0.9) |
| r2pipe | أي |
لا يلزم وجود حزم Python أخرى. يستخدم البرنامج النصي فقط المكتبة القياسية (struct, json, argparse, os, sys) بالإضافة إلى r2pipe.
python3 r2_gopclntab.py -f ./mybinary -l
python3 r2_gopclntab.py -f ./mybinary -n main.main
python3 r2_gopclntab.py -f ./mybinary -v -l
python3 r2_gopclntab.py -f ./mybinary --apply
python3 r2_gopclntab.py -f ./mybinary --json
#!pipe python3 r2_gopclntab.py --r2pipe --apply -v
## مرجع CLI```
usage: r2_gopclntab.py [-h] [-f FILE] [-n FUNCNAME] [-v] [-l]
[--apply] [--json] [--files] [--r2pipe]
| العلم | الوصف |
|---|---|
-f FILE, --file FILE | مسار الملف الثنائي Go لتحليله. يقوم السكريبت بتشغيل مثيل r2 خاص به. |
--r2pipe | الاتصال بجلسة r2 قيد التشغيل (للاستخدام داخل وحدة تحكم r2). |
يمكن دمج الأعلام بحرية. عندما لا يتم إعطاء علم إخراج، يكون السلوك الافتراضي هو طباعة الرأس وقائمة الدوال الكاملة.
جميع الأمثلة أدناه تم تشغيلها على ملف ثنائي Go 1.26 Mach-O arm64 مُجرّد (مبني بـ -ldflags="-s -w"). البرنامج الاختباري يعرّف main.main، main.fibonacci، main.helloWorld، وmain.addNumbers. قام المترجم بدمج helloWorld وaddNumbers داخلياً، لذا لا تظهران في gopclntab.
التشغيل بدون أعلام (أو فقط -f) يطبع الرأس المُحلل متبوعًا بجدول الدوال الكامل:```
$ python3 r2_gopclntab.py -f ./gotest_stripped
يبدو أن الإدخال فارغ. لا يوجد نص للترجمة.```
============================================================
Go pclntab Header
============================================================
Magic: 0xFFFFFFF1
Go version: 1.20+
Pointer size: 8
Min LC (quantum):4
Num functions: 2030
Num files: 261
funcnameOffset: 0x48
cuOffset: 0x14D20
filetabOffset: 0x158D8
pctabOffset: 0x19A98
pclnOffset: 0x55E40
============================================================
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x100001000 go:buildid
0x100001070 internal/abi.BoundsDecode (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/bounds.go:86)
0x100001150 internal/abi.NoEscape (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/escape.go:19)
0x100001160 internal/abi.Kind.String (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go:143)
0x1000011E0 internal/abi.TypeOf (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go:181)
0x1000011F0 internal/abi.(*Type).ExportedMethods (/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go:453)
...
0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)
0x1000A0C20 main.main (/tmp/gotest/main.go:20)
0x1000A0D20 go:textfipsstart
0x1000A0D30 go:textfipsend
[+] 2030 function(s) shown
يضيف تقدم فحص الأقسام، وتفاصيل دقة textStart، ومعلومات الإزاحة الداخلية:``` $ python3 r2_gopclntab.py -f ./gotest_stripped -v
* أُضيفت أوضاع الواجهة الداكنة والفاتحة (يمكن تغييرها من شريط الأدوات)
* التاريخ: 24 يناير 2022
* الإصدار: 2.2.0
* حقوق النشر (c) 2015-2022 [Yogesh Khatri](https://github.com/ydkhatri) ([@SwiftForensics](https://twitter.com/SwiftForensics))```
[*] Running radare2 analysis...
[*] Binary format: mach0, endian: little, arch: arm, bits: 64
[*] Scanning binary for gopclntab magic bytes...
[*] Scanning section '0.__TEXT.__text' (0x100001000, 0x9FD44)...
[*] Scanning section '1.__TEXT.__symbol_stub1' (0x1000A0D60, 0x2B8)...
[*] Scanning section '2.__TEXT.__rodata' (0x1000A1020, 0xACC2)...
[*] Scanning section '3.__TEXT.__gopclntab' (0x1000ABCE8, 0xA48AE)...
[*] Found magic at vaddr=0x1000ABCE8
[*] Parsed header: PcHeader(magic=0xFFFFFFF1, version=1.20+, ptrSize=8, minLC=4,
nfunc=2030, nfiles=261, textStart=0x0)
[*] textStart is 0, using .text section vaddr: 0x100001000
[*] Parsed 2030 functions
============================================================
Go pclntab Header
============================================================
Magic: 0xFFFFFFF1
Go version: 1.20+
...
يقوم بتصفية قائمة الدوال حسب تطابق السلسلة الفرعية. إذا وُجد تطابق تام، يتم طباعة عنوانه بشكل منفصل.
بحث عن السلسلة الفرعية لجميع الدوال التي تحتوي على main.:```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "main."
Please provide the Markdown content to translate.```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x100041920 runtime.main.func2 (/opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/proc.go:207)
0x10006CE30 runtime.main.func1 (/opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/proc.go:174)
0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)
0x1000A0C20 main.main (/tmp/gotest/main.go:20)
[+] 4 function(s) shown (filtered from 2030 total)
بحث تطابق تام:``` $ python3 r2_gopclntab.py -f ./gotest_stripped -n "main.fibonacci"
- [AutomataModoki](https://web.archive.org/web/20200812071648/http://www.scriptkiddie.de/) - صندوق أدوات Scriptkiddie يحتوي على جميع أدوات scriptkiddie الشائعة، تنطلق من قائمة واجهة مستخدم رسومية واحدة.
- [R3C0Nizer](https://github.com/JacobIngalls/R3C0Nizer) - أداة لجمع المعلومات عن النطاقات، البريد الإلكتروني، عناوين IP، والمؤسسات.
- [AutoRecon](https://github.com/JacobIngalls/AutoRecon) - سكريبت shell بسيط لأتمتة عمليات جمع المعلومات وفحص المجلدات.
- [Auto Pentest](https://github.com/JacobIngalls/Automated-Pentest) - سكريبت اختبار اختراق مؤتمت تم إنشاؤه بلغة Python.
- [ThunderSearch](https://github.com/robotama/ThunderSearch) - واجهة مستخدم رسومية للبحث في قواعد بيانات الثغرات المختلفة، باستخدام مصادر وواجهات برمجة تطبيقات متنوعة عبر الإنترنت.```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x1000A0BB0 main.fibonacci (/tmp/gotest/main.go:13)
[+] 1 function(s) shown (filtered from 2030 total)
[+] Exact match: main.fibonacci @ 0x1000A0BB0
البحث عن التفاصيل الداخلية لوقت التشغيل المتعلقة بجمع القمامة (GC):``` $ python3 r2_gopclntab.py -f ./gotest_stripped -n "runtime.gc"
[No content provided in the INPUT section. Please supply the Markdown text to translate.]```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x10001F310 runtime.gcinit (/opt/homebrew/.../src/runtime/mgc.go:179)
0x10001F3C0 runtime.gcenable (/opt/homebrew/.../src/runtime/mgc.go:211)
0x10001F730 runtime.gcStart (/opt/homebrew/.../src/runtime/mgc.go:733)
0x10001FFE0 runtime.gcMarkDone (/opt/homebrew/.../src/runtime/mgc.go:1015)
0x100020A50 runtime.gcMarkTermination (/opt/homebrew/.../src/runtime/mgc.go:1344)
0x100021C10 runtime.gcBgMarkWorker (/opt/homebrew/.../src/runtime/mgc.go:1750)
0x1000223D0 runtime.gcMark (/opt/homebrew/.../src/runtime/mgc.go:1956)
0x1000227A0 runtime.gcSweep (/opt/homebrew/.../src/runtime/mgc.go:2049)
...
0x100076C60 runtime.gcWriteBarrier1 (/opt/homebrew/.../src/runtime/asm_arm64.s:1533)
[+] 73 function(s) shown (filtered from 2030 total)
البحث عن fmt. (طباعة المكتبة القياسية):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "fmt."
INPUT:```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x100098AA0 fmt.(*fmt).writePadding (/opt/homebrew/.../src/fmt/format.go:66)
0x100098BF0 fmt.(*fmt).pad (/opt/homebrew/.../src/fmt/format.go:93)
0x100099590 fmt.(*fmt).fmtInteger (/opt/homebrew/.../src/fmt/format.go:197)
0x10009ADF0 fmt.Fprintf (/opt/homebrew/.../src/fmt/print.go:222)
0x10009AED0 fmt.Fprintln (/opt/homebrew/.../src/fmt/print.go:303)
0x10009D3F0 fmt.(*pp).printArg (/opt/homebrew/.../src/fmt/print.go:721)
0x10009D950 fmt.(*pp).printValue (/opt/homebrew/.../src/fmt/print.go:797)
0x10009FA60 fmt.(*pp).doPrintf (/opt/homebrew/.../src/fmt/print.go:1018)
...
البحث عن sync. (أساسيات التزامن):```
$ python3 r2_gopclntab.py -f ./gotest_stripped -n "sync."
### إعادة التوجيه
إذا لم يتم توفير علامة تصحيح صالحة، يمكنك تحديد مقبس لإعادة توجيه العميل إلى خادم آخر. من الضروري توجيه العميل إلى المسار الصحيح لضمان عدم انقطاع المسار.
### الاستخدام
CVE-2016-10033 - Wordpress <4.7.1 PHPMailer Argument Injector - Redirecting Usage: cve-2016-10033-redirect.sh 44010 /path/to/script - If no port is given, the script will try to connect to the server with the default SMTP port (25). - Set 'DEBUG' flag to '1', '2', or '3' if you want to test locally and not send the client away.
./cve-2016-10033-redirect.sh 44010 /test.php
[...]
220 wordpress.example.com ESMTP
EHLO localhost
250- wordpress.example.com
[...]
250-SIZE 20480000
250-AUTH LOGIN PLAIN CRAM-MD5
250-8BITMIME
220 2.0.0 Continue
<?php
system('whoami');
?>
<?
في هذا المثال نقوم بالاتصال بالخادم باستخدام منفذ SMTP الافتراضي (25). بعد الشعار نرسل رسالة EHLO، ثم يستجيب الخادم بقائمة من الأوامر مثل AUTH و SIZE. سنقوم بإرسال بريد إلكتروني لاستغلال الثغرة باستخدام أمر MAIL FROM. سنستخدم علامة DEBUG مضبوطة على 0 لإعادة توجيه العميل إلى مسار مختلف. سلسلة MAIL FROM هي:
a(any_host -be ${run\{${substr\{0\{1\}{$spool_directory\}}}${substr\{0\{1\}{$tod_log\}}}})
إذا لم يتم تحديد قيمة sendmail ولم يتم الكتابة فوق المتغيرات $params، ستستخدم PHPMailer الأمر التالي لإرسال بريد إلكتروني (انظر دالة isSendmail في class.phpmailer.php):
$sendmail = sprintf("%s -oi -f %s -t", $this->Sendmail, $this->Sender);
(إذا كان $sendmail فارغاً، وهو ما لا نعرفه). تم شرح ذلك في النشرة الاستشارية. يمكنك العثور على $params في دالة isMail.
لذا، ستؤدي السلسلة أعلاه إلى تنفيذ الأمر التالي:
Argv: /usr/sbin/sendmail -oi -f a( -be ${run\{${substr\{0\{1\}{$spool_directory\}}}${substr\{0\{1\}{$tod_log\}}}})
مسار ثنائي sendmail: /usr/sbin/sendmail
مسار ثنائي postdrop: /usr/sbin/postdrop
تستخدم PHPMailer وWordpress هذه المسارات لإرسال البريد الإلكتروني. ملفات مثيرة للاهتمام لقراءتها إذا كان لديك وصول إلى الخادم:
$spool_directory هو الدليل الذي تُخزَّن فيه الرسائل قبل إرسالها. في حالتي، استخدمت /var/spool/mqueue-client$tod_log هو الملف الذي تُخزَّن فيه السجلات. في حالتي، استخدمت `/var/log/mail.log````
ADDRESS FUNCTION NAME0x10006FFE0 sync.runtime_registerPoolCleanup (/opt/homebrew/.../src/runtime/mgc.go:2150) 0x100070BA0 sync.fatal (/opt/homebrew/.../src/runtime/panic.go:1160) 0x1000714E0 sync.runtime_procPin (/opt/homebrew/.../src/runtime/proc.go:7912) 0x10007B290 internal/sync.(*Mutex).lockSlow (/opt/homebrew/.../src/internal/sync/mutex.go:95) 0x10007B570 internal/sync.(*Mutex).Unlock (/opt/homebrew/.../src/internal/sync/mutex.go:187) ...
### JSON Output (--json)
مخرجات قابلة للقراءة آلياً للبرمجة النصية وتكامل خطوط الأنابيب:```
$ python3 r2_gopclntab.py -f ./gotest_stripped --json
بإمكانك بعد ذلك اختبار ميزة توقيع الالتزامات:
git log --show-signature للتحقق من التوقيع.إذا لم يتم تطبيق التوقيع، قد تحتاج إلى تكوين خيارات التوقيع للمستودع. يمكنك إعداد ذلك عبر سكريبت بسيط:
// تحقق مما إذا كان أمر git متاحًا
if (git) {
console.log('Git is available')
} else {
console.log('Git is not available')
}
يضمن هذا النهج تشغيل الوحدة لأوامر Git بدقة حتى في البيئات التي لا يتم فيها التعرف على الأمر تلقائيًا.```json { "header": { "magic": "0xFFFFFFF1", "version": "1.20+", "ptrSize": 8, "minLC": 4, "nfunc": 2030, "nfiles": 261, "textStart": "0x0" }, "functions": [ { "name": "go:buildid", "addr": "0x100001000", "args": 0, "source_file": "", "start_line": 0 }, { "name": "internal/abi.BoundsDecode", "addr": "0x100001070", "args": 8, "source_file": "/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/bounds.go", "start_line": 86 }, { "name": "main.fibonacci", "addr": "0x1000A0BB0", "args": 0, "source_file": "/tmp/gotest/main.go", "start_line": 13 }, { "name": "main.main", "addr": "0x1000A0C20", "args": 0, "source_file": "/tmp/gotest/main.go", "start_line": 20 } ], "num_source_files": 261 }
### Source File Listing (--files)
يستخرج جميع مسارات ملفات المصدر المضمنة في الثنائي:```
$ python3 r2_gopclntab.py -f ./gotest_stripped --files
analysis_mode، إذا تم ضبطه على true، سيتضمن الإخراج المدخلات المرسلة إلى تحليل النموذج. |```
Source files (261):/opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/bounds.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/escape.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/abi/type.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/cpu/cpu.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/internal/cpu/cpu_arm64.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/proc.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/mgc.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/malloc.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/runtime/panic.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/fmt/print.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/fmt/format.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/reflect/value.go /opt/homebrew/Cellar/go/1.26.1/libexec/src/reflect/type.go /tmp/gotest/main.go ... ... and 61 more
### وضع التطبيق (--apply)
يكتب جميع أسماء الدوال المستعادة في جلسة radare2. يوضح ما يلي مقارنة قبل/بعد على الملف الثنائي المجرد.
**قبل** (تحليل r2 الأصلي على الملف الثنائي المجرد، بدون تحليل gopclntab):```
Functions found by r2 natively: 1913
Disassembly at 0x1000a0c20 (main.main, unnamed):
; CODE XREF from fcn.1000a0c20 @ 0x1000a0d14(r)
24: fcn.1000a0c20 (int64_t arg1);
0x1000a0c20 900b40f9 ldr x16, [x28, 0x10]
0x1000a0c24 ff6330eb cmp sp, x16
0x1000a0c28 29070054 b.ls 0x1000a0d0c
Disassembly at 0x1000a0bb0 (main.fibonacci, unnamed):
112: fcn.1000a0bb0 (signed int64_t arg1, int64_t arg_8h);
0x1000a0bb0 900b40f9 ldr x16, [x28, 0x10]
0x1000a0bb4 ff6330eb cmp sp, x16
0x1000a0bb8 a9020054 b.ls 0x1000a0c0c
r2 وجد 1913 دالة لكنه لم يسمِ أيًا منها (فقط تسميات fcn.XXXXXXXX مجهولة). البحث عن main.main أو main.fibonacci لا يعيد شيئًا.
تطبيق رموز gopclntab:``` [] Binary format: mach0, endian: little, arch: arm, bits: 64 [] Found magic at vaddr=0x1000ABCE8 [] Parsed header: PcHeader(magic=0xFFFFFFF1, version=1.20+, ...) [] textStart is 0, using .text section vaddr: 0x100001000 [*] Parsed 2030 functions [+] Applied 2030 function names to radare2 (0 skipped)
**بعد** (r2 session with gopclntab symbols applied):```
r2 function list matching "main" (after --apply):
0x100041510 0 0 runtime.main
0x100041920 0 0 runtime.main.func2
0x10006ce30 0 0 runtime.main.func1
Flags in go.* flagspace (last 20):
0x10009fa60 1 go.fmt._ptr_pp_.doPrintf
0x1000a0930 1 go.fmt._ptr_pp_.doPrintln
0x1000a0bb0 1 go.main.fibonacci
0x1000a0c20 1 go.main.main
0x1000a0d20 1 go.go:textfipsstart
0x1000a0d30 1 go.go:textfipsend
التفكيك في main.main يُظهر الآن الاسم المسترجع وموقع المصدر:```
;-- go.main.main:
24: fcn.1000a0c20 (int64_t arg1);
0x1000a0c20 900b40f9 ldr x16, [x28, 0x10] ; " src: /tmp/gotest/main.go:20"
0x1000a0c24 ff6330eb cmp sp, x16
0x1000a0c28 29070054 b.ls 0x1000a0d0c
التفكيك عند `main.fibonacci` يظهر الآن الاسم المستعاد وموقع المصدر:```
;-- go.main.fibonacci:
112: fcn.1000a0bb0 (signed int64_t arg1, int64_t arg_8h);
0x1000a0bb0 900b40f9 ldr x16, [x28, 0x10] ; " src: /tmp/gotest/main.go:13"
0x1000a0bb4 ff6330eb cmp sp, x16
0x1000a0bb8 a9020054 b.ls 0x1000a0c0c
البحث بالاسم يعمل الآن في جلسة r2:``` go.main.main resolves to: 0x1000a0c20 go.main.fibonacci resolves to: 0x1000a0bb0
---
## ملخص النتائج: ملف Mach-O (ثنائي اختبار مجرّد)
| المقياس | الأصلي r2 (مجرّد) | بعد r2_gopclntab.py |
|---|---|---|
| الدوال الموجودة | 1913 (تسميات `fcn.XXXX` مجهولة) | 2030 (أسماء مؤهلة بالحزمة Go كاملة) |
| دوال المستخدم المحددة | 0 | `main.main`، `main.fibonacci` مع المصدر ورقم السطر |
| ملفات المصدر المستردة | 0 | 261 (مسارات مطلقة كاملة) |
| الرموز المسماة | واجهات استيراد C فقط (`sym.imp.mmap`، إلخ.) | كل دالة Go مسماة (`go.main.main`، `go.runtime.gcStart`، إلخ.) |
| تعليقات المصدر | لا شيء | مضمنة `src: /tmp/gotest/main.go:20` في التفكيك |
| قابلة للتنقل بالاسم | لا | نعم (`s go.main.main`، `afl~runtime.gc`) |
---
## اختبار PE: Greenblood، ثنائي فدية Go
تم اختبار المحلل اللغوي ضد ثنائي PE حقيقي، Greenblood (`greenblood_1`)، وهو عينة من برامج الفدية Go تم تجميعها كملف تنفيذي PE32+ x86-64 لويندوز. لا تحتوي ثنائيات PE على قسم مخصص `.gopclntab`، لذلك هذا يختبر مسار التراجع عن المسح الضوئي بالبايت السحري.
### الاكتشاف والرأس```
$ python3 r2_gopclntab.py -f ./greenblood_1 -v
(لـ نص/ملف، الإدخال، المفتاح السري، التنسيق)```
[] Binary format: pe, endian: little, arch: x86, bits: 64
[] Scanning binary for gopclntab magic bytes...
[] Scanning section '.text' (0x401000, 0xF4000)...
[] Scanning section '.rdata' (0x4F5000, 0x127000)...
[] Found magic at vaddr=0x568C00
[] Parsed header: PcHeader(magic=0xFFFFFFF1, version=1.20+, ptrSize=8,
minLC=1, nfunc=2596, nfiles=345, textStart=0x401000)
[] textStart from header: 0x401000
[] Parsed 2596 functions
وجد الماسح `gopclntab` داخل القسم `.rdata` عند `0x568C00`.
نظرًا لأن هذا ملف PE قياسي (غير PIE)، فإن `textStart` هو `0x401000` (غير صفري)،
لذلك تُستخدم قيمة الترويسة مباشرة لحساب العنوان.
| الحقل | القيمة |
|---|---|
| التنسيق | PE32+ x86-64 |
| موقع gopclntab | `.rdata` عند `0x568C00` (تم العثور عليه بالمسح السحري) |
| السحر (Magic) | `0xFFFFFFF1` (Go 1.20+) |
| حجم المؤشر | 8 |
| الكم (minLC) | 1 (x86) |
| textStart | `0x401000` (من الترويسة) |
| الدوال المستردة | 2596 |
| ملفات المصدر | 345 |
### دوال البرنامج الضار المستردة
البحث عن كود البرنامج الضار نفسه (`main.`):```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "main."
0x4D91E0 main.init (:1) 0x4D9200 main.map.init.0 (/root/victims/ransom/daf/enc.go:59) 0x4D92C0 main.map.init.1 (/root/victims/ransom/daf/enc.go:126) 0x4D95C0 main.NewKeyManager (/root/victims/ransom/daf/enc.go:146) 0x4D97E0 main.getMachineFingerprint (/root/victims/ransom/daf/enc.go:173) 0x4DA2C0 main.getBIOSUUID (/root/victims/ransom/daf/enc.go:249) 0x4DA3C0 main.NewEncryptionEngine (/root/victims/ransom/daf/enc.go:287) 0x4DA560 main.(*EncryptionEngine).fileWorker (/root/victims/ransom/daf/enc.go:303) 0x4DA660 main.(*EncryptionEngine).processFile (/root/victims/ransom/daf/enc.go:319) 0x4DA740 main.(*EncryptionEngine).encryptFile (/root/victims/ransom/daf/enc.go:334) 0x4DB2A0 main.(*EncryptionEngine).EncryptPath (/root/victims/ransom/daf/enc.go:446) 0x4DB620 main.(*EncryptionEngine).shouldSkipDirectory (/root/victims/ransom/daf/enc.go:495) 0x4DB7C0 main.(*EncryptionEngine).shouldEncryptFile (/root/victims/ransom/daf/enc.go:522) 0x4DB9A0 main.(*EncryptionEngine).placeRansomNote (/root/victims/ransom/daf/enc.go:560) 0x4DBB60 main.(*EncryptionEngine).recordSuccess (/root/victims/ransom/daf/enc.go:636) 0x4DBFA0 main.(*EncryptionEngine).Wait (/root/victims/ransom/daf/enc.go:664) 0x4DC3C0 main.formatBytes (/root/victims/ransom/daf/enc.go:689) 0x4DC500 main.disableRecovery (/root/victims/ransom/daf/enc.go:706) 0x4DC720 main.isAdmin (/root/victims/ransom/daf/enc.go:732) 0x4DC8C0 main.main (/root/victims/ransom/daf/enc.go:760) 0x4DD260 main.getLogicalDrives (/root/victims/ransom/daf/enc.go:863) 0x4DD4A0 main.isAlreadyRunning (/root/victims/ransom/daf/enc.go:892) 0x4DD660 main.getDesktopPath (/root/victims/ransom/daf/enc.go:911) 0x4DD780 main.removeExecutable (/root/victims/ransom/daf/enc.go:932) ...
[+] 43 function(s) shown (filtered from 2596 total)
تم استرداد جميع دوال المستخدم البالغ عددها 43 من ملف مصدر واحد في
`/root/victims/ransom/daf/enc.go`. تكشف أسماء الدوال على الفور عن
قدرات برنامج الفدية: إدارة المفاتيح، بصمة الجهاز، تشفير الملفات
مع اجتياز المسار، وضع ملاحظة الفدية، تعطيل الاسترداد،
التحقق من الصلاحيات، تطبيق المثال الواحد القائم على الميوتكس، تعداد محركات الأقراص،
والحذف الذاتي.
### تبعيات غير المكتبة القياسية
استخراج ملفات المصدر التي ليست جزءًا من مكتبة Go القياسية:```
/root/go/pkg/mod/golang.org/x/[email protected]/windows/dll_windows.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/registry/key.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/registry/value.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/security_windows.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/str.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/syscall.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/syscall_windows.go
/root/go/pkg/mod/golang.org/x/[email protected]/windows/zsyscall_windows.go
/root/victims/ransom/daf/enc.go
الاعتماد الخارجي الوحيد هو golang.org/x/[email protected] لاستدعاءات النظام الخاصة بنظام ويندوز (الوصول إلى السجل، رموز الأمان، تحميل مكتبات DLL).
البحث عن crypto يكشف عن 137 دالة متعلقة بالتشفير، بما في ذلك:```
$ python3 r2_gopclntab.py -f ./greenblood_1 -n "crypto"
لتحميله مؤقتًا في الذاكرة، استخدم `wget` أو `curl`. بمجرد التحميل، قم بتشغيل `chmod +x script.sh` لجعله قابلاً للتنفيذ.```
ADDRESS FUNCTION NAME
----------------------------------------------------------------------
0x4AEC00 crypto/cipher.NewCTR (/usr/local/go/src/crypto/cipher/ctr.go:41)
0x4AF520 crypto/cipher.StreamWriter.Write (/usr/local/go/src/crypto/cipher/io.go:36)
0x4AF840 crypto/aes.NewCipher (/usr/local/go/src/crypto/aes/aes.go:36)
0x4C3760 crypto/rand.(*reader).Read (/usr/local/go/src/crypto/rand/rand.go:45)
0x4DE6C0 crypto/internal/fips140/sha256.New (.../sha256/sha256.go:138)
0x4E3EC0 crypto/internal/fips140/sha3.NewCShake128 (.../sha3/shake.go:134)
0x4EED60 crypto/internal/fips140/hmac.New (.../hmac/hmac.go:131)
0x4EF800 crypto/internal/fips140/aes.newBlock (.../aes/aes_asm.go:59)
0x4F0800 crypto/internal/fips140/aes.(*CBCEncrypter).CryptBlocks (.../aes/cbc.go:26)
0x4F0FE0 crypto/internal/fips140/aes.(*CTR).XORKeyStream (.../aes/ctr.go:41)
...
[+] 137 function(s) shown (filtered from 2596 total)
مُلف تعريف استخدام التشفير: AES (وضعي CBC وCTR)، SHA-256، SHA-512، HMAC، CSHAKE128، وDRBG (مُولِّد البتات العشوائي الحتمي). هذا يتوافق مع برامج الفدية التي تُنشئ مفاتيح تشفير خاصة بكل جهاز مُشتقَّة من بصمة الجهاز، وتُشفِّر الملفات باستخدام AES-CTR، وتستخدم HMAC للتحقق من السلامة.
$ python3 r2_gopclntab.py -f ./greenblood_1 --apply [+] Applied 2596 function names to radare2 (0 skipped) [+] Function names applied. Use 'afl' in r2 to see them.
تم تطبيق جميع الدوال البالغ عددها 2596 بنجاح دون تخطي أي منها.
---
## حالات استخدام للهندسة العكسية
### 1. الفرز والتحديد
تحديد ما إذا كان الملف الثنائي هو Go فورًا، وإصدار Go الذي تم بناؤه به، والحزم التي يستخدمها. يكشف إخراج `--files` عن إصدار سلسلة أدوات Go (من مسارات الملفات مثل `/usr/local/go/1.26.1/...`) وكل مسار لملف مصدري، بما في ذلك المكتبات الخارجية. بالنسبة للبرامج الضارة، يكشف هذا فورًا ما إذا كانت العينة تستخدم `crypto/tls` أو `net/http` أو `os/exec` أو حزم أخرى ذات أهمية.
### 2. استعادة الرموز في الملفات الثنائية المجردة
حالة الاستخدام الأساسية. الملفات الثنائية لـ Go التي تم تجريدها باستخدام `-ldflags="-s -w"` تفقد جدول الرموز الخاص بها، لكن gopclntab يبقى. يعيد هذا الأداة كل اسم دالة، محولاً الدوال المجهولة مثل `fcn.1000a0c20` إلى `main.main`. ينطبق هذا على عينات البرامج الضارة، تحديات CTF، الملفات الثنائية الإنتاجية، وأي ملف تنفيذي مجرد لـ Go.
### 3. التنقل في بيئة تشغيل Go
تضم الملفات الثنائية لـ Go بيئة التشغيل بأكملها (عادةً 1500 إلى 2000+ دالة). بدون أسماء، تكون بيئة التشغيل جدارًا منيعًا من الدوال المجهولة. مع الأسماء، يمكنك تحديد موقع `runtime.mallocgc`، `runtime.gopanic`، `runtime.newproc`، `runtime.gcStart` فورًا وفهم ما يفعله الملف الثنائي في كل موقع استدعاء.
### 4. فصل كود المستخدم عن بيئة التشغيل
من خلال البحث عن `main.` أو مسار حزمة التطبيق، يمكنك عزل كود المستخدم فقط عن بيئة التشغيل. في الأمثلة أعلاه، يكشف التصفية عن `main.` فورًا عن `main.main` و `main.fibonacci` من بين 2030 دالة إجمالية. يمكنك أيضًا البحث بأسماء الحزم الخارجية (مثل `-n "github.com/user/repo"`) لتحديد التبعيات الخارجية.
### 5. سياق على مستوى المصدر
تأتي كل دالة مع مسار ملفها المصدري ورقم سطر البداية. هذا يعني أنه يمكنك مقارنة التفكيك مع كود مصدر مكتبة Go القياسية (وهو مفتوح)، حتى عند تحليل ملف ثنائي مجرد. معرفة أن دالة تبدأ في السطر 733 من `runtime/mgc.go` يسمح لك بقراءة المصدر الأصلي جنبًا إلى جنب مع التفكيك.
### 6. التوجيه والأتمتة
يتيح وضع `--json` إمكانية البرمجة النصية. غَذِّ الإخراج في أدوات استيراد IDA/Ghidra، أدوات المقارنة، مولدي قواعد YARA، أو أي مسار تحليل. على سبيل المثال، لاستخراج جميع الدوال المتعلقة بالتشفير:```bash
python3 r2_gopclntab.py -f sample.exe --json \
| jq '.functions[] | select(.name | contains("crypto"))'
بعد --apply، تصبح جلسة r2 بأكملها قابلة للتصفح حسب اسم Go. يمكنك
الانتقال إلى الدوال (s go.main.main)، والبحث في قائمة الدوال
(afl~runtime.gc)، وفحص المراجع المتقاطعة (axf go.main.fibonacci)، ورؤية
تعليقات مواقع المصدر مضمنة في مخرجات التفكيك (pd). هذا
يحول r2 من مفكك عام إلى بيئة تحليل واعية بـ Go.
بالنسبة لملفات PE الثنائية وملفات ELF/Mach-O المجردة بقوة التي تفتقر إلى رأس القسم، تتراجع الأداة إلى مسح جميع الأقسام بحثًا عن البايت السحري المكون من 4 بايتات متبوعًا ببايتات التحقق (pad=0, ptrSize في {4,8}, minLC في {1,2,4}).
يُستخدم السحر 0xFFFFFFF1 في Go 1.20 حتى Go 1.26 على الأقل.
يستخدم المحلل نهجًا من مرحلتين للعثور على بيانات gopclntab:
المرحلة 1 (ELF/Mach-O): استعلام قائمة أقسام radare2 (iSj) والبحث عن
أقسام مسماة .gopclntab أو .data.rel.ro.gopclntab أو __gopclntab.
المرحلة 2 (PE/التراجع): إذا لم يتم العثور على قسم مسمى، قم بمسح جميع الأقسام للبايت السحري المكون من 4 بايتات. يتم التحقق من كل مرشح عن طريق التأكد من أن البايتات 4-7 تطابق النمط المتوقع: بايتان حشو صفري، وحجم مؤشر صالح (4 أو 8)، وكم تعليمات صالح (1 أو 2 أو 4). هذا يزيل الإيجابيات الخاطئة الناتجة عن أنماط بايتات المصادفة.
في Go >= 1.18، تُخزن نقاط دخول الدوال في functab كإزاحات نسبية. يتطلب حساب العنوان الظاهري المطلق قاعدة:``` absolute_addr = base + entryoff
يتم حل القاعدة بالمنطق التالي:```
Is magic 0xFFFFFFFB (Go 1.2)?
YES -> base = 0 (entries are absolute addresses)
NO -> Is magic 0xFFFFFFFA (Go 1.16)?
YES -> base = .text section vaddr (entries are absolute)
NO -> (Go 1.18 / 1.20+)
Is header.textStart != 0?
YES -> base = header.textStart
NO -> base = .text section vaddr
حالة textStart == 0 تحدث في Go >= 1.22 للثنائيات Mach-O و PIE. عندما يحدث ذلك، تكون قيم entryoff نسبية لبداية القسم .text، لذا يستفسر المحلل عن العنوان الافتراضي لـ.text من r2 ويستخدمه كأساس.
تم التحقق من ذلك مقابل ثنائي Go 1.26 Mach-O arm64:
textStart في الترويسة: 0x0.text: 0x100001000functab[1].entryoff: 0x700x100001000 + 0x70 = 0x100001070internal/abi.BoundsDecode عند 0x100001070يختلف تخطيط البنية _func بين Go 1.18 و Go 1.20+. التغيير الوحيد هو إدراج حقل startLine بطول 4 بايت عند الإزاحة 36 في Go 1.20+، مما يزيح funcID و flag و nfuncdata بمقدار 4 بايتات:
يتحقق المحلل من الرقم السحري لتحديد أي تخطيط يجب استخدامه.
يتم تخزين فهارس ملفات المصدر وأرقام الأسطر كـ "برامج بيانات PC" مضغوطة في منطقة pctab. يقوم كل برنامج بتشفير تسلسل من أزواج (value_delta, pc_delta) باستخدام أعداد صحيحة متغيرة الطول مع ترميز zig-zag للقيم الموقعة. يقوم المحلل بفك ترميز هذه لحل:
_func.pcfile -> برنامج pctab -> فهرس الملف -> cutab -> filetab -> سلسلة مسار الملف_func.pcln -> برنامج pctab -> رقم السطر (بالإضافة إلى إزاحة startLine لـ Go 1.20+)الدوال المضمنة لا تظهر في جدول الدوال الأعلى مستوى. إنها مشفرة في بنى FUNCDATA_InlTree / PCDATA_InlTreeIndex، والتي لا يقوم هذا الأداة حاليًا بفك تشفيرها. في الثنائي الاختباري، تم تضمين main.helloWorld و main.addNumbers بواسطة المترجم وبالتالي لا تظهر في المخرجات.
دعم Go 1.2 هو بأفضل جهد. تنسيق Go 1.2 يختلف بشكل كبير (لا يوجد funcnametab منفصل، ولا cutab، ومؤشرات مطلقة في functab) ونادرًا ما يُواجه في الممارسة.
معماريات Big-endian يتم التعامل معها من حيث المبدأ (يتم اكتشاف endian من معلومات r2 الثنائية واستخدامها لجميع قراءات البنى) ولكن لم يتم اختبارها.
وضع --apply ينشئ دوال وهمية باستخدام af+ مما قد يتعارض مع التحليل التلقائي الخاص بـ r2. قد يعطي تشغيله على جلسة نظيفة (قبل أو بدلاً من aaa) نتائج أفضل في بعض الحالات.
التوثيق التقني التفصيلي متوفر في مجلد documentation/:
| العلم | الوصف |
|---|
-l, --list | طباعة كل دالة مستردة مع عنوانها. |
-n NAME, --funcname NAME | طباعة الدوال التي يحتوي اسمها على NAME فقط (مطابقة جزئية). في حالة وجود تطابق تام، يتم طباعة عنوانه بشكل منفصل. |
-v, --verbose | طباعة رسائل التقدم، وحقول الرأس المُحللة، والإزاحات الداخلية. |
--apply | كتابة أسماء الدوال المستردة في جلسة radare2 كتعريفات دالة (af+)، وعلم في مساحة الأعلام go.، وتعليقات مع اسم Go الأصلي وموقع المصدر. |
--json | إخراج الرأس وقائمة الدوال الكاملة بصيغة JSON إلى stdout. |
--files | طباعة قائمة مسارات ملفات المصدر المستخرجة من جدول الملفات. |
-h, --help | إظهار رسالة المساعدة. |
| التنسيق | طريقة اكتشاف القسم | مُختبر |
|---|
| ELF (لينكس) | اسم القسم .gopclntab أو .data.rel.ro.gopclntab | نعم |
| Mach-O (macOS) | اسم القسم __gopclntab (داخل مقطع __TEXT) | نعم |
| PE (ويندوز) | مسح البايت السحري (لا يوجد قسم مخصص) | نعم (مُختبر على Greenblood، PE64 لبرمجية فدية Go) |
| السحر | إصدار Go | textStart في الرأس | نوع functab.entry | حقل startLine | الحالة |
|---|
0xFFFFFFFB | 1.2 | لا | uintptr (مطلق) | لا | مدعوم |
0xFFFFFFFA | 1.16 | لا | uintptr (مطلق) | لا | مدعوم |
0xFFFFFFF0 | 1.18 - 1.19 | نعم | uint32 (نسبي) | لا | مدعوم |
0xFFFFFFF1 | 1.20+ | نعم (قد يكون 0) | uint32 (نسبي) | نعم | مدعوم |
| الحقل | إزاحة Go 1.18 | إزاحة Go 1.20+ |
|---|
entryOff | 0 | 0 |
nameOff | 4 | 4 |
args | 8 | 8 |
cuOffset | 32 | 32 |
startLine | (غائب) | 36 |
funcID | 36 | 40 |
flag | 37 | 41 |
nfuncdata | 39 | 43 |