Hissss
4 minutos de lectura
Se nos proporciona un binario llamado auth
:
$ file auth
auth: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=3507aa01d32c34dc8e8c6462b764adb90a82768d, stripped
Descompilaciónn
Si miramos las strings del binario, encontraremos muchas referencias a Python:
$ strings auth | grep python
b_asyncio.cpython-38-x86_64-linux-gnu.so
b_bisect.cpython-38-x86_64-linux-gnu.so
b_blake2.cpython-38-x86_64-linux-gnu.so
b_bz2.cpython-38-x86_64-linux-gnu.so
b_codecs_cn.cpython-38-x86_64-linux-gnu.so
b_codecs_hk.cpython-38-x86_64-linux-gnu.so
b_codecs_iso2022.cpython-38-x86_64-linux-gnu.so
b_codecs_jp.cpython-38-x86_64-linux-gnu.so
b_codecs_kr.cpython-38-x86_64-linux-gnu.so
b_codecs_tw.cpython-38-x86_64-linux-gnu.so
b_contextvars.cpython-38-x86_64-linux-gnu.so
b_csv.cpython-38-x86_64-linux-gnu.so
b_ctypes.cpython-38-x86_64-linux-gnu.so
b_datetime.cpython-38-x86_64-linux-gnu.so
b_decimal.cpython-38-x86_64-linux-gnu.so
b_hashlib.cpython-38-x86_64-linux-gnu.so
b_heapq.cpython-38-x86_64-linux-gnu.so
b_lzma.cpython-38-x86_64-linux-gnu.so
b_md5.cpython-38-x86_64-linux-gnu.so
b_multibytecodec.cpython-38-x86_64-linux-gnu.so
b_multiprocessing.cpython-38-x86_64-linux-gnu.so
b_opcode.cpython-38-x86_64-linux-gnu.so
b_pickle.cpython-38-x86_64-linux-gnu.so
b_posixshmem.cpython-38-x86_64-linux-gnu.so
b_posixsubprocess.cpython-38-x86_64-linux-gnu.so
b_queue.cpython-38-x86_64-linux-gnu.so
b_random.cpython-38-x86_64-linux-gnu.so
b_sha1.cpython-38-x86_64-linux-gnu.so
b_sha256.cpython-38-x86_64-linux-gnu.so
b_sha3.cpython-38-x86_64-linux-gnu.so
b_sha512.cpython-38-x86_64-linux-gnu.so
b_socket.cpython-38-x86_64-linux-gnu.so
b_ssl.cpython-38-x86_64-linux-gnu.so
b_struct.cpython-38-x86_64-linux-gnu.so
barray.cpython-38-x86_64-linux-gnu.so
bbinascii.cpython-38-x86_64-linux-gnu.so
bgrp.cpython-38-x86_64-linux-gnu.so
blibpython3.8.so.1.0
bmath.cpython-38-x86_64-linux-gnu.so
bmmap.cpython-38-x86_64-linux-gnu.so
bpyexpat.cpython-38-x86_64-linux-gnu.so
breadline.cpython-38-x86_64-linux-gnu.so
bresource.cpython-38-x86_64-linux-gnu.so
bselect.cpython-38-x86_64-linux-gnu.so
btermios.cpython-38-x86_64-linux-gnu.so
bunicodedata.cpython-38-x86_64-linux-gnu.so
bzlib.cpython-38-x86_64-linux-gnu.so
xinclude/python3.8/pyconfig.h
xlib/python3.8/config-3.8-x86_64-linux-gnu/Makefile
&libpython3.8.so.1.0
Por lo tanto, este binario está compilado a partir de Python. Por lo general, la forma de extraer el código de Python es usar pyinstxtractor para obtener bytecode y luego uncompyle6
para obtener el código fuente. Sin embargo, esta vez uncompyle6
no fue exitoso.
Afortunadamente, descubrí que pycdc es otro proyecto para traducir el bytecode de Python en código fuente.
Usaré un contenedor de Docker para evitar instalar muchas cosas:
$ docker run --rm -v "$PWD"/:/htb -it python:3.10 bash
root@5d6855763cad:/# wget -q https://raw.githubusercontent.com/extremecoders-re/pyinstxtractor/master/pyinstxtractor.py
root@5d6855763cad:/# git clone https://github.com/zrax/pycdc
...
root@5d6855763cad:/# cd /pycdc
root@5d6855763cad:/pycdc# apt update && apt install -y cmake
...
root@5d6855763cad:/pycdc# cmake .
...
root@5d6855763cad:/pycdc# make
...
root@5d6855763cad:/pycdc# cd /htb
root@5d6855763cad:/htb# python3 /pyinstxtractor.py auth
[+] Processing auth
[+] Pyinstaller version: 2.1+
[+] Python version: 3.8
[+] Length of package: 7196547 bytes
[+] Found 68 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: auth.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python 3.8 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: auth
You can now use a python decompiler on the pyc files within the extracted directory
root@5d6855763cad:/htb# /pycdc/pycdc auth_extracted/auth.pyc
# Source Generated with Decompyle++
# File: auth.pyc (Python 3.8)
import sys
password = input('Enter password> ')
if len(password) != 12:
print("Sorry! You've entered the wrong password.")
sys.exit(0)
if ord(password[0]) != 48 and password[11] != '!' and ord(password[7]) != ord(password[5]) and 143 - ord(password[0]) != ord(password[4]) and ord(password[1]) ^ ord(password[3]) != 30 and ord(password[2]) * ord(password[3]) != 5610 and password[1] != 'p' and ord(password[6]) - ord(password[8]) != -46 and ord(password[6]) ^ ord(password[7]) != 64 and ord(password[10]) + ord(password[5]) != 166 and ord('n') - ord(password[9]) != 1 or password[10] != str(3):
print('Sorry, the password is incorrect.')
else:
print(f'''Well Done! HTB{{{password}}}''')
Análisis de código fuente
Aquí tenemos el código fuente en Python. Como se puede ver, el programa espera una contraseña, y hay algunas verificaciones aplicadas en los caracteres de contraseña:
$ ./auth
Enter password>
Si alguna de las comprobaciones es incorrecta, el programa saldrá. Por lo tanto, mostremos las condiciones intentemos satisfacerlas todas. Este es el código fuente después de formatearlo un poco:
password = input('Enter password> ')
if len(password) != 12:
print("Sorry! You've entered the wrong password.")
exit(0)
if ord(password[0]) != 48 and \
password[11] != '!' and \
ord(password[7]) != ord(password[5]) and \
143 - ord(password[0]) != ord(password[4]) and \
ord(password[1]) ^ ord(password[3]) != 30 and \
ord(password[2]) * ord(password[3]) != 5610 and \
password[1] != 'p' and \
ord(password[6]) - ord(password[8]) != -46 and \
ord(password[6]) ^ ord(password[7]) != 64 and \
ord(password[10]) + ord(password[5]) != 166 and \
ord('n') - ord(password[9]) != 1 or \
password[10] != str(3):
print('Sorry, the password is incorrect.')
else:
print(f'''Well Done! HTB{{{password}}}''')
Solución
Sabemos que la contraseña contiene 12 caracteres. A primera vista, ya sabemos esto:
ord(password[0]) != 48
password[0] = chr(48)
password[0] = '0'
password[11] != '!'
password[11] = '!'
password[1] != 'p'
password[1] = 'p'
password[10] != str(3)
password[10] = str(3)
password[10] = '3'
La forma típica de resolver este tipo de retos es usar z3
, pero solo eran unas pocas condiciones. Podemos hacerlo a mano:
143 - ord(password[0]) != ord(password[4])
password[4] = chr(143 - ord(password[0])) = chr(143 - 48)
password[4] = '_'
ord(password[1]) ^ ord(password[3]) != 30
password[3] = chr(30 ^ ord(password[1])) = chr(30 ^ ord('p'))
password[3] = 'n'
ord(password[2]) * ord(password[3]) != 5610
password[2] = chr(5610 // ord(password[3])) = chr(5610 // ord('n'))
password[2] = '3'
ord(password[10]) + ord(password[5]) != 166
password[5] = chr(166 - ord(password[10])) = chr(166 - ord('3'))
password[5] = 's'
ord(password[7]) != ord(password[5])
password[7] = password[5]
password[7] = 's'
ord(password[6]) ^ ord(password[7]) != 64
password[6] = chr(64 ^ ord(password[7])) = chr(64 ^ ord('s'))
password[6] = '3'
ord(password[6]) - ord(password[8]) != -46
password[8] = chr(46 + ord(password[6])) = chr(46 + ord('3'))
password[8] = 'a'
ord('n') - ord(password[9]) != 1
password[9] = chr(ord('n') - 1)
password[9] = 'm'
En resumen, tenemos:
password[0] = '0'
password[1] = 'p'
password[2] = '3'
password[3] = 'n'
password[4] = '_'
password[5] = 's'
password[6] = '3'
password[7] = 's'
password[8] = 'a'
password[9] = 'm'
password[10] = '3'
password[11] = '!'
Flag
Y esta es la flag:
$ ./auth
Enter password> 0p3n_s3sam3!
Well Done! HTB{0p3n_s3sam3!}