Conversor de Houaiss para Babylon - parte 1
Wanderley Caloni, 2008-02-27

#engenharia reversa #projetos #houaiss2babylon

Este artigo é sobre desmontar e montar novamente. Iremos descobrir como as entradas do dicionário Houaiss eletrônico estão gravadas em um primeiro momento, para depois remontarmos essa informação de maneira que ela possa ser usada em outro dicionário de uso mais flexível, o Babylon. Ou seja, este não é um guia de vandalismo. Estava apenas querendo usar um dicionário de qualidade excelente em outro dicionário cuja interface é muito boa.

Sobre pirataria

Considero o Houaiss o melhor dicionário da atualidade, uso todo santo dia e tenho todo o respeito por ele. Possuo uma cópia legalizada exatamente por isso. Além, é óbvio, pelo escandaloso cinismo que seria se eu, desenvolvedor de software, pirateasse os que utilizo. Porém, acredito que tudo tenha um limite: respeito os direitos de quem desenvolve o programa se o programa se dá ao respeito de ser pago. Quer dizer, eu realmente uso muito esse dicionário, e ele é útil para mim. Logo, nada mais justo do que adquiri-lo como manda a lei.

Assim como adquiri o Houaiss, também comprei o Babylon, um programa-dicionário, cuja interface permite buscar o significado das palavras lidas no computador simplesmente clicando nelas. A qualidade de seu dicionário português embutido é medíocre, mas o que ele ganha mesmo é em sua interface fácil para acessar palavras. Exatamente por faltar um dicionário em português de peso no Babylon, e eu ter adquirido outro muito melhor, quis que ambos funcionassem juntos, ou seja, acesso o Babylon e tenho o resultado adicional desse meu dicionário tupiniquim.

O Babylon possui um mecanismo para criação de dicionários chamado Babylon Builder. É muito simples e fácil de usar (além de ser gratuito). Sabendo que possuo ambas as licenças desses dois programas me sinto mais aliviado em tentar desencriptar a base de dados do primeiro para construir um dicionário para o segundo, e assim realizar meu sonho de consumo: um Babylon com um dicionário de peso!

Licença do Houaiss

Instalação

É necessário que, na hora da instalação, seja escolhida a opção de copiar os arquivos para o disco. Estarei utilizando o path padrão de um Windows em português, que é “C:\Arquivos de Programas\Houaiss”.

Instalação do Houaiss

A estrutura de diretórios interna da instalação é bem simples:

  • Raiz. Arquivos de ajuda, desinstalador, executável principal, etc.

  • Quadros. Figuras com conhecimentos gerais, como calendários, signos, línguas mais faladas, etc.

  • Dicionario. Provavelmente onde está todo o dicionário, cerca de 120 MB.

Se analisarmos o conteúdo dos arquivos dentro da pasta Dicionario vamos descobrir que ele se parece com “garbage nonsense”, apesar de existir um certo padrão. O padrão revela que pode se tratar de uma criptografia muito simples, talvez até um simples XOR.

for %i in (*.*) do type %i | less

Saída dos arquivos do dicionário

Análise

Sabendo que o conteúdo do dicionário está em arquivos localizados no disco, e que teoricamente o programa não deve copiar todo o conteúdo para a memória, iremos depurar o processo do dicionário de olho nas chamadas da função ReadFile quando clicarmos em uma definição de palavra.

windbg -pn houaiss2.exe
0:001> bp kernel32!ReadFile "dd @$csp L6" $$ Dando uma olhada nos parâmetros
g

Ao clicar na definição de “programa-fonte”, o breakpoint é ativado:

0012fa70  0040a7a9 00000200 <span style="color: #ff0000;">08bbf1d0 00000200 </span>0012fa80  0012fa88 00000000
eax=0012fa88 ebx=00000200 ecx=00000200 edx=08bbf1d0 esi=08bbf1d0 edi=00000200
eip=7c80180e esp=0012fa70 ebp=0012facc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
kernel32!ReadFile:
7c80180e 6a20            push    20h

$$ O buffer de saída é <span style="color: #ff0000;">08bbf1d0 
</span>
$$ O número de bytes lidos é <span style="color: #ff0000;">200</span>




0:000> db 08bbf1d0 L80
08bbf1d0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
08bbf1e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
08bbf1f0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
08bbf200  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
08bbf210  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
08bbf220  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
08bbf230  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
08bbf240  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................





0:000> bp /1 @$ra "db 08bbf1d0 L80"
0:000> g
08bbf1d0  1f 65 67 64 5c 67 56 62-56 22 5b 64 63 69 5a 02  .egd\gVbV"[dciZ.
08bbf1e0  ff 38 68 23 62 23 02 ff-59 70 51 5e 15 68 4d 4d  .8h#b#..YpQ^.hMM
08bbf1f0  72 02 ff 49 5e 63 5b 02-ff 2f 65 67 64 5c 67 56  r..I^c[../egd\gV
08bbf200  62 56 15 59 5a 15 58 64-62 65 6a 69 56 59 64 67  bV.YZ.XdbejiVYdg
08bbf210  15 5a 62 15 68 6a 56 15-5b 64 67 62 56 15 64 67  .Zb.hjV.[dgbV.dg
08bbf220  5e 5c 5e 63 56 61 21 15-56 63 64 69 56 59 64 15  ^\^cVa!.VcdiVYd.
08bbf230  65 5a 61 64 15 65 67 64-5c 67 56 62 56 59 64 67  eZad.egd\gVbVYdg
08bbf240  15 5a 62 15 6a 62 56 15-61 5e 63 5c 6a 56 5c 5a  .Zb.jbV.a^c\jV\Z
eax=00000001 ebx=00000200 ecx=7c801898 edx=7c90eb94 esi=08bbf1d0 edi=00000200
eip=0040a7a9 esp=0012fa88 ebp=0012facc iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
Houaiss2+0xa7a9:
0040a7a9 85c0            test    eax,eax

Depois da leitura, não temos muitas alternativas a não ser fazer o tracking de chamadas até que o mesmo buffer esteja desencriptado. Esse é o caminho natural das coisas, mas poderia haver complicações secundárias, como uma cópia de buffer antes de seu uso. Estou usando passos simples porque realmente foi muito simples descobrir o segredo da ofuscação.

0:000> p
Houaiss2+0xa7ab:
0040a7ab 7507            jne     Houaiss2+0xa7b4 (0040a7b4)              [br=1]
0:000> p
Houaiss2+0xa7b4:
0040a7b4 8b0424          mov     eax,dword ptr [esp]  ss:0023:0012fa88=00000200
0:000> p
Houaiss2+0xa7b7:
0040a7b7 5a              pop     edx
0:000> p
Houaiss2+0xa7b8:
0040a7b8 5f              pop     edi
0:000> p
Houaiss2+0xa7b9:
0040a7b9 5e              pop     esi
0:000> p
Houaiss2+0xa7ba:
0040a7ba 5b              pop     ebx
0:000> p
Houaiss2+0xa7bb:
0040a7bb c3              ret
0:000> p
Houaiss2+0xb9062:
004b9062 8945f8          mov     dword ptr [ebp-8],eax ss:0023:0012fac4=00000000
0:000> p
Houaiss2+0xb9065:
004b9065 0375f8          add     esi,dword ptr [ebp-8] ss:0023:0012fac4=00000200
0:000> p
Houaiss2+0xb9068:
004b9068 807b1900        cmp     byte ptr [ebx+19h],0       ds:0023:090cf1f9=01
0:000> p
Houaiss2+0xb906c:
004b906c 7410            je      Houaiss2+0xb907e (004b907e)             [br=0]
0:000> p
Houaiss2+0xb906e:
004b906e 8b4df8          mov     ecx,dword ptr [ebp-8] ss:0023:0012fac4=00000200
0:000> p
Houaiss2+0xb9071:
004b9071 8b933c200300    mov     edx,dword ptr [ebx+3203Ch] ds:0023:0910121c=00000000
0:000> p
Houaiss2+0xb9077:
004b9077 8bc3            mov     eax,ebx
0:000> p
Houaiss2+0xb9079:
004b9079 e8eef9ffff      <span style="color: #ff0000;">call Houaiss2+0xb8a6c (004b8a6c)</span>





0:000> p
Houaiss2+0xb9





0:000> db 08bbf1d0 L80
08bbf1d0  2a 70 72 6f 67 72 61 6d-61 2d 66 6f 6e 74 65 0d  *programa-fonte.
08bbf1e0  0a 43 73 2e 6d 2e 0d 0a-64 7b 5c 69 20 73 58 58  .Cs.m...d{\i sXX
08bbf1f0  7d 0d 0a 54 69 6e 66 0d-0a 3a 70 72 6f 67 72 61  }..Tinf..:progra
08bbf200  6d 61 20 64 65 20 63 6f-6d 70 75 74 61 64 6f 72  ma de computador
08bbf210  20 65 6d 20 73 75 61 20-66 6f 72 6d 61 20 6f 72   em sua forma or
08bbf220  69 67 69 6e 61 6c 2c 20-61 6e 6f 74 61 64 6f 20  iginal, anotado
08bbf230  70 65 6c 6f 20 70 72 6f-67 72 61 6d 61 64 6f 72  pelo programador
08bbf240  20 65 6d 20 75 6d 61 20-6c 69 6e 67 75 61 67 65   em uma linguage

Pois bem. Logo depois de chamar a função Houaiss2+0xb8a6c magicamente o buffer incompreensível se transformou no início da definição da palavra “programa-fonte”. Como não temos o programa-fonte do Houaiss, teremos que descer mais um nível no “assemblão”, mesmo.

(Note que reexecutei os passos anteriores para cair na mesma condição)

Houaiss2+0xb9079:
004b9079 e8eef9ffff      call    Houaiss2+0xb8a6c (004b8a6c)
0:000> <span style="color: #ff0000;">t</span>





eax=08bbf1a0 ebx=08bbf1a0 ecx=00000200 edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a6c esp=0012fa98 ebp=0012facc iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
Houaiss2+0xb8a6c:
004b8a6c 53              push    ebx
0:000> p
eax=08bbf1a0 ebx=08bbf1a0 ecx=00000200 edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a6d esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
Houaiss2+0xb8a6d:
004b8a6d 03ca            add     ecx,edx
0:000> p
eax=08bbf1a0 ebx=08bbf1a0 ecx=00000200 edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a6f esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
Houaiss2+0xb8a6f:
004b8a6f 49              dec     ecx
0:000> p
eax=08bbf1a0 ebx=08bbf1a0 ecx=000001ff edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a70 esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
Houaiss2+0xb8a70:
004b8a70 2bca            sub     ecx,edx
0:000> p
eax=08bbf1a0 ebx=08bbf1a0 ecx=000001ff edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a72 esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
Houaiss2+0xb8a72:
004b8a72 7c12            jl      Houaiss2+0xb8a86 (004b8a86)             [br=0]
0:000> p
eax=08bbf1a0 ebx=08bbf1a0 ecx=000001ff edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a74 esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
Houaiss2+0xb8a74:
004b8a74 41              inc     ecx
0:000> p
eax=08bbf1a0 ebx=08bbf1a0 ecx=00000200 edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a75 esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
<span style="color: #ff0000;">Houaiss2+0xb8a75</span>:
004b8a75 33db            xor     ebx,ebx
0:000> p
eax=08bbf1a0 ebx=00000000 ecx=00000200 edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a77 esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
Houaiss2+0xb8a77:
004b8a77 8a5c1030        mov     <span style="color: #ff0000;">bl</span>,byte ptr [eax+<span style="color: #ff0000;">edx</span>+30h]  ds:0023:<span style="color: #ff0000;">08bbf1d0</span>=1f
0:000> p
eax=08bbf1a0 ebx=0000001f ecx=00000200 edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a7b esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
Houaiss2+0xb8a7b:
004b8a7b 83c30b          <span style="color: #ff0000;">add ebx,0Bh
</span>0:000> p
eax=08bbf1a0 ebx=0000002a ecx=00000200 edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a7e esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
Houaiss2+0xb8a7e:
004b8a7e 885c1030        mov     byte ptr [eax+<span style="color: #ff0000;">edx</span>+30h],<span style="color: #ff0000;">bl </span>ds:0023:<span style="color: #ff0000;">08bbf1d0</span>=1f
0:000> p
eax=08bbf1a0 ebx=0000002a ecx=00000200 edx=00000000 esi=00000200 edi=02fe8661
eip=004b8a82 esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
Houaiss2+0xb8a82:
004b8a82 42              <span style="color: #ff0000;">inc edx
</span>0:000> p
eax=08bbf1a0 ebx=0000002a ecx=00000200 edx=00000001 esi=00000200 edi=02fe8661
eip=004b8a83 esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
Houaiss2+0xb8a83:
004b8a83 49              <span style="color: #ff0000;">dec ecx
</span>0:000> p
eax=08bbf1a0 ebx=0000002a ecx=<span style="color: #ff0000;">000001ff </span>edx=<span style="color: #ff0000;">00000001 </span>esi=00000200 edi=02fe8661
eip=004b8a84 esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
Houaiss2+0xb8a84:
004b8a84 75ef            jne
<span style="color: #ff0000;">Houaiss2+0xb8a75</span> (004b8a75)             [br=1]
0:000> p
eax=08bbf1a0 ebx=0000002a ecx=000001ff edx=00000001 esi=00000200 edi=02fe8661
eip=004b8a75 esp=0012fa94 ebp=0012facc iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
<span style="color: #ff0000;">Houaiss2+0xb8a75</span>:
004b8a75 33db            xor     ebx,ebx
...

Estamos diante de um loop, que, ao analisar o valor de ecx, sabemos que se repete 0x200 vezes, que é exatamente o número de bytes lidos pela função ReadFile. Coincidência? Seria, se não estivesse bem no meio do loop _a referência ao próprio _buffer usado na leitura (08bbf1d0).

Acredito que para todo profissional de engenharia reversa a parte mais emocionante é a descoberta do grande segredo por trás do desafio, o porquê das coisas estarem como estão e o que fazer para desfazer a mágica da segurança: a chave!

Houaiss2+0xb8a7b:
004b8a7b 83c30b          <span style="color: #ff0000;">add ebx,0Bh</span>

Note que essa operação é realizada para cada byte lido do buffer usado na leitura do arquivo. Conseqüentemente, não é difício de imaginar que o valor 0x0B é a chave usada para ofuscar o dicionário em arquivo, subtraindo esse valor de cada byte. Para desfazer a ofuscação, portanto, basta adicionar novamente o mesmo valor, que é exatamente o que faz a instrução _assembly _acima, e o meu singelo código de desofuscação do dicionário Houaiss abaixo:

#define _CRT_SECURE_NO_DEPRECATE
#include <windows.h>
#include <stdio.h>

//#define HOUAISS_PATH "C:\\Projects\\Temp\\HouaissReader\\Houaiss\\"


int WINAPI WinMain(HINSTANCE, HINSTANCE, PSTR cmdLine, int)
{
	CHAR HOUAISS_PATH[MAX_PATH] = { };

	sscanf(cmdLine, "-p \"%[^\"]s", HOUAISS_PATH);

	if( HOUAISS_PATH[0] == 0 )
	{
		MessageBox(NULL, "How to use:\r\nHouCalc.exe -p \"C:\\HouaissPath\\\"", 
			"Houaiss Decipher v. alpha", 0);
		return 0;
	}

	for( int fileIdx = 1; fileIdx < 64; ++fileIdx )
	{
		CHAR path1[MAX_PATH];
		CHAR path2[MAX_PATH];

		sprintf(path1, "%sdeah%03d.dhx", HOUAISS_PATH, fileIdx);
		sprintf(path2, "%sdeah%03d.txt", HOUAISS_PATH, fileIdx);

		HANDLE file1 = CreateFile(path1, GENERIC_READ, FILE_SHARE_READ,
			NULL, OPEN_EXISTING, 0, NULL);

		HANDLE file2 = CreateFile(path2, GENERIC_READ | GENERIC_WRITE, 0,
			NULL, CREATE_ALWAYS, 0, NULL);

		if( file1 != INVALID_HANDLE_VALUE && file2 != INVALID_HANDLE_VALUE )
		{
			DWORD fileSize = GetFileSize(file1, NULL);

			if( SetFilePointer(file2, fileSize, NULL, FILE_BEGIN) )
			{
				SetEndOfFile(file2);

				HANDLE map1 = CreateFileMapping(file1, NULL, PAGE_READONLY, 0, 0, NULL);
				HANDLE map2 = CreateFileMapping(file2, NULL, PAGE_READWRITE, 0, 0, NULL);

				if( map1 && map2 )
				{
					PBYTE view1 = (PBYTE) MapViewOfFile(map1, FILE_MAP_READ, 0, 0, 0);
					PBYTE view2 = (PBYTE) MapViewOfFile(map2, FILE_MAP_WRITE, 0, 0, 0);

					if( view1 && view2 )
					{
						for( DWORD i = 0; i < fileSize; ++i )
						{
							view2[i] = view1[i] + 0x0B;
						}
					}

					if( view1 )
						UnmapViewOfFile(view1);
					if( view2 )
						UnmapViewOfFile(view2);
				}

				if( map1 )
					CloseHandle(map1);
				if( map2 )
					CloseHandle(map2);
			}
		}

		if( file1 )
			CloseHandle(file1);
		if( file2 )
			CloseHandle(file2);
	}

	return 0;
}
 

Nos próximos capítulos

Parte da mágica já foi feita, talvez a mais importante e divertida. Daqui pra lá deixaremos o WinDbg de lado e analisaremos o formato em que o texto do dicionário é armazenado, ignorando sua ofuscação básica, que não é mais um problema. Como o artigo já está extenso o suficiente, vou deixar a continuação dessa empreitada para uma futura publicação.