PwnMe CTF 2025
Miscellaneous
Decode Runner
Miscellaneous
PWNME{d4m_y0U_4r3_F4st!_4nd_y0u_kn0w_y0ur_c1ph3rs!}
Welcome to Decode Runner ! You will receive 100 encoded words. Your goal is to decode them and send back the decoded word. You have 3 seconds to respond to each word. Good luck!
Author :
Offpath
Flag format:
PWNME{.........................}
Connect :
nc --ssl [host] 443
by Offpath
So we are given a service that sends us encoded words and we have to decode them and send back the decoded word. We have 3 seconds to respond to each word. After connecting to the service, we are given a hint and the encoded word. The hint is used to determine which decoding function to use.
We can use dcode.fr cipher indetifier to identify the cipher used in the encoded word. After many conversations with ChatGPT, GitHub Copilot, Deepseek, and some code searching to write the decoding functions for each hint, I came up with the following script:
def trithemius_decrypt(ciphertext, initial_shift=3):
decrypted_text = ""
for i, char in enumerate(ciphertext):
shift = initial_shift + i # Incremental shift
decrypted_char = chr(((ord(char) - ord('a') - shift) % 26) + ord('a'))
decrypted_text += decrypted_char
return decrypted_text
def shankar_speech_defect(text):
table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
table2 = "DFGHJKLMNUOPQRSTIVWXYZBACE"
return "".join([table2[table.index(c)] if c in table else c for c in text])
def nato_phonetic_alphabet(text):
table = {
'Alfa': 'A', 'Bravo': 'B', 'Charlie': 'C', 'Delta': 'D', 'Echo': 'E', 'Foxtrot': 'F', 'Golf': 'G', 'Hotel': 'H',
'India': 'I', 'Juliett': 'J', 'Kilo': 'K', 'Lima': 'L', 'Mike': 'M', 'November': 'N', 'Oscar': 'O', 'Papa': 'P',
'Quebec': 'Q', 'Romeo': 'R', 'Sierra': 'S', 'Tango': 'T', 'Uniform': 'U', 'Victor': 'V', 'Whiskey': 'W', 'X-ray': 'X',
'Yankee': 'Y', 'Zulu': 'Z', 'Zero': '0', 'One': '1', 'Two': '2', 'Three': '3', 'Four': '4', 'Five': '5', 'Six': '6',
'Seven': '7', 'Eight': '8', 'Nine': '9', 'Dash': '-', 'Stop': '.'
}
return "".join([table[c] for c in text.split()])
def chuck_norris_code(text):
# Splitting the encoded text into parts
parts = text.split()
binary_string = ""
# Iterate through the encoded chunks in pairs
for i in range(0, len(parts), 2):
prefix = parts[i] # Either "00" (for 0s) or "0" (for 1s)
sequence = parts[i+1] # The sequence of zeroes indicating length
if prefix == "00": # Represents a sequence of 0s
binary_string += "0" * len(sequence)
elif prefix == "0": # Represents a sequence of 1s
binary_string += "1" * len(sequence)
# Convert binary string to ASCII text
decoded_text = "".join(chr(int(binary_string[i:i+7], 2)) for i in range(0, len(binary_string), 7))
return decoded_text
WABUN = {
'A': '--.--', # a
'I': '.-', # i
'U': '..-', # u
'E': '-.---', # e
'O': '.-...', # o
'KA': '.-.-', # ka
'KI': '-.-..', # ki
'KU': '...-', # ku
'KE': '-.--', # ke
'KO': '----', # ko
'SA': '-.-.-', # sa
'SHI': '--.-..', # shi
'SU': '---.-', # su
'SE': '.---.', # se
'SO': '---.', # so
'TA': '-.', # ta
'CHI': '..-.', # chi
'TSU': '.--.', # tsu
'TE': '.-.--', # te
'TO': '..-..', # to
'NA': '.-.', # na
'NI': '-.--.', # ni
'NU': '....', # nu
'NE': '--.-', # ne
'NO': '..--', # no
'HA': '-...', # ha
'HI': '--..-', # hi
'FU': '--..', # fu
'HE': '.', # he
'HO': '-..', # ho
'MA': '-..-', # ma
'MI': '..-.-', # mi
'MU': '-', # mu
'ME': '-...-', # me
'MO': '-..-.', # mo
'YA': '.--', # ya
'YU': '-..--', # yu
'YO': '--', # yo
'RA': '...', # ra
'RI': '--.', # ri
'RU': '-.--.', # ru
'RE': '---', # re
'RO': '.-.-.', # ro
'WA': '-.-', # wa
'WO': '.---', # wo
'N': '.-.-.', # n
',': '.-..-', # Japanese comma
# 'PERIOD': '.-.-.-', # Japanese period
# 'LPAREN': '-.--.', # Left parenthesis
# 'RPAREN': '.-..-.', # Right parenthesis
# 'QUOTE_OPEN': '.-.-.', # Opening quote
# 'QUOTE_CLOSE': '.-.-..', # Closing quote
'1': '.----',
'2': '..---',
'3': '...--',
'4': '....-',
'5': '.....',
'6': '-....',
'7': '--...',
'8': '---..',
'9': '----.',
'0': '-----',
'SPACE': '/', # Word separator
}
# Create reverse mapping for decoding
REVERSE_WABUN = {v: k for k, v in WABUN.items()}
def wabun_code(text):
# decode the wabun code
return "".join([REVERSE_WABUN.get(c, c) for c in text.split()])
# Define the letter and figure shift codes for Baudot (5-bit code)
LETTERS_SHIFT = 0b11111 # 31 - Shift to letters set
FIGURES_SHIFT = 0b11011 # 27 - Shift to figures set
# Original Baudot code (Murray code/ITA1)
# Maps 5-bit codes to characters in the letters set
BAUDOT_LETTERS = {
0b00000: ' ', # NULL/BLANK
0b00001: 'E',
0b00010: 'LF', # Line Feed
0b00011: 'A',
0b00100: ' ', # SPACE
0b00101: 'S',
0b00110: 'I',
0b00111: 'U',
0b01000: 'CR', # Carriage Return
0b01001: 'D',
0b01010: 'R',
0b01011: 'J',
0b01100: 'N',
0b01101: 'F',
0b01110: 'C',
0b01111: 'K',
0b10000: 'T',
0b10001: 'Z',
0b10010: 'L',
0b10011: 'W',
0b10100: 'H',
0b10101: 'Y',
0b10110: 'P',
0b10111: 'Q',
0b11000: 'O',
0b11001: 'B',
0b11010: 'G',
0b11011: 'FIGS', # Figures shift
0b11100: 'M',
0b11101: 'X',
0b11110: 'V',
0b11111: 'LTRS', # Letters shift
}
# Maps 5-bit codes to characters in the figures set
BAUDOT_FIGURES = {
0b00000: ' ', # NULL/BLANK
0b00001: '3',
0b00010: 'LF', # Line Feed
0b00011: '-',
0b00100: ' ', # SPACE
0b00101: "'", # Bell signal in some implementations
0b00110: '8',
0b00111: '7',
0b01000: 'CR', # Carriage Return
0b01001: '$', # WRU (Who are you?) in some implementations
0b01010: '4',
0b01011: "'", # Bell
0b01100: ',',
0b01101: '!',
0b01110: ':',
0b01111: '(',
0b10000: '5',
0b10001: '+',
0b10010: ')',
0b10011: '2',
0b10100: '#', # Pound sign in some implementations
0b10101: '6',
0b10110: '0',
0b10111: '1',
0b11000: '9',
0b11001: '?',
0b11010: '&',
0b11011: 'FIGS', # Figures shift
0b11100: '.',
0b11101: '/',
0b11110: ';',
0b11111: 'LTRS', # Letters shift
}
# Create reverse mappings for encoding
REVERSE_BAUDOT_LETTERS = {v: k for k, v in BAUDOT_LETTERS.items() if v not in ['LTRS', 'FIGS', 'CR', 'LF']}
REVERSE_BAUDOT_FIGURES = {v: k for k, v in BAUDOT_FIGURES.items() if v not in ['LTRS', 'FIGS', 'CR', 'LF']}
# Special control character mappings
CONTROL_CHARS = {
'\n': 0b00010, # LF
'\r': 0b01000, # CR
}
def baudot_code(baudot_codes):
# decode the baudot code
result = []
current_shift = None
for code in baudot_codes:
# Handle shift codes
if code == LETTERS_SHIFT:
current_shift = LETTERS_SHIFT
continue
elif code == FIGURES_SHIFT:
current_shift = FIGURES_SHIFT
continue
# If we haven't determined shift yet, default to letters
if current_shift is None:
current_shift = LETTERS_SHIFT
# Decode based on current shift
if current_shift == LETTERS_SHIFT:
char = BAUDOT_LETTERS.get(code, '?')
else: # current_shift == FIGURES_SHIFT
char = BAUDOT_FIGURES.get(code, '?')
# Skip shift characters in output
if char not in ['LTRS', 'FIGS']:
if char == 'CR':
result.append('\r')
elif char == 'LF':
result.append('\n')
else:
result.append(char)
return ''.join(result)
def baudot_code_from_binary_string(binary_string):
"""Decode a binary string representation of Baudot code to text"""
# Remove any whitespace and split into 5-bit chunks
binary_string = binary_string.replace(' ', '')
codes = []
for i in range(0, len(binary_string), 5):
if i + 5 <= len(binary_string):
code = int(binary_string[i:i+5], 2)
codes.append(code)
return baudot_code(codes)
def leet_speak(text):
table = {
'4': 'a', '8': 'b', 'C': 'C', 'D': 'D', '3': 'e', 'F': 'F', '6': 'g', 'H': 'H',
'1': 'i', 'J': 'J', 'K': 'K', 'L': 'L', 'M': 'M', 'N': 'N', '0': 'o', 'P': 'P',
'Q': 'Q', 'R': 'R', '5': 's', '7': 't', 'U': 'U', 'V': 'V', 'W': 'W', 'X': 'X',
'Y': 'Y', '2': 'z', '|<': 'k'
}
return "".join([table.get(c, c) for c in text])
def latin_gibberish(text):
words = text.split()
decrypted_words = []
# Common Latin suffixes that may be appended
suffixes = ["US", "UM", "A", "IS", "OS", "ES", "AE", "E", "I", "IT"]
for word in words:
# Remove the suffix if present
for suffix in suffixes:
if word.endswith(suffix.lower()):
word = word[:-len(suffix)]
break # Stop after removing the first matching suffix
# Reverse the remaining characters
decrypted_word = word[::-1]
decrypted_words.append(decrypted_word)
return " ".join(decrypted_words)
guitar_chords = {
"x24442": "B",
"x02220": "A",
"xx0232": "D",
"320003": "G",
"022100": "E",
"133211": "F",
"224442": "B",
"032010": "C",
"x35553": "C",
"x57775": "D",
"xx0212": "D",
"x13331": "F",
"355433": "G",
"577655": "A",
"799877": "B",
"x32010": "C",
}
def guitar_chords_notation(text):
return "".join([guitar_chords.get(c, c) for c in text.split()])
table = {
'1': '..',
'2': './',
'3': '/-',
'4': '//',
'5': '-.',
'6': '--',
'7': '/.',
'8': '-/',
'9': '.-'
}
morse_table = {
'.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D', '.': 'E', '..-.': 'F', '--.': 'G', '....': 'H',
'..': 'I', '.---': 'J', '-.-': 'K', '.-..': 'L', '--': 'M', '-.': 'N', '---': 'O', '.--.': 'P',
'--.-': 'Q', '.-.': 'R', '...': 'S', '-': 'T', '..-': 'U', '...-': 'V', '.--': 'W', '-..-': 'X',
'-.--': 'Y', '--..': 'Z', '.----': '1', '..---': '2', '...--': '3', '....-': '4', '.....': '5',
'-....': '6', '--...': '7', '---..': '8', '----.': '9', '-----': '0'
}
def morbit_decrypt(ciphertext , keyword):
morse = ""
for c in ciphertext:
print(c)
if c in table:
morse += table[c]
# split by /
morse = morse.split('/')
plaintext = ""
for m in morse:
if m in morse_table:
plaintext += morse_table[m]
else:
plaintext += ' '
return plaintext
from pwn import *
context.log_level = 'debug'
r = remote('decoderunner-6bc786c74ac9f4f0.deploy.phreaks.fr', 443, ssl=True)
r.recvuntil(b'and send back the decoded word. You have 3 seconds to respond to each word. Good luck!\n')
r.recvline()
r.recvline()
r.recvline()
while True:
is_hint_or_cipher = r.recvline().strip().decode()
is_hint_or_cipher = is_hint_or_cipher.split(': ')
if is_hint_or_cipher[0] == 'cipher':
r.sendline(nato_phonetic_alphabet(is_hint_or_cipher[1]).lower())
continue
hint = is_hint_or_cipher[1]
r.recvuntil(b'cipher: ')
cipher = r.recvline().strip().decode()
known_cipher = {
'4rC': 'arc',
'D4N53r': 'danser',
'xx0232 022100 320003 x02220 320003 022100': 'degage',
'8r0U3773': 'brouette',
'x24442 022100 x02220 xx0232 022100 xx0232': 'beaded',
'00001 10010 00001 10110 10100 00011 01100 10000': 'elephant',
'10111 00111 00011 01010 10000 10001': 'quartz',
'11010 00111 00110 10000 00011 01010 00001': 'guitare',
'x24442 x02220 xx0232 320003 022100 xx0232': 'badged',
'00110 01100 10000 00001 01010 01100 00001 10000': 'internet',
'10011 00011 10010 01010 00111 00101': 'walrus',
'-.-.. -.- ..--': 'kiwano',
'01011 11000 00111 00001 10000': 'gamte',
'|<4N60Ur0U': 'kangourou',
'5293212292': 'danser',
'932932971': 'ananas',
'5781972922': 'navire',
'557121732': 'chien'
}
if cipher in known_cipher:
r.sendline(known_cipher[cipher])
continue
if hint == '1337 ...':
r.sendline(leet_speak(cipher).lower())
elif hint == 'He can\'t imagine finding himself in CTF 150 years later...':
r.sendline(baudot_code_from_binary_string(cipher).lower())
elif hint == 'It looks like Morse code, but ...':
r.sendline(wabun_code(cipher).lower())
elif hint == 'Did you realy see slumdog millionaire ?':
r.sendline(shankar_speech_defect(cipher).lower())
elif hint == 'Born in 1462 in Germany...':
r.sendline(trithemius_decrypt(cipher))
elif hint == 'He can snap his toes, and has already counted to infinity twice ...':
r.sendline(chuck_norris_code(cipher))
elif hint == 'what is this charabia ???':
r.sendline(latin_gibberish(cipher))
elif hint == 'Hendrix would have had it...':
r.sendline(guitar_chords_notation(cipher).lower())
elif hint == 'A code based on pairs of dots and dashes. Think of a mix of Morse code and numbers... (AZERTYUIO)':
r.sendline(morbit_decrypt(cipher, 'AZERTYUIO').lower())
I want to use dcode.fr API but it is not available for public they said? :(.
Mafia at the end 1
Miscellaneous
Blockchain
PWNME{1ls_0nt_t0vt_Sh4ke_dz8a4q6}
You’re an agent, your unit recently intercepted a mob discussion about an event that’s going to take place on August 8, 2024. You already know the location, though. A password for the event was mentioned. Your job is to find it and return it so that an agent can go to the scene and collect evidence.
Note : The contract is deployed on sepolia network
Authors :
wepfen, teazer
Flag format:
PWNME{.........................}
by wepfen and teazer
Another PCAP challenge. This time we have a PCAP file with a lot of traffic. We found the IRC traffic.
Following the TCP stream and we found chat messages.
In the chat, we found the contract address 0xCAB0b02864288a9cFbbd3d004570FEdE2faad8F8
, because the contract is deployed on the sepolia network, we can find the contract on the etherscan.
Mafia at the end 2
Miscellaneous
Blockchain
PWNME{th3_H0us3_41way5_w1n_bu7_sh0uld_be_4fr41d_0f_7h3_ul7im4te_g4m8l3r!}
You’re in the final step before catching them lacking. Prove yourself by winning at the casino and access the VIP room !
But remember, the house always win.
Note : To connect to the casino with Metamask, we recommend you to use another browser and a “trash” MetaMask wallet to avoid some weird behavior.
Author :
Wefpen, Tzer
Flag format:
PWNME{.........................}
Connect :
nc mafia2.phreaks.fr 10020
by Wefpen and Tzer
Given Setup.sol
and Casino.sol
, we can see that the casino contract uses a PRNG to determine the outcome of the game. The PRNG is seeded with the prevrandao
value from the previous block. We can use this information to predict the outcome of the game.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract CasinoPWNME {
bool public isWinner;
uint256 public multiplier = 14130161972673258133;
uint256 public increment = 11367173177704995300;
uint256 public modulus = 4701930664760306055;
uint private state;
constructor (){
state = block.prevrandao % modulus;
}
function checkWin() public view returns (bool) {
return isWinner;
}
function playCasino(uint number) public payable {
require(msg.value >= 0.1 ether, "My brother in christ, it's pay to lose not free to play !");
PRNG();
if (number == state){
isWinner = true;
} else {
isWinner = false;
}
}
function PRNG() private{
state = (multiplier * state + increment) % modulus;
}
}
So we can see the state
is at storage slot 0x4
. We can get the value of the state by calling web3.eth.getStorageAt(casinoAddress, "0x4")
. We can then calculate the next state by using the PRNG formula.
bool public isWinner;
uint256 public multiplier = 14130161972673258133;
uint256 public increment = 11367173177704995300;
uint256 public modulus = 4701930664760306055;
uint private state;
Here is the code to predict the outcome of the game:
const stateStorage = await web3.eth.getStorageAt(casinoAddress, "0x4");
console.log("State:", BigInt(stateStorage).toString());
const multiplier = BigInt("14130161972673258133");
const increment = BigInt("11367173177704995300");
const modulus = BigInt("4701930664760306055");
let state = BigInt(stateStorage) % modulus;
state = (multiplier * state + increment) % modulus;
Note: The code above is write to the casino website source code. and modify playCasino
arguments from 0
to state
value. (The challenge start instance error, so we can’t connect to the casino)
Reverse Engineering
Back to the past
Reverse Engineering
PWNME{4baf3723f62a15f22e86d57130bc40c3}
Using the provided binary and the encrypted file, find a way to retrieve the flag contained in “flag.enc”. Note that the binary would have been run in May 2024. Note: The flag is in the format PWNME{…}
Author :
Fayred
Flag format:
PWNME{.........................}
by Fayred
The binary uses the current time as a seed for the random number generator. The random number generator is used to generate a key to xor the content of the file flag.enc
. The key can be recovered by using the same seed as the binary.
int __fastcall main(int argc, const char **argv, const char **envp)
{
char v3; // cl
int v5; // edx
char v6; // cl
const char *v7; // rsi
int v8; // edx
char v9; // cl
int v10; // eax
char v11; // cl
int v13; // [rsp+1Ch] [rbp-124h]
unsigned int v14; // [rsp+20h] [rbp-120h]
__int64 v15; // [rsp+28h] [rbp-118h]
char v16[264]; // [rsp+30h] [rbp-110h] BYREF
unsigned __int64 v17; // [rsp+138h] [rbp-8h]
v17 = __readfsqword(0x28u);
if ( argc > 1 )
{
v14 = time(0LL, argv, envp);
printf((unsigned int)"time : %ld\n", v14, v5, v6);
srand(v14);
v7 = "rb+";
v15 = fopen64(argv[1]);
if ( v15 )
{
while ( 1 )
{
v13 = getc(v15, v7);
if ( v13 == -1 )
break;
fseek(v15, -1LL, 1LL);
v10 = rand();
v7 = (const char *)v15;
fputc(v13 ^ (unsigned int)(v10 % 127), v15);
}
fclose(v15);
strcpy(v16, argv[1]);
strcat(v16, ".enc");
if ( (unsigned int)rename(argv[1], v16) )
{
printf((unsigned int)"Can't rename %s filename to %s.enc", (unsigned int)argv[1], (unsigned int)argv[1], v11);
return 1;
}
else
{
return 0;
}
}
else
{
printf((unsigned int)"Can't open file %s\n", (unsigned int)argv[1], v8, v9);
return 1;
}
}
else
{
printf((unsigned int)"Usage: %s <filename>\n", (unsigned int)*argv, (_DWORD)envp, v3);
return 1;
}
}
By examining the binary, we can see that it encrypts a file using the following process:
- It retrieves the current time (
time(0LL)
) and uses it as a seed for the random number generator (srand(time)
). - It opens the specified file and reads its contents byte by byte.
- Each byte is XOR-ed with a random number generated using
rand() % 127
. - The modified file is saved with the
.enc
extension.
The binary uses a custom implementation of rand()
:
__int64 __fastcall srand(int a1)
{
__int64 result; // rax
result = (unsigned int)(a1 - 1);
seed = result;
return result;
}
unsigned __int64 rand()
{
seed = 0x5851F42D4C957F2DLL * seed + 1;
return (unsigned __int64)seed >> 33;
}
This is a linear congruential generator (LCG). Since the only input to this function is the seed (derived from time).
Since the encryption key is derived from time(0LL)
, we can determine the exact seed by checking the file’s modification time (os.path.getmtime
). This allows us to reproduce the same random sequence and decrypt the file.
We can implement the decryption process as follows:
import os
filename = 'flag.enc'
def srand(seed):
global current_seed
current_seed = seed - 1
def rand():
global current_seed
current_seed = (0x5851F42D4C957F2D * current_seed + 1) & 0xFFFFFFFFFFFFFFFF
return current_seed >> 33
mod_time = int(os.path.getmtime(filename))
srand(mod_time)
f = open(filename, 'rb')
content = f.read()
flag = bytearray()
for byte in content:
flag.append(byte ^ (rand() % 127))
print(f'Flag: {"".join(map(chr, flag))}')
C4 License
Reverse Engineering
PWNME{8d0f21d2a2989b739673732d8155022b}
Using the license of ‘Noa’ and the provided binary, develop a keygen to create a valid license for the 100 requested users.
Author :
Fayred
Flag format:
PWNME{.........................}
Connect :
nc --ssl [Host] 443
by Fayred
They provide a binary and a license for user Noa
. The binary is a QT application that reads a license key from the user and validates it. The license key is a base64 encoded JSON object with the user and the serial key. The serial key is encrypted using the RC4 algorithm with a random key generated from the CRC32 of the user. The encrypted data is then hashed using SHA1 and compared with the expected hash.
We start by analyzing the given license for Noa, which is:
eyJ1c2VyIjogIk5vYSIsICJzZXJpYWwiOiAiZTNiZmJkZjE2MzE0ZWJlZDdiZDJjNjA4YWU1MzA2OTI3MjRjYzNhNSJ9
Decoding it from base64 gives:
{
"user": "Noa",
"serial": "e3bfbdf16314ebed7bd2c608ae530692724cc3a5"
}
Decompiling the binary, we see that this is QT
application. The binary reads the user input to validate the license.
We can see that the binary calls had a class RC4
and a function checker
that validates the license. The checker
function is called when the user clicks the button on_checkKey_clicked
.
In the on_checkKey_clicked
function, the input from the user is decoded from base64 and passed to the checker
function.
The checker
function is responsible for validating the license. The decompiled code of the checker
function is as follows:
__int64 __fastcall checker(__int64 *a1, const QByteArray *a2)
{
__int64 v3; // rdx
__int64 v4; // rsi
unsigned int v5; // eax
int v6; // ebx
volatile signed __int32 *v7; // rdi
signed __int32 v8; // et0
volatile signed __int32 **v9; // r13
unsigned int v10; // edx
_BYTE *v11; // rax
unsigned __int64 v12; // rsi
__int64 v13; // rax
volatile signed __int32 *v14; // rdi
__int64 v15; // r12
signed __int32 v16; // et0
volatile signed __int32 *v17; // rdi
signed __int32 v18; // et0
volatile signed __int32 *v19; // rdi
signed __int32 v20; // et0
volatile signed __int32 *v22; // [rsp+8h] [rbp-160h] BYREF
volatile signed __int32 *v23; // [rsp+10h] [rbp-158h] BYREF
volatile signed __int32 *v24; // [rsp+18h] [rbp-150h] BYREF
unsigned __int8 v25[276]; // [rsp+20h] [rbp-148h] BYREF
unsigned __int8 v26[4]; // [rsp+134h] [rbp-34h] BYREF
unsigned __int64 v27; // [rsp+138h] [rbp-30h]
v3 = *((unsigned int *)a1 + 2);
v4 = *a1;
v27 = __readfsqword(0x28u);
v5 = crc32(0LL, v4, v3);
srand(v5);
v6 = rand();
*(_DWORD *)v26 = _byteswap_ulong(rand() % 0xFFFF * (v6 % 0xFFFF));
RC4::RC4((RC4 *)v25, v26);
QByteArray::fromHex((QByteArray *)&v24, a2);
RC4::decrypt((QByteArray *)&v22, (RC4 *)v25, (__int64 *)&v24);
v7 = v24;
if ( !*v24 || *v24 != -1 && (v8 = _InterlockedSub(v24, 1u), v7 = v24, !v8) )
QArrayData::deallocate(v7, 1LL, 8LL);
v9 = &v23;
QCryptographicHash::hash(&v23, &v22, 2LL);
QByteArray::toHex((QByteArray *)&v24);
v10 = *((_DWORD *)v24 + 1);
if ( v10 )
{
v11 = (char *)v24 + *((_QWORD *)v24 + 2);
v12 = 0LL;
while ( *v11 )
{
v12 = (unsigned int)(v12 + 1);
++v11;
if ( v10 == (_DWORD)v12 )
{
v12 = v10;
break;
}
}
}
else
{
v12 = 0LL;
}
v13 = QString::fromAscii_helper((QString *)((char *)v24 + *((_QWORD *)v24 + 2)), (const char *)v12, v10);
v14 = v24;
v15 = v13;
if ( !*v24 || *v24 != -1 && (v16 = _InterlockedSub(v24, 1u), v14 = v24, !v16) )
QArrayData::deallocate(v14, 1LL, 8LL);
v17 = v23;
if ( !*v23 || *v23 != -1 && (v18 = _InterlockedSub(v23, 1u), v17 = v23, !v18) )
QArrayData::deallocate(v17, 1LL, 8LL);
LOBYTE(v9) = (unsigned int)QString::compare_helper(
v15 + *(_QWORD *)(v15 + 16),
*(unsigned int *)(v15 + 4),
"b039d6daea04c40874f80459bff40142bd25b995",
0xFFFFFFFFLL,
1LL) == 0;
if ( !*(_DWORD *)v15 || *(_DWORD *)v15 != -1 && !_InterlockedSub((volatile signed __int32 *)v15, 1u) )
QArrayData::deallocate(v15, 2LL, 8LL);
v19 = v22;
if ( !*v22 || *v22 != -1 && (v20 = _InterlockedSub(v22, 1u), v19 = v22, !v20) )
QArrayData::deallocate(v19, 1LL, 8LL);
return (unsigned int)v9;
}
Where arguments are:
a1
is the usera2
is the serial key
The function checker
does the following:
- Calculate the CRC32 of the user
- Seed the random number generator with the CRC32 value
- Generate a random key
- Initialize the RC4 object with the random key
- Decrypt the serial key
- Calculate the SHA1 hash of the decrypted serial key
- Compare the hash with the expected hash
So to generate a valid license for a user, we need to:
- Calculate the CRC32 of the user
- Seed the random number generator with the CRC32 value
- Generate a random key
- Initialize the RC4 object with the random key
- Encrypt the data
- Send the base64 encoded JSON object with the user and the encrypted data
We the create a Python script to automate the process. It performs the necessary transformations and sends valid licenses to the challenge server for 100 different users.
from pwn import *
import ctypes
import binascii
import hashlib
import base64
import json
libc = ctypes.CDLL('libc.so.6')
libc.srand(libc.time(0))
class RC4:
def __init__(self, key: bytes):
# Initialize the array with values 0-255
self.state = [i for i in range(256)]
# Reset position tracking
self.x = 0
self.y = 0
# Key scheduling algorithm
j = 0
for i in range(256):
# In C++ code, a2[v3 & 3] is accessing key with modulo 4
# Assuming key is a bytes object or bytearray
j = (j + self.state[i] + key[i % len(key)]) & 0xFF
# Swap values
self.state[i], self.state[j] = self.state[j], self.state[i]
def get_bit(self):
# Increment x and wrap around to stay within 0-255
self.x = (self.x + 1) % 256
# Get value at state[x]
x_val = self.state[self.x]
# Update y using value at state[x]
self.y = (self.y + x_val) % 256
# Swap values at state[x] and state[y]
self.state[self.x], self.state[self.y] = self.state[self.y], self.state[self.x]
# Return value at state[(state[x] + state[y]) % 256]
return self.state[(self.state[self.x] + self.state[self.y]) % 256]
def decrypt(self, encrypted_data: bytes):
result = bytearray()
for byte in encrypted_data:
# XOR each byte with the next byte from the RC4 keystream
decrypted_byte = byte ^ self.get_bit()
result.append(decrypted_byte)
return bytes(result)
def encrypt(self, data: bytes):
return self.decrypt(data)
def decrypt(key, data):
crc32_value = binascii.crc32(key)
libc.srand(crc32_value)
v6 = libc.rand()
random_key = libc.rand() % 0xFFFF * (v6 % 0xFFFF)
random_key = random_key.to_bytes(4, 'big')
rc4 = RC4(random_key)
return rc4.decrypt(binascii.unhexlify(data))
def encrypt(key, data):
crc32_value = binascii.crc32(bytearray(key))
libc.srand(crc32_value)
v6 = libc.rand()
random_key = libc.rand() % 0xFFFF * (v6 % 0xFFFF)
random_key = random_key.to_bytes(4, 'big')
rc4 = RC4(random_key)
encrypted_data = rc4.encrypt(data)
return encrypted_data.hex()
def checker(key, data):
sha1_hash = hashlib.sha1(decrypt(key, data)).hexdigest()
expected_hash = "b039d6daea04c40874f80459bff40142bd25b995"
return sha1_hash == expected_hash
data = decrypt(b'Noa',b'e3bfbdf16314ebed7bd2c608ae530692724cc3a5')
print(f'Decrypted data: {data}')
print(f'Encrypted data: {encrypt(b"Noa", data)}')
print(f'Checker: {checker(b"Noa", b"e3bfbdf16314ebed7bd2c608ae530692724cc3a5")}')
context.log_level = 'debug'
r = remote('c4license-3a7a8c81137937c1.deploy.phreaks.fr', 443, ssl=True)
current = 0
while True:
r.recvuntil(b'Your license for ')
user = r.recv()
user = user.split(b' ')[0]
json_data = {
'user': user.decode(),
'serial': encrypt(user, data)
}
base64_json = base64.b64encode(json.dumps(json_data).encode()).decode()
r.sendline(base64_json)
current += 1
if current == 100:
break
r.interactive()
Flattened Vyper
Reverse Engineering
PWNME{SuCh_4_M3t4R3veRS3r}
I achieve to obtain this smart contract, but I can’t understand what it does. Can you help me?
The only information that I have are the followings:
- The flag is cut in three parts and each part is emitted once.
- The first part is emitted in raw bytes.
- The second part is emitted in base58 encoding.
- The last part has to be xor-ed with the second part.
Author :
Fabrisme
Flag format:
PWNME{.........................}
by Fabrisme
So we have EVM
bytecode that we need to reverse engineer. The contract is written in Vyper
which is a higher-level language that compiles to EVM
bytecode. The contract is flattened, so we need to decompile it to the original source code.
0x6003361161001843405d6106b4576106ac43405d6106b4565b5f3560e01c346106b043405d6106b4576301002b1f81186106aa43405d6106b457608436106106b043405d6106b45760243560040160408135116106b043405d6106b4578035602082018181606037508060405250507f1b47819435df544ae4e6a35d3c2d0eb2900cab1460ec254d464c1d82d70db60a7fe4b87e6bca20abb51b195ca36f2af2f5438b8a072af4f6f657b9a8304e8d36910133186106b043405d6106b4577f400000000000000000000000000000000000000000000000000000000000000060a0527f100000000000000000000000000000000000000000000000000000000000000060c0527f6d1cd107e7ef14bc558622a86cb621d9f18c50764e98df43777f3b33164b87dd7f42e0da5ae4babe43a564859fc944bb6033a02fb2741ff60444793a962b5ccf627fa2b11742daceaab03c583a6aa15e32d664450c3eb36da7897f6ce121490078597f8d4d1c1fd99b004fccba9d5d04aca86fa66973fa89ea8ece4c6ae08474173fa6181803517f781d48306de91b1cbdc32a7036761292d1c1cf57e84fe74689f7583d7e24c64f7f781d48306de91b1cbdc32a7036761292d1c1cf57e84fe74689f7583d7e24c6ef18517f0512bd13110722311710cf5327ac435a7a97c643656412a9b8a1abcd1a6916c77f459142deccea264542a00403ce80c4b0a4042bb3d4341aad06905269ed6f0b097f4083ffcddded047455b0cb50e92c87eade93edf0b1500804be31f9a4f7061dc2180335181861069f43405d6106b4577f805d92843fa8a2a28a4c797f73b9b4d5a4d8fd4f515d4e8cdfc2c303969081497f4c45d48945056a0c8203311b215240ed84ad80e70629f350834b13c2c0f510d47f0768ff01e42043ef7236fc833ca34516c059f32dce8faaa7c3085cb73a85b9df18037fadaf670881744dc7aa596a2d1eb68dbc21fef929481f4bc6ec75331d53acbd2e7f61ec705ea65e9bbf5b5a26cc5fa4f55ad1074377dce1ffbc52d65a8816af56a47f9c1a44f72090b2084eff4360bf11986150f7b5b16b3d4c0a999ed8953cfd668a010360e05260207f5a589468edfec83b6b027d7dc5bb900ed2921f1620aae0da1aa294cc3ef1dd717fa5a76b97120137c494fd82823a446ff12d6de0e9df551f25e55d6b33c10e236f01a17f101aa2511b4501cd6f4bedff54bee2f014409dfdf0c34e4a64b6f4f4e72d12a17f6aaf1061dc80372e146e902c25be0914f9997523a728f840c73791d7d1c5554e7fed7a429f015e2d1de96ca3ecadf167436726c87d156b2102064cdc2db8a65eba7f8a0afd773820c30d4395e19e96c0e921f3358da90bb44eb99631bbcc1afc52d27f2933690f7726bee93b2fb03b006b8c4f0cd1d2602dd08d3884e88cb07c9985557ff498467872da19bd835f3a0d95241a369f38625609e70acb1761497dddfe741101010101516060201861069f43405d6106b4577f7d32a137160083987c5b53f4a7153ac04f8d6c93569d179ad4df3963572af76b7f9650daf8bc6e9af151f6c7125f922329197841d23a7933641b5c2b5a836f208d7f0d973dfa900ec9b8498e449d6a71d2eed81d94c326c4acc325db9c6420acc6bd01037fc86afc4c1f60387b16cce4e4b7c47baf30b9fc18389a2563b2afbe90f43b938b7f9de848aa2e1f7fadbc76fdeb1e6d86e23f62c033260728029884a9e1533cf0907f4535f7c4dddcbd2e353778b864f7e5aadeb48786211942f0066b5715f41e13447fcd1941de2602e740c239ec7184cd0608b8cebb856e7243bca25b386695fc5d4f7f11fea6990cda43a993f3c8cb366da2c23305d78b65ad90ea8578bc704a1b53617f81385805084428d13c1994377979238f1c9f574353e344bf9674b56f2380894f031818010160e0527fb861afb70639f08b7f0a674d3ce0216ce6746772b2c753574d99d19c2507759b7f479e5048f9c60f7480f598b2c31fde93198b988d4d38aca8b2662e63daf88a85017f5a589468edfec83b6b027d7dc5bb900ed2921f1620aae0da1aa294cc3ef1dd717fa5a76b97120137c494fd82823a446ff12d6de0e9df551f25e55d6b33c10e236f01a1610a286002604435181861069f43405d6106b4577f6751ff9969e5beee7bb9fd6731aba2e2a213bd96a1f54b0a24d11452a915ab967fa3f4105dff5270ad5b285974bd7f411880965825ce709ecdb52d7d484df31bfd7f493267cb0f8113a9bd75b52869d3344723cd0d18ac091431f0c8c0557d4d69c37f8a7d2dab1437a704175dec5cd4ac755fa35b553d62798afc45e5bd1d30be723e180360e052602060e0a1600160e052602060e06106a843405d6106b4565b5f60e052602060e05bf35b505b5f5ffd5b5f80fd5b43405c8081181856
We can use ethervm.io/decompile to decompile the bytecode to the original source code. And ethervm.io to view the opcode of decompiled code.
So the decompiled code have three LOG1
instructions that emit the flag in three parts. The first part is emitted in raw bytes, the second part is emitted in base58 encoding, and the last part has to be xor-ed with the second part.
First Part
The first part is emitted in raw bytes.
02A9 7F PUSH32 0xadaf670881744dc7aa596a2d1eb68dbc21fef929481f4bc6ec75331d53acbd2e
02CA 7F PUSH32 0x61ec705ea65e9bbf5b5a26cc5fa4f55ad1074377dce1ffbc52d65a8816af56a4
02EB 7F PUSH32 0x9c1a44f72090b2084eff4360bf11986150f7b5b16b3d4c0a999ed8953cfd668a
030C 01 ADD
030D 03 SUB
030E 60 PUSH1 0xe0
0310 52 MSTORE
0311 60 PUSH1 0x20
0313 7F PUSH32 0x5a589468edfec83b6b027d7dc5bb900ed2921f1620aae0da1aa294cc3ef1dd71
0334 7F PUSH32 0xa5a76b97120137c494fd82823a446ff12d6de0e9df551f25e55d6b33c10e236f
0355 01 ADD
0356 A1 LOG1
Here is the more readable version of the first part.
A = ADD(0x9c1a44f72090b2084eff4360bf11986150f7b5b16b3d4c0a999ed8953cfd668a, 0x61ec705ea65e9bbf5b5a26cc5fa4f55ad1074377dce1ffbc52d65a8816af56a4)
A = SUB(A, 0xadaf670881744dc7aa596a2d1eb68dbc21fef929481f4bc6ec75331d53acbd2e)
// Offset and store A at B
B = 0xe0
STORE A at B
// Size
C = 0x20
// Topic
D = ADD(0xa5a76b97120137c494fd82823a446ff12d6de0e9df551f25e55d6b33c10e236f, 0x5a589468edfec83b6b027d7dc5bb900ed2921f1620aae0da1aa294cc3ef1dd71)
LOG1(D, C, B)
Second Part
The second part is emitted in base58 encoding.
0495 7F PUSH32 0xc86afc4c1f60387b16cce4e4b7c47baf30b9fc18389a2563b2afbe90f43b938b
04B6 7F PUSH32 0x9de848aa2e1f7fadbc76fdeb1e6d86e23f62c033260728029884a9e1533cf090
04D7 7F PUSH32 0x4535f7c4dddcbd2e353778b864f7e5aadeb48786211942f0066b5715f41e1344
04F8 7F PUSH32 0xcd1941de2602e740c239ec7184cd0608b8cebb856e7243bca25b386695fc5d4f
0519 7F PUSH32 0x11fea6990cda43a993f3c8cb366da2c23305d78b65ad90ea8578bc704a1b5361
053A 7F PUSH32 0x81385805084428d13c1994377979238f1c9f574353e344bf9674b56f2380894f
055B 03 SUB
055C 18 XOR
055D 18 XOR
055E 01 ADD
055F 01 ADD
0560 60 PUSH1 0xe0
0562 52 MSTORE
0563 7F PUSH32 0xb861afb70639f08b7f0a674d3ce0216ce6746772b2c753574d99d19c2507759b
0584 7F PUSH32 0x479e5048f9c60f7480f598b2c31fde93198b988d4d38aca8b2662e63daf88a85
05A5 01 ADD
05A6 7F PUSH32 0x5a589468edfec83b6b027d7dc5bb900ed2921f1620aae0da1aa294cc3ef1dd71
05C7 7F PUSH32 0xa5a76b97120137c494fd82823a446ff12d6de0e9df551f25e55d6b33c10e236f
05E8 01 ADD
05E9 A1 LOG1
Third Part
The third part has to be xor-ed with the second part.
05FE 7F PUSH32 0x6751ff9969e5beee7bb9fd6731aba2e2a213bd96a1f54b0a24d11452a915ab96
061F 7F PUSH32 0xa3f4105dff5270ad5b285974bd7f411880965825ce709ecdb52d7d484df31bfd
0640 7F PUSH32 0x493267cb0f8113a9bd75b52869d3344723cd0d18ac091431f0c8c0557d4d69c3
0661 7F PUSH32 0x8a7d2dab1437a704175dec5cd4ac755fa35b553d62798afc45e5bd1d30be723e
0682 18 XOR
0683 03 SUB
0684 60 PUSH1 0xe0
0686 52 MSTORE
0687 60 PUSH1 0x20
0689 60 PUSH1 0xe0
068B A1 LOG1
Python Script
Here is the python script to get the flag.
import base58
flag = ''
a = 0x9c1a44f72090b2084eff4360bf11986150f7b5b16b3d4c0a999ed8953cfd668a + 0x61ec705ea65e9bbf5b5a26cc5fa4f55ad1074377dce1ffbc52d65a8816af56a4
a -= 0xadaf670881744dc7aa596a2d1eb68dbc21fef929481f4bc6ec75331d53acbd2e
a %= (2**256) # Stay in 256-bit
print(hex(a))
data = bytes.fromhex(hex(a)[2:])
data = data.replace(b'\x00', b'')
flag += data.decode()
b = 0x81385805084428d13c1994377979238f1c9f574353e344bf9674b56f2380894f - 0x11fea6990cda43a993f3c8cb366da2c23305d78b65ad90ea8578bc704a1b5361
b ^= 0xcd1941de2602e740c239ec7184cd0608b8cebb856e7243bca25b386695fc5d4f
b ^= 0x4535f7c4dddcbd2e353778b864f7e5aadeb48786211942f0066b5715f41e1344
b += 0x9de848aa2e1f7fadbc76fdeb1e6d86e23f62c033260728029884a9e1533cf090
b += 0xc86afc4c1f60387b16cce4e4b7c47baf30b9fc18389a2563b2afbe90f43b938b
b %= (2**256) # Stay in 256-bit
print(hex(b))
data = bytes.fromhex(hex(b)[2:])
data = data.replace(b'\x00', b'')
flag += base58.b58decode(data).decode()
c = 0x8a7d2dab1437a704175dec5cd4ac755fa35b553d62798afc45e5bd1d30be723e ^ 0x493267cb0f8113a9bd75b52869d3344723cd0d18ac091431f0c8c0557d4d69c3
c -= 0xa3f4105dff5270ad5b285974bd7f411880965825ce709ecdb52d7d484df31bfd
c %= (2**256) # Stay in 256-bit
print(hex(c))
c ^= b
c %= (2**256) # Stay in 256-bit
print(hex(c))
data = bytes.fromhex(hex(c)[2:])
data = data.replace(b'\x00', b'')
flag += data.decode()
print(f'Flag: {flag}')
Mimirev
Reverse Engineering
PWNME{R3v3rS1ng_Compil0_C4n_B3_good}
A new and obscure programming language, MimiLang, has surfaced. It runs on a peculiar interpreter, but something about it feels… off. Dive into its inner workings and figure out what’s really going on. Maybe you’ll uncover something unexpected.
Author :
Lxt3h
Flag format:
PWNME{.........................}
by Lxt3h
Given a file mimicompiler
which is a binary file. We can open it in Ghidra and see the decompiled code.
So we can see that the binary is a compiler and interpreter for the language. The binary has some functions like github_com_Lexterl33t_mimicompiler_compiler__ptr_Compiler_Compile
and github_com_Lexterl33t_mimicompiler_vm__ptr_VM_Run
. We found interesting statement parser function github_com_Lexterl33t_mimicompiler_compiler__ptr_Parser_VerifyProofStatement
which is used to verify the proof.
v44[0] = "verifyProof";
v44[1] = 11LL;
v44[2] = v42;
v44[3] = 28LL;
v44[4] = v41;
The statement verifyProof
is used to verify the proof. So in the VM
namespace we can also see how the verifyProof
statement is used. The function github_com_Lexterl33t_mimicompiler_vm__ptr_VM_VerifyProof
is used to verify the proof.
So it will pop the top two values from the stack and compare them. The first value is stored in the v84
(y
) variable and the second value is stored in the v85
(x
) variable.
if ( v85 + v84 == 314159 )
{
v22 = v85 * v84;
v23 = (v85 * v85 + v84 * v84 * v84 - v85 * v84) % 1048573;
if ( v23 == 273262 ) {
// Do some operation and call decryptFlag
}
}
So we need to find the value of x
and y
such that x + y = 314159
and (x * x + y * y * y - x * y) % 1048573 = 273262
. Because this is compiler and interpreter, we can write the solver in the language itself (because we know the syntax of the language from the decompiled code and its support to solve the problem).
init vv = 0;
init vvv = 1;
init vvvv = 0;
while vvv < 314159 {
if vvvv == 273262 {
verifyProof(vvv, vv);
rea vvv = 314159;
break;
} else {
rea vvv = vvv + 1;
rea vv = 314159 - vvv;
rea vvvv = (vvv * vvv + vv * vv * vv - vvv * vv) % 1048573;
}
}
Why there rea vvv = 314159;
? Because the break
statement is not working as expected. So we need to assign the value of vvv
to 314159
to break the loop.
Compile and running the code with following command:
./mimicompiler -f solver.mimi
Super secure network
Reverse Engineering
PWNME{Crypt0_&_B4ndwidth_m4k3s_m3_f33l_UN83474813!!!}
The target is hiding hisself in a small hotel of the town. One of our agents managed to capture the communications from the parking lot… Unfortunately, the target seems to use software on his phone to protect himself, reading the data exchanged with the server is impossible ! You have the captured trace and the recovered software. It’s up to you.
Author :
Prince2lu
Flag format:
PWNME{.........................}
by Prince2lu
The provided software contains an initialization function, init_module
, which appears to set up a secure communication mechanism using Diffie-Hellman key exchange and AES encryption. Below is the relevant portion of the code:
__int64 __fastcall init_module(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6)
{
_QWORD v7[3]; // [rsp+0h] [rbp-58h] BYREF
__int64 v8; // [rsp+18h] [rbp-40h]
__int64 v9; // [rsp+20h] [rbp-38h]
__int64 v10; // [rsp+28h] [rbp-30h] BYREF
__int64 v11; // [rsp+30h] [rbp-28h] BYREF
int v12; // [rsp+3Ch] [rbp-1Ch]
__int64 v13; // [rsp+40h] [rbp-18h]
__int64 v14; // [rsp+48h] [rbp-10h]
unsigned int v15; // [rsp+54h] [rbp-4h]
v15 = 0;
v14 = 0LL;
l1111l11l11l111l = l111lll11l1ll11l(60LL, a2, a3, a4, a5, a6, 0LL, 0LL, 0LL, 0x800000000LL, 0LL, 0LL);
get_random_bytes(&lll1ll1l111l111l, 8LL);
v10 = 2LL;
LODWORD(v9) = 1;
v7[1] = &l1111l11l11l111l;
v7[2] = &v10;
v11 = lll1l11l1l1l1111(2LL, lll1ll1l111l111l, l1111l11l11l111l);// pow
v7[0] = &v11;
LODWORD(v8) = 8;
v15 = crypto_dh_key_len(v7);
if ( v15 )
{
v13 = (int)v15;
v12 = 17301536;
v14 = _kmalloc((int)v15, 17301536LL);
if ( v14 )
{
if ( !(unsigned int)crypto_dh_encode_key(v14, v15, v7) && (int)lll1llll111ll1l1(3232235836LL, 3333LL) >= 0 )
{
qword_B5C0 = (__int64)sub_E0A;
byte_B5D8 = 2;
dword_B5DC = 0;
dword_B5E0 = 0x80000000;
qword_B600 = (__int64)sub_104E;
byte_B618 = 2;
dword_B61C = 4;
dword_B620 = 0x80000000;
if ( !(unsigned int)nf_register_net_hook(&init_net, &qword_B5C0)
&& !(unsigned int)nf_register_net_hook(&init_net, &qword_B600) )
{
llll1l1ll1lll1l1(l1ll111lll11lll1, v14, v15);
}
}
}
}
return 0LL;
}
The function init_module
initializes the module and sets up the network hooks. The Diffie-Hellman key exchange is performed using the crypto_dh_encode_key
function. nf_register_net_hook
registers the network hooks for the functions sub_E0A
and sub_104E
where sub_E0A
is recv
and sub_104E
is send
.
The function sub_E0A
checks for specific magic numbers to determine the next steps:
if ( v2 == 0x86E35DE5 )
{
l11lll1l1l1ll1l1(v6);
}
else if ( v2 == 0x89E35DE5 )
{
if ( lll1ll1111l111ll )
{
v11 -= 4;
ll1lll11ll1ll11l(v6, v11 - 16, v6 + v11 - 16LL);
v11 -= 16;
v10 = sub_D4(v11 + 96, 17301536LL);
if ( v10 )
{
sub_123(v10, 96LL);
v3 = skb_put(v10, v11);
csum_partial_copy_from_user(v6, v3, v11, 0LL, &v5);
*(_WORD *)(v10 + 184) = 8;
*(_WORD *)(v10 + 186) = 0;
*(_WORD *)(v10 + 190) = 0;
sub_1C5(v10);
*(_QWORD *)(v10 + 16) = *(_QWORD *)(a2 + 16);
v9 = sub_1FC(v10);
if ( v9 )
netif_rx(v10);
}
}
}
If the magic number is 0x86E35DE5
, the function l11lll1l1l1ll1l1
is called. If the magic number is 0x89E35DE5
, encrypted data is processed using ll1lll11ll1ll11l
.
When 0x86E35DE5
is received, l11lll1l1l1ll1l1
is invoked:
void *__fastcall l11lll1l1l1ll1l1(__int64 *a1)
{
__int64 v1; // rcx
__int64 v2; // r8
__int64 v3; // r9
void *result; // rax
__int64 v5; // [rsp+8h] [rbp-30h] BYREF
_QWORD s[4]; // [rsp+10h] [rbp-28h] BYREF
__int64 v7; // [rsp+30h] [rbp-8h]
memset(s, 0, sizeof(s));
v5 = 0LL;
v7 = *a1;
v5 = lll1l11l1l1l1111(v7, lll1ll1l111l111l, l1111l11l11l111l);// sha256
result = (void *)((__int64 (__fastcall *)(__int64 *, __int64, _QWORD *, __int64, __int64, __int64, __int64 *))l1l1l1l11111111l)(
&v5,
8LL,
s,
v1,
v2,
v3,
a1);
if ( !(_DWORD)result )
{
l1l1llll1l1ll11l = sub_23F("aes", 4LL, 128LL);
sub_2AE(l1l1llll1l1ll11l, s, 32LL);
result = memset(s, 0, sizeof(s));
lll1ll1111l111ll = 1;
}
return result;
}
This function calculates the SHA-256 hash of the exchanged key and initializes the AES encryption context and l1l1llll1l1ll11l
becomes the AES encryption object.
When 0x89E35DE5
is received, ll1lll11ll1ll11l
is called:
__int64 __fastcall sub_104E(__int64 a1, __int64 a2)
{
char *dest; // [rsp+28h] [rbp-28h]
void *src; // [rsp+30h] [rbp-20h]
unsigned int n; // [rsp+3Ch] [rbp-14h]
__int64 v6; // [rsp+48h] [rbp-8h]
if ( !a2 )
return 1LL;
if ( !sub_1FC(a2) )
return 1LL;
v6 = sub_215(a2);
if ( !v6 )
return 1LL;
if ( __ROL2__(*(_WORD *)(v6 + 2), 8) == 3333 )
return 1LL;
if ( !lll1ll1111l111ll )
return 1LL;
n = *(_DWORD *)(a2 + 120);
src = (void *)sub_19C(a2);
dest = (char *)_kmalloc(n + 20, 17301536LL);
if ( !dest )
return 1LL;
memcpy(dest, src, n);
get_random_bytes(&dest[n], 16LL);
ll1lll11ll1ll11l(dest, n, &dest[n]);
*(_DWORD *)&dest[n + 16] = -1981588507;
if ( !(unsigned int)llll1l1ll1lll1l1(l1ll111lll11lll1, dest, n + 20) )
return 1LL;
kfree(dest);
kfree_skb(a2);
return 2LL;
}
ll1lll11ll1ll11l
is the function that encrypts the data using AES-CTR. And l1l1llll1l1ll11l
variable is the aes object. from l11lll1l1l1ll1l1
called after server sends the public key with 0x86E35DE5
magic number.
The Communication
Because we have pcpapng
file, we can extract the data from it and reconstruct the communication between the client and the server.
First, client send dh key, public key, and modulus to the server. Where we can extract the base, public key, and modulus from the pcapng
file.
Second, server sends the public key to the client.
Third, client sends the encrypted data to the server and server decrypts it or vice versa.
The encrypted packets are encrypted using AES-CTR. Packet structure is as follows:
Field | Size | Description |
---|---|---|
Encrypted Data | Variable | Encrypted data using AES-CTR |
Nonce | 16 bytes | Random nonce used for AES-CTR |
Magic Number | 4 bytes | Magic number to determine the next steps |
Solution
So here is the python code to reconstruct the communication between the client and the server:
import binascii
import os
import struct
import hashlib
from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto import Random
def derive_dh_key(server_pubkey, client_privkey, prime):
key = pow(server_pubkey, client_privkey, prime)
key_bytes = key.to_bytes(8, byteorder='little', signed=False)
print("Derived DH Key:", key)
sha256_hash = hashlib.sha256(key_bytes).digest()
print("SHA256 Hash:", sha256_hash.hex())
return sha256_hash
# https://www.alpertron.com.ar/DILOG.HTM
aes_context = None
aes_key = None
base = 0
client_private_key = 1330218279148611220
server_private_key = 1375379614977946020
modulus = 0
def reconstruct_sub_104E(aes_context, data):
dest = bytearray(len(data) + 20)
nonce = Random.get_random_bytes(16)
print("Nonce (Random):", nonce.hex())
counter = Counter.new(128, initial_value=int.from_bytes(nonce, byteorder='big'))
cipher = AES.new(aes_key, AES.MODE_CTR, counter=counter)
dest = cipher.encrypt(data)
dest += nonce
dest += struct.pack("<I", 0x89E35BE5)
return dest
def reconstruct_l11lll1l1l1ll1l1(data):
global base, client_private_key, modulus, aes_context, aes_key
print("Received Data:", hex(data))
print("Private Key:", hex(client_private_key))
print("Base:", hex(base))
print("Modulus:", hex(modulus))
v2 = pow(data, client_private_key, modulus)
print("Shared Key:", v2)
v3 = hashlib.sha256(v2.to_bytes(8, byteorder='little')).digest()
print("Derived Key:", v3.hex())
aes_key = v3
aes_context = AES.new(v3, AES.MODE_ECB)
return None
def reconstruct_sub_E0A(aes_context, data):
global aes_key
magic_number = struct.unpack("<I", data[-4:])[0]
print("Magic Number:", hex(magic_number))
if magic_number == 0x86E35DE5:
reconstruct_l11lll1l1l1ll1l1(int.from_bytes(data[:-4], byteorder='little'))
return None
# elif magic_number != 0x89E35BE5: # 0x86E35DE5
# print("Invalid magic number")
# return None
nonce = data[-20:-4]
print("Nonce (Extracted):", nonce.hex())
dest = data[:-20]
counter = Counter.new(128, initial_value=int.from_bytes(nonce, byteorder='big'))
cipher = AES.new(aes_key, AES.MODE_CTR, counter=counter)
dest = cipher.decrypt(dest)
return dest
DH_KPP_SECRET_MIN_SIZE = 4 + 3 * 4
CRYPTO_KPP_SECRET_TYPE_DH = 1
class DHParams:
def __init__(self):
self.key_size = 0
self.p_size = 0
self.g_size = 0
self.key = None
self.p = None
self.g = None
class KPPSecret:
def __init__(self, type, len):
self.type = type
self.len = len
def dh_unpack_data(src, size):
data = src[:size]
return data, src[size:]
def dh_data_size(params):
return params.key_size + params.p_size + params.g_size
def crypto_dh_key_len(params):
return DH_KPP_SECRET_MIN_SIZE + dh_data_size(params)
def crypto_dh_decode_key(buf, params):
if not buf or len(buf) < DH_KPP_SECRET_MIN_SIZE:
return -1
ptr = bytes.fromhex(buf.decode())
secret_data, ptr = dh_unpack_data(ptr, struct.calcsize('HH'))
secret = KPPSecret(*struct.unpack('HH', secret_data))
if secret.type != CRYPTO_KPP_SECRET_TYPE_DH:
return -1
key_size_data, ptr = dh_unpack_data(ptr, struct.calcsize('i'))
params.key_size = struct.unpack('i', key_size_data)[0]
p_size_data, ptr = dh_unpack_data(ptr, struct.calcsize('i'))
params.p_size = struct.unpack('i', p_size_data)[0]
g_size_data, ptr = dh_unpack_data(ptr, struct.calcsize('i'))
params.g_size = struct.unpack('i', g_size_data)[0]
if secret.len != crypto_dh_key_len(params):
return -1
if params.key_size > params.p_size or params.g_size > params.p_size:
return -1
params.key = ptr[:params.key_size]
params.p = ptr[params.key_size:params.key_size + params.p_size]
params.g = ptr[params.key_size + params.p_size:params.key_size + params.p_size + params.g_size]
if all(b == 0 for b in params.p):
return -1
return 0
# Client's send base, public key, and modulus
crypto_dh_encode_key = b'010021000800000008000000010000005c4f31a50c2d980c6b152f4845212e1502'
params = DHParams()
crypto_dh_decode_key(crypto_dh_encode_key, params)
base = int.from_bytes(params.g, byteorder='little')
public_key = int.from_bytes(params.key, byteorder='little')
modulus = int.from_bytes(params.p, byteorder='little')
computed_pubkey = pow(base, client_private_key, modulus)
if computed_pubkey == public_key:
print("Diffie-Hellman Key Agreement: Success")
else:
print("Diffie-Hellman Key Agreement Failed")
exit(1)
# Server's public key
reconstruct_sub_E0A(None, binascii.unhexlify('65027c9877d09804e55de386'))
plaintext = b"Hello, AES-CTR encryption!"
ciphertext = reconstruct_sub_104E(aes_context, plaintext)
print("Encrypted Data (Hex):", ciphertext.hex())
encrypted_data = [
'010021000800000008000000010000005c4f31a50c2d980c6b152f4845212e1502',
'65027c9877d09804e55de386',
ciphertext.hex(),
'a9962d2730df6c3c2d16f4f7d3022967f4f7e0947e5e03c2c2c026acc8a656f5708a8aa99d05134172f8df5b71ad32f7806743c9529fb6ad7e41c8f97e168034893020a6bb15636ded20bd90e55be389',
'9e02b2aaf3fe8219de13745bce224b38130db68bf13ee3a2cfc035ab67d5420c5872d2952aa3e4ce43e368c6cd02564341ff59b05a2580bb8b7568d10944c5185739023e86901b98957cc54ae55be389',
'd529357938cf40ae503b1eea1859efefea69add0d4e0d1a5c35fe217402fd3b81194bd8cf947cc29cb6e0790ef92b4c35741ca9392753c504bc33cf2b37dd856ea02d194e55de389',
'a87e9bc7f7a3dd312c0146935ffd3e95b8f77643b70e8d7576002cc99bc64d202a3dbf268a7b1358cd163e7e3e9a134fe5e776e3b9695ef25726b78a8c35d2fae438b046e55de389',
'0dc6f6ba652c3717abd212715e08623d19dc44b856a5c8037a056a060ae3cb6e4dac2b5754bbed2bb7781da6d58d5a4a8e3c320a379cafb5e55be389',
'30a42a38d76a23bb443e477eb163fe14d52c390e40a262f4b2f0bbcafbd0fd45522d4ae3d05ca27f7942916b87c53cd10d78ed670cf5ae64e55be389',
'7e70f360d880d8dcc05874c639d46d19c273729f554abc2f0c63a4c4e829c839abd846ee5aab313e453f98103f4682b277372133e57260f9c96076a1a33edbac155ad7882173f8f264aeb8318c06b1e8909ea89e341be7d9cc7abbc957579582a446915d5f4c8f8261b7b6039f1fedd96b4e44a8ba14e1e143f8db7e567bda25fd3c0d6fbee55be389',
'6877c550aa2cebfc5f899f1847fb5ae6045f8d562277d574bee80647031fb055c7e71efbf9517ccbb0c2446b854dfed44ae867ecbc33dde85258c6f7887180e2d7be7ec4cb76739f39f0e3abb56a1b253594a96f086424c639d4af536ddd57301fcd2a9d35ac43e516d22a042f5c98bd464c0c761cf73514837545c98ff8cffc037af2eec2e55be389',
'87cf38d005dff7f4059b87f002e425b64a399593ed98470fff9c3828298ba0a9919773d20acf7c86231f37439e8230930393af9bb6806d570f3a9b15afa125ae2021a1f9e55de389',
'74501ea8ca0ef57a16c74dd16b1481f2ef51fabacfc93220da39d0488b27fb6d293c8a40828e875ae12f52fa65c34b63c6f100bdaf189580e55be389',
'f1e9b87e501092585285c05b2fe9b3dbffe0961f08d1ce22b8e71fd8b46634e4d811fd78f4006c846495c820691e153595bb1363ab979e5b0dc2fd797ce3e65fe7e68febd2d352725e5f2dc4d1e75a46f88cf0c05dfdfb945fe2be5ea47ccaa68efcad3fa151d2856f37a38a6afbfc922f40895c41288d8839e4220a3de9daf8bd1d0dee4de55be389',
'95bac19e9a3410287b34474501b83c5fb8ddf90d795160cc958fd739088acb84b318b3d580a3c3527406e0018e67ac3d40aadd6e952e2569e55de389',
'469a8133423b95ac561f8e162498a4c3652c720fe26c46f14fc709072fb687c759cd623657fc631740b7d1e87a42d5cfee0e369971294d42e55be389',
'c032ca90eabbceaebf7f5222c19a29980f9a9f483a79b5dc80d571b3f25f6d3c5a8fb7fe2e4dc83d2cfe54032683f5466bd856cf8d040290e55de389',
'ab5c22714b7d71c53d404f345a3ee217d0fce3fd0f8d625c00b7b505b97449b139390b58d8ef448f43e9a839f84e0a1a35bd44df34340d8ae55be389',
'4ecc53cd97a0a3ca6961e720c73f49c51a159f584febc30c8ce5be71cdbeacbce4bc61eb5e7ff5b80aaf33f359869d9d6650569b04f030e25b0d27c5225dbf30ae2e80e2cab6ee099f63d1f6b5ae8579fd05e9e2f315989614efe7ff05e237f219568f94c15361e0b2928ea76501ee65343653880c0c07948f954be3d3499ecccb9b34090dc1025c3fbd78b5828b9b3442928a323d3d684f053ca9793cc94bd6d7ab1bbf62a4441c70ecf4854e3d30ee442c673945e2c5f7e2a89129af3f3ad51bdc409209987071ddae949edfb84cf27f85d267fc4eb644c4379dbfe92599e3985149536b3b8511367ca341514812ddd704e55de389',
'a714fe5ff6c6fb25b6e0c7c4ab1abe6c1524055ad03b862848df5019d52e7e44f90b31197b993f0f2bfa95d24b743bb55bb13a6a6d8034cae55be389',
'47ece28b01ce83b3adb01403c647dcb5cf37db57c2d01704560ea42523464350f393aa6ab089621da42e602a895b80197d0283c4d20cf82f9b586497cdfdf27873a3abe17f830c84b5765837298868d372033dfb8942acdf1417d88d282e684b9beafdeb64bf7b3cb1e862b273f9f3143687d21169b59a5142ac9b4b7fadf2d8eb9ef6dbc2896cee584b64aee325b30341d25be3df4981dcd1a8dc8bfd5fe705d7b6b5f858253fe0d3c3299ae8d30392aab7f5443b18a9814904883f79bc98aaf8b38f75d12d24b9005458a088b8e209fb34a60d4bc7f4dcbd07321c5546f234565022e96bd56d0f0384ebf11f58f3501db2bbb1712f01480e0d016dcf8d0f7810db5f73cf0eabadce65fe40975cb2dab081f277cb88616b1addcf3c79e63b18a64df7e5cbdc5121a3726908ca6212376fc87a0108517bf3ad63e63537da41cb71197c30694e94309f436c8fc2d1bce9fbd1d4546575201a30f40b44b6b2d6d6216a95e02c2f7355f1635c5f40c0eff9b35696650885d2a2f1e189c4b746bd2ffb119f1a10ec868697e6fa05e3017dd0838cf283e8c8f25c5197b45d03aca6aaa5f43bbc22a0b558f50078edbd8de65efb9b73090dc9c981063ac4b3a3a06e8e59fc34cf5c609cb14d9737962efd9fce75f7e020fa5b3a7e1eafc504873b07617acb5ff545a99d5a5d2d637c44c81256a56179859249afeadb6d03090a67b58dccc780078f11c74729579f7a73b1e72ed1482c24bdc286a59a7a12f3855fd3114cfb1240670ae9315cfaae06a913746e6123af0a106d27a1f1fe6a76aa600f39fa673329535e975a2acf16f35893d6400ec42f06c9c716586606184f0f50a75a6bc03c06b147b76d42cc4bd0caaccb51ad4515c8e64236c283fb7b9cd5e7a2dcfd446a9b39dd8e0ae6081a863f01cc64beafd475db4cd3195aefb007d36cd5701757c2aedec4e161e21e58fab52330a0b370f7eb1e5f2f5ed86c68c77cc60bf3981657636df15cfc9d80633253b6130509a4c67e0071803a11e6511ff978da24afe5edc6e76ed3febd315470f2e98ec312a504e436ada941e89f611942eaeca8b3e33362b71f0346001dc3bc36495d78748d546aca1dbfa4257014abc01a1232a5e13e1661b004e5acd48732e445d71c09559faa2fc987fe637ccabbd1a81149bcede48b257dbdc218fc7bd5822b0792705e6ad628f795fba8b6541127c221717c86df4d1020c0aa5ce31a721f6c86d653a75f2492be6ff4a214c2857b1e2b9fe91a27a20109c179fa89811861aaa8b47eebe3e72e068fef5db2c61be3291b5e0efbd39ddc1248ace8e0d3ae99d76e3fcf0ccb47f5662ca949cf3de176fd7703a1bbd51393af4ef43760c402641201adaf9e5ffdbff8c8d86b6b3fd94475740218cf0c44f5a4fe75f658dfa428fc5b099105cb32183c90f239900d030946fa15836cc6c56762ffe924a34cfdefad56813f9288d7e99111cffbc76ffa1f7216d9944df94656c19e01bdc12e0f318d6aa3728664ef13c663ceee7c7cdb0af0060cd9630a8ca6ee57776b714efe2ec1ac1e90db18691f5dabbf0e9cd2e98d6efd7ce278374d161b37ba23da371eab299e1c13664237d0d07a5309ff656019579ea745553fef5bc8c9982d5f6e9d61363c7e9f10e27ec85b9ec8b8dc980aaff632a0a9684eb8b407bdb8a2a047bd253f50de6bb3260496076681aee55de389',
'ae79ecfe16d1db7abbd095a05b6e0fafdf90f11874bbd15bfa3dcec70e5cfe95bfd1ef18cce34291b728474ada51f46c29704645b2c19772e55be389',
'3ab84ffa9c90ab300f2abbae8e2c35de002cd8536dee90e4e51a8a73879bf1ebdb7cbf8f3c49f0413c9af6c1ea823d0852159bf81b22727ae55be389',
'433ee94cf71e14e11e804dd4273fc8b060004d1f3225ac1b16149a5af54506a1a0807d02034e4bea1c0127438bceb2542ff3272d1879432b54ae3f46a1f7d54e7e735633c416e6d59318386106d2e048eb57029b993348b65ab5bd9ae8b74034046811e3df4e414dffe06d42372d242fd9b67f3289d625b4b55011db8a2762c5c1550f71515c9bbbd252bc5153e8e9238f25037ac6f3cb6e7a00d728a53f996e1b41066e8194d1ceda9c09e2452ccc846c82740718fcd6e4b98404300b1a018f2d6b142af5b0eab8e9b5253d254bbffaf4b2134f980f86bae7e31120dd328c219dd8baf15ce18603ae8c66c6984e44b8fcfda9a386d0fc8c949a7f66bf52dc2d35c3759a25792bd007b61a66effb1dfbeba01eb950fca3d01e359ec88395b44cfa9f3044a2a0ee885bda074ec1d40f1c7bc75e4aaff449c8c0d5a0d6c4dd40fee33a1d3f2022d3c7aef60b9267c2c74860b77cd4e24e9e757764a56fadb9659760aa58768e97f8d2a79ba34b3787e4ae9a398edc0cf26fea2b268861a07b99c1fcea95bfb84641833bf0ef07b47a7efd864f0fcb142e7ab7903efc25587389b805887641f66811080a85a62896ae4da7ca3f38715812e0e95276eb87fc94f99b4261227c8ec8f6cb0b7557a801d4a6e80f85f011e476f775481e0e3dbaa52119b0d6079d9d9344f22ea1a347168cf181b72aeaf10f2c55ab101423c9bfc6392cf8d05ad512f7db5bab16c33ace1976c262ac9cbdb4c5d20923a1bd7126b0bfa6b131ae243cb682a9c22c2a2601440810a1350ae6f7620e47ead954af3b75a37a9844146ac99fd0432d3b661d925e3c670f51f7b305f39677fe2e6279582d44b774788d1f89ac4dfce89af54918ee4ec1b65d572f18f04b3cfea02023805e4222fac9e7515a94eb9a20d7428ee4e93ba9376c690b78d84b71aa501376245c202f93c97a223b03cba94a4c6f64c11682561609adee00bdac101ce53fad42cf6908e8781a252386762d6f7f844d85a25fad7e07689e687bd61ad232a4c895bcf8a4f2ea7a9ab78210ae1c92662f92cf11b1656a757411848198faee8134ea6a439184de5d679b2840798032d67492cb7e14c575154ab23532882b57622b98831078b89efc9b8ef7287bb8705789f3399a5927bb4e63cf83ffaae7f4aa632e7189bddfb8a74ad04c9acfa4800e2503fe468cd1e742e109b85751168cbbe31b15523126f183626c216fb7831946e7814cb6ab0b39e48972cae3323704268af1eb434e260294cf7768fa0eb49244631a940aa9657b3a75ab6927bb6dc35eb0282ac9b28c4ac8f60576f8a2bc3decce4cae4315187726fa44b2fa46af888a07909fc9785308729f3588df48ec4aae0fda24a5787686897cde2c8f22d6339c8a721e30b7cf154f0f47e96f123213b4f3bc0dfe04d9632584d1e4bb5698078eed602bce5a4590e397fca1a22ffbdfc04a2889bf5ae61419f0cae4a8148b9c041cb7fb4b90b2ca5fc20e100a065258ba3f50dcc96a7644a2fc2631c0ee6dd9af55924b166205e618e39b5b481ea9ed327130de4c405d2ea2c7c9ec250296559839fc057012e6087b1e15f902d20b3d84c4660d744e99069292f5ac601e4035457dca02e3482a3f6ffbd4e295a890dbbe4d60d6f7268e3c0fe3ff9d740d35916602a17e8d464afe5f2466daaec058ec3b0f16110cceaa497dbdc44cf85130e55de389',
'6dbc5f593321fdda9e60488ae01ec3c016e57d14d1341c38f019f7e978a2d88d5d048413bbd3b7b9963ecf2ebd36b6ec95825ec79e9c549b83182602d176c6bd4aee305be55be389',
'd306bb2ddd23a54385e37f6977fd618cf9bf84c07e0c6c5b95ecb048251528dc1f5cf0d0054d1529106c97cf7b941fdf8234a7aaa988026ce9f761dd2b14c17ff0a298f59190ee4495b070b5a57715cdfd28e44855a839f04e00e3bfb9a2e5674d05c9ad32545f929f9232233f89e0dd1b76ea962a19c06fc808e784a980fb5f5d11c729dfe55de389',
'fac86ac5117d45b79d54772bdc3be3a4a085b8126f34fa26f9c0a9fde263fb78bd866abd74015588510b5391d670519b1813c2a99d1a8f3ce55be389',
'fc6b312ca781f99b07689be346cb137c0f6995c5241afbc1e8953edb27101953e5098da8ea85acd1049fc2a44da54d1bac84cd59a3b5bf15e55de389',
'd324a93477f7ca75fc4e1228be1533d1e259d064e1cd25cc6745d40fa9a236e78012298fb5e5e8309ef10fc518ddc237ab5b5acb3d1a47d0e55be389',
'f359679fa5ab2e3b4555dbf31d2100820809659901da4b59a07b5881457cb83197192a48c0b404f2a0edf07c30e395d5d89d37081b4cb5aee55de389',
'a7ae9b48d41337370574d2f43b3fcd0758c6881f8827b259cf4e8aacb525cb838f82a4a8c61bb846da48e332f5289843212548b9a9806a26e55de389',
'bff04b2f9e14ecf1f4b965ecc11e6fcd2e88556e0299c5866a9db761854b475dbe5c7d1e65de0fde38c479b7a9f2ab788f0b60dbe82c757ce55be389',
'87c98375dedb36597f3790e2b1459d19887e51c44e720dfbdbb4055ce94823b0decd16e2ca91a940edfe380db4610c2a49b57ddb0924f45ce55de389',
'd21ba305beec78de830677ccbdb976ceafc5dced0ae3e6465cac995516134a57c53c38bcbeb3359b3cca0be83c7b028563695ce9ee0d27e8e55de389',
'd29e1e2002732a168f813a12017e8d243bb8b0187cd3ab384fdbb2a0f302a87a7b4bb321f5f7f0f780ebdc38ee9ea0a7657fcab6c34847b9e55be389',
'7fc806db0dbf3687e542cbadb4dcfce22081de4bb5c4ec88a5f8e8641aa3e59efa57d4bec7f6ebf5a6ab8a12182a9baa493258cf3130c4bad281fff854ef5bd96130ef74e55de389',
'be2ac8d131ba29199858b34834437c78c10ba0c464b9e6429d9e464c84663961ac298317c5ed43a431e3efdd48e9d13f046b45266ffaea4fe55be389',
'a45639793f6f469dab76011f6f98314288d32d6b81753273eba6407268255815c700e436a2e94659067e5c29a2855dd36ce4b5ba609698bae55de389',
'6a5150a543184a7110fb861d9d40cea11cf369283144ba7b8111ad0ee3171fd40a1e792840b114c5eaf58a2ccab6a3604d68380682557150e55de389',
'b2b571a66d4b2105e553f376f6c1fd056bd1c3bc85d89dbb7f217063b13eb3479e5b9fb688955d9becb298740cfdd6761f740bbc4050624ce55be389',
'55b677aa5ae672baf4ed57a5271c00b3bd2355bbee129630a616190a0d3c2574c7974c7eb1d70eb957c386a8e93b1cf5460ba6b83649f61de55de389',
'729d1e26a5161c66fdf49138cc48d1d85f266651cb8d1eef0f5f9ebd1ebf16bf6aca5b196b4bce006043d940f4bc96237c77a332f8492617e55de389',
'0f88a5f6d3ed13aa736c713d2b55e6e3350e97812e927ff68992e80474b258bce100f77e6bcea980601ab5cbdd266b0c054f0979de197830e55de389',
'7f8f9e73925b9307d510adcaec8e15f778e39b7a946990880f2b5465a70f67e4cc2aa94e6ff11fd2a721863d5ecf5957e1b2a776ee180ccbe55de389',
'3be49582026fa8a31e26122f93972e45bd0ae8dd6a0ef50af0850dff24d788b8556e32b990ae1f3f2badd2bad44c8456daa0c9dac594a6905ee3b5630796aa041156a89bafd9cf9e1050260ee55be389',
'20a3f972b0e8e65fe4f82ab48942adb819fd7549c0a73bc7a54bbe640503b61fb269c73577247fd0b4cca77a4b95fc6a6d637deddded54388bebaa62148c5ed7adad0ed2d91c7fc0b3c9b939e55be389',
'5bd507e6de008778439d7bc6b3d66a922b7bc86e5dde93400ed4a860deef71ff62ead788b7ba4a7743e11820cc29f057c21cef22f9e2397aadf440493cda5eca4d9a52b7e55de389',
'a4e5dfe479df5345763142d97dc31e90670d5a0bc12ee94a45da3becc72ad11085481105c33a9612683cfd279e49aa74629b526a945e038fe55be389',
'1f8ff7a27fd621e7f6c9712e81e59698cf024783ecc6f1f302dbeb06d719e0bfbc4d26e2880c9d60748c05aa407fc0b4579c4529bbaf909fe86213437fce84474538201ee55de389',
'7c5ec79330f0dd15347b4e73421d61cad5ad435031333d90d9abdd2c40a18d70a59b9e9ed52adcbb2c5fc530575da45f264d806855d10d38e0bff6b43c95f088cb4eca08f7485457fbf53046ee9d337ec2c1cde59b69661c993d22b34c5cf408a5956f697e27d185d54259a83cbc66bc19bb58fcee30b76d810676e510627c0fc6c6e6b5db176c0bb253430440e55be389',
'4dd28cc81ee865c00fd7b6238dc8e900c29b1e98264d1bea3e429e839ac9711340227b409e20d87c3115d3e4fa70c4066e7939e8f19c07bf57e4fc0faa04d3f4a067b54a57dbd3902fd6e2fa9ba11c37b12620f7d926798d0801deef0537cb928cf039dc2fa2ed1963edf7ee69332bcb32397594e8c173054bce2cd7d3f6876e13b6329a354c7ba5be3e09e66ee55be389',
'9ca43ef9e258adc9206258bf6cdc15d8552f134e562cc335c81b02ede11f93413aa97bd4002a9bfe8b6667fe77746b812ec4eedd6d74de12f2f4911e85d99af9b977eb7ee55de389',
'4bece9e86bd821513632d64e803f886f7868cec1b9d66ddf0775f57f83a6602e52de60b93b06fca5c7e19bf46b4a1a58aa7bd9cc853319f6e55be389',
'bf93516f44535f93f8554ce0ff586d3ea0a798479e6f561ec8d5c44db37a3ce76d8d2fc7ab96ee2ebc9ea47f65b58ce816bc2ebc11d354c34f0fdda912d4ac45b71dd1ff36581307c37696b2f1bb9112d38bdfb4eb777c1b08bab9a18b0f3a089416946aa31dfa60035dd00f07f298812de22e3926cad6318e89debf822d9b8775ea0f27bebf731819e629d4cce55be389',
'9b3f4e8dd569bb9be528b412d5f7a988d88ce940978e192dff11f28e4e3302eea6cebffef9e62a8297c1faa1264ea31df708106d1c2d7025e55de389',
'1389f3449b5cbdf51ccfbb53a43cd3cf1bfe82fecba5b362861c238bb4720f2dc93394c63d80b1552836151b162f93bf7a1154879acc9a45e55be389',
'b508fc1db3bef01cb3d60f0551c873a384b860e5d789761a5cc9d4785ba95c0832116c0a7b1488ab9984077864c5a18b1fab42cbbf513e4bbf0dfca596e7a7f2d37034efb09db5a4e7c2ddf743dc0ecca6df9904aa872d33d0cd423b93d14def085f98a09e0bb87d871edceff7eb82d01da44422e5c415943da05073eba72ebe055744070713bcdedc199d790d4cb6989da036e905eda643adbdd54e4db8b161f4f483e616b479baab3faf69abc7b03bbbbdd5b77ad68b8c46742b6abbb7466e895bd80e7ad0549a6e237bde78f4e1b14f17be879dd4452189391956052fda3e1d0be9928f657ae7aed518df690ff4add6b0e55de389',
'048a3a2bb3e77d8c465b9e3dd49bb64b2b326df0777f3208f86d30296d91350847fae4427a549092a0a20805fbbda78099b0000cb348fbf0e55be389',
'c52eeab7009ce2fe5ab0d29bac610794f1f25ed947da88a908a1e1520f55d0f8b3f5988540b20a2302a0c49fd5036de8fa9c0ee5401c47f7513952acc4a5e3141df150e770b016fa9d6e13ed35ecc28224f80c95184355f906ca9a867fcfbbafeda93819ec99ec6c8986f2803df52e9b655ea445a4f775bbc0c7dbda9433bfb480543a6e7b485758c4444c6dde93148738b4fd72dbf29e0b65c97e2e4df36ac1633e00319d7288f8cc9985271f28a0c6eaecb5142d2d3e73ca974f84ad80bdb521b56cb7d969356a009ed14009ad0d71de023a7c290dbd50679759119f38f0a25b35dadf8e9bd9fd1a1b210f00817766ebe19b195d31f64ddde874eeff26c0b586cf8a13558477e9eb9285792a21e2c81ba0d866c9ae18874e4cfe25ace18346d78fa7a1b7dc6be55de389',
'a4135fb38289797f876a6e852e79c54364bc1a5f3585d45b13fb64f46e9e00f98e34256443202a223d38aa3d703c4166e72b547f7f63884be55be389',
'73e586a33b33580621f29b8ee2d9f7a76b16978023c9b3c59fa2cff07055c025518e506d1f9f2dcb849750486f6e27070e7a2844987cb9f9e55be389',
'43bad150964c4e84d9728618d2a9d432d91196bc1b293a1a5f943dace6058ab4fc99c530bf6d6e3dc5412e06656053bbee33cad71fb3fc60abd846b49aefcf61b249b04d7d5b5036b11512880b7efa1431bcb703070ab6bbeaa7f1e8bac33244b0df6c160748914721b70c7899bd2ce0cc3dc1c7c417abf6e71ad92846237fb09e616cbc311bf2a79e0514159a6762bd6f2df177067408798fa1048a5f3cdd8c0cec3215dcea50bfc6d98728fcb2ae67f981080a8b051ee393cce29574014282877280337aacd83e22d5ea9eed7eb1bd0830ad1693837ee94ba884f57734b1490643382006579b2c8bec103c104d846fb799568aea0e06106ec063e6f782a625d922a72ed707e3350ad48e31d73d6dcb509231ab0c3d0c5a2260e2e4fb52b70e419213f4815f22e55de389',
'912c10d70e4e6be6e9fe05e60738bb5b0c126d209a78fcfd46d8257d8079d1a7b60fbb6c1b8a69603d6059661abc87641b5a614e48f1367952aa4567e368d74bfcbfa843e55be389',
]
print('\n\n')
for data in encrypted_data:
try:
decrypted_text = reconstruct_sub_E0A(aes_context, binascii.unhexlify(data))
if decrypted_text == None:
continue
print("Decrypted Message:", decrypted_text.decode('utf-8', errors='ignore'))
except Exception as e:
print(e)