Aplicações de engenharia reversa após ofuscação (parte 2)

1. Introdução


Esta publicação tem como objetivo estudar algumas técnicas de engenharia reversa. Todos os materiais são apresentados apenas para fins informativos e não se destinam a ser utilizados para qualquer ganho pessoal.

Leitura recomendada após a primeira parte
Se um cirurgião é ensinado como uma pessoa trabalha e lhe dá um bisturi, isso não significa que ele usará esse conhecimento em detrimento de alguém, e um montador experiente não sonha em escrever um super vírus.
Portanto, nessas lições, você não deve procurar dicas de rachaduras e hacks.

Assunto da pesquisa


Continuamos estudando o código de plug-in da documentação do Visual Studio Atomineer Pro (doravante APD). Vamos nos familiarizar com a ferramenta e seus recursos. Então, suponha que tenhamos uma classe em C ++.

class ClassForReadFile { public: ClassForReadFile(); }; 

configure o APD para que os comentários estejam no estilo Doxygen. Pegamos o cursor na classe e pressione CTRL + SHIFT + D. Temos o seguinte:

 /** The class for read file. */ class ClassForReadFile { public: ClassForReadFile(); }; 

O plugin adicionou uma boa descrição da classe. Está tudo ótimo! Nós seguimos em frente. Suponha que uma classe pertença a uma biblioteca e devemos exportá-la. Adicione uma macro e altere a definição da classe

 #ifdef DLL_EXPORTS #define DATA_READER_DLL_EXPORTS __declspec(dllexport) #else #define DATA_READER_DLL_EXPORTS __declspec(dllimport) #endif class DATA_READER_DLL_EXPORTS ClassForReadFile { public: ClassForReadFile(); }; 

Para C ++ (Windows OS), a situação é padrão. Confira nosso plugin. Pressione CTRL + SHIFT + D e não obtenha o que esperávamos

 /** A data reader DLL exports. */ class DATA_READER_DLL_EXPORTS ClassForReadFile { }; 

o nome da definição DATA_READER_DLL_EXPORTS foi definido como o nome da classe, em vez de ClassForReadFile , e a descrição da classe foi gerada a partir desse nome. Ou seja, no código do plug-in, essa situação, ou seja, a exportação da classe, não é processada ou está sendo processada com um erro. É isso que tentaremos corrigir.

Etapa 1


Vamos procurar pistas. Primeiramente, como a exportação de funções e classes do C / C ++ é uma situação padrão, ainda tentamos "forçar" o plug-in corretamente. No lugar da definição DATA_READER_DLL_EXPORTS , insira a própria instrução __declspec e gere a documentação

 /** The class for read file. */ class __declspec(dllexport) ClassForReadFile { }; 

E eis que eles obtiveram a descrição correta da classe! Assim, concluímos que no APD há algum código que verifica a presença da string "__declspec" na descrição da classe e ignora seu algoritmo adicional para a construção de documentação.

Descompilamos a biblioteca com o ildasm.exe padrão dos Microsoft SDKs. Encontre a linha "__declspec". Pode ser encontrada nos 2 métodos CmdDocComment :: a e CmdDocComment :: b. Classe um. Vamos sujeitá-lo a um estudo mais aprofundado.

Etapa 2


Devo dizer imediatamente que o que estamos procurando está no método CmdDocComment :: a

Aqui é onde __declspec se encontra. Somente as linhas mais interessantes são mostradas.

string a (CmdDocComment.GeneratorInfo A_0)
 List<string> e = A_0.e; //....... List<string> list = A_0.e; int num3 = 0; while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } if (list[num3] == "__declspec") { if (num3 + 1 < list.Count) { num = list[num3 + 1].IndexOf(')'); if (num >= 0) { list[num3 + 1] = list[num3 + 1].Substring(num + 1).Trim(); } } list.RemoveAt(num3); num3--; } num3++; } if (list.Count > 0 && (list[0] == "struct" || list[0] == "union")) { if (list.Count == 1) { //...... 


Concluímos isso com base no fato de que, após verificação
lista [num3] == "__declspec"
O método delete é chamado
list.RemoveAt (num3);
Raciocínio (pensando alto):

  1. O método CmdDocComment :: a tem uma variável local que contém uma matriz de seqüências de caracteres

     List<string> list = A_0.e; 
  2. O primeiro elemento dessa matriz armazena o início da descrição da função, estrutura, classe, etc., ou seja, a palavra-chave "class", "struct", "union"
     list[0] == "struct" 
  3. Cada elemento da matriz contém uma palavra separada. No nosso caso, será {"class", "DATA_READER_DLL_EXPORTS", "ClassForReadFile"}
  4. Há um loop que percorre todos os elementos da matriz "e", procura o elemento "__declspec" e o remove e tudo o que está entre colchetes
  5. Há uma condição adicional para sair do ciclo. Esta é a localização das palavras "onde" ou ":". A palavra oficial "onde" é, para ser sincero, não familiarizada, mas ":" é usada ao herdar classes

Defina um novo algoritmo e o objetivo das alterações:

1. as alterações não devem afetar o restante da funcionalidade

2. excluiremos os elementos da matriz "list" de acordo com o algoritmo
- pule o primeiro elemento;
- se o próximo elemento não for ":" e não "where" e não o final da matriz, exclua.

Escreva o ciclo desejado

 //     ,          num2 while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { // ,      .     if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } //  if (list[num3] == "__declspec"),  if (num3 != 0 && num3 < (list.Count - 1) && list[num3 + 1] != ":" && list[num3 + 1] != "where") { e.RemoveAt(index); --index; } num3++; } 

Resta programá-lo.

Etapa 3


Programe em voz alta. A programação no caso geral é a escrita de códigos-fonte, compilação e vinculação. Mas o ofuscador nos privou dessa oportunidade. Usaremos a ferramenta dnSpy recomendada. Mudaremos os códigos HEX dos comandos CIL diretamente na biblioteca, o que, como se viu, é muito emocionante e informativo! Vamos começar. Abra o dnSpy, carregue a biblioteca.

Encontre nosso método
imagem

selecione while e altere a visualização para IL
Nosso ciclo
imagem

Também darei uma lista, embora seja bastante volumosa

Nosso ciclo
 /* 0x00016710 07 */ IL_018C: ldloc.1 //         1. /* 0x00016711 1119 */ IL_018D: ldloc.s V_25 //          ( ). /* 0x00016713 6FF900000A */ IL_018F: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x00016718 72925E0070 */ IL_0194: ldstr "where" //       ,   ,   . /* 0x0001671D 287000000A */ IL_0199: call bool [mscorlib]System.String::op_Equality(string, string) //  ,      . /* 0x00016722 3AAB000000 */ IL_019E: brtrue IL_024E //    ,   value  true,    null   . /* 0x00016727 07 */ IL_01A3: ldloc.1 //         1. /* 0x00016728 1119 */ IL_01A4: ldloc.s V_25 //          ( ). /* 0x0001672A 6FF900000A */ IL_01A6: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x0001672F 72A31D0070 */ IL_01AB: ldstr ":" //       ,   ,   . /* 0x00016734 287000000A */ IL_01B0: call bool [mscorlib]System.String::op_Equality(string, string) //  ,      . /* 0x00016739 3A94000000 */ IL_01B5: brtrue IL_024E //    ,   value  true,    null   . /* 0x0001673E 07 */ IL_01BA: ldloc.1 //         1. /* 0x0001673F 1119 */ IL_01BB: ldloc.s V_25 //          ( ). /* 0x00016741 6FF900000A */ IL_01BD: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x00016746 03 */ IL_01C2: ldarg.1 //     1   . /* 0x00016747 7B12010004 */ IL_01C3: ldfld string Atomineer.Utils.CmdDocComment/GeneratorInfo::b //      ,       . /* 0x0001674C 287000000A */ IL_01C8: call bool [mscorlib]System.String::op_Equality(string, string) //  ,      . /* 0x00016751 2C07 */ IL_01CD: brfalse.s IL_01D6 //    ,   value  false,    . /* 0x00016753 09 */ IL_01CF: ldloc.3 //         3. /* 0x00016754 16 */ IL_01D0: ldc.i4.0 //    0     int32. /* 0x00016755 2F03 */ IL_01D1: bge.s IL_01D6 //     ( ),        . /* 0x00016757 1119 */ IL_01D3: ldloc.s V_25 //          ( ). /* 0x00016759 0D */ IL_01D5: stloc.3 //                3. /* 0x0001675A 07 */ IL_01D6: ldloc.1 //         1. /* 0x0001675B 1119 */ IL_01D7: ldloc.s V_25 //          ( ). /* 0x0001675D 6FF900000A */ IL_01D9: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x00016762 729E5E0070 */ IL_01DE: ldstr "__declspec" //       ,   ,   . /* 0x00016767 287000000A */ IL_01E3: call bool [mscorlib]System.String::op_Equality(string, string) //  ,      . /* 0x0001676C 2C51 */ IL_01E8: brfalse.s IL_023B //    ,   value  false,    . /* 0x0001676E 1119 */ IL_01EA: ldloc.s V_25 //          ( ). /* 0x00016770 17 */ IL_01EC: ldc.i4.1 //    1     int32. /* 0x00016771 58 */ IL_01ED: add //         . /* 0x00016772 07 */ IL_01EE: ldloc.1 //         1. /* 0x00016773 6FF700000A */ IL_01EF: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<string>::get_Count() //             . /* 0x00016778 2F37 */ IL_01F4: bge.s IL_022D //     ( ),        . /* 0x0001677A 07 */ IL_01F6: ldloc.1 //         1. /* 0x0001677B 1119 */ IL_01F7: ldloc.s V_25 //          ( ). /* 0x0001677D 17 */ IL_01F9: ldc.i4.1 //    1     int32. /* 0x0001677E 58 */ IL_01FA: add //         . /* 0x0001677F 6FF900000A */ IL_01FB: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x00016784 1F29 */ IL_0200: ldc.i4.s 41 //      int8     int32 ( ). /* 0x00016786 6FC800000A */ IL_0202: callvirt instance int32 [mscorlib]System.String::IndexOf(char) //             . /* 0x0001678B 0C */ IL_0207: stloc.2 //                2. /* 0x0001678C 08 */ IL_0208: ldloc.2 //         2. /* 0x0001678D 16 */ IL_0209: ldc.i4.0 //    0     int32. /* 0x0001678E 3221 */ IL_020A: blt.s IL_022D //     ( ),      . /* 0x00016790 07 */ IL_020C: ldloc.1 //         1. /* 0x00016791 1119 */ IL_020D: ldloc.s V_25 //          ( ). /* 0x00016793 17 */ IL_020F: ldc.i4.1 //    1     int32. /* 0x00016794 58 */ IL_0210: add //         . /* 0x00016795 07 */ IL_0211: ldloc.1 //         1. /* 0x00016796 1119 */ IL_0212: ldloc.s V_25 //          ( ). /* 0x00016798 17 */ IL_0214: ldc.i4.1 //    1     int32. /* 0x00016799 58 */ IL_0215: add //         . /* 0x0001679A 6FF900000A */ IL_0216: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32) //             . /* 0x0001679F 08 */ IL_021B: ldloc.2 //         2. /* 0x000167A0 17 */ IL_021C: ldc.i4.1 //    1     int32. /* 0x000167A1 58 */ IL_021D: add //         . /* 0x000167A2 6FCB00000A */ IL_021E: callvirt instance string [mscorlib]System.String::Substring(int32) //             . /* 0x000167A7 6F8600000A */ IL_0223: callvirt instance string [mscorlib]System.String::Trim() //             . /* 0x000167AC 6FFF00000A */ IL_0228: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::set_Item(int32, !0) //             . /* 0x000167B1 07 */ IL_022D: ldloc.1 //         1. /* 0x000167B2 1119 */ IL_022E: ldloc.s V_25 //          ( ). /* 0x000167B4 6F6701000A */ IL_0230: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::RemoveAt(int32) //             . /* 0x000167B9 1119 */ IL_0235: ldloc.s V_25 //          ( ). /* 0x000167BB 17 */ IL_0237: ldc.i4.1 //    1     int32. /* 0x000167BC 59 */ IL_0238: sub //           . /* 0x000167BD 1319 */ IL_0239: stloc.s V_25 //                index ( ). /* 0x000167BF 1119 */ IL_023B: ldloc.s V_25 //          ( ). /* 0x000167C1 17 */ IL_023D: ldc.i4.1 //    1     int32. /* 0x000167C2 58 */ IL_023E: add //         . /* 0x000167C3 1319 */ IL_023F: stloc.s V_25 //                index ( ). /* 0x000167C5 1119 */ IL_0241: ldloc.s V_25 //          ( ). /* 0x000167C7 07 */ IL_0243: ldloc.1 //         1. /* 0x000167C8 6FF700000A */ IL_0244: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<string>::get_Count() //             . /* 0x000167CD 3F3EFFFFFF */ IL_0249: blt IL_018C //    ,     . 


Agora temos uma janela nos comandos CIL, sua representação HEX, deslocamento do arquivo e descrição. Tudo em um só lugar. Muito conveniente (obrigado CrazyAlex25 ).
Vamos prestar atenção ao bloco que menciona "__declspec". Deslocamento de bloco 0x0001675A. Este será o começo de nossas edições. Em seguida, encontre o método RemoveAt. Será útil para nós inalterado. O último byte do bloco é 0x000167BF. Vá para o editor HEX Ctrl + X e escreva 0x00 neste intervalo. Vamos salvar e verificar o que as alterações levaram.
laço vazio
 while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } list.RemoveAt(num3); num3--; num3++; } 


Agora vamos implementar a nova lógica. Primeiro, adicione a condição

  if (num3 != 0 && num3 < list.Count - 1) 

A tabela mostra os novos comandos e sua descrição.
1119ldloc.sCarrega uma variável local com o índice especificado na pilha de computação (formato abreviado).
2C61brfalse.sPassa o controle para a instrução final se o valor for falso, uma referência nula ou zero. Nota : Se num3 == 0, vá para a etapa de aumentar o iterador do loop. O valor 0x64 é o deslocamento do endereço para a instrução 0x000167BF (consulte a listagem)
1119ldloc.sCarrega uma variável local com o índice especificado na pilha de cálculo (formato abreviado)
07ldloc.1Carrega uma variável local com índice 1 na pilha de computação
6FF700000Acallvirtget_Count () - chama um método de objeto com limite tardio e envia o valor de retorno para a pilha de cálculo
17ldc.i4.1Empurra o valor inteiro 1 na pilha de computação como int32
59.subSubtrai um valor de outro e empurra o resultado para a pilha de cálculo.
2F55bge.sTransfere o controle para a instrução final (formato abreviado) se o primeiro valor for maior ou igual ao segundo. Nota : Se num3> list.Count - 1, vá para a etapa de aumentar o iterador do loop. O valor 0x55 é o deslocamento do endereço antes da instrução 0x000167BF

Escrevemos esses bytes começando no deslocamento 0x0001675A. Salve e descompile novamente

Primeira condição
 while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } //     if (num3 != 0 && num3 < list.Count - 1) { list.RemoveAt(num3); num3--; } num3++; } 


Agora, adicionamos a verificação de cadeia "where" e ":". Cito o seguinte código HEX sem comentários adicionais:

 07 11 19 17 58 6F F9 00 00 0A 72 A3 1D 00 70 28 70 00 00 0A 2D 3F 07 11 19 17 58 6F F9 00 00 0A 72 92 5E 00 70 28 70 00 00 0A 2D 29 

descompilar e obter o que você planejou

Novo ciclo
 while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } if (num3 != 0 && num3 < list.Count - 1 && !(list[num3 + 1] == ":") && !(list[num3 + 1] == "where")) { list.RemoveAt(num3); num3--; } num3++; } 


Com essas alterações, o plug-in irá gerar a seguinte documentação do código:

 /** The class for read file. */ class DATA_READER_DLL_EXPORTS ClassForReadFile { }; 

Conclusão


Nesta lição, aprendemos como aplicar nosso conhecimento para corrigir bugs. Obviamente, este exemplo não reflete toda a variedade de erros e seu tratamento, mas isso não é um "crack banal". Corrigimos o bug óbvio sem ter o código fonte e sem reconstruir o aplicativo.

Source: https://habr.com/ru/post/pt445126/


All Articles