Solucion de los niveles del 16 y 17 del Hack-it de la Euskal Encounter 2006

Las soluciones de los dos últimos niveles del Hack-it de este año en la Euskal que supieron pasar los participantes. Dejaremos un poco más de tiempo para dar las soluciones de los dos siguientes niveles, así quizá os picáis un poco y conseguís superarlos vosotros mismos 😉

 

Nivel 16

La verdadera prueba de fuego para los participantes finales del hackit en la Euskal Encounter 2006. De los 4 grupos que llegaron a ella, solamente uno pasó. Se trata de una hoja de cálculo para OpenOffice que requiere una contraseña inicial y luego realiza una serie de cálculos para mostrar la contraseña o no.

Como sabemos, los formatos de OpenOffice no son más que XML comprimido con ZIP:

$ unzip level16-DHFNCIDH.ods
Archive:  level16-DHFNCIDH.ods
 extracting: mimetype
   creating: Configurations2/statusbar/
  inflating: Configurations2/accelerator/current.xml
   creating: Configurations2/floater/
   creating: Configurations2/popupmenu/
   creating: Configurations2/progressbar/
   creating: Configurations2/menubar/
   creating: Configurations2/toolbar/
   creating: Configurations2/images/Bitmaps/
  inflating: content.xml
  inflating: Basic/Standard/secret.xml
  inflating: Basic/Standard/script-lb.xml
  inflating: Basic/script-lc.xml
  inflating: styles.xml
 extracting: meta.xml
  inflating: Thumbnails/thumbnail.png
  inflating: settings.xml
  inflating: META-INF/manifest.xml

Si analizamos el código de la hoja de cálculo (Basic/Standard/secret.xml)…

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="secret" script:language="StarBasic">REM  *****  BASIC  *****

Sub Main
Dim ass as String
ass = "fEGOeYON"
GetFile (InputBox("La clave (cifrada) del próximo nivel es " & ass & ", clave para descifrarla", ""))
end sub

Sub GetFile (sName as String)
Dim dc(1) as Object
Dim ret as boolean
Dim fName as String
Randomize
'print "getFile: " & sName
fName = Cstr(Rnd * 999)
fName = "453363301535"
ret = OpenFileInZip (ThisComponent.URL(), fName)
if ret = True Then GetRest (sName, "fEGOeYON", fName)
End Sub

Sub GetRest(fName as String, ass as String, sName as String)
  Dim sSheetName As String
  Dim oSheet
  Dim oSheets
  Dim a(1,54) as String
  Dim b(54), c(54) as String
  if Len(fName) <> 8 Then
  print fName
   exit sub
  end if
  sSheetName = "EfYUY45VrdFY45e6g"
  oSheets = ThisComponent.getSheets()
  If NOT oSheets.hasByName(sSheetName) Then
    Exit Sub
  End If
  oSheet = oSheets.getByName(sSheetName)
  Dim str as String
  Dim idx as integer
  idx = 1
  For i = 15 To 25
  	For x = 5 To 9
  		if oSheet.getCellByPosition (x, i).getString() = "" then exit for
        If asc(oSheet.getCellByPosition (x, i).getString()) >= 65 AND asc(oSheet.getCellByPosition (x, i).getString()) <= 90 Then
  			a(0, idx) = oSheet.getCellByPosition (x, i).getString()
  			a(1, idx) = idx
	 		str = str & oSheet.getCellByPosition (x, i).getString()
        ElseIf asc(oSheet.getCellByPosition (x, i).getString()) >= 97 AND asc(oSheet.getCellByPosition (x, i).getString()) <= 122 Then
  			a(0, idx) = oSheet.getCellByPosition (x, i).getString()
  			a(1, idx) = idx
	 		str = str & oSheet.getCellByPosition (x, i).getString()
	 	ElseIf asc(oSheet.getCellByPosition (x, i).getString()) = 209 OR asc(oSheet.getCellByPosition (x, i).getString()) = 241 Then
  			a(0, idx) = oSheet.getCellByPosition (x, i).getString()
  			a(1, idx) = idx
	 		str = str & oSheet.getCellByPosition (x, i).getString()
	 	Else
	 		print "Mal: " & oSheet.getCellByPosition (x, i).getString() & " - " & asc(oSheet.getCellByPosition (x, i).getString())
	 		'exit sub
 		end if
  		idx = idx + 1
	next x
  next i
  If Len(str) < 54 Then exit sub
  Dim u as integer
'  For u = 1 to Len (fName)
'  	pass(u) = Cint(InStr(1, str, Mid (fName, u, 1), 0))
'  	b (u) = Cint(InStr(1, str, Mid (ass, u, 1), 0))
'  Next u
'  Print "Alfabeto: " & str
  Dim s,d,k as string
  for o = 1 To Len(fName)
	c (o) = a(0, Cint(InStr(1, str, Mid (ass, o, 1), 0)) - Cint(InStr(1, str, Mid (fName, o, 1), 0)))
	s = s & InStr(1, str, Mid (fName, o, 1), 0)
	d = d & c(o)
	k = k & InStr (1, str, c(o), 1)
  next o
  If sName = s Then
	  print "La clave, que estaba cifrada, para el siguiente nivel es: " & d
  else
  	print "¡Casi lo tienes! o no"
  end if
End Sub

'Function OpenFileInZip (docODT as String, file as String) as Object
Function OpenFileInZip (docODT as String, file as String) as Boolean
  Dim fileExists as boolean
  Dim args(1) as Object
  Dim mInputStream as Object
  On Error Resume next
  oZip=createUnoService("com.sun.star.packages.Package")
  Dim Content() as Variant
  Dim args1(0)
  Dim args2(0)
  args1(0)=docODT
  oZip.initialize(args1())
  fileExists=oZip.HasByHierarchicalName(file)
  if fileExists = True Then
	OpenFileInZip = True
	exit function
    ThePackageStream=oZip.GetByHierarchicalName(file)
    mInputStream = ThePackageStream.GetInputStream()
  else
	OpenFileInZip = False
	exit function
  end if
 args(0) = oZip
 args(1) = mInputStream
 mInputStream.closeInput()
 OpenFileInZip() = args()
End Function

Sub OnL
GetFile (InputBox("Clave secreta", "HackMe"))
end sub

Sub GetStringFromStream ( Stream ) as String
  On Error Resume next
  oData=CreateUnoService("com.sun.star.io.TextInputStream")
  oData.setInputStream(Stream)
  Dim str as String
  Dim aDel(0)
  aDel(0) = false
  str = oData.readString(aDel(), false)
  if str = "" then
  	GetStringFromStream() = null
  else
  	GetStringFromStream() = str
  end if
 Stream.closeInput()
End Sub

</script:module>

Lo primero que vemos ques que se nos va a mostrar un InputBox con el siguiente mensaje: “La clave (cifrada) del proximo nivel es ” & ass & “, clave para descifrarla”. Se nos pide una clave para descrifrar la palabra almacenada en ass, es decir “fEGOeYON”. Volveremos a esta clave para descrifrar “fEGOYON” más adelante.

Lo siguiente que comprueba es que dentro del ZIP que compone la hoja de cálculo, haya un fichero que se llame “453363301535”. Si no es así, no continúa, por lo que habrá que crearlo para que siga.

Finalmente llegamos a la función GetRest() que recibe tres parámetros, fName, el nombre del fichero que acabamos de comentar, ass, el string “fEGOYON” y sName, el string que hemos proporcionado para descifrar “fEGOYON”. Nada más comenzar esa función, se comprueba si existe la hoja llamada “EfYUY45VrdFY45e6g”. Si no existe, sale, por lo que hay que crearla.

Una vez creada esa hoja, la función GetRest() comprueba si en las celdas situadas en filas/columnas desde la 15 a la 25 y de la 5 a la 9 hay algo escrito, si no, sale. También comprueba si lo que está escrito está entre 65 y 90, entre 97 y 122 y luego 209 o 241. Si nos fijamos, es el rango de las letras, incluídas la eñe minúscula y eñe mayúscula. Existen muchas maneras de disponer el alfabeto, aunque la más lógica sería ponerla en órden, desde la A hasta la Z. Con la eñe hay controversia, podemos probar con la eñe donde toca en el alfabeto español o ponerla al final.

En función de ese alfabeto, se rellenan dos matrices que se utilizarán para comprobar la contraseña, sName, introducida inicialmente. También entra en juego el nombre del fichero que era necesario crear dentro del ZIP, “453363301535”. Estos números representan índices que se utilizarán para acceder al alfabeto de antes para descrifrar mediante una resta, la contraseña “fEGOYON”.

El problema está en dividir este número en diferentes índices, porque hay varias posibilidades. Sin embargo, hay algunas pistas que reducen esta tarea mucho, porque el 0 que hay en medio hace que ese número no pueda ir solo ni tampoco con el 1 de su derecha, sino que irá con el 3 de su izquierda, por ejemplo.

Teniendo todo esto en cuenta, después de haber generado el fichero solicitado, la hoja solicitada, rellenado el alfabeto en las celdas solicitadas y entendido el algoritmo, deshaciendo la resta (es decir, sumando), podemos deducir cuál es la contraseña que hay que introducir para que la comprobación sea correcta.

El mayor problema de este nivel es que el alfabeto que se rellena en la hoja indicada no está suficientemente fijado y se permite meter las letras en desorden, lo que hará que otras contraseñas hagan que, para esos alfabetos, el código funcione bien pero la contraseña obtenida no sea la correcta.

Nivel 17

En este nivel, tenemos un juego de GNU/Linux protegido con un serial. Deberemos poder jugar y pasarnos hasta el 7º nivel del juego para que nos aparezca la contraseña del siguiente nivel como puntuación:

$ cat LEEME.txt

Consigue pasar el 7º nivel de este juego y obtendrás
la contraseña para el siguiente nivel en el marcador
de puntuación ;-)

Al analizar el juego, vemos que es un binario para GNU/Linux IA-32, stripped:

$ file xjump-hackit
xjump-hackit: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.0, dynamically linked (uses shared libs), corrupted section header size

Al hacer un strace, vemos que pide un serial por pantalla y que al no escribirlo correctamente, sale:

write(1, "Enter REGISTER code: ", 21Enter REGISTER code: )   = 21
read(0, 123456
"123456\n", 1024)               = 7
write(1, "\n", 1
)                       = 1
write(1, "NOT REGISTERED!\n", 16NOT REGISTERED!
)       = 16
munmap(0xb7f26000, 4096)                = 0
exit_group(-1)                          = ?

Lo siguiente que deberemos hacer es trazar el programa con el gdb, estableciendo un breakpoint es su entry_point:

$ objdump -x xjump-hackit

xjump-hackit:     formato del fichero elf32-i386
xjump-hackit
arquitectura: i386, opciones 0x00000102:
EXEC_P, D_PAGED
dirección de inicio 0x08049310

Como vemos, el entry_point es bastante típico, 0x08049310. Probemos ahora con el gdb…

$ gdb ./xjump-hackit
GNU gdb 6.4.90-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...(no debugging symbols found)
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) break *0x08049310
Breakpoint 1 at 0x8049310
(gdb) run
Starting program: /home/txipi/projects/hackit/EE2006/solucion/xjump-hackit
warning: shared library handler failed to enable breakpoint
(no debugging symbols found)

Breakpoint 1, 0x08049310 in ?? ()
(gdb)

Intentamos un desensamblado con disassemble, pero al no tener una función de referencia, no sabe desensamblar. Podemos ver el desensamblado utilizando objdump o biew, o bien pidiendo un desensamblado en función de dos direcciones de memoria:

(gdb) disassemble 0x08049310 0x08059310
Dump of assembler code from 0x8049310 to 0x8059310:
0x08049310:     xor    %ebp,%ebp
0x08049312:     pop    %esi
0x08049313:     mov    %esp,%ecx
0x08049315:     and    $0xfffffff0,%esp
0x08049318:     push   %eax
0x08049319:     push   %esp
0x0804931a:     push   %edx
0x0804931b:     push   $0x804bac0
0x08049320:     push   $0x804ba50
0x08049325:     push   %ecx
0x08049326:     push   %esi
0x08049327:     push   $0x8049df0
0x0804932c:     call   0x804917c
0x08049331:     hlt
[...]

Dado que el programa tiene muchos visos de haber sido programado en C y compilado con GCC, este código corresponderá a las secciones preparatorias que se ejecutan antes del propio código de usuario. De hecho, si analizamos el código de la dirección que se llama con call, veremos que se trata de los típicos jmp de funciones dinámicas que usan la PLT y la GOT:

(gdb) disassemble  0x804917c  0x805917c
Dump of assembler code from 0x804917c to 0x805917c:
0x0804917c:     jmp    *0x804f1ac
0x08049182:     push   $0x120
0x08049187:     jmp    0x8048f2c
0x0804918c:     jmp    *0x804f1b0
0x08049192:     push   $0x128
0x08049197:     jmp    0x8048f2c
0x0804919c:     jmp    *0x804f1b4
0x080491a2:     push   $0x130
[...]

Por lo tanto, lo más recomendable será ir trazando paso a paso con el gdb hasta llegar al código donde se pida el serial, para ver si se comprueba a continuación. Realmente, quizá sea más fácil poner breakpoints en todas las direcciones que se meten en la pila en el código del principio. Veamos el código comentado:

(gdb) disassemble 0x8049df0 0x804Adf0
Dump of assembler code from 0x8049df0 to 0x804adf0:
0x08049df0:     push   %ebp			; preparar la pila por la llamada a función
0x08049df1:     mov    %esp,%ebp
0x08049df3:     push   %edi
0x08049df4:     push   %esi
0x08049df5:     push   %ebx
0x08049df6:     sub    $0xdc,%esp
0x08049dfc:     and    $0xfffffff0,%esp
0x08049dff:     sub    $0x10,%esp
0x08049e02:     movl   $0x804bcb7,(%esp)
0x08049e09:     call   0x804918c		; llamada a write/printf -> Enter REGISTER code:
0x08049e0e:     mov    $0x805094c,%eax
0x08049e13:     mov    %eax,0x4(%esp)
0x08049e17:     movl   $0x804bccd,(%esp)
0x08049e1e:     call   0x80490ac		; llamada a read/scanf -> 0x805094c == serial[0]
0x08049e23:     testb  $0xf,0x805094c		; es serial[0] divisible por 16?
0x08049e2a:     jne    0x8049e3d		; no? salta a error
0x08049e2c:     movsbl 0x805094d,%eax		; eax = serial[1]
0x08049e33:     lea    (%eax,%eax,2),%eax	; eax = eax * 3
0x08049e36:     cmp    $0xc3,%eax		; eax == 0xc3? -> serial[1] == 0xc3 / 3?
0x08049e3b:     je     0x8049e55		; sí, vamos a 0x8049e55

0x08049e3d:     movl   $0x804bcd0,(%esp)	; rutina de error
0x08049e44:     call   0x804906c		; llamada a write/printf -> UNREGISTERED!
0x08049e49:     movl   $0xffffffff,(%esp)
0x08049e50:     call   0x80491ec		; llamada a exit(-1)

0x08049e55:     movzbl 0x805094e,%ecx		; ecx = serial[2]
0x08049e5c:     movsbw %cl,%dx			; dx = serial[2]
0x08049e60:     lea    (%edx,%edx,2),%eax	; eax = edx * 3
0x08049e63:     shl    $0x4,%eax		; eax = eax * 16
0x08049e66:     add    %edx,%eax		; eax = eax + edx
0x08049e68:     mov    %eax,%edx		; edx = serial[2] * 49
0x08049e6a:     mov    %cl,%al			; al = serial[2]
0x08049e6c:     shr    $0x8,%edx		; edx = edx / 256
0x08049e6f:     sar    $0x7,%al			; al = al / 128
0x08049e72:     sar    $0x2,%dl			; dl = dl / 4
0x08049e75:     sub    %al,%dl			; dl = dl - al
0x08049e77:     mov    %dl,%al			; al = dl
0x08049e79:     shl    $0x2,%al			; al = al * 4
0x08049e7c:     add    %dl,%al			; al = al + dl
0x08049e7e:     shl    $0x2,%al			; al = al * 4
0x08049e81:     add    %dl,%al			; al = al + dl
0x08049e83:     cmp    %al,%cl			; al == serial[2]?
0x08049e85:     jne    0x8049e3d		; no? error
0x08049e87:     movzbl 0x805094f,%ecx		; ecx = serial[3]
0x08049e8e:     mov    $0x67,%al		; al = 0x67
0x08049e90:     imul   %cl
0x08049e92:     mov    %eax,%edx		; edx = serial[3] * 0x67
0x08049e94:     mov    %cl,%al			; al = serial[3]
0x08049e96:     shr    $0x8,%edx		; edx = edx / 256
0x08049e99:     sar    $0x7,%al			; al = al / 128
0x08049e9c:     sar    %dl			; dl = dl / 2
0x08049e9e:     sub    %al,%dl			; dl = dl - al
0x08049ea0:     mov    %dl,%al			; al = dl
0x08049ea2:     shl    $0x2,%al			; al = al * 2
0x08049ea5:     add    %dl,%al			; al = al + dl
0x08049ea7:     cmp    %al,%cl			; al == serial[3]?
0x08049ea9:     jne    0x8049e3d		; no? error
0x08049eab:     movzbl 0x8050950,%ecx		; ecx = serial[4]
0x08049eb2:     movsbw %cl,%dx			; dx = serial[4]
0x08049eb6:     mov    %edx,%eax		; eax = serial[4]
0x08049eb8:     shl    $0x4,%eax		; eax = eax / 16
0x08049ebb:     sub    %edx,%eax		; eax = eax - edx
0x08049ebd:     lea    (%edx,%eax,8),%eax	; eax = edx + eax * 8
0x08049ec0:     mov    %cl,%dl			; dl = serial[4]
0x08049ec2:     shr    $0x8,%eax		; eax = eax / 256
0x08049ec5:     sar    $0x7,%dl			; dl = dl / 128
0x08049ec8:     sar    $0x4,%al			; al = al / 16
0x08049ecb:     sub    %dl,%al			; al = al - dl
0x08049ecd:     mov    %al,%dl			; dl = al
0x08049ecf:     add    %dl,%dl			; dl = dl + dl
0x08049ed1:     shl    $0x5,%al			; al = al * 32
0x08049ed4:     add    %al,%dl			; dl = dl + al
0x08049ed6:     cmp    %dl,%cl			; dl == serial[4]?
0x08049ed8:     jne    0x8049e3d		; no? error
0x08049ede:     mov    0xc(%ebp),%edx
0x08049ee1:     mov    (%edx),%eax
0x08049ee3:     test   %eax,%eax
0x08049ee5:     mov    %eax,0x8050a24
0x08049eea:     je     0x804a841
0x08049ef0:     call   0x80492dc
[...]

Recapitulando y después de probar un poco:

  1. serial[0] tiene que ser divisible por 16.
  2. serial[1] tiene que ser 0xC3 / 3, es decir, 0x41.
  3. serial[2] compara al con cl y la suma eax suele terminar en 0x2A, así que probaremos con 0x2A, es decir, “*”.
  4. serial[3] compara al con cl y la suma eax suele terminar en 0x2A, así que probaremos con 0x2D, es decir, “-“.
  5. serial[4] tiene que ser divisible entre 0x22.
  6. El serial tiene 5 caracteres o, al menos, son los 5 primeros los que se comprueban.

Una vez pensado esto, el serial “0A*-D”, por ejemplo, sirve para conseguir jugar al juego y obtener la contraseña del siguiente nivel.

2 pensamientos en “Solucion de los niveles del 16 y 17 del Hack-it de la Euskal Encounter 2006

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *