Dificultad para comprender la lógica en la bomba binaria desmontada fase 3

8

Tengo el siguiente programa de ensamblaje del laboratorio de bombas binarias. El objetivo es determinar la palabra clave necesaria para ejecutar el binario sin activar la explode_bombfunción. Comenté mi análisis de la asamblea para este programa, pero tengo problemas para reconstruir todo.

Creo que tengo toda la información que necesito, pero aún no puedo ver la lógica subyacente real y, por lo tanto, estoy atascado. Agradecería mucho cualquier ayuda!

El siguiente es el programa desmontado en sí:

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  

El siguiente bloque contiene mi análisis.

  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 

Actualizar:

Al inspeccionar los registros antes de llamar a strings_not_equal, obtengo lo siguiente:

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

y obtengo el siguiente pseudocódigo desmontado usando Hopper:

ingrese la descripción de la imagen aquí

Incluso intenté usar tanto el número encontrado en eax como la cadena vista anteriormente como mi palabra clave, pero ninguno de los dos funcionó.

scy8
fuente
3
Le agradezco que publique una pieza de desmontaje bien comentada y notas detalladas sobre lo que cree que hace el código. Si solo más personas pusieran esta cantidad de esfuerzo al publicar preguntas sobre el laboratorio de bombas ...
fuz
2
rep movslcopia palabras largas de 32 bits de una dirección %esia otra %edi, incrementando ambas en 4 cada vez, un número de veces igual a ecx. Piensa en ello como memcpy(edi, esi, ecx*4). Ver felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq (está movsden notación Intel).
Nate Eldredge
2
Entonces no son 19 bytes sino 19 * 4 = 76 bytes.
Nate Eldredge
@NateEldredge ah, veo que tiene sentido porque la cadena en la ubicación de la memoria 0x80490abtiene una longitud de 77. ¡Y muchas gracias por el enlace!
scy8
@fuz gracias, desafortunadamente no me ha ayudado a armar todo hasta el momento
scy8

Respuestas:

3

La función realiza una copia modificada de una cadena del almacenamiento estático en un búfer mal colocado.


Esto se ve raro. El malloctamaño depende de strlen+1, pero el memcpytamaño es una constante de tiempo de compilación. Aparentemente, su descompilación muestra que la dirección era un literal de cadena, por lo que parece que está bien.

Probablemente, esa optimización perdida ocurrió debido a una string_length()función personalizada que tal vez solo se definió en otra .c(y la bomba se compiló sin la optimización del tiempo de enlace para la alineación de archivos cruzados). Por size_t len = string_length("some string literal");lo tanto, no es una constante de tiempo de compilación y el compilador emitió una llamada en lugar de poder usar la longitud constante conocida de la cadena.

Pero probablemente lo usaron strcpyen la fuente y el compilador lo incluyó como a rep movs. Como aparentemente está copiando de un literal de cadena, la longitud es una constante de tiempo de compilación y puede optimizar esa parte del trabajo que strcpynormalmente tiene que hacer. Normalmente, si ya calculó la longitud, es mejor usarla en memcpylugar de strcpycalcularla nuevamente sobre la marcha, pero en este caso realmente ayudó al compilador a hacer un mejor código para esa parte que si hubieran pasado el valor de retorno string_lengtha memcpy, de nuevo porque string_lengthno se pudo alinear y optimizar.


   <+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)

Comentarios como ese son redundantes; la instrucción en sí misma ya lo dice. Esto está guardando dos registros conservados de llamadas para que la función pueda usarlos internamente y restaurarlos más tarde.

Tu comentario sobre el subes mejor; Sí, crecer la pila es el significado semántico de nivel superior aquí Esta función reserva algo de espacio para los locales (y para que los argumentos de función se almacenen en movlugar de pushed).


Las rep movsdcopias 0x13 * 4 bytes, incrementando ESI y EDI para apuntar más allá del final de la región copiada. Entonces, otra movsdinstrucción copiaría otros 4 bytes contiguos a la copia anterior.

El código en realidad copia otros 2, pero en lugar de usar movsw, usa una movzwcarga de palabras y una movtienda. Esto hace un total de 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

En algunas (pero no en todas) las CPU habría sido eficiente usarlas rep movsbo rep movswcon los recuentos apropiados, pero eso no fue lo que eligió el compilador en este caso. movzxtambién conocido como AT&T movzes una buena manera de hacer cargas estrechas sin penalizaciones de registro parcial. Es por eso que los compiladores lo hacen, para que puedan escribir un registro completo a pesar de que solo van a leer los 8 o 16 bits bajos de ese registro con una instrucción de almacenamiento.

Después de esa copia de un literal de cadena en buf, tenemos un byte load / store que copia un carácter con buf. Recordar en este punto EAX todavía está apuntando a buf, el mallocvalor de retorno. Entonces está haciendo una copia modificada de la cadena literal.

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

Tal vez si la fuente no hubiera derrotado la propagación constante, con un nivel de optimización lo suficientemente alto, el compilador podría haber colocado la cadena final en el lugar .rodatadonde podría encontrarla, trivializando esta fase de bomba. :PAGS

Luego almacena punteros como argumentos de pila para comparar cadenas.

<+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>

Cómo "hacer trampa": mirando el resultado del tiempo de ejecución con GDB

Algunos laboratorios de bombas solo le permiten ejecutar la bomba en línea, en un servidor de prueba, que registraría explosiones. No puede ejecutarlo bajo GDB, solo use el desmontaje estático (como objdump -drwC -Mintel). Entonces, el servidor de prueba podría registrar cuántos intentos fallidos tuvo. por ejemplo, como CS 3330 en cs.virginia.edu que encontré con google, donde el crédito completo requiere menos de 20 explosiones.

El uso de GDB para examinar la memoria / registros a mitad de camino a través de una función hace que esto sea mucho más fácil que solo trabajar desde el análisis estático, de hecho trivializando esta función donde la entrada única solo se verifica al final. por ejemplo, solo mira a qué otro argumento se le pasa strings_not_equal. (Especialmente si usa GDB jumpo set $pc = ...comandos para saltear los controles de explosión de bomba).

Establezca un punto de interrupción o un solo paso justo antes de la llamada a strings_not_equal. Úselo p (char*)$eaxpara tratar EAX como a char*y mostrarle la cadena C (terminada en 0) que comienza en esa dirección. En ese punto, EAX tiene la dirección del búfer, como puede ver desde la tienda hasta la pila.

Copie / pegue el resultado de la cadena y listo.

Otras fases con múltiples entradas numéricas generalmente no son tan fáciles de probar con un depurador y requieren al menos algunas matemáticas, pero las fases de listas vinculadas que requieren que tengas una secuencia de números en el orden correcto para el recorrido de la lista también se vuelven triviales si sabe cómo usar un depurador para establecer registros para que las comparaciones tengan éxito a medida que llega a ellos.

Peter Cordes
fuente
Muchas gracias! Finalmente lo entiendo.
mlz7
¡Esto también aclaró mi confusión! ¡Gracias!
scy8
2

rep movslcopia palabras largas de 32 bits de una dirección %esia otra %edi, incrementando ambas en 4 cada vez, un número de veces igual a %ecx. Piensa en ello como memcpy(edi, esi, ecx*4).

Consulte https://felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq (es movsd en notación Intel).

Entonces esto es copiar 19*4=76bytes.

Nate Eldredge
fuente
gracias, también tenía otras cosas por las que estaba un poco confundido (enumeradas en la parte inferior) si pudieran ayudarme a comprenderlas mejor
scy8