Packed Full Of Surprises
Description:
I encrypted a file with a secret flag, but now I can’t seem to figure out how to decrypt it, can you help?
- Challenge File: encrypt, flag.txt.enc
Solutions from: @bbmetr original writeup
Solution:
1. Check debug symbols from encrypt binary file
file encrypt
encrypt: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), statically linked, no section header
From the output, binary is labeled “statically linked” and “no section header” suggests it has been packed or compressed
2. Unpack the binary
upx -d encrypt
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.4 Markus Oberhumer, Laszlo Molnar & John Reiser May 9th 2024
File size Ratio Format Name
-------------------- ------ ----------- -----------
23959 <- 7072 29.52% linux/amd64 encrypt
Unpacked 1 file.
3. Disassemble the binary and view the main function
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
int v5; // [rsp+0h] [rbp-870h] BYREF
unsigned int v6; // [rsp+4h] [rbp-86Ch]
FILE *stream; // [rsp+8h] [rbp-868h]
FILE *s; // [rsp+10h] [rbp-860h]
__int64 v9; // [rsp+18h] [rbp-858h]
__int64 v10[2]; // [rsp+20h] [rbp-850h] BYREF
__int64 v11[4]; // [rsp+30h] [rbp-840h] BYREF
char v12[1024]; // [rsp+50h] [rbp-820h] BYREF
char ptr[1048]; // [rsp+450h] [rbp-420h] BYREF
unsigned __int64 v14; // [rsp+868h] [rbp-8h]
v14 = __readfsqword(0x28u);
stream = fopen("flag.txt", "rb");
s = fopen("flag.txt.enc", "wb");
if ( !stream || !s )
handleErrors("Error opening file");
v11[0] = 0xEFCDAB8967452301LL;
v11[1] = 0xFEDCBA9876543210LL;
v11[2] = 0x8796A5B4C3D2E1F0LL;
v11[3] = 0xF1E2D3C4B5A6978LL;
v10[0] = 0x706050403020100LL;
v10[1] = 0xF0E0D0C0B0A0908LL;
v9 = EVP_CIPHER_CTX_new();
v3 = EVP_aes_256_cfb128();
EVP_EncryptInit_ex(v9, v3, 0LL, v11, v10);
while ( 1 )
{
v6 = fread(v12, 1uLL, 0x400uLL, stream);
if ( (int)v6 <= 0 )
break;
EVP_EncryptUpdate(v9, ptr, &v5, v12, v6);
fwrite(ptr, 1uLL, v5, s);
}
EVP_EncryptFinal_ex(v9, ptr, &v5);
fwrite(ptr, 1uLL, v5, s);
EVP_CIPHER_CTX_free(v9);
fclose(stream);
fclose(s);
return 0;
}
From the disassemble code, it contain functions like EVP_CIPHER_CTX_new()
, EVP_aes_256_cfb128()
, EVP_EncryptInit_ex(v9, v3, 0LL, v11, v10)
, EVP_EncryptUpdate(v9, ptr, &v5, v12, v6);
and EVP_EncryptFinal_ex(v9, ptr, &v5);
. From this source, we know it use OpenSSL library to encrypt the files.
In short:
- EVP_CIPHER_CTX_new() : Creates a cipher context
- EVP_CIPHER_CTX_free() : Clears all information from a cipher context and frees up any allocated memory associate with it, including context itself
- EVP_EncryptInit_ex(): Sets up cipher context for encryption with cipher type from ENGINE implementation
4. Create a decryption script
in c:
#include <stdio.h>
#include <openssl/evp.h>
int main()
{
size_t bytes_read;
int bytes_read_int;
int bytes_encrypted;
unsigned char iv[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
unsigned char key[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f};
unsigned char flag_buf [1024];
unsigned char enc_buf [1048];
EVP_CIPHER *cipher;
EVP_CIPHER_CTX *cipher_context;
FILE *fp_flag = fopen("flag.txt","wb");
FILE *fp_enc = fopen("flag.txt.enc","rb");
cipher_context = EVP_CIPHER_CTX_new();
cipher = EVP_aes_256_cfb128();
EVP_DecryptInit_ex(cipher_context, cipher, (ENGINE *)0x0, key, iv);
while( 1 )
{
bytes_read = fread(enc_buf, 1, 0x400, fp_enc);
bytes_read_int = (int)bytes_read;
if (bytes_read_int < 1) break;
EVP_DecryptUpdate(cipher_context, flag_buf, &bytes_encrypted, enc_buf, bytes_read_int);
fwrite(flag_buf, 1, (size_t)bytes_encrypted, fp_flag);
}
EVP_DecryptFinal_ex(cipher_context, flag_buf, &bytes_encrypted);
fwrite(flag_buf, 1, (size_t)bytes_encrypted, fp_flag);
EVP_CIPHER_CTX_free(cipher_context);
fclose(fp_flag);
fclose(fp_enc);
return 0;
}
or in python:
from Crypto.Cipher import AES
def to_little_endian(hex_str):
# 0x,'LL' delete
cleaned_str = hex_str.replace('0x', '').replace('LL', '')
# padding
if len(cleaned_str) % 2 != 0:
cleaned_str = '0' + cleaned_str
# change_little_endian
little_endian_str = ''.join(reversed([cleaned_str[i:i+2] for i in range(0, len(cleaned_str), 2)]))
return little_endian_str
v11_0 = to_little_endian('0xEFCDAB8967452301LL')
v11_1 = to_little_endian('0xFEDCBA9876543210LL')
v11_2 = to_little_endian('0x8796A5B4C3D2E1F0LL')
v11_3 = to_little_endian('0xF1E2D3C4B5A6978LL')
v10_0 = to_little_endian('0x706050403020100LL')
v10_1 = to_little_endian('0xF0E0D0C0B0A0908LL')
key = bytes.fromhex(v11_0 + v11_1 + v11_2 + v11_3)
iv = bytes.fromhex(v10_0 + v10_1)
print(f"Key: {key.hex()}")
print(f"IV: {iv.hex()}")
read file
with open('flag.txt.enc', 'rb') as f:
encrypted_data = f.read()
print(f"Encrypted data (hex): {encrypted_data.hex()}")
AES-256-CFB128
cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
decrypted_data = cipher.decrypt(encrypted_data)
print("Decrypted Data (UTF-8):", decrypted_data.decode('utf-8'))
Flag: PCTF{UPX_15_2_3A$y_t0_uNp4cK}