Hissss
4 minutes to read
We have a binary called 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
Decompilation
If we look at the strings of the binary, we will find a lot of references to 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
Therefore, this binary is compiled from Python. Usually, the way to extract Python code is using pyinstxtractor to obtain bytecode and then uncompyle6
to get the source code. However, this time uncompyle6
was not successful.
Luckily, I found out that pycdc is another project to translate Python bytecode into source code.
I will use a Docker container to avoid installing a lot of stuff:
$ 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}}}''')
Source code analysis
Here we have the Python source code. As can be seen, the program expects a password, and there are some checks applied on the password characters:
$ ./auth
Enter password>
If any of the checks is wrong, the program will exit. Therefore, let’s show the conditions and try to satisfy them all. This is the source code after formatting it a bit:
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}}}''')
Solution
We know that the password contains 12 characters. At first glance, we already know this:
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'
The typical way to solve this kind of challenges is use z3
, but I was lazy and there were a few conditions to meet. We can do it manually:
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'
To sum up, we have:
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
And we have the flag:
$ ./auth
Enter password> 0p3n_s3sam3!
Well Done! HTB{0p3n_s3sam3!}