Eu tenho o seguinte programa de montagem do laboratório de bombas binárias. O objetivo é determinar a palavra-chave necessária para executar o binário sem acionar a explode_bomb
função. Comentei minha análise da montagem deste programa, mas estou tendo problemas para montar tudo.
Acredito que tenho todas as informações necessárias, mas ainda não consigo ver a lógica subjacente e, portanto, estou presa. Eu apreciaria muito qualquer ajuda!
A seguir, o próprio programa desmontado:
0x08048c3c <+0>: push %edi
0x08048c3d <+1>: push %esi
0x08048c3e <+2>: sub $0x14,%esp
0x08048c41 <+5>: movl $0x804a388,(%esp)
0x08048c48 <+12>: call 0x80490ab <string_length>
0x08048c4d <+17>: add $0x1,%eax
0x08048c50 <+20>: mov %eax,(%esp)
0x08048c53 <+23>: call 0x8048800 <malloc@plt>
0x08048c58 <+28>: mov $0x804a388,%esi
0x08048c5d <+33>: mov $0x13,%ecx
0x08048c62 <+38>: mov %eax,%edi
0x08048c64 <+40>: rep movsl %ds:(%esi),%es:(%edi)
0x08048c66 <+42>: movzwl (%esi),%edx
0x08048c69 <+45>: mov %dx,(%edi)
0x08048c6c <+48>: movzbl 0x11(%eax),%edx
0x08048c70 <+52>: mov %dl,0x10(%eax)
0x08048c73 <+55>: mov %eax,0x4(%esp)
0x08048c77 <+59>: mov 0x20(%esp),%eax
0x08048c7b <+63>: mov %eax,(%esp)
0x08048c7e <+66>: call 0x80490ca <strings_not_equal>
0x08048c83 <+71>: test %eax,%eax
0x08048c85 <+73>: je 0x8048c8c <phase_3+80>
0x08048c87 <+75>: call 0x8049363 <explode_bomb>
0x08048c8c <+80>: add $0x14,%esp
0x08048c8f <+83>: pop %esi
0x08048c90 <+84>: pop %edi
0x08048c91 <+85>: ret
O bloco a seguir contém minha análise
5 <phase_3>
6 0x08048c3c <+0>: push %edi // push value in edi to stack
7 0x08048c3d <+1>: push %esi // push value of esi to stack
8 0x08048c3e <+2>: sub $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)
9
10 0x08048c41 <+5>: movl $0x804a388,(%esp) // put 0x804a388 into loc esp points to
11
12 0x08048c48 <+12>: call 0x80490ab <string_length> // check string length, store in eax
13 0x08048c4d <+17>: add $0x1,%eax // increment val in eax by 0x1 (str len + 1)
14 // at this point, eax = str_len + 1 = 77 + 1 = 78
15
16 0x08048c50 <+20>: mov %eax,(%esp) // get val in eax and put in loc on stack
17 //**** at this point, 0x804a388 should have a value of 78? ****
18
19 0x08048c53 <+23>: call 0x8048800 <malloc@plt> // malloc --> base ptr in eax
20
21 0x08048c58 <+28>: mov $0x804a388,%esi // 0x804a388 in esi
22 0x08048c5d <+33>: mov $0x13,%ecx // put 0x13 in ecx (counter register)
23 0x08048c62 <+38>: mov %eax,%edi // put val in eax into edi
24 0x08048c64 <+40>: rep movsl %ds:(%esi),%es:(%edi) // repeat 0x13 (19) times
25 // **** populate malloced memory with first 19 (edit: 76) chars of string at 0x804a388 (this string is 77 characters long)? ****
26
27 0x08048c66 <+42>: movzwl (%esi),%edx // put val in loc esi points to into edx
***** // at this point, edx should contain the string at 0x804a388?
28
29 0x08048c69 <+45>: mov %dx,(%edi) // put val in dx to loc edi points to
***** // not sure what effect this has or what is in edi at this point
30 0x08048c6c <+48>: movzbl 0x11(%eax),%edx // edx = [eax + 0x11]
31 0x08048c70 <+52>: mov %dl,0x10(%eax) // [eax + 0x10] = dl
32 0x08048c73 <+55>: mov %eax,0x4(%esp) // [esp + 0x4] = eax
33 0x08048c77 <+59>: mov 0x20(%esp),%eax // eax = [esp + 0x20]
34 0x08048c7b <+63>: mov %eax,(%esp) // put val in eax into loc esp points to
***** // not sure what effect these movs have
35
36 // edi --> first arg
37 // esi --> second arg
38 // compare value in esi to edi
39 0x08048c7e <+66>: call 0x80490ca <strings_not_equal> // store result in eax
40 0x08048c83 <+71>: test %eax,%eax
41 0x08048c85 <+73>: je 0x8048c8c <phase_3+80>
42 0x08048c87 <+75>: call 0x8049363 <explode_bomb>
43 0x08048c8c <+80>: add $0x14,%esp
44 0x08048c8f <+83>: pop %esi
45 0x08048c90 <+84>: pop %edi
46 0x08048c91 <+85>: ret
Atualizar:
Ao inspecionar os registradores antes que strings_not_equal seja chamado, recebo o seguinte:
eax 0x804d8aa 134535338
ecx 0x0 0
edx 0x76 118
ebx 0xffffd354 -11436
esp 0xffffd280 0xffffd280
ebp 0xffffd2b8 0xffffd2b8
esi 0x804a3d4 134521812
edi 0x804f744 134543172
eip 0x8048c7b 0x8048c7b <phase_3+63>
eflags 0x282 [ SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
e recebo o seguinte pseudocódigo desmontado usando o Hopper:
Eu até tentei usar o número encontrado no eax e a string vista anteriormente como minha palavra-chave, mas nenhum deles funcionou.
rep movsl
copia palavras longas de 32 bits de endereço%esi
para endereço%edi
, incrementando as duas por 4 a cada vez, um número de vezes igual aecx
. Pense nisso comomemcpy(edi, esi, ecx*4)
. Consulte felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq (estámovsd
na notação Intel).0x80490ab
tem um comprimento de 77. E muito obrigado pelo link!Respostas:
A função cria uma cópia modificada de uma string do armazenamento estático, em um buffer de malloced.
Isso parece estranho. O
malloc
tamanho depende destrlen
+1, mas omemcpy
tamanho é uma constante em tempo de compilação? Sua descompilação aparentemente mostra que o endereço era uma string literal, então parece que está bem.Provavelmente, a otimização perdida aconteceu devido a uma
string_length()
função personalizada que talvez fosse definida apenas em outra.c
(e a bomba foi compilada sem otimização do tempo de link para inlining entre arquivos). Portanto,size_t len = string_length("some string literal");
não é uma constante em tempo de compilação e o compilador emitiu uma chamada para ela, em vez de poder usar o comprimento constante conhecido da string.Mas provavelmente eles usaram
strcpy
na fonte e o compilador incorporou isso como arep movs
. Como aparentemente copia de um literal de cadeia de caracteres, o comprimento é uma constante em tempo de compilação e pode otimizar a parte do trabalho questrcpy
normalmente deve ser realizada. Normalmente, se você já calculou o comprimento, é melhor usá-lo emmemcpy
vez destrcpy
calculá-lo novamente rapidamente, mas, nesse caso, ajudou o compilador a criar um código melhor para essa parte do que se tivesse passado o valor de retornostring_length
para um valormemcpy
, novamente porquestring_length
não foi possível incorporar e otimizar.Comentários como esse são redundantes; a própria instrução já diz isso. Isso está salvando dois registros preservados de chamada para que a função possa usá-los internamente e restaurá-los mais tarde.
Seu comentário sobre o
sub
é melhor; sim, aumentar a pilha é o significado semântico de nível superior aqui. Esta função reserva algum espaço para os locais (e para os argumentos da função serem armazenados emmov
vez depush
ed).As
rep movsd
cópias são 0x13 * 4 bytes, incrementando o ESI e o EDI para apontar para o final da região copiada. Portanto, outramovsd
instrução copia outros 4 bytes contíguos à cópia anterior.Na verdade, o código copia outros 2, mas, em vez de usar
movsw
, usa umamovzw
carga de palavras e umamov
loja. Isso totaliza 78 bytes copiados.Em algumas (mas não em todas) CPUs, seria eficiente usar apenas
rep movsb
ourep movsw
com contagens apropriadas, mas não foi isso que o compilador escolheu neste caso.movzx
aka AT&Tmovz
é uma boa maneira de realizar cargas estreitas sem penalidades parciais no registro. É por isso que os compiladores fazem isso, para que eles possam escrever um registro completo, mesmo que apenas leiam os 8 ou 16 bits baixos desse registro com uma instrução de armazenamento.Após essa cópia de uma string literal no buf, temos um byte load / store que copia um caractere
buf
. Lembre-se, neste ponto, o EAX ainda está apontando parabuf
omalloc
valor de retorno. Então, está fazendo uma cópia modificada da string literal.Talvez se a fonte não tivesse derrotado a propagação constante, com um nível de otimização alto o suficiente, o compilador poderia simplesmente colocar a sequência final
.rodata
onde você a encontraria, banalizando essa fase da bomba. : PEm seguida, ele armazena ponteiros como args da pilha para comparação de strings.
Como "trapacear": olhando o resultado do tempo de execução com o GDB
Alguns laboratórios de bombas permitem executar a bomba on-line, em um servidor de teste, que registraria explosões. Você não pode executá-lo no GDB, use apenas desmontagem estática (como
objdump -drwC -Mintel
). Portanto, o servidor de teste pode registrar quantas tentativas com falha você teve. por exemplo, como o CS 3330 em cs.virginia.edu que encontrei no google, onde o crédito total requer menos de 20 explosões.O uso do GDB para examinar a memória / registros no meio de uma função torna isso muito mais fácil do que apenas trabalhar a partir de análises estáticas, na verdade banalizando essa função em que a entrada única é verificada apenas no final. por exemplo, basta olhar para o que outro argumento está sendo passado
strings_not_equal
. (Especialmente se você usar GDBsjump
ouset $pc = ...
comandos para ignorar as verificações de explosão da bomba.)Defina um ponto de interrupção ou etapa única antes da chamada para
strings_not_equal
. Usep (char*)$eax
para tratar EAX como umchar*
e mostrar a string C (terminada em 0) começando nesse endereço. Nesse ponto, o EAX mantém o endereço do buffer, como você pode ver da loja para a pilha.Copie / cole esse resultado da sequência e pronto.
Outras fases com várias entradas numéricas normalmente não são tão fáceis de combinar com um depurador e exigem pelo menos um pouco de matemática, mas as fases de lista vinculada que exigem que você tenha uma sequência de números na ordem correta para a passagem da lista também se tornam triviais se você sabe como usar um depurador para definir registros e fazer com que as comparações sejam bem-sucedidas à medida que você as atinge.
fonte
rep movsl
copia palavras longas de 32 bits de endereço%esi
para endereço%edi
, incrementando as duas por 4 a cada vez, um número de vezes igual a%ecx
. Pense nisso comomemcpy(edi, esi, ecx*4)
.Consulte https://felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq (é movsd na notação Intel).
Então, isso é copiar
19*4=76
bytes.fonte