Skip to content
KitploitKITPLOIT
أدواتعمليات الاستغلالالمدونة
Log in
إرسال
أدواتعمليات الاستغلالالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
أدوات/GitHubGitHub/jaden-bowers/tenda-router-vr-and-exploit
أمان الأنظمة المدمجةأمان إنترنت الأشياءتحليل الثغرات الأمنيةالاستغلالالهندسة العكسيةاختبار الاختراقأمن الأجهزةالتعلم والتعليمتحليل البرامج الثابتةاستغلال الملفات الثنائيةمختبرات وتدريب عملي
GitHubjaden-bowers/tenda-router-vr-and-exploit

Tenda-Router-VR-and-Exploit

شرح حول إعادة استضافة البرنامج الثابت لجهاز التوجيه Tenda AC15 وإعادة إنتاج استغلال تنفيذ الأوامر عن بُعد (CVE-2020-10987).

عرض المستودع
120منذ 10 أشهرلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

Tenda-Router-VR-and-Exploit

توضح هذه المقالة بالضبط كيف قمت بمحاكاة خادم الويب الخاص بالبرامج الثابتة لجهاز AC15 (V15.03.05.19) باستخدام QEMU، وجعلته قابلاً للوصول من متصفح المضيف، واختبرت المعالج الضعيف /goform/setUsbUnload (CVE-2020-10987) للحصول على تنفيذ أوامر داخل نظام الملفات الجذرية المُحاكى.


نظرة عامة

  • استخراج (أو في حالتنا الحصول على) نظام ملفات squashfs من صورة البرامج الثابتة والبدء في الهندسة العكسية
  • بناء نظام ضيف Debian armhf يعمل تحت qemu-system-arm
  • إعداد شبكة الضيف ومسار إعادة توجيه المنافذ إلى جهاز التوجيه المُحاكى
  • بناء سكربت الإقلاع الخاص بنا
  • استغلال جهاز التوجيه المُحاكى

استخراج نظام الملفات

أولاً، نحتاج إلى الحصول على صورة البرامج الثابتة. لم أتمكن من العثور على تنزيل الصورة لـ AC15 V15.03.05.19، لكنني تمكنت من العثور على مستودع GitHub يحتوي على نظام ملفات squashfs المستخرج بالفعل. إذا كان لدينا ملف الصورة الصحيح، فيمكننا استخراجه باستخدام binwalk كما يلي،``` user@computer $ binwalk -e AC15_V15.03.05.19.bin

DECIMAL HEXADECIMAL DESCRIPTION

64 0x40 TRX firmware header, little endian, image size: 6778880 bytes, CRC32: 0x80AD82D6, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x1A488C, rootfs offset: 0x0 92 0x5C LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4177792 bytes 1722572 0x1A48CC Squashfs filesystem, little endian, version 4.0, compression:xz, size: 5052332 bytes, 848 inodes, blocksize: 131072 bytes, created: 2017-04-19 16:18:08

user@computer $ cd _AC15_V15.03.05.19.bin.extracted

بما أنه ليس لدينا ملف الصورة الصحيح ولكن لدينا مستودع يحتوي على نظام الملفات المستخرج بالفعل، يمكننا فقط استنساخ المستودع.```
git clone https://github.com/lapinpt/Tenda-AC15-Firmware-V15.03.05.19-9061

لقد أنشأت مجلدًا باسم VR واستنسخت هذا المستودع بداخله. rootfs الخاص بي موجود في $HOME/VR/Tenda-AC15-Firmware-V15.03.05.19-9061/rootfs.

رائع، الآن لدينا البرنامج الثابت الخاص بـ AC15 V15.03.05.19. دعنا ننتقل إلى بعض الهندسة العكسية لنرى ما الذي يحدث.

الهندسة العكسية

سأستخدم Ghidra 11.4.2 للهندسة العكسية. دعنا ننتقل إلى الملف الثنائي المستهدف هنا rootfs/bin/httpd ونحمّله في Ghidra. إذا انتقلنا إلى الدالة formsetUsbUnload فسنرى،```C uVar1 = FUN_0002bd4c(param_1,"deviceName",&DAT_000f4bdc); doSystemCmd("cfm post netctrl %d?op=%d,string_info=%s",0x33,3,uVar1); FUN_0002c6cc(param_1,"HTTP/1.0 200 OK\r\n\r\n"); FUN_0002c6cc(param_1,"{"errCode":0}"); FUN_0002cc14(param_1,200); return;

هذه هي الثغرة. يتم تمرير المعامل `deviceName` مباشرة إلى `doSystemCmd` مما يسمح لنا بإرسال أي أوامر نريدها.

الآن وبما أننا سنعيد استضافة هذا البرنامج الثابت باستخدام qemu وليس أجهزة الراوتر الأصلية، فبعض البرامج ستحاول الوصول إلى أجهزة غير موجودة مما يؤدي إلى تعطّل بدء التشغيل لدينا.
وبما أن هدفنا هو استغلال خادم الويب (`httpd`)، فقد ركّزت فقط على إعادة استضافة هذا الثنائي وليس كامل عملية بدء التشغيل (`/rootfs/etc_ro/init.d/rcS`). _بعد فوات الأوان، لست متأكدًا من أن هذا كان القرار الصحيح_

لذا عند النظر إلى `rcS`، أردت العثور على أي شيء قد يفعله `rcS` مما قد يحتاجه `httpd`. في نهاية الملف يمكننا رؤية:```bash
cfmd &
echo '' > /proc/sys/kernel/hotplug
udevd &
logserver &

rcS يشغّل cfmd في الخلفية قبل أن يعمل باقي الحزمة مباشرة. بعد مزيد من البحث والاطلاع على كيفية إرسال الدالة الضعيفة للأمر، توصلت إلى الاستنتاج التالي:

  • httpd يبني cfm post
  • ثم يتواصل عميل cfm مع cfmd عبر مقبس نطاق UNIX (مثل /var/cfm_socket)

في روتين InitServer في cfmd يمكننا رؤية```C unlink("/var/cfm_socket"); strncpy(sa_unix.sun_path, "/var/cfm_socket", ...); bind(fd, (sockaddr*)&sa_unix, 0x6e); listen(fd, 5);

يقوم بإنشاء مقبس UIX ويستمع.
بشكل عام، يقرأ المعالج إطارًا ثابتًا بحجم 0x7e0 بايت من كل عميل (`RecvMsg`/`SendMsg`). أول 4 بايتات هي رمز أمر؛ ثم هناك مخزن مفاتيح بحجم 512 بايت ومخزن قيم بحجم 1500 بايت (يمكنك رؤية أحجام كائنات المكدس في المعالج). يقوم بالتبديل بناءً على رمز العملية ويرد برمز ACK:
- `2` -> Get: `GetCfmValue(key, value)` ثم يرد برمز `3`
- `0` -> Set: `SetCfmValue(key, value)` ثم يرد برمز `1`
- `0x11` -> Unset: `UnSetCfmValue(key)` ثم يرد برمز `0x12`
- `10` -> Commit: `SaveCfm2Flash()` ثم يرد بـ `0x10` (OK) أو `0xB` (خطأ)

لذا، من أجل محاكاة `cfmd`، أنشأت سكربتًا قصيرًا `cfm_stub````C
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define SOCK_PATH "/var/cfm_socket"

// minimal UNIX-domain server that httpd expects.
// Replies with an IP string when it sees the key it asks for.
int main(void) {
  int s = socket(AF_UNIX, SOCK_STREAM, 0);
  struct sockaddr_un addr = {0};
  if (s < 0) { perror("socket"); return 1; }
  unlink(SOCK_PATH);
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path)-1);
  if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
  if (listen(s, 5) < 0) { perror("listen"); return 1; }

  for (;;) {
    int c = accept(s, NULL, NULL);
    if (c < 0) { if (errno==EINTR) continue; perror("accept"); break; }
    char buf[1024]; ssize_t n = read(c, buf, sizeof(buf));
    if (n > 0) {
      // In some builds httpd asks for "lan.webiplansslen" etc.
      // Any non-empty reply that looks like an IP keeps init happy.
      const char *reply = "192.168.0.1";
      write(c, reply, strlen(reply));
    }
    close(c);
  }
  close(s);
  return 0;
}

سأوضح كيفية تجميع هذا بعد الجزء التالي.

ملف المساعدة التالي هو hooks.so. هناك بعض الدوال المستخدمة في httpd وcfm تحاول التفاعل مع عتاد غير موجود. فيما يلي ما يفترضه برنامجنا عند بدء التشغيل

  • أقسام Flash وMTD موجودة وقابلة للتركيب.
  • يوجد جهاز NVRAM (/dev/nvram) ويعيد قيمًا افتراضية سليمة.
  • تنجح الإجراءات الروتينية المتنوعة للمنصة (محمل إعدادات الطبقة السابعة، استعادة طاقة RF، إلخ).

تصبح الدوال التالية مشكلة، ولذلك يتعين علينا تطبيق الترقيع باستخدام LD_PRELOAD=/hooks.so.

  • get_flash_type() -> إذا أعادت 4، يسلك الكود مسارًا قائمًا على الملفات (cfm_file_init)، وإلا يحاول التحدث إلى MTD (وهو ما لا نملكه).
  • get_cfm_blk_size_from_cache() (و/أو البديل j_get_cfm_blk_size_from_cache) يُستشار لتحديد حجم كتلة الإعدادات.
  • يجب ألا تفشل الاستدعاءات إلى طبقات التوافق الخاصة بـ Broadcom NVRAM (bcm_nvram_get)، وإلا سيفترض النظام أن “NVRAM مدمرة” ويتجه إلى منطق الاستعادة/إعادة التشغيل.
  • من المتوقع أن تنجح الإجراءات الإضافية مثل load_l7setting_file() وrestore_power()، لكنها تتعامل مع عتاد/ملفات غير موجودة.

إليك hooks.c الخاص بنا. يعود الفضل إلى azeria-labs في النسخة الأصلية.```C #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <dlfcn.h> #include <string.h>

int j_get_cfm_blk_size_from_cache(const int i) { puts("j_get_cfm_blk_size_from_cache called....\n"); return 0x20000; // 128 KiB block — what the file path expects }

int get_flash_type() { puts("get_flash_type called....\n"); return 4; // force file-backed CFM init, not MTD }

int load_l7setting_file() { puts("load_l7setting_file called....\n"); return 1; // pretend Layer-7 settings loaded OK }

int restore_power(int a, int b) { puts("restore_power called....\n"); return 0; // success (don’t touch RF/power hardware) }

char *bcm_nvram_get(char *key) { char *value = NULL;

if (strcmp(key, "et0macaddr") == 0) { value = strdup("DE:AD:BE:EF:CA:FE"); // any valid MAC works } if (strcmp(key, "sb/1/macaddr") == 0) { value = strdup("DE:AD:BE:EF:CA:FD"); } if (strcmp(key, "default_nvram") == 0) { value = strdup("default_nvram"); // signals “nvram is OK” }

تنزيل الأداة