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:
- serial[0] tiene que ser divisible por 16.
- serial[1] tiene que ser 0xC3 / 3, es decir, 0x41.
- serial[2] compara al con cl y la suma eax suele terminar en 0x2A, así que probaremos con 0x2A, es decir, «*».
- serial[3] compara al con cl y la suma eax suele terminar en 0x2A, así que probaremos con 0x2D, es decir, «-«.
- serial[4] tiene que ser divisible entre 0x22.
- 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.
Jajaja, la 17 es un tributo a Halls verdad? ;D
A ver que cae este año…
salutes =]
~Una que se aburre en el trabajo
@Aida: xDDD mitico juego, que atrape!
Este año tengo colaboraciones especiales, stay tuned! 😉