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_bomb
funció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:
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ó.
rep movsl
copia palabras largas de 32 bits de una dirección%esi
a otra%edi
, incrementando ambas en 4 cada vez, un número de veces igual aecx
. Piensa en ello comomemcpy(edi, esi, ecx*4)
. Ver felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq (estámovsd
en notación Intel).0x80490ab
tiene una longitud de 77. ¡Y muchas gracias por el enlace!Respuestas:
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
malloc
tamaño depende destrlen
+1, pero elmemcpy
tamañ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). Porsize_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
strcpy
en la fuente y el compilador lo incluyó como arep 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 questrcpy
normalmente tiene que hacer. Normalmente, si ya calculó la longitud, es mejor usarla enmemcpy
lugar destrcpy
calcularla 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 retornostring_length
amemcpy
, de nuevo porquestring_length
no se pudo alinear y optimizar.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
sub
es 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 enmov
lugar depush
ed).Las
rep movsd
copias 0x13 * 4 bytes, incrementando ESI y EDI para apuntar más allá del final de la región copiada. Entonces, otramovsd
instrucció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 unamovzw
carga de palabras y unamov
tienda. Esto hace un total de 78 bytes copiados.En algunas (pero no en todas) las CPU habría sido eficiente usarlas
rep movsb
orep movsw
con los recuentos apropiados, pero eso no fue lo que eligió el compilador en este caso.movzx
también conocido como AT&Tmovz
es 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 abuf
, elmalloc
valor de retorno. Entonces está haciendo una copia modificada de la cadena literal.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
.rodata
donde podría encontrarla, trivializando esta fase de bomba. :PAGSLuego almacena punteros como argumentos de pila para comparar cadenas.
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 GDBjump
oset $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
. Úselop (char*)$eax
para tratar EAX como achar*
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.
fuente
rep movsl
copia palabras largas de 32 bits de una dirección%esi
a otra%edi
, incrementando ambas en 4 cada vez, un número de veces igual a%ecx
. Piensa en ello comomemcpy(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=76
bytes.fuente