Hack Back
Can you get to the bottom of what's wrong with the machine? - by Maxablancas, h4sh3m8 & rePl4stic
Last updated
Can you get to the bottom of what's wrong with the machine? - by Maxablancas, h4sh3m8 & rePl4stic
Last updated
The following post by 0xb0b is licensed under CC BY 4.0
Hack Back is another purple challenge from THM, divided into three acts. This time we first examine the victim machine to find a suspicious file in Act I. This file, if executed, is responsible for slowing down the machine. By decompiling and reverse engineering the binary, we will find some credentials. In Act II, we follow the theme of the room and hack back, trying to beat the attackers at their own game. With the credentials we found, we gained access to the email account of an attacker and used phishing to trick them into running a reverse shell to gain access to the attacker's machine. There we find a smart contract, which we use in Act III to get the money back.
At C:\
and C:\Users\Administrator
we find simpleServer.exe
, which looks very suspicious with its name and location.
Instead of using IDA on the machine, we use other tools, such as BinaryNinja. For this, we need to transfer the files to our machine. Using impacket-smbserver
we are able to create an SMB server to share the files.
We add a network drive X:
mapped to the shared folder \\10.14.90.235\smbFolder
with the username test
and password test123
.
Next, we copy the simpleServer.exe to our target machine.
We open the file in BinaryNinja and select the Pseudo C
representation.
When using strings
, some suspicious character sequences appear, but none of them seems to be the password. We scroll through the functions and spot those strings again. They are passed to the sub_140001640
function. The last one is nod passed to sub_140001640
.
This function, sub_140001640
, takes a string (arg1) and applies an XOR operation with the integer 5 to each character, effectively decoding or encoding it. The result is stored in arg2 and is null-terminated. As mentioned above, the last character string is not passed to this function, but might be also an encrypted string.
Here's a Python script to decode the given strings. The sequences found are redacted and need to be replaced with the real ones.
After running the script we find a possible credential set which allows us to answer the first two questions and head to the next task.
We start with an nmap scan and find serveral ports related to a windows machine. Including a mail server (port 25, 110, 143) and a web server (port 80).
From the decrypted strings of the binary we find not only some login credentials but also the possible host berrybears.ioc
. We add this in the /etc/hosts
file. When we visit the site, we are greeted with a ransom.
Next, we try to find some directories and pages with a recursive scan using Feroxbuster.
Of interest are /mail
and /rc
. Furthermore, /mail/doc
was also promising at the beginning, as this probably revealed the mail server used, that was vulnerable to an rce. However, it turned out not to be exploitable.
At /mail
we are greeted with a login form. We use the credentials from Act I.
We can log in and see a mail from the boss of the threat actor in the inbox. The boss needs the key to get rid of the transactions that continue to pile up. Otherwise it becomes too risky. Unfortunately, we cannot find any answers to the question in the sent folder.
Ok, let's move on to /rc
for now. And this time we have RoundCube in front of us. We reuse the credentials.
Here we find more this time. Amongst other things, we find the mail that may have been used to trick the target into opening a file to unintentionally execute it. A phishing mail.
We find the email from the boss again and a reply from our attacker. The key is not sent via mail, instead via the usual channels. The boss doesn't seem to operate as securely as his employees.
If we try to send a mail without an attachment to the boss, addressing the key, we get an answer. In this case he cannot find the key. So the boss is eager to open the file we send.
Next, we try to disguise a reverse shell as a key and send it to the boss.
An attempt was made to convert a reverse shell created with powercat into an executable using https://ps2exe.azurewebsites.net/, but unfortunately without success. Also, .bat
files or .ps1
files were not executed. (tested with a request to a hosted web server)
But there is an easier way. Via an executable that runs Powershell commands itself and uses ncat.exe
. In the first attempts it was a powercat script but that might have got deteced, since only the download from the web server could be observed.
One of many first attempts:
Compile the code:
Send To Boss:
The script gets downloaded but not executed since there is not connection back to the listener.
In this workaround, the ncat.exe
file is downloaded and saved to C:\Windows\Temp\ncat.exe
. The program then runs another command that uses ncat.exe
to connect back to the our machine, creating a reverse shell.
Compile the code:
Send To Boss:
The ncat.exe
gets downloaded...
... and executed. We receive a reverse shell on our listener. We are fisher\administrator
.
On the user's desktop we find the flag for Act II.
We also find a smart contract. This smart contract suggests that we can solve the challenge by simply providing the cleartext authentication string. This is simply XORed with 44
and compared to the stored string. So if we XOR the stored string with 44
, we'll get the correct plaintext string.
The following python script was used to decode the stored string.
We start the final machine and run a Nmap scan. We have three open ports 22 SSH, 80 a web server and port 8545 related to the smart contract challenge.
If we visit the index page of the web server, we find a smart contract challenge. Slightly modified from the challenge we found in Act II. In addition to the authentication string, we now also have to set the balance of the contract address down to 0. We can achieve that by calling the transfer function on the contract providing the deciphered data and the correct amount.
This type of challenge is very familiar and has already been used in TriCipher Summit. We can find a solution using Foundry to this at Jaxafeds writeups. Here we can see how to call the functions.
To install Foundry follow the instructions linked below:
In this case we need to call the transfer
function, pass a string and the amount.
The --legacy
flag is essential in this scenario because it specifies that the transaction should follow the legacy transaction format (pre-EIP-1559). EIP-1559 introduced a new transaction format with separate maxFeePerGas
and maxPriorityFeePerGas
fields, which are not supported by nodes or configurations set up for legacy transactions, like the one you're interacting with on chain ID 31337
. Without --legacy
, the default behavior attempt an EIP-1559 transaction, causing the "unsupported feature: eip1559" error.
To decipher the key we just need to XOR the encrypted key with 44 found in the challenge.
We gather the information needed to make all calls to the contract.
Next, we use Foundry to make a transaction.
The command sends a transaction on the Ethereum network, invoking the transfer
function on the contract at address 0x80A89a41DF07B3A65913D67407fB00281bda7Da0
with parameters "REDACTED"
and 1000
using the specified private key on a custom RPC URL and chain ID 31337
.
After we have submitted the command we can retrieve the flag on the index page.
To verfiy that we have successfully transferred the money, we can call the getOwnerBalance()
function to check the balance of the target, and the balanceOf()
function to check the balance of the player's wallet. To do this we use the following calls:
With the following calls using Foundry we can call the functions to check the balances:
We check the balance before the transaction and see that the target has a balance of 0x3e8
, which is 1000
. The player's wallet is empty.
We make the transaction as described above.
After the transaction, we can confirm that it worked properly. The target's balance has dropped to 0
and the player's wallet now has a balance of 1000
.
The following attempt is error prone, as it worked the first time, but when confirmed a second time for the writeup, it does not work properly. It does not fully transfer the 1 ETH to the player's address and only reduces the wallet amount of the target by the transaction costs. Setting the gas value to 2000000 in the script will solve the challenge.
After confirming it a third time, the script might have worked properly. For those interested, I have left this solution in the writeup.
Alternatively, the challenge can also be solved using web3 in python. All necessary information such as wallet address, private key, contract address, RPC URL, chain ID and block time can be found on the index page.
Wallet Address: A unique identifier for a digital wallet, used to send and receive cryptocurrency on the blockchain.
Private Key: A secret key allowing the wallet owner to authorize transactions and access funds; must be kept confidential to prevent unauthorized access.
Contract Address: The unique address assigned to a deployed smart contract on the blockchain, used for interacting with its functions.
RPC URL: A Remote Procedure Call (RPC) URL provides a connection endpoint to a blockchain node, allowing communication with the blockchain network.
Chain ID: A unique identifier for a specific blockchain network (e.g., Ethereum mainnet, testnet) to prevent transaction cross-chain issues.
Block: A data structure that stores a group of validated transactions on the blockchain, linked sequentially to form the blockchain ledger.
To solve the challenge GPT was used to create a script but failed initially with a wrong gas
value. In a smart contract, gas is the unit of computational cost required to execute operations on the Ethereum blockchain, paid by users to incentivize network validators and to prevent misuse of resources.
It was to high, so the amount was not porperly withdrawn, after setting it to low the following error was thrown and gave the amount needed 21756 which was then used in the final script.
The target string is redacted and need to be replaced.
This code is designed to interact with a smart contract on an Ethereum-compatible blockchain using the Web3 library, aiming to complete a challenge by transferring tokens with specific encoded data. Here’s a step-by-step explanation:
Setting up the Web3 Connection:
The code connects to an Ethereum node at http://geth:8545
using the Web3 HTTP provider. It also defines both the smart contract address and the player's wallet address, alongside the player’s private key, which will be used to sign transactions.
Defining the Contract: The contract ABI (Application Binary Interface) specifies the available functions in the contract:
decode
: This function XOR-decodes a string using a specified key (although it is not directly used here).
transfer
: This function allows transferring a token or value with specified data
(the encoded message) and an amount
.
isSolved
: This function checks if the challenge has been successfully completed.
Encoding Data with XOR:
The xor_encode
function takes the target string "REDACTED"
and encodes it with an XOR key of 44
. This encoded data will be passed as part of the transaction in the transfer
function to meet the challenge requirements.
Building the Transaction:
To perform the transfer, the code calls the transfer
function of the contract with the encoded data and sets the transfer_amount
to 1000 tokens. It fetches a nonce (the transaction count for the player address) to ensure transaction uniqueness and builds the transaction with specific settings, including gas limits, chain ID (31337), and gas price. Setting the gas price to 21756 wont solve it, but setting it arround 2000000 will solve the challenge.
Signing and Sending the Transaction: The code signs the transaction using the player's private key and then sends it to the blockchain. Afterward, it waits for the transaction to complete, retrieving a transaction receipt for verification.
Checking if the Challenge is Solved:
Finally, the code calls the isSolved
function on the contract. If it returns True
, it confirms that the challenge has been completed successfully.
In essence, this code attempts to solve a smart contract-based challenge by performing a transfer transaction with XOR-encoded data, then verifies if the solution was successful by checking the isSolved
function on the contract. The transaction receipt provides details for tracking the transaction's success on the blockchain.
Afer running the script we get the responsed Challenge Solved: True
.
We can now click on Get Flag
to receive the final flag.