Ir para conteúdo

Como encontrar a função main?


gzn

Posts Recomendados

Se vocês puderem apresentar as soluções mais triviais de se fazer isso acho que muitos iniciantes agradecerão! rs

Começando da situação mais simples à mais complicada:

Situações:

  1. Fácil B|: Ligado dinamicamente e sem símbolos.
  2. Um pouco difícil :o: Ligado estaticamente e sem símbolos!
  3. Muito difícil :ph34r:: Ligado estaticamente, sem símbolos e com um protector popular no mercado!!

Analisando um ELF na situação 1. parece que é fácil, é só procurar pelo símbolo __libc_start_main, colocar um bp nele e observar o primeiro argumento. Mas aí eu pensei: e no caso 2. em que nós não temos símbolos e a libc está dentro do nosso binário? :ph34r:

E se fosse um PE, quais dicas você daria para as situações acima?

Obs.: a situação número 3 se vocês não quiserem comentar deixem de lado, é um tanto difícil de dar dicas visto que não especifiquei nenhum protetor específico (aliás, nem sei quais existem e muito menos os mais populares! rs). Era só pra vocês comentarem quais seriam as técnicas gerais para se chegar na main (ex., como você começaria a analisar o binário e que tipo de técnicas você usaria como por exemplo assinaturas, algoritmos de comparação de código que procuram similaridades com funções previamente conhecidas, o uso de estatística e probabilidade, inteligência artificial, etc.).

Participe e comente o que você faria em cada situação!

 

Link para o comentário
Compartilhar em outros sites

Você quer dizer com um debugger ou software automatizado?
No debugger eu encontro a main "na mão" mesmo.
O entry point aponta para _start.
O primeiro call dentro de _start é para __libc_start_main.

O primeiro argumento para __libc_start_main é o endereço da main.
Você pode pegar esse endereço sendo passado para ele ou acompanhar a execução até a hora que ele dá call na main.
Para x64 ele passa o primeiro argumento em rdi, e eu vi aqui que __libc_start_main preserva esse valor na pilha em [rsp+24]
Mais para frente ele pega o endereço da main e joga em rax, e depois dá call rax.
Como podemos ver no print abaixo:

5ad7ccdceb763_Capturadetelade2018-04-1819-53-39.png.b1044b7e98b2224d116aa2d62603d4ad.png

 

Mas como eu já disse, o primeiro argumento é o endereço da main. Se você quer pegar o endereço com uma ferramenta automática, eu pegaria daí.
Veja rdi recebendo o endereço da main:

5ad7cd9560a55_Capturadetelade2018-04-1819-58-05.png.3e3ee9f78dd2caf373825301ff40faaa.png

 

A seta verde está bem no entry point, isto é, no início de _start.
Se eu fosse fazer uma ferramenta eu pegaria o entry point e a partir dele buscaria pelo call.
Ao achar o call, os 4 bytes anteriores é o endereço da main.
Se assumirmos que o código de _start nunca muda, então nem precisaríamos fazer uma busca.
É só calcular entry_point + 0x20
Só que claro, o endereço "raw" e não virtual.

5ad7d129c7ea2_Capturadetelade2018-04-1820-12-56.png.0b01fa08d0b31fa24cb944377b439b38.png

Link para o comentário
Compartilhar em outros sites

3 horas atrás, Felipe.Silva disse:

Você quer dizer com um debugger ou software automatizado?
No debugger eu encontro a main "na mão" mesmo.
O entry point aponta para _start.
O primeiro call dentro de _start é para __libc_start_main.

O primeiro argumento para __libc_start_main é o endereço da main.
Você pode pegar esse endereço sendo passado para ele ou acompanhar a execução até a hora que ele dá call na main.
Para x64 ele passa o primeiro argumento em rdi, e eu vi aqui que __libc_start_main preserva esse valor na pilha em [rsp+24]
Mais para frente ele pega o endereço da main e joga em rax, e depois dá call rax.
Como podemos ver no print abaixo:

5ad7ccdceb763_Capturadetelade2018-04-1819-53-39.png.b1044b7e98b2224d116aa2d62603d4ad.png

 

Mas como eu já disse, o primeiro argumento é o endereço da main. Se você quer pegar o endereço com uma ferramenta automática, eu pegaria daí.
Veja rdi recebendo o endereço da main:

5ad7cd9560a55_Capturadetelade2018-04-1819-58-05.png.3e3ee9f78dd2caf373825301ff40faaa.png

 

A seta verde está bem no entry point, isto é, no início de _start.
Se eu fosse fazer uma ferramenta eu pegaria o entry point e a partir dele buscaria pelo call.
Ao achar o call, os 4 bytes anteriores é o endereço da main.
Se assumirmos que o código de _start nunca muda, então nem precisaríamos fazer uma busca.
É só calcular entry_point + 0x20
Só que claro, o endereço "raw" e não virtual.

5ad7d129c7ea2_Capturadetelade2018-04-1820-12-56.png.0b01fa08d0b31fa24cb944377b439b38.png

Bacana meu amigo! Você comentou sobre a situação 1. (aquela situação que eu até comentei em que simplesmente poderíamos pegar o primeiro argumento de __libc_start_main).

Agora, minha dúvida mesmo é situação nº 2 [em diante] onde você pega um binário estático!

Um pouco difícil :o: Ligado estaticamente e sem símbolos!

Por exemplo: na sua máquina tenta fazer esse procedimento acima que você sugeriu no binário ola_mundo.static que você consegue com o seguinte código: https://github.com/gu10ng1t/re/tree/master/bin

É só clonar o repo, dar cd em bin/ e digitar make.

Muito obrigado pelo post amigo, achei muito bacana o seu passo a passo! :D Tenta tenta fazer esse "desafio" e compartilha com a gente o que você acha que daria para fazer pra achar a main de maneira que você pudesse aplicar essa técnica em outros binários compilados assim estaticamente e sem símbolos. 

Link para o comentário
Compartilhar em outros sites

@Felipe.Silva realmente você está certo! Até para resolver no caso de um binário estático (pelo menos se for fazer o binário da forma tradicional com libc). Parece mesmo que esse deslocamento da entrada+0x20 não varia muito não (pelo menos, até agora nos meus testes não vi nada diferente).

Obs.: essa técnica parece funcionar melhor só em casos em que é usada a instrução mov (ex.: mov rdi, 0xFFF...). Algumas vezes nesse ponto (próximo a entrada+0x20) é usado LEA ao invés de MOV, nesse caso essa técnica precisa ser adaptada.

Link para o comentário
Compartilhar em outros sites

19 horas atrás, gzn disse:

@Felipe.Silva realmente você está certo! Até para resolver no caso de um binário estático (pelo menos se for fazer o binário da forma tradicional com libc). Parece mesmo que esse deslocamento da entrada+0x20 não varia muito não (pelo menos, até agora nos meus testes não vi nada diferente).

Obs.: essa técnica parece funcionar melhor só em casos em que é usada a instrução mov (ex.: mov rdi, 0xFFF...). Algumas vezes nesse ponto (próximo a eip+0x20) é usado LEA ao invés de MOV, nesse caso essa técnica precisa ser adaptada.

Como eu disse:
« Se eu fosse fazer uma ferramenta eu pegaria o entry point e a partir dele buscaria pelo call.
Ao achar o call, os 4 bytes anteriores é o endereço da main »

Falei do +0x20 somente se assumirmos que _start nunca muda. Se é o caso de mudar, então podemos procurar pelo call. (só procurar por FF 15)
De acordo com os manuais da Intel o valor imediato sempre ficará em ultimo(como podemos ver aqui).
Então não importa que instrução seja usada, desde que ela esteja logo antes do call vai funcionar. (e se o call não mudar também)

Eu fiz um script em Python aqui. Testei no programa que você passou e em um Olá mundo.
Nos dois funcionou tranquilo.

5ad907ae78f4a_Capturadetelade2018-04-1918-18-01.png.a0644cd73035e261b8ca5ffe95f13ddb.png

 

Link: https://github.com/Silva97/tools/blob/master/mainsearch.py
Faz uns testes ai para ver o que dá.

EDIT:
Eu fiz um teste aqui com o ls e agora que entendi o que você quis dizer com usar o LEA.
Ele usa um endereço relativo de acordo com o valor de RIP... Muda muita coisa. '-'
Vou tentar adaptar o algoritmo para identificar isso.

Link para o comentário
Compartilhar em outros sites

@gzn. Feito. Agora o script identifica quando usar a instrução LEA. Testei com o ls e funcionou.
Só que com isso nós conseguimos o endereço raw... Endereço na memória não vai rolar.
Mas com o endereço raw já nos dar a possibilidade de meter um breakpoint... E quando abrir no debugger nós restauramos a instrução que foi alterada.

5ad918ae8e2c7_Capturadetelade2018-04-1919-29-03.png.3102832674464a5efdf1f3159b04ca6f.png

Em relação a packers eu estou na mesma que você. Não conheço nenhum :P
Se alguém puder ao menos indicar um packer para que a gente possa fazer uns testes...
Eu nem sei como um packer funciona. kkkkk

Link de novo para o script: https://github.com/Silva97/tools/blob/master/mainsearch.py

Link para o comentário
Compartilhar em outros sites

Muito bacana seu script @Felipe.Silva! Creio que não só eu mas os outros que estão iniciando acharam motivador você escrever esse script e ainda na linguagem python (o que é até bom pra quem ta fazendo o curso Pythonicos!).

Citar

Em relação a packers eu estou na mesma que você. Não conheço nenhum :P
Se alguém puder ao menos indicar um packer para que a gente possa fazer uns testes...
Eu nem sei como um packer funciona. kkkkk

kkk é isso aí amigo, quando você souber de um e fizer uns testes não deixa de comenta ai no fórum. Seus posts são muito bem organizados e você tem uma didática legal (a maneira como você explica passo a passo).

valeu!

Link para o comentário
Compartilhar em outros sites

  • 6 meses depois...
  • 1 mês depois...

Legal o post e a discussão, well done!

 

Só uma dúvida de carater preciosista, @Felipe.Silva. Se bem entendi, quando você diz "endereço raw", você está se referindo ao RVA (relative virtual address), seria isso?

 

Aproveitando a deixa: @gzn, não vejo o porquê de linkar estaticamente e sem símbolos faria diferença na hora de achar a função main. Qual seria o impacto disso? Além disso, quando inclui a condição de ser modificado por um "protetor de mercado", você está interessado de achar a main do binário resultante ou do binário "original"?

 

[]s!

Link para o comentário
Compartilhar em outros sites

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...