jueves, 26 de diciembre de 2013

Writeup prueba 4 (rompeme.exe) - h4ckc0nt3st - GSICKMINDS 2013




Usamos el IDA Pro para el análisis de RompeMe.exe, primero para encontrar la rutina de checkeo de licencia,

wsprintfA(String1, "%08X.lic", v4);
result = GetFileAttributesA(String1);
if ( result != -1 )
{
result = (DWORD)CreateFileA(String1, 0x80000000, 1u, 0, 3u, 0x20u, 0);
if ( result != -1 )
{
v2 = (void *)result;
v3 = check_license((HANDLE)result);

El nombre de archivo depende de parametros de hardware como CPUID, MAC de la tarjeta, y número de serie de disco. Para la máquina de prueba se encontró usando SysInternals ProcMon y el API Monitor:


En la máquina de prueba la licencia era AAF2FC0D.lic .
El contenido de la licencia se comprueba con la rutina:

int __stdcall check_license(HANDLE hFile)
{
HLOCAL v1; // ST20_4@1
int v2; // ST1C_4@1
DWORD NumberOfBytesRead; // [sp+4h] [bp-Ch]@1
DWORD FileSystemFlags; // [sp+8h] [bp-8h]@1
DWORD VolumeSerialNumber; // [sp+Ch] [bp-4h]@1

GetVolumeInformationA(0, 0, 0, &VolumeSerialNumber, 0, &FileSystemFlags, 0, 0);
ReadFile(hFile, &license_file_content, 0x100u, &NumberOfBytesRead, 0);
v1 = decrypt_license((char *)&license_file_content, (int)&word_404149);
wsprintfA(String1, "%08X", VolumeSerialNumber);
v2 = (*(_DWORD *)&String1[4] ^ *((_DWORD *)v1 + 1)) + (*(_DWORD *)String1 ^ *(_DWORD *)v1) == 0;
LocalFree(v1);
return v2;
}

Donde la función de descifrado de la licencia es

HLOCAL __stdcall decrypt_license(char *content, int mac)
{
HLOCAL result; // eax@1
signed int i; // ecx@2
char v4; // bl@4
__int16 mac1; // [sp+4h] [bp-Ch]@1
__int16 mac0; // [sp+6h] [bp-Ah]@1
char v7; // [sp+Dh] [bp-3h]@1
__int16 mac2; // [sp+Eh] [bp-2h]@1

mac0 = *(_WORD *)mac;
mac1 = *(_WORD *)(mac + 2);
mac2 = *(_WORD *)(mac + 4);
v7 = *content;
result = LocalAlloc(0x40u, (unsigned __int8)*content + 1);
if ( result )
{
for ( i = 1; (char)i <= v7; ++i )
{
v4 = HIBYTE(mac2) ^ content[i];
*((char *)result + i - 1) = v4;
mac2 = mac1 + mac0 * (mac2 + (unsigned __int8)v4);
}
}
return result;
}

El fichero de licencia contiene cifrado el número de serie de disco, la clave es la dirección MAC. Para probar que esto es asi hemos escrito un generador de números de serie:

$ cat lic.py
#!/usr/bin/python

from struct import unpack

mac = '00-0C-29-BE-4B-F7' # get via ipconfig /all
vol = '501B-3C9D' # get via dir

m0, m1, m2 = unpack('3H', mac.replace('-','').decode('hex'))
content = vol.replace('-', '')

result = ''
for c in content:
#print hex(m2)
c = ord(c)
tmp = (m2/256) ^ c
tmp = tmp % 256
result += chr(tmp)
m2 = m1 + m0 * (m2 + c) # tmp
m2 %= 65536

print result.encode('hex')
open('AAF2FC0D.lic','w').write(chr(len(result)) + result)

$ ./lic.py ; hexdump -C AAF2FC0D.lic
c28edbb4f14df712

En nuestro caso la licencia válida es –

$ hexdump -C AAF2FC0D.lic
00000000 08 c2 8e db b4 f1 4d f7 12 |......M..|
00000009

En el caso de la validación web, los pares de usuario/comprobación de licencia puede ser desactivado por completo, porque no se usa para validaciones futuras, así que deshabilitando la comprobación podríamos generar una clave. Pero podemos generar licencias sin ningún parcheado del binario

Para la validación de claves de usuario, RompeMe.exe crea un proceso desde el ejecutable almacenado en la sección de recursos, pide al nuevo proceso que calcule la validación de la clave de usuario, calcula la validación del nombre de usuario, los compara y si los dos coinciden, prueba superada.

Creación de este nuevo proceso:

v4 = GetTickCount();
wsprintfA(ApplicationName, "%08X", v4);
result = CreateFileA(ApplicationName, 0x40000000u, 2u, 0, 2u, 0x20u, 0);
hFile = result;
if ( result != (void *)-1 )
{
WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0);
CloseHandle(hFile);
StartupInfo.cb = 68;
GetStartupInfoA(&StartupInfo);
wsprintfA(&CommandLine, "%08X", hw_parameters);
CreateProcessA(ApplicationName, &CommandLine, 0, 0, 0, 0, 0, 0, &StartupInfo, &ProcessInformation);
WaitForSingleObject(ProcessInformation.hProcess, 0xFFFFFFFF);
if ( results_received == 1 )
check_results(results);
else
nop();
result = (void *)DeleteFileA(ApplicationName);

El binario de este proceso puede ser obtenido simplemente arrancando el proceso principal en el debugger y copiando el fichero temporal del directorio de trabajo

$ ls -la 00739347
-rwxr-xr-x 1 user user 10240 Oct 24 22:17 00739347

$ sha256sum 00739347
41b2928d4ad584f8b975d051f6e53fe6c3231d786db8be1faaa96c21fde4cbc7 00739347

Después de arrancar, el binario del proceso auxiliar pide al proceso principal parametros:

LRESULT __stdcall StartAddress(LPARAM lParam)
{
return SendMessageA(dword_40400C, 1034u, 0, lParam);
}

El proceso principal responde con un handler del control de dialogo para la clave

case 1034:
PostMessageA(a4, 1035u, wParam, hwnd);
break;

El proceso auxiliar calcula el código de validación y lo devuelve al proceso principal

if ( a2 == 1035 )
{
hWnd = a3;
dword_404014 = a4;
if ( (char *)a4 + (_DWORD)a3 )
solve_quadratic_equation(lpParameter, dword_40400C);


SendMessageA(::hWnd, 13u, 260u, (LPARAM)sz);
SendMessageA(dword_404014, 13u, 260u, (LPARAM)&unk_40411C);
if ( !unk_40411C
|| (v4 = lstrlenA(sz), CharUpperBuffA(sz, v4), v4 < 6)
|| v4 > 6
|| (LOBYTE(v2) = is_hexstring(sz), v2) )
{
result = SendMessageA(hWnd, 16u, 0, 0);
}
else
{
v5 = *(_WORD *)sz;
v6 = 0;
w0 = (char)hex2word(&v5);
v5 = *(_WORD *)&sz[2];
v6 = 0;
w1 = (char)hex2word(&v5);
v5 = *(_WORD *)&sz[4];
v6 = 0;
w2 = (char)hex2word(&v5);
result = check(w0, w1, w2, hWnd, a2);
}
return result;

El algoritmo del código de validación:

La clave debe ser de longitud 6 (hex), cada byte es un parámetro de una ecuación cuadrática

aa * x^2 + bb*x + cc, donde aabbcc es la clave introducida por el usuario

El binario auxiliar lo calcula y manda de vuelta una de las raices de la ecuación

det = (signed int)((double)w1 * (double)w1 + (double)-4 * (double)w0 * (double)w2);
if ( det < 0 )
{
result = SendMessageA(a4, 16u, 0, 0);
}
else
{
PostMessageA(hWnd, 1036u, (signed int)((sqrt((double)det) + (double)w1 * (double)-1) / (double)w0 / (double)2), 0);
result = SendMessageA(a4, 0x10u, 0, 0);
}

El binario principal calcula el valor medio del valor ASCII de las letras del nombre de usuario, y lo compara con el valor de validación del binario auxiliar

.text:004029B4 check_results proc near ; CODE XREF: StartAddress+151#p
.text:004029B4
.text:004029B4 lParam = dword ptr -104h
.text:004029B4 arg_0 = dword ptr 8
.text:004029B4
.text:004029B4 push ebp
.text:004029B5 mov ebp, esp
.text:004029B7 add esp, 0FFFFFEFCh
.text:004029BD lea eax, [ebp+lParam]
.text:004029C3 push eax ; lParam
.text:004029C4 push 104h ; wParam
.text:004029C9 push 0Dh ; Msg
.text:004029CB push username ; hWnd
.text:004029D1 call SendMessageA
.text:004029D6 lea edi, [ebp+lParam]
.text:004029DC xor eax, eax
.text:004029DE mov al, [edi]
.text:004029E0 or al, al
.text:004029E2 jz short loc_402A0B
.text:004029E4 xor edx, edx
.text:004029E6
.text:004029E6 loc_4029E6: ; CODE XREF: check_results+39#j
.text:004029E6 add edx, eax
.text:004029E8 inc edi
.text:004029E9 mov al, [edi]
.text:004029EB cmp al, 0
.text:004029ED jnz short loc_4029E6
.text:004029EF lea esi, [ebp+lParam]
.text:004029F5 sub edi, esi
.text:004029F7 mov eax, edx
.text:004029F9 xor edx, edx
.text:004029FB idiv edi
.text:004029FD xor eax, [ebp+arg_0]
.text:00402A00 or eax, eax
.text:00402A02 jnz short loc_402A0B
.text:00402A04 call winner

Así que la generación de claves es trivial, usaremos la ecuación cuadrática

(x-N)*(x-1)=0
x^2 - (N+1)*x + N = 0

donde N = sum(name)/len(name), entonces la clave será la concatenación de los valores

key = 01 || (255 – N) || N

Generador de claves final:

$ cat kg.py
#!/usr/bin/python

from sys import argv

name = 'pADAwan'
if len(argv) > 1:
name = argv[-1]

sum = 0
for c in name:
sum += ord(c)
avg = sum / len(name)

key = '01%02x%02x' % (255-avg, avg)

print 'Name:', name
print 'Key:', key

Clave valida para nosotros

$ ./kg.py
Name: pADAwan
Key: 01a55a




No hay comentarios:

Publicar un comentario