Cyp.ress (rev)
You will get sser.pyc when you reverse the title. Now reverse it back for me.
Last updated
You will get sser.pyc when you reverse the title. Now reverse it back for me.
Last updated
We are given a compiled .pyc
file. On running it, it prints What is the flag?>
and waits for our input. After providing our input, the binary takes some time to respond. Finally, it responds with a crossed arms emoji to tell us that our input was wrong.
Seeing as to how this is a .pyc
file, my first instinct was to use tools such as uncompyle6
, pycdc
and https://pylingual.io to get python code from it. Unfortunately, these all did not work since the version of Python used was 3.12, which was too new. Therefore, the best I could do was use pycdas
to give me byte-code disassembly. This was the disassembler output:
import os
import requests
from Crypto.Cipher import AES
import hashlib
# WARNING: Decompyle incomplete
elijah@soyabean:/mnt/c/Users/chiae/Downloads/hkcert24/rev/cypress_5788c6411dc79b08e280746a07306538/pycdc$ ./pycdas ../sser.cpython-312.pyc
sser.cpython-312.pyc (Python 3.12)
[Code]
File Name: sser.py
Object Name: <module>
Qualified Name: <module>
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 6
Flags: 0x00000000
[Names]
'os'
'requests'
'Crypto.Cipher'
'AES'
'hashlib'
'get_nonce'
'input'
'encode'
'flag'
'nonce'
'post'
'hex'
'r'
'bytes'
'fromhex'
'text'
'c0'
'sha256'
'digest'
'key'
'iv'
'new'
'MODE_CFB'
'cipher'
'encrypt'
'c1'
'print'
[Locals+Names]
[Constants]
0
None
(
'AES'
)
[Code]
File Name: sser.py
Object Name: get_nonce
Qualified Name: get_nonce
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 4
Flags: 0x00000003 (CO_OPTIMIZED | CO_NEWLOCALS)
[Names]
'os'
'urandom'
'hashlib'
'sha256'
'digest'
[Locals+Names]
'nonce'
[Constants]
None
16
b'pow/'
3
b'\x00\x00\x00'
[Disassembly]
0 RESUME 0
2 NOP
4 LOAD_GLOBAL 1: NULL + os
14 LOAD_ATTR 2: urandom
34 LOAD_CONST 1: 16
36 CALL 1
44 STORE_FAST 0: nonce
46 LOAD_GLOBAL 5: NULL + hashlib
56 LOAD_ATTR 6: sha256
76 LOAD_CONST 2: b'pow/'
78 LOAD_FAST 0: nonce
80 BINARY_OP 0 (+)
84 CALL 1
92 LOAD_ATTR 9: digest
112 CALL 0
120 LOAD_CONST 0: None
122 LOAD_CONST 3: 3
124 BINARY_SLICE
126 LOAD_CONST 4: b'\x00\x00\x00'
128 COMPARE_OP 40 (==)
132 POP_JUMP_IF_FALSE 2 (to 138)
134 LOAD_FAST 0: nonce
136 RETURN_VALUE
138 JUMP_BACKWARD 68 (to 4)
'What is the flag?> '
'https://c12-cypress.hkcert24.pwnable.hk/'
'nonce'
(
'json'
)
b'key/'
16
b'iv/'
'🙆🙅'
[Disassembly]
0 RESUME 0
2 LOAD_CONST 0: 0
4 LOAD_CONST 1: None
6 IMPORT_NAME 0: os
8 STORE_NAME 0: os
10 LOAD_CONST 0: 0
12 LOAD_CONST 1: None
14 IMPORT_NAME 1: requests
16 STORE_NAME 1: requests
18 LOAD_CONST 0: 0
20 LOAD_CONST 2: ('AES',)
22 IMPORT_NAME 2: Crypto.Cipher
24 IMPORT_FROM 3: AES
26 STORE_NAME 3: AES
28 POP_TOP
30 LOAD_CONST 0: 0
32 LOAD_CONST 1: None
34 IMPORT_NAME 4: hashlib
36 STORE_NAME 4: hashlib
38 LOAD_CONST 4: 'What is the flag?> '
40 CALL_INTRINSIC_1 1 (INTRINSIC_PRINT)
42 POP_TOP
44 LOAD_CONST 3: <CODE> get_nonce
46 MAKE_FUNCTION 0
48 STORE_NAME 5: get_nonce
50 PUSH_NULL
52 LOAD_NAME 6: input
54 BUILD_STRING 0
56 CALL 1
64 LOAD_ATTR 15: encode
84 CALL 0
92 STORE_NAME 8: flag
94 PUSH_NULL
96 LOAD_NAME 5: get_nonce
98 CALL 0
106 STORE_NAME 9: nonce
108 PUSH_NULL
110 LOAD_NAME 1: requests
112 LOAD_ATTR 20: post
132 LOAD_CONST 5: 'https://c12-cypress.hkcert24.pwnable.hk/'
134 LOAD_CONST 6: 'nonce'
136 LOAD_NAME 9: nonce
138 LOAD_ATTR 23: hex
158 CALL 0
166 BUILD_MAP 1
168 KW_NAMES 7: ('json',)
170 CALL 2
178 STORE_NAME 12: r
180 LOAD_NAME 13: bytes
182 LOAD_ATTR 29: fromhex
202 LOAD_NAME 12: r
204 LOAD_ATTR 30: text
224 CALL 1
232 STORE_NAME 16: c0
234 PUSH_NULL
236 LOAD_NAME 4: hashlib
238 LOAD_ATTR 34: sha256
258 LOAD_CONST 8: b'key/'
260 LOAD_NAME 9: nonce
262 BINARY_OP 0 (+)
266 CALL 1
274 LOAD_ATTR 37: digest
294 CALL 0
302 LOAD_CONST 1: None
304 LOAD_CONST 9: 16
306 BINARY_SLICE
308 STORE_NAME 19: key
310 PUSH_NULL
312 LOAD_NAME 4: hashlib
314 LOAD_ATTR 34: sha256
334 LOAD_CONST 10: b'iv/'
336 LOAD_NAME 9: nonce
338 BINARY_OP 0 (+)
342 CALL 1
350 LOAD_ATTR 37: digest
370 CALL 0
378 LOAD_CONST 1: None
380 LOAD_CONST 9: 16
382 BINARY_SLICE
384 STORE_NAME 20: iv
386 PUSH_NULL
388 LOAD_NAME 3: AES
390 LOAD_ATTR 42: new
410 LOAD_NAME 19: key
412 LOAD_NAME 3: AES
414 LOAD_ATTR 44: MODE_CFB
434 LOAD_NAME 20: iv
436 CALL 3
444 STORE_NAME 23: cipher
446 LOAD_NAME 23: cipher
448 LOAD_ATTR 49: encrypt
468 LOAD_NAME 8: flag
470 CALL 1
478 STORE_NAME 25: c1
480 PUSH_NULL
482 LOAD_NAME 26: print
484 LOAD_CONST 11: '🙆🙅'
486 LOAD_NAME 16: c0
488 LOAD_NAME 25: c1
490 COMPARE_OP 55 (!=)
494 BINARY_SUBSCR
498 CALL 1
506 POP_TOP
508 RETURN_CONST 1: None
Providing this to ChatGPT and asking for the Python code, we get:
import os
import requests
from Crypto.Cipher import AES
import hashlib
# Function to generate a nonce meeting specific conditions
def get_nonce():
while True:
# Generate a 16-byte random nonce
nonce = os.urandom(16)
# Calculate SHA-256 hash of the prefix b'pow/' and the nonce
hash_val = hashlib.sha256(b'pow/' + nonce).digest()
# Check if the first three bytes of the hash are b'\x00\x00\x00'
if hash_val[:3] == b'\x00\x00\x00':
return nonce
# Prompt the user for the flag and encode it
flag = input("What is the flag?> ").encode()
# Generate the nonce
nonce = get_nonce()
# Send nonce to server and receive response
url = "https://c12-cypress.hkcert24.pwnable.hk/"
response = requests.post(url, json={"nonce": nonce.hex()})
# Server returns a hexadecimal string in the response text, convert it to bytes
c0 = bytes.fromhex(response.text)
# Derive the AES encryption key and IV from the nonce
key = hashlib.sha256(b'key/' + nonce).digest()[:16]
iv = hashlib.sha256(b'iv/' + nonce).digest()[:16]
# Initialize AES cipher in CFB mode
cipher = AES.new(key, AES.MODE_CFB, iv)
# Encrypt the flag with the cipher
c1 = cipher.encrypt(flag)
# Compare the encrypted flag with the server-provided value and print appropriate emoji
print("🙆" if c0 == c1 else "🙅")
The Python code takes the user's input and converts it into bytes and stores it in the variable flag
. Then, it generates a nonce, stopping only when the first 3 bytes of the SHA256 hash of b'pow/' + nonce
are null bytes. The nonce is then sent to the server, which sends back a ciphertext within response.text
. Using the nonce, a key
and iv
are generated, and used to encrypt the user's input (flag
) using AES CFB mode. The binary then checks if the server-provided ciphertext matches the user's encrypted input.
Once we understand the code, getting the plaintext flag is trivial. We simply need to send some valid nonce to the server and get the correct ciphertext. Using our nonce, we can compute the key
and iv
ourselves, and decrypt the server's ciphertext for the flag. Below is my solve script:
import os
import requests
from Crypto.Cipher import AES
import hashlib
# Function to generate a nonce meeting specific conditions
def get_nonce():
while True:
# Generate a 16-byte random nonce
nonce = os.urandom(16)
# Calculate SHA-256 hash of the prefix b'pow/' and the nonce
hash_val = hashlib.sha256(b'pow/' + nonce).digest()
# Check if the first three bytes of the hash are b'\x00\x00\x00'
if hash_val[:3] == b'\x00\x00\x00':
return nonce
# Generate the nonce
nonce = bytes.fromhex("c5f4b55e8094602125df24bfc40b16c0")
# obtain the ciphertext from the server
ct = bytes.fromhex("852c4b3055dbd46474d2a5ba72927066c895784c91b733fd3add727e292300b1a692ad56def986e394be34f3d564435665c214b30d92157144527cbcddbf27d3e5")
# Derive the AES encryption key and IV from the nonce
key = hashlib.sha256(b'key/' + nonce).digest()[:16]
iv = hashlib.sha256(b'iv/' + nonce).digest()[:16]
# Initialize AES cipher in CFB mode
cipher = AES.new(key, AES.MODE_CFB, iv)
# Decrypt the ciphertext with the key and iv
print(cipher.decrypt(ct)) # hkcert24{y0u_c4n_h00k_func710ns_t0_35c4p3_fr0m_r3v3r5e_3n9e3r1n9}