Plug-in ida_medigate C++ para IDA Pro
[TOC]
A engenharia reversa do código C++ compilado não é divertida. A engenharia reversa estática do código C++ compilado é frustrante. A principal razão que torna isso tão difícil são as funções virtuais. Em contraste com o código C compilado, não há um fluxo de código claro. Muitas vezes pode-se gastar muito tempo tentando entender qual é a próxima função virtual chamada, em vez de apenas ver a função como no código C compilado.
Quando alguém investiga uma função virtual, a quantidade de tempo que ele precisa de esforço para encontrar sua refex não faz sentido.
Depois de muitos projetos C++ RE, desisti e decidi que precisava de uma ferramenta flexível (Python) e estável (que pudesse manter facilmente) para esse tipo de pesquisa. A maior parte deste plugin foi escrita em janeiro de 2018 e recentemente decidi limpar a poeira e adicionar suporte às novas classes IDA (7.2).
Este plugin não pretende funcionar sempre "pronto para uso", mas sim ser mais uma ferramenta para o reversor.
O plugin consiste em duas partes:
Esta primeira parte não depende da segunda parte, portanto é possível utilizar o plugin para fazer engenharia reversa de um binário que não contém RTTI, definindo essas classes manualmente com base na API do plugin.
O que torna o plugin único é o fato de utilizar o mesmo ambiente com o qual o pesquisador já está familiarizado, não adicionando nenhum menu ou objeto novo, e baseado nos blocos de construção conhecidos do IDA (estrutura, união, tipo para membros da estrutura, etc. ) - Isso permite que o plug-in suporte abstração C++ para todas as arquiteturas suportadas pelo IDA .
Nota: O analisador RTTI analisa RTTI x86/x64 g++, mas sua estrutura permite adicionar suporte para mais arquiteturas e compiladores facilmente.
plugins/
) Copie medigate_cpp_plugin
para o diretório plugins
e adicione o caminho do código-fonte ao seu arquivo idapythonrc.py
Copie plugins/ida-referee/referee.py
para o mesmo diretório.
Supondo que o código-fonte binário original seja o seguinte ( examples/a.cpp
):
using namespace std ;
class A {
public:
int x_a;
virtual int f_a ()=0;
};
class B : public A {
public:
int x_b;
int f_a (){x_a = 0 ;}
virtual int f_b (){ this -> f_a ();}
};
class Z {
public:
virtual int f_z1 (){cout << " f_z1 " ;}
virtual int f_z2 (){cout << " f_z2 " ;}
};
class C : public B , public Z {
public:
int f_a (){x_a = 5 ;}
int x_c;
int f_c (){x_c = 0 ;}
virtual int f_z1 (){cout << " f_z3 " ;}
};
int main ()
{
C *c = new C ();
c-> f_a ();
c-> f_b ();
c-> f_z1 ();
c-> f_z2 ();
return 0 ;
}
O binário é removido, mas contém RTTI.
Quando apenas carregamos o binário, a função main
( sub_84D
na versão de 32 bits) se parece com:
Inicie o analisador g++ RTTI e execute-o, usando:
from ida_medigate.rtti_parser import GccRTTIParser
GccRTTIParser.init_parser()
GccRTTIParser.build_all()
Agora atualize a estrutura C (consulte a seção Comentários), converta v0
em C *
e descompile novamente:
Para os casos em que não há RTTI, nossa infraestrutura ainda permite definir manualmente a classe c++. Para o mesmo exemplo (examples/a32_stripped), você pode criar manualmente a estrutura B, selecionar sua tabela virtual e digitar
from ida_medigate import cpp_utils
cpp_utils.make_vtable("B")
make_vtable
também pode obter vtable_ea
e vtable_ea_stop
em vez da área selecionada.
Em seguida, crie a estrutura C e aplique a herança:
cpp_utils.add_baseclass("C", "B")
Agora você pode reconstruir a vtable da classe C selecionando-a e digitando:
cpp_utils.make_vtable("C")
Adicione a estrutura Z, reconstrua sua vtable também e agora é a parte legal:
cpp_utils.add_baseclass("C", "Z", 0x0c, to_update=True)
que aplicam a herança C de Z no deslocamento 0x0c e atualizam a estrutura também (ver comentários).
A última coisa que resta é atualizar a segunda vtable de C, aquela que implementa a interface de Z. Marque esta vtable e digite:
cpp_utils.make_vtable("C", offset_in_class=0x0c)
ida_medigate sabe que esta vtable é a vtable da classe Z e o resultado será:
O resultado final é o mesmo do caso RTTI:
Quando uma nova estrutura vtable é criada (pelo analisador RTTI ou manualmente pelo usuário), cada função que ainda não foi alterada é renomeada, descompilada e definida como seu primeiro argumento this
.
Clique duas vezes no membro de uma estrutura que corresponde a tal função para navegar até a função, para que o usuário possa ler o fluxo do código C++ de maneira conveniente!
Cada mudança de nome ou tipo de uma função ou de seu membro de ponteiro de função correspondente em vtables é conectada e sincronizada entre todos eles. Isso significa, por exemplo, que o usuário pode alterar o tipo de membro vtable através da janela do descompilador, e esse novo tipo (protótipo) também será aplicado na função de destino.
Na linha 15 da imagem anterior, há uma chamada para B::sub_9A8 (B::f_b no código fonte). Este argumento de função é B *
:
Mas, esta função também pode ser chamada por uma instância C
(up-casting). queremos ver a função virtual que sua instância chamaria. Suponha que existam muitas classes derivadas em potencial, portanto, converter this
para C *
nem sempre é possível. Por isso, implementamos uma união para cada classe base que possui filhos que possuem uma tabela virtual diferente. Pode-se optar por mostrar uma tabela virtual derivada diferente das derivadas de B
clicando em alt+y (o atalho para escolher diferentes membros da união):
então, em última análise, podemos "lançar" apenas chamadas específicas para diferentes funções virtuais:
O Santo Graal dos frustrados engenheiros reversos de C++. Mantemos refexs de funções virtuais para os membros da estrutura vtable que as representam!
Combinar isso com ida-referee
nos permite rastrear todas as refexs de chamadas de funções virtuais!
Uma limitação: só podemos rastrear chamadas virtuais que já foram descompiladas. Felizmente, a auto-análise sabe preencher um tipo de argumento entre funções, portanto, com o processo iterativo de lançar mais argumentos-> descompilar todas as funções relevantes -> ler o código novamente e lançar mais argumentos (...) esta habilidade torna-se realmente poderoso!
A forma como marcamos os membros da estrutura como subclasses no IDAPython não é sincronizada imediatamente com o BID. O hack que fazemos é editar a estrutura para que uma sincronização seja acionada. Você também pode usar
utils.refresh_struct(struct_ptr)
que adiciona um campo fictício no final da estrutura e depois o indefini.