Hola!,
Queria contarles acerca de un bug que encontre mientras hacia un exploit para un producto de la compañia CA (Computer Associates - www.ca.com).
El producto en cuestion es el archi-conocido ARCserve Backup, un software corporativo para realizar backups.
Los que trabajan en esta cuestion de buscar bugs y hacer exploits saben de sobra que la mayoria de los productos ofrecidos por esta empresa (CA) pareciera que vienen programados por chicos de 15 años que solo saben utilizar strcpy() y gets(); ARCserve esta lleno de bugs y para comprobarlo, basta con solo colocar "arcserve backup vulnerability" en Google para ver la cantidad de advisories que mencionan vulnerabilidades en este software (acá).
En fin, quiero contarles un poco como fue que di con un bug extra en la misma función que ya tenía otro bug! XD.
La historia fue mas o menos asi:
Estaba tratando de hacer un exploit para CVE-2008-4397. Dicha vulnerabilidad nos permite ejecutar comandos de manera remota, sin autenticacion e incluso lograr dropear un .exe que nos de acceso completo a la maquina victima. Por ejemplo, si enviaramos el comando "cmd /c ntsd -c calc.exe" se ejecutaria el ntsd con la calculadora attacheada.
La funcion vulnerable es accesible mediante RPC (MSRPC). La interfaz que contiene dicha funcion es:
[
uuid(506b1890-14c8-11d1-bbc3-00805fa6962e),
version(1.0)
]
Esa interfaz se encuentra en la libreria Asdbapi.dll y es accesible mediante el servicio llamado "CA Message Service" que escucha en el puerto 6504/TCP y que corre con privilegios de SYSTEM.
La funcion vulnerable es la siguiente:
/* opcode: 0x156, address: 0x28EAAAB0 */
long sub_28EAAAB0 (
[in] handle_t arg_1,
[in][ref][string] char * arg_2,
[in][ref][string] char * arg_3,
[in][ref][string] char * arg_4,
[in][ref][string] char * arg_5,
[in] long arg_6,
[in][ref][size_is(arg_1)] char * arg_7,
[in] long arg_8,
[in, out][ref] long * arg_9,
[out][ref][size_is(*arg_9)] char ** arg_10
);
}
Como podemos ver, el OPCODE que representa a la funcion es el 0x156. Si desensamblamos esta funcion con IDA, veremos que comienza de esta manera:
.text:28EAAAB0 ; int __stdcall sub_28EAAAB0(PRPC_MESSAGE Message)
.text:28EAAAB0 sub_28EAAAB0 proc near
.text:28EAAAB0
.text:28EAAAB0 Args= dword ptr -128h
.text:28EAAAB0 var_124= dword ptr -124h
.text:28EAAAB0 ppMemory= byte ptr -120h
.text:28EAAAB0 var_11C= dword ptr -11Ch
.text:28EAAAB0 var_118= dword ptr -118h
.text:28EAAAB0 var_114= dword ptr -114h
.text:28EAAAB0 pStubMsg= _MIDL_STUB_MESSAGE ptr -110h
.text:28EAAAB0 var_34= dword ptr -34h
.text:28EAAAB0 var_30= dword ptr -30h
.text:28EAAAB0 var_2C= dword ptr -2Ch
.text:28EAAAB0 var_28= dword ptr -28h
.text:28EAAAB0 lpBuffer= dword ptr -24h
.text:28EAAAB0 var_20= dword ptr -20h
.text:28EAAAB0 var_1C= dword ptr -1Ch
.text:28EAAAB0 var_18= dword ptr -18h
.text:28EAAAB0 var_10= dword ptr -10h
.text:28EAAAB0 var_4= dword ptr -4
.text:28EAAAB0 Message= dword ptr 8
.text:28EAAAB0
.text:28EAAAB0 push ebp
.text:28EAAAB1 mov ebp, esp
.text:28EAAAB3 push 0FFFFFFFFh
.text:28EAAAB5 push offset unk_28EC0BC0
.text:28EAAABA push offset _except_handler3
.text:28EAAABF mov eax, large fs:0
.text:28EAAAC5 push eax
.text:28EAAAC6 mov large fs:0, esp
.text:28EAAACD sub esp, 11Ch
.text:28EAAAD3 push ebx
.text:28EAAAD4 push esi
.text:28EAAAD5 push edi
.text:28EAAAD6 mov [ebp+var_18], esp
.text:28EAAAD9 push offset stru_28EBC410 ; pStubDescriptor
.text:28EAAADE lea eax, [ebp+pStubMsg]
.text:28EAAAE4 push eax ; pStubMsg
.text:28EAAAE5 mov esi, [ebp+Message]
.text:28EAAAE8 push esi ; pRpcMsg
.text:28EAAAE9 call ds:NdrServerInitiali
Esa parte corresponde al prologo de la funcion vulnerable. Si nos fijamos, un poco mas abajo realiza un par de comprobaciones sobre los tipos de datos enviados en el paquete, si se corresponde con los tipos esperados, entonces continuara por esta parte:
.text:28EAAC0F loc_28EAAC0F:
.text:28EAAC0F mov [ebp+var_4], ebx
.text:28EAAC12 lea edx, [ebp+ppMemory]
.text:28EAAC18 mov [ebp+var_28], edx
.text:28EAAC1B mov dword ptr [ebp+ppMemory], ebx
.text:28EAAC21 lea eax, [ebp+ppMemory]
.text:28EAAC27 push eax ; ppMemory
.text:28EAAC28 push esi ; int
.text:28EAAC29 push ecx ; int
.text:28EAAC2A mov ecx, [ebp+lpBuffer]
.text:28EAAC2D push ecx ; lpBuffer
.text:28EAAC2E push edi ; nNumberOfBytesToWrite
.text:28EAAC2F mov edx, [ebp+var_124]
.text:28EAAC35 push edx ; int
.text:28EAAC36 mov eax, [ebp+var_34]
.text:28EAAC39 push eax ; int
.text:28EAAC3A mov ecx, [ebp+var_30]
.text:28EAAC3D push ecx ; int
.text:28EAAC3E mov edx, [ebp+Args]
.text:28EAAC44 push edx ; Args
.text:28EAAC45 mov eax, [ebp+var_1C]
.text:28EAAC48 push eax ; int
.text:28EAAC49 call sub_28E19360
.text:28EAAC4E add esp, 28h
.text:28EAAC51 mov [ebp+var_11C], eax
.text:28EAAC57 mov [ebp+pStubMsg.BufferLength], 17h
.text:28EAAC61 cmp esi, ebx
.text:28EAAC63 jz short loc_28E
En la funcion sub_28E19360 es un wrapper que solo pushea los argumentos para la funcion ASDB_ReportRemoteExecuteCML:
.text:28E19360 ; int __cdecl sub_28E19360(int, char Args, int, int, int, DWORD nNumberOfBytesToWrite, LPCVOID lpBuffer, int, int, int ppMemory)
.text:28E19360 sub_28E19360 proc near
.text:28E19360
.text:28E19360 Args= byte ptr 8
.text:28E19360 arg_8= dword ptr 0Ch
.text:28E19360 arg_C= dword ptr 10h
.text:28E19360 arg_10= dword ptr 14h
.text:28E19360 nNumberOfBytesToWrite= dword ptr 18h
.text:28E19360 lpBuffer= dword ptr 1Ch
.text:28E19360 arg_1C= dword ptr 20h
.text:28E19360 arg_20= dword ptr 24h
.text:28E19360 ppMemory= dword ptr 28h
.text:28E19360
.text:28E19360 mov eax, [esp+ppMemory]
.text:28E19364 mov ecx, [esp+arg_20]
.text:28E19368 mov edx, [esp+arg_1C]
.text:28E1936C push eax ; ppMemory
.text:28E1936D mov eax, [esp+4+lpBuffer]
.text:28E19371 push ecx ; int
.text:28E19372 mov ecx, [esp+8+nNumberOfBytesToWrite]
.text:28E19376 push edx ; int
.text:28E19377 mov edx, [esp+0Ch+arg_10]
.text:28E1937B push eax ; lpBuffer
.text:28E1937C mov eax, [esp+10h+arg_C]
.text:28E19380 push ecx ; nNumberOfBytesToWrite
.text:28E19381 mov ecx, [esp+14h+arg_8]
.text:28E19385 push edx ; int
.text:28E19386 mov edx, dword ptr [esp+18h+Args]
.text:28E1938A push eax ; int
.text:28E1938B push ecx ; int
.text:28E1938C push edx ; Args
.text:28E1938D call ASDB_ReportRemoteExecuteCML
.text:28E19392 add esp, 24h
.text:28E19395 retn
.text:28E19395 sub_28E19360 endp
Esta funcion en la responsable de ejecutar los comandos enviados desde el exterior, por ejemplo, si enviamos: "..\\..\\..\\..\\..\\..\\..\\..\\..\\Windows\\system32\\cmd /c \"""\""""
Le estamos indicando el path (mas precisamente, el bug es del tipo path traversal) del cmd que sera el que nos ejecute el comando que le indiquemos con los parametros que le indiquemos.
Los "..\\" son para trigerear la primera de las vulnerabilidades, el path traversal, pero si nos fijamos un poco mas adentro, en una de las funciones (sub_28E3F6B0), tenemos un sprintf():
[...]
.text:28E3F8D9 lea edx, [esp+7C0h+Args]
.text:28E3F8E0 push offset aSSSRSOSS ; "%s\\%s -s -r %s -o %s %s"
.text:28E3F8E5 push edx ; Dest
.text:28E3F8E6 call ds:sprintf
[...]
El bug es obvio, no hay ningun tipo de chequeo sobre el tamaño del string que se va a copiar al stack, con lo cual, enviando como comando un string lo suficientemente "largo" lograriamos sobreescribir el RET de la funcion e incluso el exception handler y saltar a ejecutar codigo.
Previo a llegar a esta zona del bug, hay un chequeo(sub_28E01230) sobre uno de los argumentos que se envian, el paquete debe de contener el hostname de la maquina a la cual estamos atacando:
[...]
.text:28E01250 push eax
.text:28E01251 push offset Buffer
.text:28E01256 call ds:_mbsicmp
.text:28E0125C add esp, 8
.text:28E0125F test eax, eax
.text:28E01261 jz short loc_28
[...]
Si el salto se produce, entonces llegaremos a la zona vulnerable, en caso contrario nos hechara fuera.
Para conseguir el hostname podemos hacer un request utilizando SMB, utilizando python + impacket es sencillo.
Esto que les he comentado, lo probe en la version CA ARCserve Backup r11.5 sin ningun SP ni patch level.
El bug fue arreglado en el SP2 de 11.5 en el cual reescribieron la funcion vulnerable.
Aclaro que en realidad aca son dos bugs. Si se fijan en el PoC mas abajo, lo primero que hacemos es trigerear el path traversal y luego el overflow.
El PoC (Proof Of Concept) lo pueden encontrar
aqui:
Bueno, eso es todo por el momento.
Hasta la proxima!.