August 21, 2026
FORCEDENTRY — Pegasus’ning iMessage orqali zero-click hujumi qanday ishlagan?
2021-yilda Citizen Lab Pegasus bilan bog‘liq FORCEDENTRY exploit chain’ini aniqladi. Keyinchalik Google Project Zero ushbu exploitni…

By ayub
5 min read
2021-yilda Citizen Lab Pegasus bilan bog'liq FORCEDENTRY exploit chain'ini aniqladi. Keyinchalik Google Project Zero ushbu exploitni reverse-engineer'ing orqali, uning ichida nimalar sodir bo'lishini batafsil tahlil qilib chiqdi.
FORCEDENTRY bir necha murakkab exploit'lardan foydalangan xolda ishledi. Dastlabki bug hujumchiga faqat memory corruption imkoniyatini beradi. Keyin esa shu imkoniyatdan foydalanib, yanada kuchliroq primitive'lar hosil qilinadi.
Ya'ni jarayon taxminan shunday:
bitta bug → memory corruption → memory’ni boshqarish → code execution → sandbox’dan chiqish.bitta bug → memory corruption → memory’ni boshqarish → code execution → sandbox’dan chiqish.Shu primitive'lar bir-biriga ulanib, oxirida to'liq exploit chain'ni hosil bo'ladi.
1. Attachment → PDF → JBIG2
Exploit maxsus tayyorlangan attachment'dan boshlanadi. Fayl .gif ko'rinishida yuborilgan bo'lsa ham, uning ichidagi haqiqiy ma'lumot PDF formatida bo'lgan.
Shu orqali fayl image processing pipeline orqali o'tib, PDF parserga yetib boradi.
PDF ichida esa JBIG2 stream joylashgan. JBIG2 rasm ma'lumotlarini segment va symbollar orqali qayta ishlaydi.
Muammo aynan shu symbol'lar sonini hisoblash jarayonida yuzaga keladi. Dastur qancha joy kerakligini hisoblab, keyin shuncha memory ajratadi:
numSymbols += segment->size;
symbols = malloc(
numSymbols * sizeof(Symbol *)
);numSymbols += segment->size;
symbols = malloc(
numSymbols * sizeof(Symbol *)
);Lekin numSymbols 32-bit qiymat bo'lgani uchun, katta qiymat berilganda integer overflowga olib kelgan:
0xffffffff + 0x10
↓
0x0000000f0xffffffff + 0x10
↓
0x0000000f
Natijada dastur katta buffer ajratishi kerak bo'lsa ham, overflow sabab kichik buffer ajratgan.
Kerak:
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
Ajratildi:
[ ][ ]Kerak:
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
Ajratildi:
[ ][ ]Keyingi code esa hali ham ko'p symbol bor deb hisoblaydi. Shu yerda kichik buffer'ga katta hajmda yozish boshlanadi va bu keyingi heap corruption bosqichiga olib keladi.
2. Undersized allocation → heap corruption
Oldingi bosqichda numSyms overflow qilgani uchun, syms uchun ajratilgan buffer kerak bo'lganidan ancha kichik bo'lib qoldi.
Lekin keyingi loop symbol dictionary ichidagi barcha bitmap pointerlarini syms buffer'iga yozishda davom etadi:
for (k = 0; k < symbolDict->getSize(); ++k) {
syms[kk++] = symbolDict->getBitmap(k);
}for (k = 0; k < symbolDict->getSize(); ++k) {
syms[kk++] = symbolDict->getBitmap(k);
}Ya'ni:
Ajratilgan memory:
[ P ][ P ][ P ][ P ]
│
└── buffer tugadi
Yozish davom etadi:
[ P ][ P ][ P ][ P ][ P ][ P ][ P ][ ... ]
↑
out-of-boundsAjratilgan memory:
[ P ][ P ][ P ][ P ]
│
└── buffer tugadi
Yozish davom etadi:
[ P ][ P ][ P ][ P ][ P ][ P ][ P ][ ... ]
↑
out-of-boundsShu yerda heap buffer overflow yuz beradi.
Lekin exploitning shunchaki xotirani buzib ilovani crash qilish bilan to'xtamaydi.
Hujumchi heap'ni oldindan o'zi xohlagan tartibda tayyorlaydi (heap grooming). Natijada syms bufferiga ajratilgan joy tugagach, undan keyin yozilgan pointerlar tasodifiy xotiraga emas, aynan yonida joylashgan GList backing buffer ustiga tushadi va uni overwrite qiladi.
GList backing buffer esa JBIG2 segmentlariga ishora qiluvchi pointerlarni saqlaydi. Pointerlar o'zgargach, dastur keyingi safar GList'dan segmentlarni o'qiganda hujumchi ko'rsatgan manzillarga murojaat qiladi. Shu mexanizm exploitning keyingi bosqichlarini amalga oshirishga yo'l ochadi.
3. Corrupted Bitmap → Memory Read/Write
GList pointerlari o'zgartirilgach, exploit keyingi bosqichga o'tadi. Endi dastur JBIG2 segmentlarini qayta ishlaganda, hujumchi nazorat qilayotgan ma'lumotlardan foydalanadi. Natijada JBIG2Bitmap object'ining ichki maydonlari overwrite qilinadi va bitmap hujumchi xohlagan qiymatlar bilan ishlay boshlaydi.
Soddalashtirilgan bitmap object:
struct JBIG2Bitmap {
void *data;
int width;
int height;
int stride;
};struct JBIG2Bitmap {
void *data;
int width;
int height;
int stride;
};Bu object bitmap haqida eng muhim ma'lumotlarni saqlaydi:
data— rasm piksellari joylashgan xotira manzili.width— rasmning eni.height— rasmning bo'yi.stride— xotirada bitta qator (row) nechta byte egallashi.
Decoder har safar biror pixel yozishi kerak bo'lsa, qaysi manzilga yozishni shu formula orqali hisoblaydi:
data[y * stride + x] = value;data[y * stride + x] = value;Ya'ni x va y koordinatalari asosida kerakli pixelning xotiradagi manzili topiladi va o'sha joyga qiymat yoziladi.
Normal holatda x va y bitmap chegarasidan chiqmaydi. Shuning uchun yozuv faqat rasmning o'z xotirasida amalga oshadi.
Bitmap
┌──────────────────────┐
│ Pixel Pixel Pixel │
│ Pixel Pixel Pixel │
│ Pixel Pixel Pixel │
└──────────────────────┘Bitmap
┌──────────────────────┐
│ Pixel Pixel Pixel │
│ Pixel Pixel Pixel │
│ Pixel Pixel Pixel │
└──────────────────────┘Heap overflow'dan keyin JBIG2Bitmap objectining metadata'si o'zgaradi. Eng muhim maydonlardan biri — data pointeri yoki stride qiymati.
Masalan, data endi bitmap buffer'ni emas, heap'dagi boshqa xotira manzilini ko'rsatishi mumkin.
Oldin:
Bitmap ─────→ bitmap buffer
Keyin:
Bitmap ─────→ heap memory
│
├── object
├── pointer
└── metadataOldin:
Bitmap ─────→ bitmap buffer
Keyin:
Bitmap ─────→ heap memory
│
├── object
├── pointer
└── metadataDecoder esa bundan bexabar. U hali ham "bitmap'ga pixel yozayapman" deb hisoblaydi va odatdagi formuladan foydalanadi:
data[y * stride + x] = value;data[y * stride + x] = value;Lekin data yoki stride o'zgargani uchun hisoblangan manzil endi bitmap ichida emas, heap'dagi boshqa obyektlarga to'g'ri keladi.
Natijada decoder tasodifiy emas, hujumchi tanlagan xotira addresiga ma'lumot yozishi yoki undan o'qishi mumkin. Shu nuqtada oddiy heap corruption kuchliroq imkoniyatga — arbitrary memory read/write primitive'ga aylanadi.
4. JBIG2 Weird Machine
JBIG2 decoder faqat rasmni ochib beradigan parser emas. U bitmap'lar ustida turli logical operations bajarishni ham qo'llab-quvvatlaydi. Masalan:
AND
OR
XOR
XNORAND
OR
XOR
XNOROddiy holatda bu amallar bitmap'larni birlashtirish yoki ularni o'zgartirish uchun ishlatiladi. Ammo hujumchilar aynan shu imkoniyatni butunlay boshqa maqsadda ishlatishgan.
Ular minglab JBIG2 segmentlarini ketma-ket bog'lab, bu mantiqiy amallardan hisob-kitoblar bajaradigan tizim yaratishgan. Ya'ni bitmap operatsiyalari yordamida mantiqiy amallar, arifmetik hisoblashlar va xotira bilan ishlash ketma-ketligi qurilgan.
Google Project Zero tahliliga ko'ra, FORCEDENTRY payload'i 70 000 dan ortiq JBIG2 segment ishlatgan. Har bir segment juda kichik vazifani bajaradi, lekin ularning barchasi ketma-ket ishlaganda bitta katta dastur kabi ishlaydi.
Jarayonni soddalashtirib quyidagicha tasavvur qilish mumkin:
JBIG2 decoder
↓
Logical operations
↓
Computation
↓
Memory manipulationJBIG2 decoder
↓
Logical operations
↓
Computation
↓
Memory manipulationShu sababli bu usul weird machine deb ataladi. Decoder yangi CPU yoki virtual mashina emas, lekin undagi mavjud imkoniyatlar shunday kombinatsiya qilinadiki, u dastur bajaradigan muhitga aylanadi.
Natijada exploit o'ziga kerak bo'lgan hisob-kitoblarni JBIG2 decoder ichining o'zida bajaradi va keyingi bosqich — Remote Code Execution (RCE) uchun zarur bo'lgan xotira o'qish, yozish va pointerlarni boshqarish kabi primitivlarni tayyorlaydi.
5. Weird Machine → RCE
Shu nuqtaga kelib, exploit oddiy memory corruption'dan ancha kuchli primitive'lar zanjirini hosil qilgan:
integer overflow
↓
heap corruption
↓
corrupted bitmap
↓
memory read/write
↓
JBIG2 weird machine
↓
RCEinteger overflow
↓
heap corruption
↓
corrupted bitmap
↓
memory read/write
↓
JBIG2 weird machine
↓
RCEDastlabki integer overflowning o'zi code execution bermaydi. U avval heap'dagi ma'lumotlarni buzish imkonini beradi. Keyinchalik buzilgan JBIG2Bitmap object orqali exploit xotirani o'qish va yozishni nazorat qila boshlaydi.
Weird machine esa shu imkoniyatlarni birlashtirib, kerakli hisob-kitoblar va memory manipulation'ni decoder ichida bajaradi. Shu orqali exploit oxir-oqibat process ichida attacker-controlled code execution darajasiga yetadi.
Ammo bu hali yakun emas.
RCE olingandan keyin exploit hali sandbox ichida ishlayapti. Demak, attacker process ichida kod bajara olsa ham, iOS sandbox uning system resurslariga kirishini cheklab turadi.
Shu sababli keyingi maqsad — sandbox'dan chiqish.
6. Sandbox Escape
Bu bosqich JBIG2'dagi kabi yangi memory corruption'ga emas, iOS frameworklaridagi mavjud functionality'lardan foydalanishga tayanadi.
Muhim komponentlar orasida:
NSKeyedArchiver
NSExpression
NSPredicate
Objective-C runtime
XPCNSKeyedArchiver
NSExpression
NSPredicate
Objective-C runtime
XPCNSExpression va NSPredicate oddiy data container emas — ular expression'larni evaluate qilish imkoniyatiga ega. Exploit bu imkoniyatlardan oldindan tayyorlangan objectlar bilan birga foydalanib, process ichida kerakli Objective-C functionality'ni chaqirish uchun primitive hosil qiladi.
Keyingi bosqichda XPC orqali sandbox tashqarisidagi system service'lar bilan interaction qilish imkoniyati ishga tushadi.
Soddalashtirib:
JBIG2 RCE
↓
Sandbox ichidagi process
↓
Objective-C primitives
↓
Framework functionality
↓
XPC interaction
↓
Privileged system service
↓
Sandbox escapeJBIG2 RCE
↓
Sandbox ichidagi process
↓
Objective-C primitives
↓
Framework functionality
↓
XPC interaction
↓
Privileged system service
↓
Sandbox escapeUmumiy chain quyidagicha:
JBIG2 vulnerability
↓
Memory corruption
↓
Weird machine
↓
RCE
↓
Objective-C / framework primitives
↓
XPC
↓
Sandbox escapeJBIG2 vulnerability
↓
Memory corruption
↓
Weird machine
↓
RCE
↓
Objective-C / framework primitives
↓
XPC
↓
Sandbox escapeBu yerda JBIG2 exploit code execution'ni beradi, keyingi framework va IPC primitive'lari esa shu execution'ni sandbox chegarasidan tashqariga olib chiqishda ishlatiladi.
References
Google Project Zero — RCE analysis