Dificuldade em entender a lógica na fase 3 da bomba binária desmontada

8

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_bombfunçã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:

insira a descrição da imagem aqui

Eu até tentei usar o número encontrado no eax e a string vista anteriormente como minha palavra-chave, mas nenhum deles funcionou.

scy8
fonte
3
Agradeço por postar uma peça de desmontagem bem comentada e notas detalhadas sobre o que você acha que o código faz. Se ao menos mais pessoas
enviassem
2
rep movslcopia palavras longas de 32 bits de endereço %esipara endereço %edi, incrementando as duas por 4 a cada vez, um número de vezes igual a ecx. Pense nisso como memcpy(edi, esi, ecx*4). Consulte felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq (está movsdna notação Intel).
Nate Eldredge
2
Portanto, não são 19 bytes, mas sim 19 * 4 = 76 bytes.
Nate Eldredge
@NateEldredge ah, vejo que faz sentido, porque a string no local da memória 0x80490abtem um comprimento de 77. E muito obrigado pelo link!
scy8 7/04
@fuz obrigado, infelizmente isso não me ajudou a juntar tudo até agora
scy8 7/04

Respostas:

3

A função cria uma cópia modificada de uma string do armazenamento estático, em um buffer de malloced.


Isso parece estranho. O malloctamanho depende de strlen+1, mas o memcpytamanho é 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 strcpyna fonte e o compilador incorporou isso como a rep 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 que strcpynormalmente deve ser realizada. Normalmente, se você já calculou o comprimento, é melhor usá-lo em memcpyvez de strcpycalculá-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 retorno string_lengthpara um valor memcpy, novamente porque string_lengthnão foi possível incorporar e otimizar.


   <+0>:     push   %edi // push value in edi to stack
   <+1>:     push   %esi // push value of esi to stack
   <+2>:     sub    $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)

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 em movvez de pushed).


As rep movsdcópias são 0x13 * 4 bytes, incrementando o ESI e o EDI para apontar para o final da região copiada. Portanto, outra movsdinstruçã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 uma movzwcarga de palavras e uma movloja. Isso totaliza 78 bytes copiados.

  ...
      # at this point EAX = malloc return value which I'll call buf
<+28>:    mov    $0x804a388,%esi            # copy src = a string literal in .rodata?
<+33>:    mov    $0x13,%ecx
<+38>:    mov    %eax,%edi                  # copy dst = buf
<+40>:    rep movsl %ds:(%esi),%es:(%edi)   # memcpy 76 bytes and advance ESI, EDI

<+42>:    movzwl (%esi),%edx
<+45>:    mov    %dx,(%edi)        # copy another 2 bytes (not moving ESI or EDI)
 # final effect: 78-byte memcpy

Em algumas (mas não em todas) CPUs, seria eficiente usar apenas rep movsbou rep movswcom contagens apropriadas, mas não foi isso que o compilador escolheu neste caso. movzxaka AT&T movzé 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 para bufo mallocvalor de retorno. Então, está fazendo uma cópia modificada da string literal.

<+48>:    movzbl 0x11(%eax),%edx
<+52>:    mov    %dl,0x10(%eax)             # buf[16] = buf[17]

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 .rodataonde você a encontraria, banalizando essa fase da bomba. : P

Em seguida, ele armazena ponteiros como args da pilha para comparação de strings.

<+55>:    mov    %eax,0x4(%esp)               # 2nd arg slot = EAX = buf
<+59>:    mov    0x20(%esp),%eax              #  function arg = user input?
<+63>:    mov    %eax,(%esp)                  # first arg slot = our incoming stack arg
<+66>:    call   0x80490ca <strings_not_equal>

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 GDBs jumpou set $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. Use p (char*)$eaxpara tratar EAX como um char*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.

Peter Cordes
fonte
Muito obrigado! Eu finalmente entendo.
mlz7 7/04
Isso esclareceu minha confusão também! Obrigado!
scy8
2

rep movslcopia palavras longas de 32 bits de endereço %esipara endereço %edi, incrementando as duas por 4 a cada vez, um número de vezes igual a %ecx. Pense nisso como memcpy(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=76bytes.

Nate Eldredge
fonte
obrigado, eu também tinha algumas outras coisas que eu estava um pouco confuso sobre (listados na parte inferior) se você poderia me ajudar a compreendê-las melhor
scy8