Hello hackers, this isn’t my first time to try to solve blockchain challenges. I got some basic understanding of how it works in terms of smart contracts and the cryptography involved, but this time I was lucky to give it a big try even after the qualification round.
Smart CrackMe
The challenge was given a public deployed contract with address
0x31678C496E63BcEbd1398D25C9B21cDcb3cc5e23
on
Etherscan
along with its ABI. It has only one transaction and nothing else
but the contract bytecode.
0x6080604052600436106100345760003560e01c806308c5dc7f146100395780633ccfd60b146100695780638da5cb5b14610080575b600080fd5b610053600480360381019061004e919061040c565b6100ab565b6040516100609190610495565b60405180910390f35b34801561007557600080fd5b5061007e6102d6565b005b34801561008c57600080fd5b506100956103d1565b6040516100a2919061047a565b60405180910390f35b600080600090505b603081101561012f5782600082603081106100d1576100d06105ae565b5b602091828204019190069054906101000a900460ff1618600282603081106100fc576100fb6105ae565b5b602091828204019190066101000a81548160ff021916908360ff160217905550808061012790610536565b9150506100b3565b5060436002600060308110610147576101466105ae565b5b602091828204019190069054906101000a900460ff1660ff1614801561019a57506079600260016030811061017f5761017e6105ae565b5b602091828204019190069054906101000a900460ff1660ff16145b80156101d257506043600280603081106101b7576101b66105ae565b5b602091828204019190069054906101000a900460ff1660ff16145b801561020b5750605460026003603081106101f0576101ef6105ae565b5b602091828204019190069054906101000a900460ff1660ff16145b8015610244575060466002600460308110610229576102286105ae565b5b602091828204019190069054906101000a900460ff1660ff16145b801561027d5750607b6002600560308110610262576102616105ae565b5b602091828204019190069054906101000a900460ff1660ff16145b156102cc5733600460006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600190506102d1565b600090505b919050565b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610366576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161035d906104b0565b60405180910390fd5b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc479081150290604051600060405180830381858888f193505050501580156103ce573d6000803e3d6000fd5b50565b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000813590506104068161060b565b92915050565b600060208284031215610422576104216105dd565b5b6000610430848285016103f7565b91505092915050565b610442816104e1565b82525050565b610451816104f3565b82525050565b60006104646018836104d0565b915061046f826105e2565b602082019050919050565b600060208201905061048f6000830184610439565b92915050565b60006020820190506104aa6000830184610448565b92915050565b600060208201905081810360008301526104c981610457565b9050919050565b600082825260208201905092915050565b60006104ec826104ff565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60006105418261051f565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156105745761057361057f565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600080fd5b7f43616c6c6572206973206e6f7420746865206f776e6572210000000000000000600082015250565b61061481610529565b811461061f57600080fd5b5056fea26469706673582212202df7f55fc48becfec4da6f96cff3b6fd616f35a2e864984c3078521a32ff8c4064736f6c63430008070033
And the contract ABI:
// SmartCrackMe.abi
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "key",
"type": "uint8"
}
],
"name": "check_flag",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address payable",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
First thing noticed that the contract section has a button to decompile the bytecode and after decompiling, it gives us the following code:
# Palkeoramix decompiler.
def storage:
stor2 is array of uint256 at storage 2
owner is addr at storage 4
def owner(): # not payable
return owner
#
# Regular functions
#
def _fallback() payable: # default function
revert
def withdraw(): # not payable
if owner != caller:
revert with 0, 'Caller is not the owner!'
call owner with:
value eth.balance(this.address) wei
gas 2300 * is_zero(value) wei
if not ext_call.success:
revert with ext_call.return_data[0 len return_data.size]
def unknown08c5dc7f(uint256 _param1) payable:
require calldata.size - 4 >=′ 32
require _param1 == uint8(_param1)
idx = 0
while idx < 48:
stor2[0.03125 / idx].field_0 = uint8(stor(0.03125 / idx)[uint8(idx)] xor _param1) * 256^(idx % 32) or !(255 * 256^(idx % 32)) and stor2[0.03125 / idx].field_0
if idx == -1:
revert with 'NH{q', 17
idx = idx + 1
continue
if uint8(stor2.length) != 67:
return 0
if stor2.length.field_8 != 121:
return 0
if stor2.length.field_16 != 67:
return 0
if stor2.length.field_24 != 84:
return 0
if stor2.length.field_32 != 70:
return 0
if stor2.length.field_40 != 123:
return 0
owner = caller
return 1
The website uses
Panoramix
python decompiler and as we see there’re only 3 functions
owner
,
withdraw
, and the last one
check_flag
that had been decompiled into this weird name
unknown08c5dc7f
.
-
owner
: Returns only the address of the contract owner. -
withdraw
: Checks if the method caller address same as the contract owner address. If so withdraw the ballance, if not it aborts. -
check_flag
: It takes 8-bit integer value, does some calculations on the defined array of size 2 and lastly it checks if the final array value has the correct flag format.
I was stuck at the decompiled line inside the loop. It has weird syntax and didn’t know how represent the first part. Actualy, I’ve tried to assume that some parts are useless and tweak the others but nothing worked. So, I decided to look at the decompiler repo and see if there’s any options that I could use to help me decompile the bytecode correctly, but unfortunately still nothing.
I asked one of my teammates if he knows any smart contract decompiler and he told me I could use JEB. I started off and loaded the bytecode into JEB, it shows me the opcode instructions and when I tried to decompile to the source code it gives a decompilation error. I’ve tried to read the instructions and map it to what I’ve in the code above, but still no luck.
On the next day, I asked our reverse mate to take another look on the decompiled weird instructions, but he couldn’t understand a thing from it. He decided to decompile it using his own JEB version and this time the source code showed up on his screen.
function start() {
*0x40 = 0x80;
if($msg.value != 0x0) {
revert(0x0, 0x0);
}
var3 = storage[0x0];
storage[0x0] = (var3 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00) | 0xda;
var3 = storage[0x0];
storage[0x0] = (var3 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff) | 0xe000;
var3 = storage[0x0];
storage[0x0] = (var3 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffff) | 0xda0000;
var3 = storage[0x0];
storage[0x0] = (var3 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffff) | 0xcd000000;
...
var3 = storage[0x1];
storage[0x1] = (var3 & 0xffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffff) | 0xfc00000000000000;
var3 = storage[0x1];
storage[0x1] = (var3 & 0xffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff) | 0xf40000000000000000;
var3 = storage[0x1];
storage[0x1] = (var3 & 0xffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffff) | 0xdc000000000000000000;
var3 = storage[0x1];
storage[0x1] = (var3 & 0xffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffff) | 0xf400000000000000000000;
var3 = storage[0x1];
storage[0x1] = (var3 & 0xffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffff) | 0xa10000000000000000000000;
var3 = storage[0x1];
storage[0x1] = (var3 & 0xffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffff) | 0xdc000000000000000000000000;
var3 = storage[0x1];
storage[0x1] = (var3 & 0xffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffff) | 0xcb00000000000000000000000000;
var3 = storage[0x1];
storage[0x1] = (var3 & 0xffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffff) | 0xa60000000000000000000000000000;
var3 = storage[0x1];
storage[0x1] = (var3 & 0xffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffff) | 0xe4000000000000000000000000000000;
var0 = msg.sender;
var3 = storage[0x4];
storage[0x4] = ((address(var0)) * 0x1) | (var3 & 0xffffffffffffffffffffffff0000000000000000000000000000000000000000);
codecopy(0x0, 0xb77, 0x658);
return(0x0, 0x658);
}
First, we know that
storage
variable has size 2 from the previous decompiled code and now
the code has flatten out the iterations one by one. He tried to
run the 48 lines above and the result wasn’t usefull, we tried
to play with it, but still empty hand. The competition has ended
and we still couldn’t figure it out. After a day I decided to
give it another try and this time look a bit closer on the last
decompiled code. I ran it and did a bruteforce xoring over the
result and I noticed the reversed flag appeared with byte
0x99
🙂.
Final code:
from pwn import xor
storage = [0]*2
var3 = storage[0x0];
storage[0x0] = (var3 & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00) | 0xda;
...
var3 = storage[0x1];
storage[0x1] = (var3 & 0xffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffff) | 0xe4000000000000000000000000000000;
part1 = xor(long_to_bytes(storage[0]), 0x99)[::-1].decode()
part2 = xor(long_to_bytes(storage[1]), 0x99)[::-1].decode()
print(part1+part2)
Flag:
CyCTF{ju57_l1Ke_7EH_900d_0ld_CR4CkME2_RemEm8ER?}