<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Vifly 的博客</title><link>https://viflythink.com/</link><description>Recent content on Vifly 的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 07 Dec 2024 00:00:00 +0800</lastBuildDate><atom:link href="https://viflythink.com/atom.xml" rel="self" type="application/rss+xml"/><item><title>2024 东京圣地巡礼</title><link>https://viflythink.com/Tokyo_2024/</link><pubDate>Sat, 07 Dec 2024 00:00:00 +0800</pubDate><guid>https://viflythink.com/Tokyo_2024/</guid><description>&lt;img src="https://viflythink.com/Tokyo_2024/show.jpg" alt="Featured image of post 2024 东京圣地巡礼" />&lt;p>2024 年十月下旬，我第一次达成了去日本的梦想，为此已经做了很久的计划，不知道是否因为前面已经消耗了太多精力，在飞机广播已经到达东京时心里反倒没有多少波澜。现在回想起来，矛盾叠加态算是最能总结这次旅行状态的形容词：东京对我而言既熟悉又陌生，旅行过程可谓既心累又欣喜，旅行的结果也可说是既满足又有遗憾。在对本次旅行的回忆中，我顶着特种兵旅游后遗症写完了本文。本文主要聚焦于我的圣地巡礼，当然也有一些个人的经验总结，希望各位能愉快地读完。最后请注意图多杀猫，为了节省流量，不重要的图片（主要是动漫游戏截图）采用了高压缩率的 Webp 格式（&lt;em>PS：不会还有读者在用 IE 吧&lt;/em>）；而照片由 Google Pixel 手机拍摄，所以夜景会显得十分明亮，以及由于 Pixel Camera 经常出现的拍摄照片色调偏冷问题，我简单使用了 Google 相册的滤镜以尽量还原真实色彩。&lt;/p>
&lt;h1 id="一些经验教训">一些经验教训
&lt;/h1>&lt;p>出发前，我主要参考了 &lt;a class="link" href="https://kirikira.moe/post/50/" target="_blank" rel="noopener"
>Kiri 老师的日本极乐行博文&lt;/a>，当然还有其它 Google 收集到的信息，做了充足的准备，所以与网上见到的踩坑经历相比，我这次旅行可说是相当的好。&lt;/p>
&lt;p>虽然这么说，现实在下飞机过海关时就给了我当头一棒，出海关时需要填写一个表格声明自己没携带禁止物品入境，之前我刚好忘记查日本海关的流程了，现场的流程说明也不够详细，导致毫无准备被这个事情卡住；询问海关职员时真正体会到了日本人的英语有多么的烂，虽说早就知道这点，但实际交流时才会发现怎么我们讲的不是同一门语言吗，最后掏出谷歌翻译用日语交流终于大概搞懂了要做什么，为此在海关卡了快一个小时才去拿托运的行李。吸取了这个教训后接下来与日本人交流都是能用简单英语就用，不行就直接上谷歌翻译。至于手机流量，Pixel 有 esim 槽位，省去了买实体卡的麻烦，你高兴的话一天换一家运营商也不是问题。&lt;/p>
&lt;p>拿完行李后来到机场的到达大厅，可以先买/领取一些东西，例如东京地铁通票（Tokyo Subway Ticket，等下再说这个东西）、手机卡、Welcome Suica（现在没有实体 Suica 了，需要实体卡就只能选这个只有 28 天有效期的游客专用卡）等。另外还可以在 ATM 取一些日元备用，在日本待这么多天发现确实有些地方只收现金。这里展开说一下取现的事情，考虑到本文读者基本都有银联储蓄卡，那么取现可以在支持银联的 ATM 上进行（机器会标注是否支持银联，我还没遇到过不支持的），取现时会根据当天银联的汇率从你账户的人民币余额中扣除，在此过程中一定会扣除一笔约 100 日元的手续费付给日本的银行，不同的日本银行 ATM 收取的手续费并不相同，例如 Seven 银行收取 110 日元的手续费，但他家的 ATM 有中文界面；除此以外你的发卡行也有可能收取一笔手续费，想避免这笔钱的话可以考虑兴业银行的寰宇人生卡等明确免收境外取现手续费的储蓄卡，当然你也可以用信用卡取现，交行的优逸白金卡等也是免除这笔钱的，但记得先溢存款以免被收取现金借贷利息。&lt;/p>
&lt;p>在机场搞定一切后就该坐上地铁前往目的地了，不过众所周知东京的轨道交通系统是极其复杂的，所以一定要遵循谷歌地图的说明。例如谷歌地图上面写了无需换乘那就待在车上乖乖不要动，因为有可能过了某个站后你所在的车自动从一个线路名变为另一个线路名；还有就是注意谷歌地图上建议坐什么时刻往什么方向的列车，因为有可能同一侧站台会出现去往不同终点的列车，可千万不要什么都不看就上去了。在有多条线路的地铁站内找自己要坐的线路时，记住自己要坐的线路的代号有助于看懂指示牌，谷歌地图会显示要坐的线路的代号与颜色，例如 G 对应银座线。至于车费，iPhone 用户直接在 Apple 钱包添加西瓜卡就行，Pixel 则需要日版才行，当然你也可以选择 Welcome Suica 或 PASMO（仅限东京这片区域，但不会过期）这类实体 IC 卡，西瓜卡除了乘车外也可用于在便利店等地方消费。不过有不少人也会想着用通票省钱，如果你像我一样只在东京这一带旅游，那么知名的 JR PASS 肯定是亏本的，而上面提到的东京地铁通票，可在有效期内无限搭乘都营地下铁线全线与东京 METRO 全线，换句话说 JR 与双字母打头的车不能坐，其它都行，听上去很棒对吧，但很不幸地，有不少时候坐 JR 更省时间，而且有少数地方也没有满足条件的列车可乘，例如离开机场基本坐的都是京急机场线（KK），双字母打头所以没法用，所以各位还要再考虑一下为了省钱选择东京地铁通票是否值得。而我则买多了东京地铁通票，临走时还得在机场办理退票，每张票被收取 220 日元的退票手续费，真是亏死了。&lt;/p>
&lt;p>剩下就是如何付款的问题了，现金百分百可用，像是在机场购买东京地铁通票是只收取现金的，而蓝绿支付的覆盖面明显比不上信用卡。这次出行我带了好几张信用卡，原本想薅一下银联的羊毛，但试了几个商家发现似乎都不支持银联信用卡插卡支付，当店员表示试试刷卡时我果断拒绝了，毕竟刷磁条是一种非常不安全的用卡方式（容易盗刷），通过芯片付款才是推荐的方式，最后发现还是 Visa 卡好用，直接一拍即付真的很舒服。只不过 Visa 卡（以及 MasterCard）要&lt;a class="link" href="https://www.douban.com/note/508378675/" target="_blank" rel="noopener"
>注意动态货币转换费（DCC）的问题&lt;/a>，例如我在任天堂店购物时就遇到了店员询问付款用 JPY 还是 USD 的情况。所以我目前结论就是：银联卡不好用，Visa 与 MasterCard 虽然方便但要注意 DCC，也许下次来日本前应该申请一张 AMEX 卡。至于更多的境外用卡注意事项，各位的发卡行应该都有说明，自行查阅即可。&lt;/p>
&lt;p>最后则是本次最大的一个教训：不要特种兵。除了没搭乘红眼航班外这次旅行都跟一般所说的特种兵旅游挺相似的，当初制订旅行计划时考虑到在日本待的天数不少，所以我把一堆地点都塞进了旅行计划里（虽说后面已经砍了不少），至于结果嘛：每天都在赶时间去不同的地点，在一个地方没待多久就走了，一天下来只觉得好像干了不少事情又好像什么都没做。所以制订旅行计划时一定要留出足够的弹性时间，毕竟随便出现一点意外都能打乱安排很紧凑的计划；而且也不要忽视前往目的地的交通时间，像我这样一天去好几个地点的话，花在路上的时间占比真的很高。各位第一次准备去日本时想必都很激动，很多之前想去的地方都仿佛触手可及，但我在这里还是泼下冷水，第一次来日本旅游基本上不可能把想去的地方都逛完，先把最想去以及限时的地点排在前面，尽量避免一天逛多个地点，排在后面的那些地点又不会跑，下次再逛也行。本次旅行是如此的特种兵，以至于都没机会品尝日本的美食，毕竟很多知名的店都需要走好一段路才能找到，到了以后还需要排队，对于时间安排十分紧凑的计划而言这是完全无法接受的。也还有别的遗憾，不少地方都没仔细逛完，例如没在秋叶原好好扫店，像秋叶原这样的地方专门安排两三天逛是十分合理的，而我总计可能就逛了一天。&lt;/p>
&lt;h1 id="fate-stay-night-heavens-feel-剧场版">Fate Stay Night Heaven&amp;rsquo;s Feel 剧场版
&lt;/h1>&lt;p>很残念的一点是 Fate Stay Night 的绝大部分取景地都不在东京（冬木市的原型是兵库县神户市），而我本次旅游的主要活动范围却是在东京。只不过让我倍感幸运的是，在&lt;a class="link" href="https://anitabi.cn/map" target="_blank" rel="noopener"
>圣地巡礼地图&lt;/a>上随便扫了一下后看到正好在银座有一处 HF 剧场版的原型，本来银座就在我的必须要去的地点清单上，只能说那可真是太巧了。&lt;/p>
&lt;p>这次圣地巡礼的地点是 HF 剧场版圣杯战争即将开始的那个傍晚樱在放学后去的热带鱼店。此时的樱早已知道圣杯战争与作为圣杯的自己的命运，当她看着这些在水里畅游的鱼儿时心里在想什么呢，是在走上一条不归路前带有留恋地最后看一眼美好的事物呢，还是羡慕着水中的鱼儿这样无忧无虑的生活呢？&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/sakura_tropical_fish.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>稍微谷歌了一下，看上去这家热带鱼店还是有些名气的，居然有个&lt;a class="link" href="http://www.paupau.co.jp/html/map.html" target="_blank" rel="noopener"
>官网&lt;/a>，上面明确写了如何前往，所以一路跟着导航走就行。到了以后发现工作日的下午店里没什么人，在准备走的时候才进来两个 JK。拍了两张照片，第一张的布局与上面剧场版的图片较为相似，而第二张是选了灯光相似的位置拍的。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/PAUPAU_AQUA_GARDEN_1.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/PAUPAU_AQUA_GARDEN_2.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>好，就只有这一处地点了，虽说我知道硕大一个东京都区域还有几个 Fate 的取景地，但由于时间不够，便只有这一处 Fate 的圣地巡礼了。对此我已经感到满足，毕竟第一次来日本若是完全没有我心中的经典作品 Fate Stay Night 的圣地巡礼的话那可真是太遗憾了。&lt;/p>
&lt;h1 id="p5r">P5R
&lt;/h1>&lt;p>被称为“东京旅游模拟器”的 P5R 真是名不虚传，随便逛几个热门景点都能达成 P5R 的圣地巡礼。而要说在 P5R 出现的那么多个地点中哪个最能体现东京大都市的繁华，那肯定是涩谷了，无论是错综复杂又热闹非凡的地下通道，还是总是挤满了人的十字路口，想必都给各位玩家留下了深刻的印象。现在开始一边播放&lt;a class="link" href="https://share.viflythink.com/music/P5R/Tokyo%20Daylight.mp3" target="_blank" rel="noopener"
>《Tokyo Daylight》&lt;/a>，一边来到涩谷重温 P5R 中游走在东京的岁月吧。&lt;/p>
&lt;p>坐上地铁前往涩谷，出了地铁车厢后就发现自己在地下迷路了，我在游戏里的涩谷地下通道本来就是只记住了大概的地图，到了现实就更不用说，根据指示牌前往忠犬八公像，然后正巧选择了 A8 出口，当走出地铁站的那一刻我突然有种很熟悉的感觉，一转头，这不就是涩谷站前广场吗。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_station.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_station_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>对我来说，这一刻是难以用语言来表达的一种狂喜且混杂了不知道多少种情绪的感觉；虚幻与现实的融合，使我产生了一种完全代入 Joker 的错觉。藏于脑海中的记忆被激活，顺着记忆随便走一下，就看到了太阳平常街头演讲的地方，只可惜绿皮车厢早已不见。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_1.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_animate_1.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>当然，也看到了著名的忠犬八公像，由于此时临近万圣节，所以还有个小灯笼装饰呢。与游戏相同的是也有很多人排队跟忠犬八公像合照，以下这张是好不容易抓拍到的。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_2.jpg"
width="1594"
height="2239"
loading="lazy"
class="gallery-image"
data-flex-grow="71"
data-flex-basis="170px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_animate_2.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>以及彩票店，不知道你还是否记得游戏里中了彩票的快乐呢。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_3.jpg"
width="2069"
height="1399"
loading="lazy"
class="gallery-image"
data-flex-grow="147"
data-flex-basis="354px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_animate_3.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>转完这一圈后走两步就能看到非常知名的十字路口了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_crossroad.jpg"
width="2260"
height="1476"
loading="lazy"
class="gallery-image"
data-flex-grow="153"
data-flex-basis="367px"
>&lt;/p>
&lt;p>可惜这一天来涩谷只是中转而已，没有什么时间拍更多照片了，接下来则是另一天在雨中前往涩谷拍摄夜景。&lt;/p>
&lt;p>这天刚到时雨还挺大，所以决定先在地下进行圣地巡礼。不知道是否是进行过翻修的原因，我愣是没怎么找到与原作相似的店，唯一的收获就是看到了在尽头的花店。再想到已经不见的绿皮车厢，只能感慨变化真快啊。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_flower_store.jpg"
width="2254"
height="1421"
loading="lazy"
class="gallery-image"
data-flex-grow="158"
data-flex-basis="380px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_flower_store_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>等雨变小后回到涩谷站前广场，走过十字路口后给显眼的大盛（太平）堂书店拍了一张。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_bookshop.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_bookshop_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>接下来沿着道路就能找到 Joker 平常打工的几家商店。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_matsuya.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_matsuya_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>777 便利店是全家与 7-11 的杂交体吗（笑）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_store.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_store_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>还有大爆炸汉堡，对应的应该是汉堡王，不过现实与原作相差也太大了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_burger_king_1.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_burger_king_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>决定像 Joker 一样尝试大爆炸汉堡挑战（不是）。在二楼看着雨中人来人往时不由自主就哼起了&lt;a class="link" href="https://share.viflythink.com/music/P5R/Beneath%20the%20Mask%20-rain%2C%20instrumental%20version-.mp3" target="_blank" rel="noopener"
>《Beneath the Mask》雨天纯音乐版&lt;/a>。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_burger_king_2.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>最后还找到了武器店和天鹅绒房间所在的小巷。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_gun_model_store.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shibuya_gun_model_store_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>虽说还有些遗憾，不过涩谷之旅到此结束，接下来还有其它 P5R 的圣地巡礼。&lt;/p>
&lt;p>热闹的浅草寺是其中之一，只顾着逛也没拍什么照片。期间遇到一位外国小哥询问我的上衣图案是不是 EVA（我当天确实穿着 EVA 联名衣服），在我回答 YES 以后给我展示了他手臂上的朗基努斯之枪纹身，能正巧遇见一位同样喜欢 EVA 的人真是一个惊喜，可惜英语不行想不到如何表达我的欣喜，只能互赞了一句 Nice 后告别了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/senso_ji.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>在浅草这一带给晴空塔拍了一张。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Tokyo_skytree_1.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>然后上晴空塔领略东京夜景，手机长焦确实不太行，这里就贴一张还算能看的。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Tokyo_skytree_2.jpg"
width="4624"
height="3472"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>晴空塔有专门的玻璃地板区域，可以体会一下在高处俯视地面的感觉（恐高症注意）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Tokyo_skytree_3.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Tokyo_skytree_4.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>这段时间还有跟咒术回战的联动。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Tokyo_skytree_5.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>最后走出晴空塔后再拍一张。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Tokyo_skytree_6.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>秋叶原的照片主要放在了命运石之门圣地巡礼的部分，这里只放一张看着跟游戏比较像的场景。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Akihabara.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Akihabara_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>从河口湖返回新宿时在歌舞伎町找到了游戏中的新宿电影院原型。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shinjuku.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/shinjuku_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>以及其中一天去三鹰之森吉卜力美术馆时发现吉祥寺和井之头公园就在附近，由于实在没时间所以没去井之头公园逛，随便拍了一下吉祥寺。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/kichijoji_1.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/kichijoji_2.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/kichijoji_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>游戏里对吉祥寺商业街的拱顶还原很真实，下雨时走到拱顶里会自动收起雨伞。可惜的是没找到对应的爵士酒吧，不然真想进去点一杯喝的慢慢欣赏爵士乐。&lt;/p>
&lt;p>这里顺便再贴两张在三鹰之森吉卜力美术馆室外拍的照片（室内不允许拍摄）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/ghibli_museum_1.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/ghibli_museum_2.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>最后还有东京站，可以在这里坐车离开东京，对应第三学期结局 Joker 离开东京时的场景应该是东京站丸之内站房与丸之内站前广场。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Tokyo_station_1.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>再往前走感觉就会蹦出来一个一回合八动的伊斯塔呢（不好意思串到真女神转生 V 了）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Tokyo_station_2.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Tokyo_station_3.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;h1 id="命运石之门">命运石之门
&lt;/h1>&lt;p>由于石头门的主要场景都是在秋叶原的关系，前往二次元圣地秋叶原的同时顺便完成了对石头门的圣地巡礼。可惜我通关石头门已经是相当久远的事情了，所以下面贴的原作对照图都是从&lt;a class="link" href="https://www.bilibili.com/opus/222908450479832804" target="_blank" rel="noopener"
>石头门的圣地巡礼指南&lt;/a>来的。现在开始我们的秋叶原圣地巡礼之旅吧，一切都是命运石之门的选择。&lt;/p>
&lt;p>坐上 JR 线前往秋叶原，刚出站回头一看就看到了第一个圣地巡礼地点：JR 秋叶原站。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/JR_Akihabara.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/JR_Akihabara_animate.webp"
width="1192"
height="670"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>前方走两步并上桥后就看到了著名的 crossfield 秋叶原十字场，由于游戏里出现次数太多以至于第一眼就认出来了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/crossfield.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/crossfield_animate.webp"
width="1192"
height="670"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>一路上逛了一些店铺，不得不说真的好多扭蛋机，见到 SMT（真女神转生）主题的我也忍不住去玩了一下，虽说没抽到杰克霜精不过能抽到杰克灯笼也不错。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/SMT_Gacha.webp"
width="3472"
height="4624"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/SMT_Jack_Lantern.webp"
width="3472"
height="4624"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>最后跟着导航来到了世界的广播会馆，不过并没有看到时间机器砸在上面（笑）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Gradio.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Gradio_animate.webp"
width="1192"
height="670"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>上面的商店也有不少值得逛的，只能说整个秋叶原都很令人流连忘返，可惜由于时间不够根本就没买多少东西；而为了信息安全御守，我在另一天早上还抽空去了神田神社，工作日早上人挺少的。极为先进的御守自动贩卖机里并没有信息安全御守，所以还得等十点商店开门再购买。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Kanda_jinja_1.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Kanda_jinja_2.jpg"
width="2260"
height="1697"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Kanda_jinja_3.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>买回来的信息安全御守贴纸给我的机箱贴上了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/PC.webp"
width="4624"
height="3472"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;h1 id="bang-dream-its-mygo">BanG Dream! It&amp;rsquo;s MyGO!!!!!
&lt;/h1>&lt;p>最后就是本次圣地巡礼的重头戏：MyGO。这是本次旅行中拍摄照片数最多的项目，MyGO 与石头门的取景地一样都是集中在一个区域，给圣地巡礼省了很多来回走的时间，而且与石头门不一样，其名场面实在太多了，还在 go 的我对这些记忆犹新，所以自然就成为了占比最大的圣地巡礼项目，而我在池袋的这一天也是最爽的一天。&lt;/p>
&lt;p>中午到达池袋站，选择了 35 号出口而非其它距离西武百货更近的出口，一出站又是出现了与 P5R 涩谷圣地巡礼相似的感觉，回头一看就是熟悉的 Soyorin 诞生地。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Ikebukuro_exit_35_1.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>也不知道为什么，这张照片的天空效果十分像是画作，晚上路过时又拍了一张，这张就与原作较为相似了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Ikebukuro_exit_35_2.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Soyorin.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>然后前往西武池袋本店的空中庭院，根据直达电梯上的指示前往对应楼层，出了电梯后根据指示牌走两步就见到了第八集中初华与祥子相见的屋顶，上面还挺宽阔的，桥与周围的绿植所占的面积并不大。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/roof_garden.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Uika_and_Sakiko.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>一年相见一次，而且是在桥上，结合夏季大三角等暗示，初华与祥子也是一对十分沉重的组合呢。&lt;/p>
&lt;p>接下来的池袋周边圣地巡礼地点则主要参考&lt;a class="link" href="https://anitabi.cn/map?bangumiId=428735&amp;amp;c=139.7156%2C35.7303&amp;amp;z=16.9" target="_blank" rel="noopener"
>这个地图&lt;/a>，基本上一条街从头走到尾就逛完了。首先见到的是承载了不少回忆的卡拉 OK 店，只能说真的很像。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Karaoke.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Karaoke_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>接着则是能被当作 RiNG 原型的两栋建筑。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Mixalive.jpg"
width="1402"
height="2171"
loading="lazy"
class="gallery-image"
data-flex-grow="64"
data-flex-basis="154px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Uniqlo.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/RiNG.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>再往前走则是 MyGO 的一张主视觉图所在的十字路口了，这个确实挺难拍的，必须注意不要拍到上面很难看的桥，所以随手拍了一张算了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Ikebukuro_crossroad.jpg"
width="4189"
height="1362"
loading="lazy"
class="gallery-image"
data-flex-grow="307"
data-flex-basis="738px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/mygo_main.webp"
width="1316"
height="740"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>此时这一片区域已经差不多了，还有一两个有点难找到的地点我直接放弃了。接下来就是 Sunshine City 了，从十字路口走不到十分钟就到。在阳光下的大楼真漂亮啊。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/sunshine_city.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>爱灯斜坡就在这栋楼的旁边，只不过与原作有点区别的是咖啡店（星巴克）在二楼而不是一楼，一楼是赛百味。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/sunshine_city_slope_1.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>这里要夸一句斜坡的设计很有想法，同时兼顾了美观与无障碍。我在想象着 Soyo 和 Taki 坐在这上面的同时拍下了这张照片（笑）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/sunshine_city_slope_2.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/sunshine_city_slope_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>当然，这斜坡可不止贡献了这一个场景，在小灯快要摔下去时初华及时出手也是在这里。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/sunshine_city_slope_3.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>斜坡的另一侧则是豊島区立東池袋中央公園，就是第五集中爱音二次破防的地方。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/East_Ikebukuro_center_park.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/East_Ikebukuro_center_park_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>顺便回忆一下爱音刚开完 EVA 的眼神（笑）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Anon_crash.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>到此池袋站附近的区域已经达成圣地巡礼了，可惜由于时间不够所以没前往 Sunshine City 的星象馆，不然这里就可以同时达成 MyGO 与 P5R 的圣地巡礼（双厨狂喜）。&lt;/p>
&lt;p>接下来就是鬼子母神前区域，先给平常小灯下车的鬼子母神前站拍张照。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/guizimushenqian_station.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>出了车站后走几分钟就到了熟悉的千登世桥了，可惜周围在施工，没有樱花，也不会突然出现一个祥子把我扑倒。唉，一去不复返的白月光，把灯带到阳光下的祥子，你现在还好吗。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/qiandengshi_bridge_1.jpg"
width="2255"
height="1693"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/qiandengshi_bridge_2.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>晚上再次路过时补了一张 ED 出现过的场景，以及突然想起来还出现过“这家伙竟然无视灯”的场景。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/qiandengshi_bridge_3.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/qiandengshi_bridge_animate.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>然后就是等晚上前往小灯家附近的桥了，谷歌地图并没有标这座桥的具体名称，我也是比照着圣地巡礼地图的位置找到了这里。可以看出这座桥的颜色与动画里的有些差别，而且仔细观察就会发现第三集中祥子大喊的那个方向护栏是比另一侧更高的，根本探不出头。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/become_human_bridge_1.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>不过嘛，另外一侧还是可以探出头大喊成为人类的。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/become_human_bridge_2.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>接下来就是对比图了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/become_human_bridge_3.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/become_human_bridge_animate_1.webp"
width="834"
height="1109"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>当然还有各位对小灯告白的场景（笑）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/become_human_bridge_4.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/become_human_bridge_animate_2.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/become_human_bridge_animate_3.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/become_human_bridge_5.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/become_human_bridge_animate_4.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>最后，来飞鸟山公园圣地巡礼 Soyo 的惊天一跪。在谷歌地图找了好一会才找到喷水广场，在夜晚没什么人的公园行走确实有点恐怖，而且很残念的一点是我到的时候喷泉并没有启用。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Asukayama_park_1.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Asukayama_park_animate_1.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Asukayama_park_2.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Asukayama_park_animate_2.webp"
width="1920"
height="1080"
loading="lazy"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>虽说喷泉附近没人，但我到的时候不远处还有人在外放音乐，只能说 Soyo 那时是真的急了，不管有没有可能有人路过就下跪。&lt;/p>
&lt;h1 id="其它">其它
&lt;/h1>&lt;p>除了圣地巡礼外，还有一些有意思的地方这里也说一下。&lt;/p>
&lt;p>逛涩谷的时候特意去了在 PARCO 的任天堂东京店，店里的摆设真好看，只是商品都挺贵的，钱包着实有点难受。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Nintendo_Animal_Crossing.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Nintendo_Splatoon.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Nintendo_Link.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Nintendo_Mario.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>上野公园一带有大量的博物馆，其中的国立西洋美术馆在这段时间正好有莫奈作品特展，能在短短的一个下午看到馆方精心策划展出的大量名家真迹可是不可多得的机会，仅仅是这个展览就可以说是不枉此行了。只有少部分地方允许拍照，且能拍照的作品都是睡莲，而莫奈其实还有以塞纳河、查令十字桥等作为主题的作品，这些就只能去现场慢慢观赏了，这里放点睡莲让大家观赏一下。&lt;em>PS：在日本看印象派作品是不是搞错了什么。&lt;/em>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Monet_1.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Monet_2.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Monet_3.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Monet_4.jpg"
width="1736"
height="2312"
loading="lazy"
class="gallery-image"
data-flex-grow="75"
data-flex-basis="180px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Monet_5.jpg"
width="2312"
height="1736"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;h1 id="总结">总结
&lt;/h1>&lt;p>十分满足，这次去日本满足了大部分心愿。对我来说，去日本旅行不止意味着去陌生的地方体验此前并未试过的经历，更是一趟圆梦之旅，终于来到了多年来只在动漫与游戏中了解到的地方，来实际感受东京这座城市的气息。只有实际去过以后才会真正体会到，现实中的东京并不如 ACG 作品中的那么美好，但大街小巷里独特的市井气息还是令人流连忘返。&lt;/p>
&lt;p>购物也是东京旅游的一个常见环节，在这里展示一下我的秋叶原战利品吧。由于还在 go，所以图里占比最大的是 MyGO 的周边，还有一张 Mujica 刚出的迷你专辑；摇曳露营的盘子也十分诱人，管不住手买的 SMT 扭蛋，以及躲在角落的型月猫 Arc 钥匙扣（猫猫可爱吗）。这些东西都是在时间不多且选择困难症发作的情况下挑选出来的，对我来说每次看到它们就能想到在东京逛街的快乐。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Tokyo_2024/Akihabara_goods.webp"
width="4624"
height="3472"
loading="lazy"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="319px"
>&lt;/p>
&lt;p>最后就是一些遗憾了，没能与 &lt;a class="link" href="https://farseerfc.me/" target="_blank" rel="noopener"
>FC 老师&lt;/a>面基，秋叶原只是草草逛过，上野公园一带的博物馆也没有完整逛完，没有去附近的旧岩崎家宅邸庭园（久远寺有珠家的原型）进行魔法使之夜的圣地巡礼，以上进行圣地巡礼的作品也有些遗漏的地方，更别提还有其它在东京取景的作品被忽略掉了。不过这些遗憾让我更期待下一次的日本之旅了，只去一次根本不够啊。&lt;/p></description></item><item><title>2024 中科大信息安全大赛题解</title><link>https://viflythink.com/Hackergame_2024_writeups/</link><pubDate>Tue, 12 Nov 2024 00:00:00 +0800</pubDate><guid>https://viflythink.com/Hackergame_2024_writeups/</guid><description>&lt;img src="https://viflythink.com/Hackergame_2024_writeups/show.jpg" alt="Featured image of post 2024 中科大信息安全大赛题解" />&lt;p>又到了更新年更系列的时间，真没想到今年组委会还在 go（好吧，&lt;a class="link" href="https://viflythink.com/Tokyo_2024/#bang-dream-its-mygo" target="_blank" rel="noopener"
>其实我也是&lt;/a>，本文头图同样来源于此）。对我来说，今年花在比赛上的时间明显比往年少了，一方面是因为&lt;a class="link" href="https://viflythink.com/Tokyo_2024/" target="_blank" rel="noopener"
>刚旅游回来&lt;/a>太累了，而另一方面则是在 LLM 的帮助下一些简单的题目不再需要更多的时间来解决了。今年只拿了 1850 分，与&lt;a class="link" href="https://viflythink.com/Hackergame_2023_writeups/" target="_blank" rel="noopener"
>上一年&lt;/a>相比算是退步了，依旧在三百名左右徘徊，只能说我才是最该多练习的人（笑）。&lt;/p>
&lt;h1 id="签到">签到
&lt;/h1>&lt;p>今年的签到题又选了某知名手游来玩梗，要求在 60 秒内输入 12 种不同语言的启动，作为 CTF 玩家那肯定不会傻傻按照这个要求来做的，随便输入点什么然后点击下面的“等不及了，马上启动！”，看到跳转的 URL 有一个 pass 参数，将参数值改为 true，搞定。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2024_writeups/get_2024_qiandao_flag.jpg"
width="1920"
height="973"
loading="lazy"
alt="成功获取签到题的 flag"
class="gallery-image"
data-flex-grow="197"
data-flex-basis="473px"
>&lt;/p>
&lt;h1 id="喜欢做签到的-ctfer-你们好呀">喜欢做签到的 CTFer 你们好呀
&lt;/h1>&lt;p>Sodayo，我最喜欢做签到了，不过怎么又是选了某个手游作为题图玩梗（虽然跟签到不是同一个）。&lt;/p>
&lt;p>搜索中科大 CTF 战队招新找到了&lt;a class="link" href="https://github.com/Nebula-CTFTeam/Recruitment-2024" target="_blank" rel="noopener"
>相关文章&lt;/a>，琢磨了一会后得出结论除非这题考隐写术或者挖 Git 提交历史不然这里应该没什么好挖的，然后找到了&lt;a class="link" href="https://nebuu.la/" target="_blank" rel="noopener"
>战队主页&lt;/a>，居然是模拟终端进行交互的网页，随手试了几个指令后发现 &lt;code>env&lt;/code> 给出了第一个 flag：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2024_writeups/get_flag1_from_nebula.jpg"
width="1024"
height="163"
loading="lazy"
alt="env 拿到 Nebula 的第一个 flag"
class="gallery-image"
data-flex-grow="628"
data-flex-basis="1507px"
>&lt;/p>
&lt;p>至于另一个 flag，找不到什么思路，想到这是 web 题，尝试在前端代码里搜索 flag，发现了惊喜：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2024_writeups/search_flag_on_nebula_js.jpg"
width="811"
height="1024"
loading="lazy"
alt="在 Nebula 前端代码里搜索 flag"
class="gallery-image"
data-flex-grow="79"
data-flex-basis="190px"
>&lt;/p>
&lt;p>第一个结果看上去是 &lt;code>ls&lt;/code> 列出的结果，用 &lt;code>ls -a&lt;/code> 列出所有项目试试：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2024_writeups/get_flag2_from_nebula.jpg"
width="1024"
height="177"
loading="lazy"
alt="拿到 Nebula 的第二个 flag"
class="gallery-image"
data-flex-grow="578"
data-flex-basis="1388px"
>&lt;/p>
&lt;h1 id="猫咪问答hackergame-十周年纪念版">猫咪问答（Hackergame 十周年纪念版）
&lt;/h1>&lt;p>又见到考验信息检索能力的题目，还是十周年纪念版呢。&lt;/p>
&lt;p>第一小题，搜索“中科大信息安全大赛 2015”找到一篇报道：&lt;a class="link" href="https://tengdahuanbao.com/html/20151211c6485a87199page.html" target="_blank" rel="noopener"
>中国科学技术大学2015信息安全大赛圆满结束&lt;/a>，其中提到：&lt;/p>
&lt;blockquote>
&lt;p>主办方在10月17号晚在3A204举办了一场赛前培训&lt;/p>
&lt;/blockquote>
&lt;p>第二小题暴力破解，搜近五年的 Hackergame 新闻稿并查找里面的注册参加人数，最后发现答案是 2019 年的 Hackergame，人数是 2682。&lt;em>PS：怎么近两年的 Hackergame 新闻稿都没有精确的注册参加人数。&lt;/em>&lt;/p>
&lt;p>第三小题从问题来看这应该能从 &lt;a class="link" href="https://github.com/ustclug/hackergame2018-writeups/blob/master/official/ustcquiz/README.md" target="_blank" rel="noopener"
>Hackergame 2018 的猫咪问答题解&lt;/a> 找到答案，而且给了提示：仅由中文汉字构成，所以答案是程序员的自我修养。&lt;/p>
&lt;p>第四小题搜索“USENIX Security ustc”并把时间限定在今年就能找到&lt;a class="link" href="https://www.usenix.org/system/files/usenixsecurity24-ma-jinrui.pdf" target="_blank" rel="noopener"
>对应论文&lt;/a>，不想看论文所以把 PDF 丢给 LLM 得到答案：336。&lt;/p>
&lt;p>第五小题，可能有不少人都知道这个最近发生的争议性很大的新闻，此事掀起了新一轮有关开源与地缘政治之间关系的讨论，正好之前看到的&lt;a class="link" href="https://lwn.net/Articles/995186/" target="_blank" rel="noopener"
>对应报道&lt;/a>有附上&lt;a class="link" href="https://git.kernel.org/linus/6e90b675cf94" target="_blank" rel="noopener"
>这个提交的链接&lt;/a>，得到答案：6e90b6。&lt;/p>
&lt;p>第六小题，跑不起来 LLM 的童鞋怎么办呢，先尝试一下搜索引擎吧。谷歌输入 llama 3 tokenizer 后有个候选结果：llama 3 tokenizer online，搜索这个然后随便选择&lt;a class="link" href="https://token-counter.app/meta/llama-3" target="_blank" rel="noopener"
>一个网站&lt;/a>，把猫咪问答页面 HTML 复制粘贴上去，计算出来的 token count 是 1812，提交结果发现不对，那就靠&lt;a class="link" href="https://gist.github.com/vifly/67e439ac69eff8c4c6ae94fe2ca92b15#file-cat_answers-py" target="_blank" rel="noopener"
>脚本&lt;/a>暴破吧，最后得到的答案是 1833。&lt;/p>
&lt;h1 id="打不开的盒">打不开的盒
&lt;/h1>&lt;p>既然题目都这样说了，那先尝试打开这个 3D 模型看看吧，随手找了一个软件：&lt;a class="link" href="https://github.com/fstl-app/fstl" target="_blank" rel="noopener"
>fstl&lt;/a>，变换角度后看到了 flag：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2024_writeups/get_flag_from_stl.jpg"
width="1024"
height="586"
loading="lazy"
alt="打开 STL 文件看到 flag"
class="gallery-image"
data-flex-grow="174"
data-flex-basis="419px"
>&lt;/p>
&lt;p>&lt;em>PS：脖子快扭了，以及对于这个 flag：flag{Dr4W_Us!nG_fR3E_C4D!!w0W}，只能说 free CAD 我还真没用过。&lt;/em>&lt;/p>
&lt;h1 id="比大小王">比大小王
&lt;/h1>&lt;p>本题来源于今年一堆人在小猿口算里欺负小学生的事情（无语），看来出题人也想让大家体会一下寻找口算平台漏洞的快乐，原以为是可以像当时的小猿口算那样直接篡改分数，分析了一下前端代码后发现不行，那解题思路就只剩下把题目全自动化求解然后提交，正好适合萌新入门自动化脚本，唯一要注意的事情是求解完后要等九秒再提交，不然会出现“检测到时空穿越”的错误（上一年是不是见过这个😂），代码&lt;a class="link" href="https://gist.github.com/vifly/67e439ac69eff8c4c6ae94fe2ca92b15#file-hack_compare-py" target="_blank" rel="noopener"
>在这里&lt;/a>。&lt;/p>
&lt;h1 id="旅行照片-40">旅行照片 4.0
&lt;/h1>&lt;p>没想到今年还有社会工程学题目，只能说挖信息的方法真是多种多样啊。虽说已经连续几年见到旅行照片了，不过今年还是只完成了最简单的部分。&lt;/p>
&lt;h2 id="题目-1-2">题目 1-2
&lt;/h2>&lt;p>第一小题考虑到校门数量也不多，直接暴破吧，得到答案：东校区西门。第二小题搜索“中科大2024ACG音乐会日期”就能获得答案：20240519。&lt;/p>
&lt;h1 id="nodejs-is-web-scale">Node.js is Web Scale
&lt;/h1>&lt;p>从本题开始进入真正的 web 安全领域了，我对 JS 不怎么熟悉，后端代码里虽说有一行“Run commands which are constant and obviously safe.”这样有暗示性的注释，但我也不知道怎么利用，把代码丢给 LLM 让其分析有什么可利用的地方，直接找到了关键点：可以通过原型链污染添加任意指令。我只能表示居然还能这么玩😦，JS 真是太神奇了。执行以下两条指令即可：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bash" data-lang="Bash">&lt;span class="line">&lt;span class="cl">curl -X POST &lt;span class="s2">&amp;#34;https://&amp;lt;REPLACE_IT&amp;gt;.hack-challenge.lug.ustc.edu.cn:8443/set&amp;#34;&lt;/span> -H &lt;span class="s2">&amp;#34;Content-Type: application/json&amp;#34;&lt;/span> -d &lt;span class="s1">&amp;#39;{&amp;#34;key&amp;#34;: &amp;#34;__proto__.flag&amp;#34;, &amp;#34;value&amp;#34;: &amp;#34;cat /flag&amp;#34;}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl &lt;span class="s2">&amp;#34;https://&amp;lt;REPLACE_IT&amp;gt;.hack-challenge.lug.ustc.edu.cn:8443/execute?cmd=flag&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="paolugpt">PaoluGPT
&lt;/h1>&lt;p>既然给了后端源码那肯定要先读一遍，发现 view 函数里有不对劲的地方：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">execute_query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;select title, contents from messages where id = &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">conversation_id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个 SQL 语句居然用字符串操作，我立刻就想到了通过 SQL 注入直接拖库，询问 LLM 得到只要构造出 &lt;code>select title, contents from messages where id = '114514' or 1=1&lt;/code> 这样的语句就可以拿到 messages 表里的全部行了，至于原来字符串里的 &amp;rsquo; 如何解决呢，用&lt;a class="link" href="https://book.hacktricks.xyz/pentesting-web/sql-injection#comments" target="_blank" rel="noopener"
>注释&lt;/a>把第二个 &amp;rsquo; 给注释掉就行。兴高采烈的我尝试了将 conversation_id 设置为 &lt;code>114514' OR 1=1 --&lt;/code> 后发送请求，然后发现只返回了第一条，再次阅读代码发现是查询数据库用了 fetchone，只能返回一行。那就只能写个&lt;a class="link" href="https://gist.github.com/vifly/67e439ac69eff8c4c6ae94fe2ca92b15#file-hack_paolugpt-py" target="_blank" rel="noopener"
>脚本&lt;/a>遍历全部行来拖库了，执行脚本然后在保存的文件里查找 flag 得到本题的两个 flag。第二个 flag 需要注意 HTML 转义，其应当为 flag{enJ0y_y0uR_Sq1_&amp;amp;_1_would_xiaZHOU_hUI_guo_abeff7b2f2}。&lt;/p>
&lt;h1 id="惜字如金-30">惜字如金 3.0
&lt;/h1>&lt;p>又碰到了熟悉的惜字如金，上一年就是靠它拿到了一点 math 题的分数，今年看来也是这样。&lt;/p>
&lt;h2 id="题目-a">题目 A
&lt;/h2>&lt;p>我不知道这算 math 题吗，唯一的难点应该是有些童鞋不熟悉 Python 语法。选一个带语法高亮的编辑器打开文件并添加字符修复语法错误就行，题目甚至还很贴心地提醒你最后结果应当是每行有 80 个字符，用 Kate 编辑完成并保存后发现它在保存时自动把填充的空格去掉了，又写了个&lt;a class="link" href="https://gist.github.com/vifly/67e439ac69eff8c4c6ae94fe2ca92b15#file-padding-py" target="_blank" rel="noopener"
>脚本&lt;/a>把每一行自动填充到 80 个字符，提交拿到 flag。这里附上&lt;a class="link" href="https://gist.github.com/vifly/67e439ac69eff8c4c6ae94fe2ca92b15#file-original_answer_a-py" target="_blank" rel="noopener"
>还原后的 answer_a.py&lt;/a>。&lt;/p>
&lt;h1 id="不太分布式的软总线">不太分布式的软总线
&lt;/h1>&lt;p>看到题目，作为 Fcitx5 输入法贡献者的我瞬间开始兴奋了：这个我知道。D-Bus 作为 Linux 上常见的一种 IPC（进程间通信）手段，在很多你没有注意到的地方都有所应用，例如在 X11 下 Fcitx 主要就是通过 D-Bus 与其它应用进行通信，由作为客户端的应用发送按键等数据到作为服务端的 Fcitx，然后 Fcitx 负责返回候选词等事项，具体代码在&lt;a class="link" href="https://github.com/fcitx/fcitx5/tree/master/src/frontend/dbusfrontend" target="_blank" rel="noopener"
>这里&lt;/a>。当然，Fcitx 还通过 D-Bus 注册了其它的方法，例如各位使用 Fcitx5 的童鞋可以通过 &lt;code>dbus-send --session --print-reply --dest=org.fcitx.Fcitx5 /controller org.fcitx.Fcitx.Controller1.DebugInfo&lt;/code> 来查看当前与 Fcitx5 通信的应用及其使用的前端（Fcitx5 支持的所有前端在&lt;a class="link" href="https://github.com/fcitx/fcitx5/tree/master/src/frontend" target="_blank" rel="noopener"
>此&lt;/a>查看）。&lt;/p>
&lt;p>如何在自己的应用中使用 D-Bus 呢，只要理解作为接收方怎样做才能接收到消息就行。作为接收方，你通常需要有 name、object、interface 等并注册才能收到消息，它们的关系如下图：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2024_writeups/dbus.jpg"
width="944"
height="789"
loading="lazy"
alt="D-Bus"
class="gallery-image"
data-flex-grow="119"
data-flex-basis="287px"
>&lt;/p>
&lt;p>因上图没有体现出来，这里补充一下，每个 interface 都可以有多个属于自己的 property、method、signal，以及 name 是全局唯一的。&lt;/p>
&lt;p>现在用刚学到的知识来从发送端的角度理解一下上面的 Fcitx5 的例子：&lt;code>org.fcitx.Fcitx5&lt;/code> 就是 name（可以理解为监听的地址），&lt;code>/controller&lt;/code> 是 object（更准确来说是 object path），而 &lt;code>org.fcitx.Fcitx.Controller1.DebugInfo&lt;/code> 则是 method。还有一点要注意，&lt;code>--session&lt;/code> 参数指示 dbus-send 把消息发送到 session 总线上，与之相对的是 &lt;code>--system&lt;/code> 参数，顾名思义它会把消息发送到系统总线，按&lt;a class="link" href="https://elmarco.pages.freedesktop.org/dbus-page/docs/getting-started/" target="_blank" rel="noopener"
>这里&lt;/a>的说法系统总线是总是可以被访问的。&lt;/p>
&lt;p>现在我们开始看本题的源码（flagserver.c），可以看到其监听系统总线（说明我们给其发送的消息总是可达的）并且注册的 name 为 &lt;code>cn.edu.ustc.lug.hack.FlagService&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-C" data-lang="C">&lt;span class="line">&lt;span class="cl">&lt;span class="n">owner_id&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">g_bus_own_name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">G_BUS_TYPE_SYSTEM&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;cn.edu.ustc.lug.hack.FlagService&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">G_BUS_NAME_OWNER_FLAGS_NONE&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">on_bus_acquired&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">on_name_acquired&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">on_name_lost&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">NULL&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">NULL&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注册的 object path：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-C" data-lang="C">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">g_dbus_connection_register_object&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">connection&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;/cn/edu/ustc/lug/hack/FlagService&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">introspection_data&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">interfaces&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">interface_vtable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">NULL&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="cm">/* user_data */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">NULL&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="cm">/* GDestroyNotify */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>至于 method 则是在 xml 字符串里，这里就不贴了。&lt;/p>
&lt;h2 id="what-dbus-gonna-do">What DBus Gonna Do?
&lt;/h2>&lt;p>要求传入一个特定的字符串：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-C" data-lang="C">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nf">g_variant_is_of_type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parameters&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nf">G_VARIANT_TYPE&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;(s)&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nf">respond_error_msg&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">invocation&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Give me a string, please.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>按照 &lt;a class="link" href="https://dbus.freedesktop.org/doc/dbus-send.1.html" target="_blank" rel="noopener"
>dbus-send 的文档&lt;/a>可以构造出来以下指令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bash" data-lang="Bash">&lt;span class="line">&lt;span class="cl">dbus-send --system --print-reply --dest&lt;span class="o">=&lt;/span>cn.edu.ustc.lug.hack.FlagService /cn/edu/ustc/lug/hack/FlagService cn.edu.ustc.lug.hack.FlagService.GetFlag1 string:&lt;span class="s2">&amp;#34;Please give me flag1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>将其保存为 shell 文件并上传就搞定了，与 Fcitx5 的例子相比也就多了一个需要传入的参数，应该不难理解。&lt;/p>
&lt;h2 id="if-i-could-be-a-file-descriptor">If I Could Be A File Descriptor
&lt;/h2>&lt;p>要求必须是文件描述符：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-C" data-lang="C">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nf">g_variant_is_of_type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parameters&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nf">G_VARIANT_TYPE&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;(h)&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nf">respond_error_msg&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">invocation&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Give me a file descriptor, please.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>而在 &lt;a class="link" href="https://dbus.freedesktop.org/doc/dbus-send.1.html" target="_blank" rel="noopener"
>dbus-send 的文档&lt;/a> 列举出来的支持的数据类型中并不包括文件描述符，所以只能写 C 语言代码了。&lt;/p>
&lt;p>还注意到代码里会检测文件描述符是否存在于文件系统：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-C" data-lang="C">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">g_snprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">sizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="s">&amp;#34;/proc/self/fd/%d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fd&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">gchar&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">link&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nf">g_file_read_link&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">NULL&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">link&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="nb">NULL&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">g_strstr_len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">link&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nf">respond_error_msg&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">invocation&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Please don&amp;#39;t give me a file on disk to trick me!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我从来没觉得写 C 快乐过，把这些需求丢给 LLM，其给出了使用 socket pair 的办法，代码在&lt;a class="link" href="https://gist.github.com/vifly/67e439ac69eff8c4c6ae94fe2ca92b15#file-dbus_2-c" target="_blank" rel="noopener"
>此&lt;/a>，运行以下指令编译然后上传：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bash" data-lang="Bash">&lt;span class="line">&lt;span class="cl">gcc dbus_2.c &lt;span class="sb">`&lt;/span>pkg-config --cflags --libs gio-2.0&lt;span class="sb">`&lt;/span> -o dbus_2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="总结">总结
&lt;/h1>&lt;p>今年的 SQL 和 D-Bus 题还是玩的很开心的，前者尝试构造 payload 的过程还是相当有趣的，而 D-Bus 的话正好是我之前想写的内容。另外 PowerfulShell 是相当令我残念的一道题，都已经找到 &lt;code>$-&lt;/code> 和 &lt;code>$_&lt;/code> 这样的变量了，就是没想到要赋值给 &lt;code>_1&lt;/code> 这样的变量。可惜今年毕竟投入的时间不多，看到 C 和汇编基本都是绕路走，像是 D-Bus 的第三小题都懒得看了，事后看题解发现也不难，不知道明年是否会有更多的时间来玩呢，我们下一年再见吧。&lt;/p></description></item><item><title>2023 中科大信息安全大赛题解</title><link>https://viflythink.com/Hackergame_2023_writeups/</link><pubDate>Sun, 05 Nov 2023 00:00:00 +0800</pubDate><guid>https://viflythink.com/Hackergame_2023_writeups/</guid><description>&lt;img src="https://viflythink.com/Hackergame_2023_writeups/show.jpg" alt="Featured image of post 2023 中科大信息安全大赛题解" />&lt;p>又到了更新年更系列的时间，本文的题图来自于今年 Hackergame 中的“逆向工程不需要 F5“这一道题。对于今年的 Hackergame，能明显看到为了吸引更多新人所做的努力，包括增加了不少送分题，还新增了 AI 方向的题目，这使得博主拿到了 2600 分，正好是&lt;a class="link" href="https://viflythink.com/Hackergame_2022_writeups/" target="_blank" rel="noopener"
>上一年&lt;/a>得分的两倍（虽说如此，排名也没怎么上升）。由于今年解决的题目变多了，所以本文的总文字量也比往年明显增多，不知道明年会不会更长呢，敬请期待吧。&lt;/p>
&lt;h1 id="hackergame-启动">Hackergame 启动
&lt;/h1>&lt;p>这个题目很显然玩的是“XX 启动”的梗，点进去以后要求录一段音频并达到与原音频 100% 相似的程度，由于是 web 题，所以先尝试录了一段音频（当然我禁用了麦克风），看到网页跳转到了一个新 URL 并显示相似度有百分之七十多，而 URL 中的 similarity 参数与之对应，把参数改为 100 就会跳到另一个界面，点击“获取 flag”就拿到 flag 了（&lt;em>PS:为啥还配了音乐啊&lt;/em>）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2023_writeups/get_2023_qiandao_flag.jpg"
width="2560"
height="1331"
loading="lazy"
alt="成功获取签到题的 flag"
class="gallery-image"
data-flex-grow="192"
data-flex-basis="461px"
>&lt;/p>
&lt;h1 id="猫咪小测">猫咪小测
&lt;/h1>&lt;p>又是每年必有的考验信息检索能力的题目，不过为什么今年我又没用上暴力破解的方法。&lt;/p>
&lt;p>第一小题，如何在图书馆找到一本图书的大致位置呢，一般来说图书馆都会按图书类别将其放到不同楼层，而《A Classical Introduction To Modern Number Theory 2nd ed.》是一本外文书籍，所以按&lt;a class="link" href="https://lib.ustc.edu.cn/%E6%9C%AC%E9%A6%86%E6%A6%82%E5%86%B5/%E5%9B%BE%E4%B9%A6%E9%A6%86%E6%A6%82%E5%86%B5%E5%85%B6%E4%BB%96%E6%96%87%E6%A1%A3/%E8%A5%BF%E5%8C%BA%E5%9B%BE%E4%B9%A6%E9%A6%86%E7%AE%80%E4%BB%8B/" target="_blank" rel="noopener"
>西区图书馆简介&lt;/a>
所说外文书库的位置是 12 楼，这就是正确答案。&lt;/p>
&lt;p>第二小题提到的论文挺有趣的，没想到有人会提出这个问题，直接在 Google 搜索“universe chicken density site:arxiv.org”且把时间限定在一年内就找到了&lt;a class="link" href="https://arxiv.org/abs/2303.17626" target="_blank" rel="noopener"
>对应论文&lt;/a>，从论文摘要可以得出 23 这个答案。&lt;/p>
&lt;p>第三小题直接搜索“build kernel support bbr”就行，答案是 CONFIG_TCP_CONG_BBR。&lt;/p>
&lt;p>第四小题搜索“mypy infinite loop”且把时间限定在一年内，从一堆结果中找到了&lt;a class="link" href="https://drops.dagstuhl.de/opus/volltexte/2023/18237/pdf/LIPIcs-ECOOP-2023-44.pdf" target="_blank" rel="noopener"
>对应论文&lt;/a>，第一页中摘要下方有一行“Supplementary Material Software (ECOOP 2023 Artifact Evaluation approved artifact)”，即使不怎么阅读论文，从 2023 这个词也可以看出 ECOOP 大概率是会议名称。&lt;/p>
&lt;h1 id="更深更暗">更深更暗
&lt;/h1>&lt;p>没想到这道题目这么简单，随手翻了一下 main.js 就找到了获得 flag 的方法，如下图所示，getFlag 函数用于计算 flag，而且还贴心地写了一行注释提醒我函数输入就是自己的 token，所以直接在浏览器控制台里执行函数内的逻辑就拿到了 flag。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2023_writeups/get_deeper_and_darker_flag.jpg"
width="2447"
height="1266"
loading="lazy"
alt="更深更暗中的 getFlag 函数"
class="gallery-image"
data-flex-grow="193"
data-flex-basis="463px"
>&lt;/p>
&lt;h1 id="旅行照片-30">旅行照片 3.0
&lt;/h1>&lt;p>今年延续前两年的传统又出了一道社会工程学题目，而且每年的解题思路都有所不同，值得好评。&lt;/p>
&lt;h2 id="题目-1-2">题目 1-2
&lt;/h2>&lt;p>首先从奖牌照片中的“Excoluisse per artes”文字经过 Google 搜索后得知这是诺贝尔奖，接着搜索“Nobel prize Koshiba”就能知道对应的是诺贝尔物理学奖得主 Masatoshi Koshiba（小柴昌俊），任职于东京大学，所以学长在东京大学读书。然后在&lt;a class="link" href="https://en.wikipedia.org/wiki/List_of_University_of_Tokyo_people" target="_blank" rel="noopener"
>东京大学校友列表&lt;/a>里找到其他诺贝尔物理学奖得主，经过逐个确认后发现 Takaaki Kajita（梶田隆章）是出生最晚的，在其&lt;a class="link" href="https://en.wikipedia.org/wiki/Takaaki_Kajita" target="_blank" rel="noopener"
>简介页面&lt;/a>中看到得诺贝尔奖时在 Institute for Cosmic Ray Research（简称 ICRR）工作，故得到了第二小题的答案。然而第一小题令我很头痛，没有从照片上找到可以确认见面日期的信息，最后从题目提到的“今年暑假”得到灵感，直接暴力尝试今年七月到八月的所有日期，最后试出来答案是 2023-08-10。&lt;/p>
&lt;h2 id="题目-3-4">题目 3-4
&lt;/h2>&lt;p>首先，从 Google 地图可以看出东京大学附近的博物馆都集中在上野公园一带，然后在 Google 街景地图中开始寻找与照片相似的地方，一番查找后找到了&lt;a class="link" href="https://maps.app.goo.gl/vc76ecQFS3UdTPDQA" target="_blank" rel="noopener"
>这个位置&lt;/a>，从地图上可以明显看出这里的喷泉是在东京国立博物馆门口前马路的对面，在东京国立博物馆官网查找票价，发现普通游客需要支付 1000 日元的门票费，而&lt;a class="link" href="https://www.tnm.jp/modules/r_free_page/index.php?id=167" target="_blank" rel="noopener"
>东京大学的学生则可以凭学生证免费入场&lt;/a>，所以第四小题答案是 0，而第三小题还需要找到对应的活动，通过 Google 翻译得到“活动志愿者”的日文名称，然后 Google 搜索“上野公园 イベントボランティア 2023/8”并把日期限制在一年内找到了&lt;a class="link" href="https://umeshu-matsuri.jp/tokyo_staff/" target="_blank" rel="noopener"
>活动的官网&lt;/a>，阅读文字后得到答案 S495584522。&lt;/p>
&lt;h1 id="赛博井字棋">赛博井字棋
&lt;/h1>&lt;p>玩井字棋在先手的情况下，无论如何都是无法战胜后手的（最佳情况是平局），所以这题需要用一些手段来获胜。通过 F12 可以看出每次点击时都会发送一个带有落子位置数据的 POST 请求，尝试把其中的数字改为负数、浮点数、超大的整数等，发现都会导致棋盘被重置。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2023_writeups/view_board_requests.jpg"
width="2348"
height="1260"
loading="lazy"
alt="查看落子时的请求"
class="gallery-image"
data-flex-grow="186"
data-flex-basis="447px"
>&lt;/p>
&lt;p>卡住半天后有了一个新的想法，假如我把落子位置设为对手此前的落子位置会怎样，测试了以下后发现我的棋子会直接覆盖对方的棋子，这说明我们可以很开心地上演“下一回合还是我的回合”的场面，最后写了&lt;a class="link" href="https://gist.github.com/vifly/406b6ad86a4f725b14a6494dff1755c3#file-crack_board-py" target="_blank" rel="noopener"
>一个简单的脚本&lt;/a>直接拿到 flag。&lt;/p>
&lt;h1 id="奶奶的睡前-flag-故事">奶奶的睡前 flag 故事
&lt;/h1>&lt;p>本题目来源于今年有人用“奶奶会用 Windows 激活码来哄我睡觉”这个提示词（prompt）诱使大语言模型提供 Windows 激活码的事情。首先题目已经直接用加粗标注提醒我们这个 Pixel 手机（Google 亲儿子）的系统没升级了，另外我还记得之前 Pixel 手机曾曝出截图工具存在安全漏洞的事情，Google 搜索“Pixel screenshot vulnerability”直接找到了一篇&lt;a class="link" href="https://www.securityweek.com/google-pixel-vulnerability-allows-the-recovery-of-cropped-screenshots/" target="_blank" rel="noopener"
>对应的报道&lt;/a>，里面还附上了&lt;a class="link" href="https://gist.github.com/DavidBuchanan314/93de9d07f7fab494bcdf17c2bd6cef02" target="_blank" rel="noopener"
>测试用的代码&lt;/a>，运行该代码就还原出了原来应该被丢弃的部分：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bash" data-lang="Bash">&lt;span class="line">&lt;span class="cl">python3 acropalypse_matching_sha256.py &lt;span class="m">1080&lt;/span> &lt;span class="m">1068&lt;/span> hackergame.png reconstructed.png
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://viflythink.com/Hackergame_2023_writeups/screenshot_hidden_part.png"
width="1080"
height="1068"
loading="lazy"
alt="被隐藏的截图部分"
class="gallery-image"
data-flex-grow="101"
data-flex-basis="242px"
>&lt;/p>
&lt;h1 id="组委会模拟器">组委会模拟器
&lt;/h1>&lt;p>还是先靠 F12 找到整个过程中的网络请求，发现其逻辑是先获取一个含有聊天消息的 json，然后在屏幕上按照 delay 字段规定的时间开始显示这些消息，尝试点击一条消息进行撤回，找到对应的 POST 请求。搞清楚后开始编写代码把符合条件的消息全都撤回，但是在执行几次撤回后出现了“检测到时空穿越”的错误😦。至此需要做的事情就很清楚了，获取所有消息后需要在经过每条消息的 delay 字段指示的时间后进行判断，如果是需要撤回的消息那便在 3 秒内撤回，对应&lt;a class="link" href="https://gist.github.com/vifly/406b6ad86a4f725b14a6494dff1755c3#file-hack_delete_msg-py" target="_blank" rel="noopener"
>这个脚本&lt;/a>。&lt;/p>
&lt;h1 id="虫">虫
&lt;/h1>&lt;p>我并非无线电爱好者，所以这题对我来说有点难度。根据题目所说的“接收来自国际空间站（ISS）的图片”尝试 Google“recv image from ISS”，得知需要通过 SSTV（慢电视扫描）来从接收到的无线电音频信号中接收图片这一点信息，然后想到能否直接用 SSTV 对题目给的音频文件进行解码以获取图片呢。Google 后找到&lt;a class="link" href="https://www.chonky.net/hamradio/decoding-sstv-from-a-file-on-a-linux-system" target="_blank" rel="noopener"
>一篇教程&lt;/a>，但其中用的软件有点过时，我都已经用 pipewire 替代 pulseaudio 了，所以我所做的操作就是先安装 QSSTV 这个软件，打开后点击开始接收，然后在命令行运行（甚至都不需要 VLC）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bash" data-lang="Bash">&lt;span class="line">&lt;span class="cl">pw-play --target &lt;span class="s2">&amp;#34;QSSTV&amp;#34;&lt;/span> insect.wav
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这就可以在 QSSTV 中看到图片像素一行行地出现了，怪不得这个叫慢电视扫描，以及接收过程中的信号显示图表看上去很好玩，推荐没用过 QSSTV 的童鞋试一试。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2023_writeups/qsstv_get_flag.jpg"
width="1123"
height="910"
loading="lazy"
alt="通过 QSSTV 获取 flag 图片"
class="gallery-image"
data-flex-grow="123"
data-flex-basis="296px"
>&lt;/p>
&lt;h1 id="json--yaml">JSON ⊂ YAML?
&lt;/h1>&lt;p>做这题前我真没想到 YAML 有这么多的坑，甚至都不是 JSON 的超集（之前一直认为是的），第二小题试了一下“\t”以及大致扫了一遍 ruamel-yaml 的 bug 列表后就放弃了。总而言之我以后还是用 TOML 作为配置文件格式吧。&lt;em>PS：万一明年 Hackergame 用 TOML 出了一道类似的题目那就&amp;hellip;&lt;/em>&lt;/p>
&lt;h2 id="json--yaml-11">JSON ⊄ YAML 1.1
&lt;/h2>&lt;p>找到了一篇&lt;a class="link" href="https://john-millikin.com/json-is-not-a-yaml-subset" target="_blank" rel="noopener"
>说明 JSON 并非 YAML 子集的文章&lt;/a>，按照里面提供的例子输入 {&amp;ldquo;a&amp;rdquo;: 1e2} 拿到了本题的 flag。&lt;/p>
&lt;h1 id="git-git">Git? Git!
&lt;/h1>&lt;p>没想到出题人还记得怒斥“不讲武德”的马保国啊。首先尝试用&lt;a class="link" href="http://thegreycorner.com/pentesting_stuff/writeups/gitsecrets.html" target="_blank" rel="noopener"
>这里&lt;/a>提到的 &lt;code>git grep -n 'flag' $(git rev-list --all)&lt;/code> 在所有提交涉及的文件中查找 flag，没有找到，看上去删的很干净。正当我束手无策时，破罐子破摔地试了进入 .git/objects 查看了一下，发现文件并不多，考虑到即使相关提交已经被彻底干掉了，文件的历史版本在这里也会被继续保留，所以说能不能读取这里的文件找到 flag 呢，找到&lt;a class="link" href="https://matthew-brett.github.io/curious-git/reading_git_objects.html" target="_blank" rel="noopener"
>相关教程&lt;/a>后写了&lt;a class="link" href="https://gist.github.com/vifly/406b6ad86a4f725b14a6494dff1755c3#file-find_flag_from_git_objects-py" target="_blank" rel="noopener"
>一个脚本&lt;/a>进行尝试，成功找到了 flag（不解码提取到的内容是因为有些字符不能被 UTF-8 解码）。&lt;/p>
&lt;h1 id="http-集邮册">HTTP 集邮册
&lt;/h1>&lt;p>第二小题做不出来导致第三小题也没心思做了，草草地拿下第一题就躺了。&lt;/p>
&lt;h2 id="5-种状态码">5 种状态码
&lt;/h2>&lt;p>什么都不改直接点“发送”即可拿到 200。&lt;/p>
&lt;p>随便删点东西导致这个请求不合法以拿到 400。&lt;/p>
&lt;pre tabindex="0">&lt;code>GET HTTP/1.1\r\n
Host: example.com\r\n\r\n
&lt;/code>&lt;/pre>&lt;p>尝试访问非默认的路径拿到了 404。&lt;/p>
&lt;pre tabindex="0">&lt;code>GET /test HTTP/1.1\r\n
Host: example.com\r\n\r\n
&lt;/code>&lt;/pre>&lt;p>既然尝试了访问非默认的路径，不妨再试试用别的方法访问默认路径，于是拿到了 405。&lt;/p>
&lt;pre tabindex="0">&lt;code>POST / HTTP/1.1\r\n
Host: example.com\r\n\r\n
&lt;/code>&lt;/pre>&lt;p>最后，我记得 Nginx 默认配置下还没支持 HTTP 2.0，所以靠此拿到了 505。&lt;/p>
&lt;pre tabindex="0">&lt;code>GET / HTTP/2.0\r\n
Host: example.com\r\n\r\n
&lt;/code>&lt;/pre>&lt;h1 id="docker-for-everyone">Docker for Everyone
&lt;/h1>&lt;p>Docker 的安全性也是一个老生常谈的问题了，本题虽不是把 Docker 当沙盒用这样的经典情况，但 X 忽略了 docker 用户组对 Unix 用户管理体系的致命威胁（毕竟 Docker 本身可没提供完善的用户权限管理）。&lt;a class="link" href="https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user" target="_blank" rel="noopener"
>Docker 官方文档&lt;/a>明确指出：&lt;/p>
&lt;blockquote>
&lt;p>The &lt;code>docker&lt;/code> group grants root-level privileges to the user.&lt;/p>
&lt;/blockquote>
&lt;p>所以本题需要解决的问题就是：作为 docker 用户组内的用户，如何获取等同于 Root 用户的权限。找到了一个&lt;a class="link" href="https://gist.github.com/nileshsimaria/82ed9eaf116832a8d7128ecb08dddc11" target="_blank" rel="noopener"
>简单的教程&lt;/a>，由于题目提到 /flag 是软链接，所以先 &lt;code>ls -l /flag&lt;/code> 查看具体情况，果不其然是 Root 用户才能读取的文件，链接到 /dev/shm/flag，所以执行以下操作就能拿到 flag：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bash" data-lang="Bash">&lt;span class="line">&lt;span class="cl">docker run -it --rm -v /:/host alpine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat /host/dev/shm/flag
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="惜字如金-20">惜字如金 2.0
&lt;/h1>&lt;p>难得解决了一道 math 题，做完以后发现只需要仔细观察即可解决，并不需要什么密码学知识。分析过程可能有些长，实际上我写起来都有点累，不感兴趣的读者直接跳过即可。&lt;/p>
&lt;p>查看代码，发现其核心逻辑就是按给定的索引从密码本中取出字符并打印，但这个密码本被惜字如金处理了，需要设法还原。首先查看密码本所在的 get_cod_dict 函数，从 &lt;code>check_equals(set(len(s) for s in cod_dict), {24})&lt;/code> 可以看出 cod_dict 内每个列表的字符串长度都应当是 24，另外肉眼可以看出经过惜字如金处理后函数里多次添加的列表长度都是一样的（记得用等宽字体），统计出来的长度都是 23，说明惜字如金处理把每个列表里的一位字符给删掉了。得知这些信息后我差点就想仿照去年的惜字如金题解那样开始暴力破解还原数据了，但是看到 &lt;code>check_equals(flag.index('flag{'), 0)&lt;/code> 等代码后觉得可以通过这些判断条件排除一些选项节约暴破时间。&lt;/p>
&lt;p>首先我们知道最后得到的 flag 应当以 flag{ 作为开头，} 作为结束，那么先从 f 开始，对应的索引值为 53，那么说明第三个列表的第六个字符应当是 f，但实际的列表第六位是 5，前一位才是 f，说明这里的 f 被删掉了，直接还原一个列表；接着是 l，对应 41，说明第二个列表的第十八个字符应当是 l，实际也没错，那么说明 l 之后有一个字符被删掉了；a 对应 85，说明第四个列表的第十四个字符应当是 a，实际也确实如此，但前一个字符也是 a，没法得知到底是 a 之前还是之后的字符被删掉了；g 对应 109，说明第五个列表的第十四个字符应当是 g，实际对应位置的却是 u，g 在前一位，说明 g 之前有一个字符被删掉了；接着是 {，对应 75，说明第四个列表的第四个字符应当是 {，实际上是 l，{ 在前一位，由此我们终于知道第四个列表是 { 之前的 c 或 t 被惜字如金删掉了；最后是 }，对应 28，在第二个列表，由于其在第十八个字符（l）之前，所以没有告诉我们新的信息。&lt;/p>
&lt;p>整合以上信息后得到以下列表（用空格分隔已知与未知哪里被删掉的部分）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">cod_dict&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;nymeh1niwemflcir}echaet &amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cod_dict&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;a3g7}kidgojernoetl sup?h&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cod_dict&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;ulw!ff5soadrhwnrsnstnoeq&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cod_dict&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;ct {l-findiehaai{oveatas&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cod_dict&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;ty9kxborszst guyd?!blm-p&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接下来是否要尝试还原未知的字符呢，我们尝试看一下需要用到的索引是否存在于未知哪里被删掉的部分（第一个列表除外）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">53&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">41&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">85&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">109&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">75&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">33&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">48&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">77&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">90&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">17&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">118&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">36&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">25&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">13&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">89&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">90&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">63&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">25&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">31&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">77&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">27&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">118&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">24&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">62&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">54&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">61&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">25&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">63&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">77&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">36&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">32&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">67&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">113&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">28&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">check&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">42&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">47&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">check&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">72&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">74&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">check&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">96&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">108&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>看上去给出的索引都刚好没有落在未知哪里被删掉的部分（第一个列表未知），据此还原密码本并修正语法错误后试试打印 flag，提交后发现答案正确，本题解决。最后附上&lt;a class="link" href="https://gist.github.com/vifly/406b6ad86a4f725b14a6494dff1755c3#file-xzrj_print_flag-py" target="_blank" rel="noopener"
>我还原出来的代码&lt;/a>，直接运行即可获取 flag。&lt;/p>
&lt;h1 id="-高频率星球">🪐 高频率星球
&lt;/h1>&lt;p>本题没什么难度，就是解题过程实在让我有点提不起劲。用 &lt;code>asciinema play asciinema_restore.rec&lt;/code> 可以看到 flag.js 的内容被打印出来，如果直接把打印的内容重定向到文件里就会发现其中有很多的转义字符，这里我直接&lt;a class="link" href="https://gist.github.com/vifly/406b6ad86a4f725b14a6494dff1755c3#file-player-py" target="_blank" rel="noopener"
>修改 asciinema 对应的代码&lt;/a>令其先清除一部分转义字符再将内容保存到 rec.js 中，修改代码后运行 &lt;code>asciinema play asciinema_restore.rec&lt;/code> 得到 rec.js，然后根据 &lt;code>node rec.js&lt;/code> 的报错信息修改 rec.js，直到它能正确打印 flag 为止。&lt;/p>
&lt;h1 id="-小型大语言模型星球">🪐 小型大语言模型星球
&lt;/h1>&lt;p>如何通过提示词操控语言模型输出特定的回答是目前安全领域的热门方向，Hackergame 今年也顺应潮流新增了这道涉及语言模型的 AI 题。作为炼丹师看到这道题目也是一下就起劲了，只不过试了一下提示词后才发现使用的语言模型不是像 ChatGPT 这样用于聊天任务的模型，而是专门用于补全任务，即根据给定的提示词（或者说上文），生成有关联的下文。这导致原本用于 GPT 的经典提示词都没法使用了，所以第一小题我用一个取巧的方法解决了，至于剩下的小题，理论上可以训练一个生成对抗网络（GAN）来找到能让语言模型输出特定词语的提示词，但由于实在没空遂放弃。&lt;/p>
&lt;h2 id="you-are-smart">You Are Smart
&lt;/h2>&lt;p>翻看 &lt;a class="link" href="https://arxiv.org/pdf/2305.07759.pdf" target="_blank" rel="noopener"
>TinyStories 对应的论文&lt;/a>，发现其是用 LLM 生成了一批数据后用于训练一个小型语言模型，既然是用梯度下降法训练的，那么模型肯定会在相当程度上拟合训练集，下载接近 2G 的&lt;a class="link" href="https://huggingface.co/datasets/roneneldan/TinyStories/blob/main/TinyStories-train.txt" target="_blank" rel="noopener"
>训练集&lt;/a>到本地，然后 &lt;code>rg &amp;quot;you are smart&amp;quot; TinyStories-train.txt&lt;/code> 找到含有所需输出的句子，挑了一句的前半部分作为提示词输入模型（我选的是“You are not silly,”，在第 3738500 行出现），成功让其输出含有“you are smart”的句子就解决了。&lt;/p>
&lt;h1 id="为什么要打开-flag-">为什么要打开 /flag 😡
&lt;/h1>&lt;h2 id="ld_preload">LD_PRELOAD
&lt;/h2>&lt;p>对于在 Linux 上给 CLI 应用设置过代理的人来说，proxychains 是大概率用过的软件，其原理就是通过 LD_PRELOAD 这个环境变量劫持应用所用到的动态链接库，让其使用经过魔改遵循 proxychains 代理设置的用于网络通信的函数。当我们的 fopen 被同样的方式劫持后该如何打开想要的文件呢，很简单，参考给 C 语言软件实现插件系统的做法，在运行时加载正确的 glibc 即可，毕竟 LD_PRELOAD 只能影响初始化时的行为，但运行时加载就无能为力了。&lt;/p>
&lt;p>所用到的代码在&lt;a class="link" href="https://gist.github.com/vifly/406b6ad86a4f725b14a6494dff1755c3#file-read_real_flag-c" target="_blank" rel="noopener"
>这&lt;/a>，需要注意的就是服务端的 decode 没做异常处理，所以 printf 时做了一点过滤以防止出现无法解码的内容，在本地的 Debian 12 环境中编译好后上传即可。&lt;/p>
&lt;h1 id="总结">总结
&lt;/h1>&lt;p>由于题目量增多，今年可以说是在马不停蹄地尝试解题，在不同的待解题目之间来回切换。最开心的当然是解决了 math 第一题与 binary 的第一小题，几次参赛的经历让我发现自己遇到汇编题基本都没辙，没法让自己的思维和二进制的数据亲密贴贴（笑），但今年不需要解析汇编代码的 LD_PRELOAD 题还是没问题的嘛。希望明年的 Hackergame 能继续玩梗，多来点有趣的题目。&lt;/p></description></item><item><title>我的新 Arch</title><link>https://viflythink.com/New-Install-Arch/</link><pubDate>Tue, 08 Aug 2023 00:00:00 +0800</pubDate><guid>https://viflythink.com/New-Install-Arch/</guid><description>&lt;img src="https://viflythink.com/New-Install-Arch/show.jpg" alt="Featured image of post 我的新 Arch" />&lt;p>前段时间存储相关的元件价格大跳水，博主终于等到了这个时机给自己组装了一台期待已久的台式机。不得不说今年的 PC 组件价格十分魔幻，SSD 甚至能比 HDD 便宜，于是我的台式机只配备了 SSD，另外基于不打算给老黄送钱的心理，也暂时没配置独显，以上使得自己的组装工作避免了一大堆麻烦，不用给 HDD 接 SATA 线，也不用给独显接电源线，整个装机过程非常的轻松愉快。在电脑能开机后，接下来当然是给它装上熟悉的 Arch Linux 用来日常工作与娱乐。尽管在之前的&lt;a class="link" href="https://viflythink.com/Try_Arch_Linux/" target="_blank" rel="noopener"
>从 Debian 迁移到 Arch Linux&lt;/a> 一文已经说过了我是如何安装 Arch 的，然后还安利了一部分软件，但时过境迁，由于自己能力的进步与发现了更好的软件，这回也折腾了一些之前没试过的新玩意，所以本文既是新安装 Arch 的随手记录，也是对旧文的一个更新，推荐与上面的旧文对比阅读。&lt;/p>
&lt;h1 id="安装基本系统">安装基本系统
&lt;/h1>&lt;p>与之前相比，这次安装 Arch 就大胆地实验了一些没尝试过的新玩意。之前完全按教程配置双系统时使用了 GRUB 作为引导加载程序（boot loader），而由于这次不打算在电脑上安装 Windows，所以用 systemd-boot 取而代之。然后也使用了 Btrfs 作为我的新 Arch 的根文件系统（具体原因见下文），当然，还要加上 LUKS 全盘加密，为了方便硬盘解密，还要配置 &lt;a class="link" href="https://0pointer.net/blog/unlocking-luks2-volumes-with-tpm2-fido2-pkcs11-security-hardware-on-systemd-248.html" target="_blank" rel="noopener"
>systemd 248 版本新增的 systemd-cryptenroll&lt;/a> 让我能用 YubiKey 开机自动解密硬盘，避免输入一长串密码。以上组合使得我能参考的资料极其有限，仅仅 systemd-boot + Btrfs 的话看 Arch Wiki 足矣，但加上全盘加密后事情就变得相当复杂，&lt;a class="link" href="https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system#Btrfs_subvolumes_with_swap" target="_blank" rel="noopener"
>Arch Wiki 上的 Btrfs + LUKS 示例&lt;/a>是假设用户也加密 /boot 的（须 EFI 与 boot 分区不同），仅仅不加密 EFI，所以只能用 GRUB，而我的分区规划是把 EFI 分区挂载到 /boot（即两者为同一个东西），且我的威胁模型中也没有那么高级的攻击者，因此我不打算加密 /boot。不同的情况导致我不得不另外查找资料，所幸的是还能找到一篇 &lt;a class="link" href="https://nerdstuff.org/posts/2020/2020-004_arch_linux_luks_btrfs_systemd-boot/" target="_blank" rel="noopener"
>Installing Arch Linux with Btrfs, systemd-boot and LUKS&lt;/a>。根据这篇文章与 Arch Wiki 安装好了新系统，完成后确认可以通过密码解锁硬盘，然后还得研究如何使用 systemd-cryptenroll。按上面的文章配置是肯定不行的，initramfs 里都没有 systemd，开机根本就不会检测 YubiKey。为此我找到了一篇跟自己的需求较为相似的 &lt;a class="link" href="https://lemmy.eus/post/2898" target="_blank" rel="noopener"
>Setting up Arch + LUKS + BTRFS + systemd-boot + apparmor + Secure Boot + TPM 2.0&lt;/a>，尽管我不打算像这篇文章一样配置安全启动与 TPM 以自动解锁硬盘，但我们都是使用 systemd-cryptenroll 的，按照其中的思路修改一下配置，转而使用 YubiKey 是没问题的。systemd-cryptenroll 支持的设备类型非常广泛，FIDO 2、TPM 2 与 PKCS#11 都可以，而 YubiKey 则能充当以上除了 TPM 外的其它安全设备（除了贵以外没什么缺点），这里我把 YubiKey 当作 FIDO 2 设备使用。经过折腾，最终确认 /etc/mkinitcpio.conf 应该包含以下内容（应该还可以删减一些 Hook，懒得试了）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bash" data-lang="Bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">HOOKS&lt;/span>&lt;span class="o">=(&lt;/span>base udev systemd autodetect modconf kms keyboard sd-vconsole block sd-encrypt btrfs filesystems fsck&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>/boot/loader/entries/arch.conf 则应该是（假设使用 linux 内核）：&lt;/p>
&lt;pre tabindex="0">&lt;code>title Arch Linux
linux /vmlinuz-linux
initrd /initramfs-linux.img
options rd.luks.name=&amp;lt;Your-UUID&amp;gt;=luks root=/dev/mapper/luks rootflags=subvol=@ rd.luks.options=&amp;lt;Your-UUID&amp;gt;=fido2-device=auto,discard rw
&lt;/code>&lt;/pre>&lt;p>这里提醒一下想照抄作业的读者，我并没有加密 /boot，没有启用安全启动，且在挂载加密盘时允许 discard，这都会降低安全性，如果对此比较在意请选择别的方案。&lt;/p>
&lt;h2 id="为什么选择-btrfs">为什么选择 Btrfs
&lt;/h2>&lt;p>此前我一直在用经典的 Ext4 作为使用 Linux 时的文件系统，但这么多年过去了，以下几点令我开始认真地考虑尝试使用 Btrfs：&lt;/p>
&lt;ol>
&lt;li>Btrfs 可以说已是成熟状态，&lt;a class="link" href="https://btrfs.readthedocs.io/en/latest/Status.html" target="_blank" rel="noopener"
>绝大部分功能已经稳定&lt;/a>，&lt;a class="link" href="https://www.linux.com/news/how-facebook-uses-linux-and-btrfs-interview-chris-mason/" target="_blank" rel="noopener"
>Facebook 也在生产环境中使用 Btrfs&lt;/a>。&lt;/li>
&lt;li>从 Ext4 迁移到 Btrfs 没有太多的痛苦（前提是不使用问题较多的 btrfs-convert 原地替换 Ext4，所以在新系统上使用是最简单的），基本不需要多少额外的配置，只要理解 Btrfs 的子卷怎么挂载就行，而常见的数据库与容器应用都会自动创建 Btrfs 的子卷，用户确实不需要怎么操心。&lt;/li>
&lt;li>数据安全性更有保障，默认采用 CoW（Copy on Write），带有 checksum 等措施使得意外丢失数据的可能性大大降低。当然，这并不能完全杜绝断电丢失最新写入数据的情况，因为数据的可靠保存不仅需要底层的支持，还需要上层应用的额外关注。另外，checksum 的存在使得文件的任何损坏都可以被及时发现，即使你的硬盘里的内容翻转了一比特都能被检测出来，避免了静默错误的出现（至于如何处理损坏的内容则是另一回事了）。&lt;/li>
&lt;li>还有一堆现代的特性，例如&lt;a class="link" href="https://wiki.archlinux.org/title/Btrfs#Compression" target="_blank" rel="noopener"
>透明压缩&lt;/a>，这个基本上每个人都会用到；快照，每次更新系统或折腾容易玩崩的东西前先拍个快照，方便一键还原；增量备份，通过&lt;a class="link" href="https://wiki.archlinux.org/title/Btrfs#Send/receive" target="_blank" rel="noopener"
>对子卷的 send/receive&lt;/a>，可以轻松地把子卷增量备份到另一个使用 Btrfs 的硬盘中。除此以外还有一些我暂时用不到的特性与功能，如 RAID、容量配额等等，具体可在 &lt;a class="link" href="https://wiki.archlinux.org/title/Btrfs" target="_blank" rel="noopener"
>Arch Wiki&lt;/a> 中查看。&lt;/li>
&lt;/ol>
&lt;p>当然，Btrfs 目前也还没到能受到所有人的欢迎的程度：它的读写速度要慢于 Ext4（CoW 的通病），而一旦检测到 checksum 出错则会强制要求用户进行处理也令有些人感到不爽。对于我来说，比较不满的一点是其本身没有加密功能，必须要与 LUKS 之类的方案一起使用才能达成全盘加密，希望未来能有更好的解决方案。&lt;/p>
&lt;h1 id="日常使用">日常使用
&lt;/h1>&lt;h2 id="kde">KDE
&lt;/h2>&lt;p>我在之前已经吐槽过安装 kde-applications 包组后会有一堆用不到的软件，甚至还写了一个&lt;a class="link" href="https://gist.github.com/vifly/33d1a4f63b0b7319c6db9af9d3bdbdb0" target="_blank" rel="noopener"
>简单的 Python 脚本&lt;/a>来删除它们。现在我发现更好的解决方法应该是在安装时就对此进行处理，根据我的需求这里给各位推荐一个安装 KDE 的方案。首先不要按 &lt;a class="link" href="https://wiki.archlinux.org/title/KDE#Plasma" target="_blank" rel="noopener"
>Arch Wiki&lt;/a> 上说的先安装 Xorg，直接安装 &lt;a class="link" href="https://archlinux.org/groups/x86_64/plasma/" target="_blank" rel="noopener"
>plasma 包组&lt;/a>即可，其中的依赖关系会导致 plasma 所需的 Xorg 组件也被安装，如果你需要使用 Wayland 的话请再安装 plasma-wayland-session 包。另外，安装包组有一个好处，那就是可以排除自己不想安装的软件包，例如我就排除了：&lt;/p>
&lt;pre tabindex="0">&lt;code>discover
milou
oxygen
oxygen-sounds
plasma-sdk
plasma-vault
plasma-workspace-wallpapers
&lt;/code>&lt;/pre>&lt;p>经过以上操作后已经装好了基础的桌面环境，但我们还需要配套的应用，而如果直接安装 kde-applications-meta 的话会装上一堆用不到的软件和游戏，选择 kde-applications 包组的话需要手动排除的项目也不少，更好的办法是根据 &lt;a class="link" href="https://gitlab.archlinux.org/archlinux/packaging/packages/kde-applications-meta/-/blob/main/PKGBUILD" target="_blank" rel="noopener"
>kde-applications-meta 的 PKGBUILD&lt;/a> 创建一个属于自己的 kde-applications-meta。这里给出我日常使用软件所构成的 &lt;a class="link" href="https://gist.github.com/vifly/253e3184f5a1d87c1292b131cba1c09c" target="_blank" rel="noopener"
>meta 包&lt;/a>，下载该 PKGBUILD 后在文件所处的目录下执行 &lt;code>makepkg -si&lt;/code> 即可。&lt;em>PS：如果你仔细查看该 PKGBUILD 就会发现里面没有包含 KDE 家的图片浏览器 gwenview，原因是我使用 nomacs 作为日常的图片浏览器。&lt;/em>&lt;/p>
&lt;h2 id="美化">美化
&lt;/h2>&lt;p>在我刚开始使用 KDE 后没多久就写过一篇&lt;a class="link" href="https://viflythink.com/KDE_to_Windows10/" target="_blank" rel="noopener"
> KDE 的美化教程&lt;/a>，探索了将 KDE 的外观变成 Windows 10 的可能性。经过了几年，由于 KDE 的版本更新导致部分选项有所改变，且我找到了一些更好的美化方案，所以抽空大改了一波上面的博文，想要美化自己的 KDE 桌面的童鞋继续查看该博文即可。&lt;/p>
&lt;h2 id="steam">Steam
&lt;/h2>&lt;p>就日常娱乐来说，Steam 当然是一个好选择，感谢 V 社一直以来对 Linux 游戏的支持（特别是其开发的 Proton 极大地扩充了 Linux 下可游玩的游戏范围），现在 Linux 下大部分游戏的体验已经不亚于 Windows，只需要打开 Steam 一键安装就能开玩。不过对于一个强迫症来说，Steam 及其安装的游戏在家目录下乱扔垃圾这点还是令我挺不爽的。之前我一直在用 &lt;a class="link" href="https://liolok.com/zhs/containerize-steam-with-systemd-nspawn/" target="_blank" rel="noopener"
>systemd-nspawn&lt;/a> 隔离相关应用，但后来发现为此专门准备一个容器所占的空间太大了点，而且隔离级别较高还要解决一些游戏的运行问题，于是在新系统上决定尝试改用 Flatpak 作为安装与使用 Steam 的方式（虽然 Flatpak 会创建 ~/.var 这个目录）。首先根据 &lt;a class="link" href="https://wiki.archlinux.org/title/steam#Flatpak" target="_blank" rel="noopener"
>Arch Wiki&lt;/a> 先安装 Steam 本体，再加上一些个人使用的配套应用：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bash" data-lang="Bash">&lt;span class="line">&lt;span class="cl">flatpak install flathub com.valvesoftware.Steam com.valvesoftware.Steam.Utility.protontricks com.valvesoftware.SteamLink
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后直接在开始菜单找到并启动 Steam 看看效果，基本上与在本机系统内直接安装 steam 没什么区别。如果中文字体不对劲，安装 ttf-liberation 应该能解决。&lt;/p>
&lt;p>如果你使用 KDE 且安装了 flatpak-kcm 这个包，此时便可以在系统设置中的“应用程序”-&amp;gt;“Flatpak 权限设置”中对安装的 Flatpak 应用进行管理，比如设置更细分的权限与应用运行时的环境变量等等。举个例子，这里我就给 Steam 创建了一个新的 Btrfs 子卷（挂载到 /games）用于安装游戏：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/New-Install-Arch/set_flatpak_steam_fs_access.png"
width="1466"
height="1026"
loading="lazy"
alt="set_flatpak_steam_fs_access"
class="gallery-image"
data-flex-grow="142"
data-flex-basis="342px"
>&lt;/p>
&lt;p>除此以外，如果你因没有配置透明代理而在使用 Steam 时撞到墙，那么可以通过修改 desktop 文件让 Steam 使用代理。具体做法是，先执行 &lt;code>cp /var/lib/flatpak/exports/share/applications/com.valvesoftware.Steam.desktop ~/.local/share/applications/&lt;/code> 把 Steam 的 desktop 文件复制到用户目录下，然后开始修改对应复制过来的文件，在 &amp;ldquo;Actions=&amp;rdquo; 开头的一行把 &amp;ldquo;RunWithProxy;&amp;rdquo; 添加到该行末尾，然后在文件的末尾添加以下内容：&lt;/p>
&lt;pre tabindex="0">&lt;code>[Desktop Action RunWithProxy]
Name=Run with proxy
Name[zh_CN]=使用代理运行
Name[zh_TW]=使用代理運行
Exec=/usr/bin/flatpak run --branch=stable --arch=x86_64 --command=/app/bin/steam-wrapper --file-forwarding --env=https_proxy=http://127.0.0.1:1090 com.valvesoftware.Steam -tcp @@u %U @@
&lt;/code>&lt;/pre>&lt;p>以上的 Exec 就是在原来的默认值上加了两个参数，让 Steam 使用 TCP 连接，以遵循新增的 https_proxy 环境变量，记得把 https_proxy 修改为自己的 HTTP 代理地址与端口号，这里提供一下&lt;a class="link" href="https://gist.github.com/vifly/ccdfd05899263e43ed269bbca8ec40f4" target="_blank" rel="noopener"
>我修改后的文件&lt;/a>以供参考。保存好文件后将其链接到 ~/Desktop 下，在需要让 Steam 翻墙时，只需右键点击桌面上的 Steam 图标然后选择“使用代理运行”即可。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/New-Install-Arch/run_steam_with_proxy.png"
width="463"
height="766"
loading="lazy"
alt="run_steam_with_proxy"
class="gallery-image"
data-flex-grow="60"
data-flex-basis="145px"
>&lt;/p>
&lt;h2 id="输入法">输入法
&lt;/h2>&lt;p>目前我已经把输入法方案从 Fcitx4 + RIME 切换到了 Fcitx5 自带的拼音输入法。当年使用 RIME 的主要原因是包括 google pinyin 在内 Fcitx4 自带的拼音输入法的候选词算法都十分糟糕，而现在 fcitx5-chinese-addons 里的拼音输入法已经足够好用，与 RIME 相比，Fcitx5 拼音输入法更开箱即用，候选词算法更优秀，同时在可折腾性上也不输 RIME，支持 Lua 插件、导入第三方词库、输入法皮肤、快速输入 Emoji 与特殊 Unicode 等功能。所以除非有什么非常需要的 RIME 独占功能，否则我都一律推荐使用 Fcitx5 拼音输入法。&lt;/p>
&lt;p>只需根据 &lt;a class="link" href="https://wiki.archlinuxcn.org/wiki/Fcitx5" target="_blank" rel="noopener"
>Arch Wiki&lt;/a> 安装好对应的包组与包后重启即可：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bash" data-lang="Bash">&lt;span class="line">&lt;span class="cl">sudo pacman -S fcitx5-im fcitx5-lua fcitx5-chinese-addons
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>另外，想在 Android 手机上使用 Fcitx5 拼音输入法的童鞋可以尝试 &lt;a class="link" href="https://github.com/fcitx5-android/fcitx5-android" target="_blank" rel="noopener"
>fcitx5-android&lt;/a> 这个项目。目前日常使用所需的功能已全部就位，可以确保在手机上也有与电脑相同的输入体验。&lt;/p>
&lt;h2 id="其它">其它
&lt;/h2>&lt;p>我的音频播放器从 Rhythmbox 切换到了同属 Gnome 家的 Lollypop（KDE 请加油），与 Rhythmbox 相比，Lollypop 的界面更为现代（见下图），还支持自动从互联网下载歌曲封面图片，搜索歌词等功能。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/New-Install-Arch/Lollypop.png"
width="1305"
height="968"
loading="lazy"
alt="Lollypop"
class="gallery-image"
data-flex-grow="134"
data-flex-basis="323px"
>&lt;/p>
&lt;p>而笔记工具则令我颇为头疼，考虑到前不久 &lt;a class="link" href="https://news.ycombinator.com/item?id=36609641" target="_blank" rel="noopener"
>Evernote 的新母公司裁掉了几乎所有员工&lt;/a>的新闻，尽管后来解释称是准备将运营团队转移至欧洲，因而裁掉了原来的员工，但我实际上在 Evernote 被收购时就已经对 Evernote 的前景感到担忧，所以早就想物色一个新的笔记工具了。最近流行的笔记工具都声称自己受到了 &lt;a class="link" href="https://zettelkasten.de/introduction/" target="_blank" rel="noopener"
>Zettelkasten&lt;/a>（卡片盒）笔记法的启发，设计出了从根基上与 Evernote 这种“传统”笔记不同的结构，例如 Logseq、Obsidian、Notion、Emacs 的 org-roam 等。对于是否迁移到它们之一，或继续坚守“传统”，我目前还在犹豫，欢迎各位读者向我推荐自己喜欢的笔记软件。无论如何，先用 &lt;a class="link" href="https://github.com/vzhd1701/evernote-backup" target="_blank" rel="noopener"
>evernote-backup&lt;/a> 导出笔记后用 &lt;a class="link" href="https://github.com/akosbalasko/yarle" target="_blank" rel="noopener"
>Yarle&lt;/a> 转为 markdown 做好最坏情况下的准备总是没错的，至少目前 Evernote 还保持着一贯的开放 API，使得迁移笔记还没什么问题。&lt;/p>
&lt;p>还有之前没有推荐过的 KDE Connect，它可以实现手机与电脑的互联，例如同步通知、互传文件、多媒体控制、远程执行命令等，强大到足以被称为开源法拉利，而 Gnome 用户也可以使用 &lt;a class="link" href="https://extensions.gnome.org/extension/1319/gsconnect/" target="_blank" rel="noopener"
>GSConnect 扩展&lt;/a>享受到同样的功能。&lt;/p></description></item><item><title>2022 中科大信息安全大赛题解</title><link>https://viflythink.com/Hackergame_2022_writeups/</link><pubDate>Sat, 05 Nov 2022 00:00:00 +0800</pubDate><guid>https://viflythink.com/Hackergame_2022_writeups/</guid><description>&lt;img src="https://viflythink.com/Hackergame_2022_writeups/show.jpg" alt="Featured image of post 2022 中科大信息安全大赛题解" />&lt;p>又到了游玩 Hackergame 的时节，欢迎各位读者阅读本期的年更博文。今年博主拿到了 1300 分，与&lt;a class="link" href="https://viflythink.com/Hackergame_2021_writeups/" target="_blank" rel="noopener"
>上一年&lt;/a>相比，得分有所增加，排名反倒落后了😂；尽管如此，今年依然玩的很开心，下面就让我分享一下自己的题解吧。&lt;/p>
&lt;h1 id="签到">签到
&lt;/h1>&lt;p>打开题目页面，随便点了两下后点击“提交”，发现跳转到的页面 URL 中含有一个 result 参数，既然是 web 方向的题目，那么估计今年的签到题也是简单修改一下请求参数就可以拿到 flag 了，注意到题目描述中有：&lt;/p>
&lt;blockquote>
&lt;p>在 CPU 来得及反应之前顺利签下 2022&lt;/p>
&lt;/blockquote>
&lt;p>根据这个提示把 result 的值改为 2022 试试，flag 就这样出现了：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2022_writeups/get_2022_qiandao_flag.png"
width="1915"
height="1018"
loading="lazy"
alt="成功获取签到题的 flag"
class="gallery-image"
data-flex-grow="188"
data-flex-basis="451px"
>&lt;/p>
&lt;h1 id="猫咪问答喵">猫咪问答喵
&lt;/h1>&lt;p>这次的题目与之前两次的猫咪问答有点区别，基本上没有什么提问适合用暴力破解的方式解决，所以今年我全靠谷歌搜索完成。&lt;/p>
&lt;p>第一小题，直接搜索关键词找到一篇&lt;a class="link" href="https://cybersec.ustc.edu.cn/2022/0826/c23847a565848/page.htm" target="_blank" rel="noopener"
>新闻稿&lt;/a>，里面提到：&lt;/p>
&lt;blockquote>
&lt;p>中国科学技术大学“星云战队（Nebula）”成立于2017年3月&lt;/p>
&lt;/blockquote>
&lt;p>第二小题找起来有点麻烦，首先找到&lt;a class="link" href="https://lug.ustc.edu.cn/wiki/lug/events/sfd/#2022-%E5%B9%B4-sfd" target="_blank" rel="noopener"
> USTC LUG 的软件自由日历史记录&lt;/a>，然后打开“闪电演讲：《GNOME Wayland 使用体验：一个普通用户的视角》”对应的&lt;a class="link" href="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2022.9.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/slides/gnome-wayland-user-perspective.pdf" target="_blank" rel="noopener"
> slides&lt;/a>，在其中找到如下内容：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2022_writeups/gnome_wayland_kdenlive.png"
width="1236"
height="666"
loading="lazy"
alt="GNOME Wayland 下的 KDE 程序问题"
class="gallery-image"
data-flex-grow="185"
data-flex-basis="445px"
>&lt;/p>
&lt;p>在看到截图中标题包含的 HD 1080p 25 fps 以及菜单内容后，我们可以合理猜测这是一个视频剪辑应用，而 KDE 家的著名视频剪辑应用当然就是 Kdenlive。&lt;/p>
&lt;p>第三小题玩了一个今年的梗，该梗出自&lt;a class="link" href="https://hostloc.com/thread-1078729-1-1.html" target="_blank" rel="noopener"
> Windows 2000 撑不下去了，还有其他 NT4.0 的系统可以用吗？&lt;/a>，该帖子里的楼主说经过他修改的 Firefox 能用到 28.0，而根据&lt;a class="link" href="https://support.mozilla.org/en-US/questions/1052888#answer-706143" target="_blank" rel="noopener"
> Mozilla 技术支持的说法&lt;/a>，官方支持 Windows 2000 的最后一个版本是 12.0，所以答案是 12。&lt;/p>
&lt;p>第四小题是最麻烦的，搜索“CVE-2021-4034”与“Linux kernel argc == 0”等关键词后找到&lt;a class="link" href="https://lwn.net/Articles/882799/" target="_blank" rel="noopener"
> LWN 上的一篇文章&lt;/a>，当中提到了&lt;a class="link" href="https://lwn.net/ml/linux-kernel/20220126043947.10058-1-ariadne@dereferenced.org/" target="_blank" rel="noopener"
>最早尝试修复这个漏洞的补丁&lt;/a>，根据这个补丁的内容，其修改了 fs/exec.c 中 do_execveat_common 函数的逻辑。接下来该怎么找对应的提交哈希值呢，在 Linux 内核官网的 Git 前端找？这可太麻烦了，值得庆幸的是内核源码在 GitHub 是有镜像仓库的，所以使用 GitHub 网页的 Blame 功能就比较轻松了，打开其中的 &lt;a class="link" href="https://github.com/torvalds/linux/blob/master/fs/exec.c" target="_blank" rel="noopener"
>fs/exec.c&lt;/a>，点击 Blame 按钮然后 Ctrl + F 查找 do_execveat_common，就可以找到&lt;a class="link" href="https://github.com/torvalds/linux/blame/27bc50fc90647bbf7b734c3fc306a5e61350da53/fs/exec.c#L1900" target="_blank" rel="noopener"
>已经经过修复的代码所在的位置&lt;/a>了，在网页左侧显示的就是&lt;a class="link" href="https://github.com/torvalds/linux/commit/dcd46d897adb70d63e025f175a00a89797d31a43" target="_blank" rel="noopener"
>我们要找的提交&lt;/a>。&lt;/p>
&lt;p>第五小题，直接搜索“e4:ff:65:d7:be:5d:c8:44:1d:89:6b:50:f5:50:a0:ce”这一串指纹，找到了一条&lt;a class="link" href="https://gist.github.com/jandryuk/61b286220447300ba9dca0faa26526dd" target="_blank" rel="noopener"
> Gist 记录&lt;/a>，所以该指纹对应的域名就是 sdf.org。&lt;/p>
&lt;p>最后一道小题，搜索“USTC 网络通定价”的话首先就能看到&lt;a class="link" href="https://www.ustc.edu.cn/info/1057/4931.htm" target="_blank" rel="noopener"
>这个通知&lt;/a>，当我据此输入 2011-01-01 后发现这是错的，仔细一看下面的价格变更表，发现网络通的价格没有变动，所以这里应该再查找通知开头提到的“网字〔2003〕1号《关于实行新的网络费用分担办法的通知》”，&lt;a class="link" href="http://ustcnet.ustc.edu.cn/2003/0301/c11109a210890/pagem.htm" target="_blank" rel="noopener"
>其中&lt;/a>提到 2003 年 3 月 1 日起实行，由此得到 2003-03-01 这一正确答案。&lt;/p>
&lt;h1 id="家目录里的秘密">家目录里的秘密
&lt;/h1>&lt;p>尝试使用 ripgrep 暴力搜索 flag 相关的文字：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2022_writeups/rg_find_flag.png"
width="506"
height="125"
loading="lazy"
alt="使用 rg 搜索包含 flag 的文件"
class="gallery-image"
data-flex-grow="404"
data-flex-basis="971px"
>&lt;/p>
&lt;h2 id="vs-code-里的-flag">VS Code 里的 flag
&lt;/h2>&lt;p>逐个查看找到的文件，在 .config/Code/User/History/2f23f721/DUGV.c 的第五行找到了 flag。&lt;/p>
&lt;h1 id="heilang">HeiLang
&lt;/h1>&lt;p>黑话与蛇语有什么联系呢，研究一下本题给出的代码后就会发现所谓的 HeiLang 就是一个简单的 Python 魔改版。要用 Python 解释器执行的话，需要先转换一下语法，把含有“|”的语句展开为 Python 的列表赋值语句。总的来说是很轻松的文本查找替换工作，这里我写了&lt;a class="link" href="https://gist.github.com/vifly/1f23c692a07967c3a3175579656177cd#file-convert_hei-py" target="_blank" rel="noopener"
>一个脚本&lt;/a>用来生成可以符合 Python 语法的 getflag.py。&lt;em>PS：也可以不保存然后直接用 eval 执行。&lt;/em>&lt;/p>
&lt;h1 id="xcaptcha">Xcaptcha
&lt;/h1>&lt;p>如何证明自己是机器人，在一秒内解出三道计算题就行了。从题目描述来看是需要自己写代码进行计算了，本题可以算是 Web 爬虫的入门考验，没有任何反爬措施，只要发送请求获取网页内容，然后从中提取要计算的题目并把计算后的结果通过 post 请求发送回去即可。看&lt;a class="link" href="https://gist.github.com/vifly/1f23c692a07967c3a3175579656177cd#file-hack_xcaptcha-py" target="_blank" rel="noopener"
>我的脚本&lt;/a>就可以理解这样一个简单的爬虫是如何实现的。&lt;/p>
&lt;h1 id="旅行照片-20">旅行照片 2.0
&lt;/h1>&lt;p>难得的社会工程学题目，再次说明了一张照片能暴露多少个人信息，此类题目都十分有趣，本题也不算难，仅仅是照片上的体育馆横幅文字就能看出拍摄地点在千叶市 Zozo 海洋球馆附近。虽说如此，上一年的旅行照片我没解出来，今年的这道题则因为没找到能免费显示五个月前航班历史记录的网站而没解决第二小题。&lt;em>PS：赛后发现 &lt;a class="link" href="https://globe.adsbexchange.com/?r" target="_blank" rel="noopener"
>ADSB Exchange&lt;/a> 提供了免费的长期历史回放功能，但由于其界面过于复古，我在比赛时没找到该功能。😂&lt;/em>&lt;/p>
&lt;h2 id="照片分析">照片分析
&lt;/h2>&lt;p>安装 &lt;a class="link" href="https://exiftool.org" target="_blank" rel="noopener"
>exiftool&lt;/a> 就可以查看照片的 EXIF 信息：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Bash" data-lang="Bash">&lt;span class="line">&lt;span class="cl">exiftool ./travel-photo-2.jpg
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里有点坑的地方在于其显示的 EXIF 信息版本需要转换一下，0231 需要转为 2.31 才是正确的答案，其它的答案都是对照 EXIF 填写即可。&lt;/p>
&lt;h1 id="latex-机器人">LaTeX 机器人
&lt;/h1>&lt;h2 id="纯文本">纯文本
&lt;/h2>&lt;p>又是一个因完全信任用户输入而导致 flag 泄露的案例。我并不熟悉 LaTeX，在阅读了后端代码后以为是要在输入的 LaTeX 语句中注入 Shell 命令才能拿到 flag，随手一搜“LaTeX inject”就找到了&lt;a class="link" href="https://0day.work/hacking-with-latex/" target="_blank" rel="noopener"
>说明 CTF 中 LaTeX 可利用之处的文章&lt;/a>。按该文的说法，存在 -no-shell-escape 参数的情况下，在 LaTeX 中执行 Shell 命令是不行的，但 LaTeX 自身就有一个用于读取文件的宏：\input，所以第一小题只需要输入&lt;code>\input{/flag1}&lt;/code>就能把 flag 放到返回的图片中，然后对图片进行 OCR 并补上括号就可以完成本题了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2022_writeups/get_flag1_from_latex_bot.png"
width="1027"
height="703"
loading="lazy"
alt="从 LaTex 机器人获取 flag"
class="gallery-image"
data-flex-grow="146"
data-flex-basis="350px"
>&lt;/p>
&lt;h1 id="flag-的痕迹">Flag 的痕迹
&lt;/h1>&lt;p>Dokuwiki 作为一个成熟的软件，我们恐怕难以能找到绕过其 Action 管理的方法，所以需要从其它的方向着手。首先想到的是管理员是否忘记禁用某些同样能查看历史记录的接口，先从可能未禁用的 Action 开始尝试，翻阅&lt;a class="link" href="https://www.dokuwiki.org/devel:action_modes" target="_blank" rel="noopener"
> Dokuwiki 支持的 Action 列表&lt;/a>，发现在 revisions 下面有 diff 这个 Action，从名字来看它也是能获取历史记录的，于是尝试访问 http://202.38.93.111:15004/doku.php?do=diff 并发现 diff 没被禁用，查看之前的版本差异就得到了 flag。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2022_writeups/get_flag_from_dokuwiki.png"
width="1918"
height="1017"
loading="lazy"
alt="从 Dokuwiki 获取 flag"
class="gallery-image"
data-flex-grow="188"
data-flex-basis="452px"
>&lt;/p>
&lt;h1 id="微积分计算小练习">微积分计算小练习
&lt;/h1>&lt;p>对我来说本题是兼具挑战性与趣味性的一道题目。首先从后端代码开始思考，可以看出其主要的逻辑就是先转换用户提供的 URL（杜绝访问外网的可能性）并把 flag 放到 cookie 里，用 headless chrome 访问转换后的 URL，执行 JavaScript 代码查询网页元素并把结果打印出来。&lt;/p>
&lt;p>到此目标就很清楚了：想办法把 cookie 的内容偷出来，而能否取得 flag 与是否把微积分题目全做出来毫无关系。要获取 cookie 的内容，我们只能从输入的 URL 开始着手，因为从后端代码就可以看出没有其它的选项，所以现在的问题就变成了有什么办法能构造出一个 URL 让浏览器执行任意的代码。仔细一想，这不就是&lt;a class="link" href="https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting" target="_blank" rel="noopener"
> XSS 攻击&lt;/a>吗，真没想到会遇到一道需要参赛者构造 XSS 攻击的题目。&lt;/p>
&lt;p>要进行 XSS 攻击，首先要做的就是阅读目标站点的前端代码，从中寻找可供利用的地方。对于我们来说，浏览器访问的是展示测试分数的页面，所以该页面就是目标页面，其中重要的代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-JavaScript" data-lang="JavaScript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">urlParams&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;result&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">b64decode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">atob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">result&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;#greeting&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">innerHTML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;您好，&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">username&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;！&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;#score&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">innerHTML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;您在练习中获得的分数为 &amp;lt;b&amp;gt;&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">score&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;/b&amp;gt;/100。&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>可以看出这首先会对 result 参数进行 base64 解码，进行一番字符串操作后获得分数和姓名（上面已省略），最后更改对应的元素以进行显示。base64 解码和字符串操作看上去都没什么可利用的地方，那剩下的可能性就隐藏在显示部分了。回想起来，直接对 innerHTML 进行操作是一个不安全的行为，有可能利用这点吗？谷歌“XSS innerHTML”直接就找到了&lt;a class="link" href="https://gist.github.com/caike/35522c3da161d29fc2ce" target="_blank" rel="noopener"
>一个例子&lt;/a>，先在做题页面输入名字 &lt;code>&amp;lt;img src=x onerror=&amp;quot;alert(42)&amp;quot;&amp;gt;&lt;/code> 试试，检查随后打开的显示结果的页面是不是弹出了内容为 42 的对话框。&lt;/p>
&lt;p>测试成功后我们就可以开始构造实际的 payload 了，要做的事情就是把名字改为 &lt;code>&amp;lt;img src=x onerror='document.querySelector(&amp;quot;#score&amp;quot;).innerHTML=document.cookie'&amp;gt;&lt;/code>，这样做会导致原本应该显示分数的地方被改为显示 cookie，交给 headless chrome 访问后就可以拿到 flag 了。所以本题输入 http://202.38.93.111:10056/share?result=MDo8aW1nIHNyYz14IG9uZXJyb3I9J2RvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoIiNzY29yZSIpLmlubmVySFRNTD1kb2N1bWVudC5jb29raWUnPg%3D%3D 这个 URL 就能解决，你也可以通过以下命令构造 result 参数（我用的是 Zsh，注意 Shell 对引号的转义）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Zsh" data-lang="Zsh">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;0:&amp;lt;img src=x onerror=&amp;#39;document.querySelector(\&amp;#34;#score\&amp;#34;).innerHTML=document.cookie&amp;#39;&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> base64
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>拿到的 flag 是 flag{xS5_1OI_is_N0t_SOHARD_03d2ec39d4}，嗯，XSS 入门并不难。&lt;/p>
&lt;h1 id="一些没做出来的题目">一些没做出来的题目
&lt;/h1>&lt;p>家目录里的秘密的第二小题，虽然我知道 flag2 应该在 .config/rclone/rclone.conf 里，但居然没想到里面的 pass 项是以密文形式存储的，需要阅读 Rclone 中的对应源码才能将其还原为明文获取 flag。&lt;/p>
&lt;p>猜数字，我只能说没有熟读 IEEE 754 标准真是容易吃亏啊，计算机的浮点数表示总有些违反直觉，本题就体现了这一点：对于任意 a，a ≠ NaN 成立，但 a &amp;lt; NaN 或 NaN &amp;lt; a 均不成立。所以对应的代码用了一种很奇怪的方式判断猜测的数字是否正确：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Java" data-lang="Java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">isLess&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">guess&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">number&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">1e&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">6&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">var&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">isMore&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">guess&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">number&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">1e&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">6&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">2&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kd">var&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">isPassed&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">isLess&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;amp;&amp;amp;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">isMore&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>二次元神经网络，作为一个炼丹师看到这题可谓是倍感亲切。除非弄个 web 九宫格，神经网络也算 web 类题目，不然本题是不需要参赛者训练神经网络的，我也猜出需要在上传的模型中注入代码并让其被执行，只是因懒得想需要执行什么代码才能拿到 flag 就放弃了。这道题提醒了我们要通过模型进行 RCE（任意代码执行）是如此的简单，所以不要随便加载从网上下载的模型。虽说如此，就我个人观察而言，别说只是想玩下 AI 生成图片的业余人士，就算是业内人士也有不少并不在意这个安全问题，感觉这会成为一个常见的安全风险来源。&lt;/p>
&lt;h1 id="总结">总结
&lt;/h1>&lt;p>很不巧的，今年的比赛期间我正好沉迷于斯普拉遁3，所以没投入太多时间和精力来打比赛；而且这一年来也没怎么研究 binary 与 math 类的题目，所以对于这些类型的题目束手无策。总之，虽说今年的比赛也玩的挺开心，但并没有什么进步，我对于没能在 Hackergame 成功挑战自我这一点还是有些遗憾的，希望明年的自己能够更进一步吧。&lt;/p></description></item><item><title>P5R 天下第一</title><link>https://viflythink.com/Persona5TheRoyal/</link><pubDate>Sun, 16 Oct 2022 16:18:20 +0800</pubDate><guid>https://viflythink.com/Persona5TheRoyal/</guid><description>&lt;img src="https://viflythink.com/Persona5TheRoyal/show.jpg" alt="Featured image of post P5R 天下第一" />&lt;p>想必各位读者看到这个博文标题就知道这是一篇安利文了，今年年初通关《女神异闻录5皇家版》（简称 P5R）后觉得这可实在是太符合我的胃口了，一直有动笔狂吹一通 P5R 的冲动。只可惜 P5R 一直处于索尼 PS 平台独占状态，能玩到的玩家不多，没想到今年六月突然曝出一个令人万分惊喜的消息：P5R 将于今年十月下旬登陆包括 PC、Xbox、Switch 在内的各大平台，且首发加入 XGP。既然如此，在这之前我便简单介绍一下 P5R 并说明为什么有人评价它是“天下第一”好了；而对于已通关的玩家，这里还有对游戏涉及到的潜意识理论的整理，可以说是加量不加价了。&lt;/p>
&lt;p>在开始安利之前先说明几点，在 P5R 之前还存在 P5，两者的差别在于后者是 PS 平台独占，前者在后者的基础上追加了第三学期以及一些内容，网上对 P5R 的负面评价大部分集中在已经通关 P5 的玩家要体验第三学期也要重新经历前两个学期，所以对新玩家而言现在要买的话无脑选择 P5R 就对了。以及，虽说 P5R 的画风在当今的大型游戏（本人通关时间为 110 小时）中是独树一帜的存在，但细看的话质量也就是日系厂商的一般水准，光看截图可看不出来为什么它能配得上天下第一这个称号。不过，只要你不排斥 JRPG，那么我相信 P5R 绝对会给你留下一个深刻的印象。那么，安利开始。&lt;/p>
&lt;p>说到 JRPG，各位读者可能首先想到的就是像《勇者斗恶龙》、《最终幻想》等等这样将背景设定在另一个魔幻宇宙的知名作品，可是，如果把 JRPG 的背景放在现代会怎样呢？&lt;/p>
&lt;blockquote>
&lt;p>少年主角因某个缘由转学到 “东京” 的高中后，开始做起了奇异的梦。 &lt;br>
—— 你确实是命运之『囚』啊。 &lt;br>
少年被告知不久之后他就会迎来毁灭。&lt;br>
为了完成赋予他的 “更生” 任务，也为了将人们从邪恶欲望中拯救出来， &lt;br>
成为怪盗的主角与各种人邂逅，结下羁绊。&lt;br>
在度过了宝贵的一年后，等待你的将是……？&lt;/p>
&lt;/blockquote>
&lt;p>以上文字摘自 &lt;a class="link" href="https://asia.sega.com/persona-remaster/p5r/cn/" target="_blank" rel="noopener"
>P5R 移植版官网&lt;/a>，看完后是不是有种很神秘的感觉，明明是现代背景却有梦、命运之囚、怪盗等等令人在意的词语，不过还不止于此，女神异闻录系列都有以集体潜意识理论为基础的异世界设定：拥有极端欲望的人会产生殿堂，普罗大众的潜意识组成了印象空间；而主角及其伙伴则拥有进入这些异世界的能力，将殿堂中的秘宝偷走的话则可以让对应的人悔改，换句话说，游戏中的主角团队可以改变人的心灵。说到这里可能各位读者会有所疑问，以心灵为主题的游戏虽不多，但也不是没有优秀的先例，它们也会突出心灵的神秘性，那与之对比 P5R 有什么独特之处呢？答案就是它完美实现了一种充满中二幻想气息的华丽感，看下图，其美术从一开始就在张扬着自己独特的风格。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Persona5TheRoyal/P5R_Phantom_Thieves.jpg"
width="2880"
height="1620"
loading="lazy"
alt="心之怪盗团"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>如此华丽的画面自然也要配上同样华丽的设定，在游戏中，主角及其伙伴组成了心之怪盗团，表面上是普通的高中生，背地里则化身为神秘优雅的怪盗，潜入殿堂寻找秘宝，并在随后发布预告信，让充满丑恶嘴脸的大人痛改前非。总之，围绕着异世界，故事就此展开。&lt;/p>
&lt;figure>&lt;img src="https://viflythink.com/Persona5TheRoyal/%E9%A2%84%E5%91%8A%E4%BF%A1.jpg">&lt;figcaption>
&lt;h4>华丽的预告信&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;h1 id="畅快的体验">畅快的体验
&lt;/h1>&lt;p>要问玩 P5R 时是什么感觉，我想畅快是最能用来形容的词语。从一开始误入异世界到成立怪盗团，经历高低起伏的舆论反转，再到最后的拯救世界，整个剧情进展非常自然，是传统王道 JRPG 剧情的又一经典样例，打磨得足够好的老套剧情依然能给我带来很棒的感觉，特别是其中没有诸如为了推动剧情而降智等令人出戏的情况（个别人物的转变可能有些突兀）。&lt;/p>
&lt;p>为了畅快的体验，P5R 确实避开了 JRPG 主要的通病，例如常见的跑腿支线之类的强行拖长游戏时间的设计在本作中根本不存在，所有的支线都是要求在印象空间击败某某精英怪以让现实中的人悔改，对我来说就是顺手完成的事情；而且逻辑在本作世界观下很自洽，与我最近玩的同样是现代背景的 JRPG 作品《如龙7》相比真的好多了，后者不少支线任务流程都是把某人暴打一顿后对方幡然醒悟，我只想吐槽：就算是黑道背景的游戏也不能这么夸大暴力的作用吧，还是在印象空间击败对方的阴影让对方悔改看上去更靠谱。至于另一常见的问题：刷级，在印象空间里开车撞撞怪，以及在主线迷宫中多杀点怪，没多久等级就升上来了，虽说还是得靠战斗获取经验升级，但得益于 Atlus 祖传的优秀战斗系统，相近等级的战斗我每次都需要开动脑筋对弱点进行攻击，敌人等级低的话直接靠战车瞬杀即可，并不会进入回合制战斗，这导致我玩到最后都没对普通战斗产生厌倦，所以等级设计上也没啥问题；唯一值得吐槽的就是印象空间撞怪频率高了点，基本上没听完队友之间的对话就又撞怪了，除此以外我真的没法鸡蛋里挑骨头了。&lt;/p>
&lt;p>而上面所说的战斗系统是怎样的，好玩吗？要知道潜入异世界的过程可不是郊游，其中总是需要怪盗团控制人格面具以击败他人意识中的守卫（即阴影），一路过关斩将杀到目的地。尽管这是回合制的系统，却给了我一种在玩动作类游戏的感觉。在跑图的时候看到阴影时可以像潜行游戏那样背袭或直接用抓钩突袭它们，成功的话可以先制攻击而且有一定概率给敌人造成眩晕等 debuff，随后设法用特定属性技能攻击敌人的弱点再用因此获得的额外行动机会换手攻击，最后发动中二度爆表的总攻击。这种依靠自己的决策精妙地控制住全场的感觉真是让人欲罢不能，避免了老式 JRPG 那种你打一下我打一下的枯燥战斗流程，再加上摇滚十足的背景音乐，使得战斗相当带感。所以说，几乎每场战斗都要稍微开动下脑筋，而不是机械式地按按键就完事了。各位读者可以通过以下视频感受下打出总攻击的感受。&lt;/p>
&lt;video src="https://user-images.githubusercontent.com/48406926/196335991-1357d004-78fb-444d-9888-4359f14ad365.webm" controls="controls" preload="auto" style="max-width: 100%; height: auto;">
&lt;/video>
&lt;p>不止于此，战斗时使用的人格面具的培养也是很有深度的系统，由于主角可以在每回合更换一次人格面具，所以培养出具有各种特点的人格面具是一件挺花时间又有趣的事情。首先考虑到被敌人击中弱点后也会进入眩晕状态，所以确保自己没有弱点是第一要务，然后还需要在此基础上尽量获得更多的某类属性无效（有反射/吸收就更好了），只要属性安排恰当，经常会出现敌人攻击你却没有造成任何伤害的好玩场面。当理解完上面这点后，接下来还需要考虑人格面具的特性和主动技能/被动技能的搭配，让不同的人格面具能适用于不同的场合。要知道后期为了合成出一个符合自己需求的人格面具可要花不少心思精心设计与规划前置人格面具的继承，所以说培养人格面具的可玩性绝对不差。&lt;/p>
&lt;p>最后我必须提到 P5R 的跑图也体现出了畅快感。游戏中的殿堂偏向“一本道”，像我这样的路痴都能不依靠攻略就可以找到前往 Boss 所在地（秘宝存放处）的路，像下图见到断崖，跳就对了。但地图也不是简单到让人感到无聊，不少地图都有所谓的“立体感”，而且有些地方也有分叉路，可以看出地图虽大但并不空洞，在处处都充满了精巧的设计，这令我在攻略殿堂时莫名有一种在玩任天堂系游戏的感觉，它们都是每走一会就会给你捎来一个新玩意，让人不由自主地就想继续探索。在每个殿堂几个小时的探索过程中，可以充分地欣赏到不同的欲望组成的殿堂所特有的景色，在此之中我作为怪盗时不时通过钻通风管道、用抓钩飞天等手段到达原本无法到达的地方，也会与每个殿堂独有的机关打交道，有时停下脚步欣赏殿堂内令人惊叹的风景，当然，路上见到阴影那肯定是果断背袭拿下经验，这样一来这几个小时的探索时间怎么能不充满乐趣呢；特别是想集齐欲石的话就更是如此：最后一个欲石往往藏在一条难以发现的支路尽头，对于像我这样总想着收集齐全的玩家来说每次找到它时总是充满了成就感并对这地图设计之精妙感到佩服。&lt;em>PS：请放心，每个殿堂只有三个欲石。&lt;/em>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Persona5TheRoyal/P5R_Pyramid.jpg"
width="3840"
height="2160"
loading="lazy"
alt="P5R 金字塔"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;h1 id="jrpg-中的杰作">JRPG 中的杰作
&lt;/h1>&lt;h2 id="剧情与人物">剧情与人物
&lt;/h2>&lt;p>对于经典的 JRPG 而言，情感体验都来源于剧情与人物，而 P5R 在这两者的表现上都十分出色。&lt;/p>
&lt;p>在不剧透的前提下，我只能说在第三学期前的主线剧情是十分经典的展开，因种种机缘巧合主角在转学后与未来的同伴相结识，同时他们也接触到一些行为极其恶劣的人，例如学校里的体育老师鸭志田，其倚仗自己奥运会冠军得主的身份肆意体罚学生，还试图性侵其辅导的社团成员，为了拯救他人，主角及其同伴在获得了能潜入他人殿堂的能力后开始策划让这些人悔改的行动，以心之怪盗团的名义活动在社会之中，最后在经历了种种风雨后终于战胜了同样想利用异世界的幕后黑手，而主角也成功摆脱了“命运之囚”的状态。当然，在别的 JRPG 中战胜邪恶一般是指物理意义上的消灭，而 P5R 则是在别人的内心世界中战胜阴影，抛开这点不谈，上述的剧情都是很符合王道 JRPG 套路的，其要素可以被归纳为这一公式：羁绊 + 努力 = 胜利。尽管这听上去很老套，但 P5R 主线的承转起伏、埋下的伏笔与回收、各色反派的塑造都可以说是教科书般的经典，经典没有过不过时的说法，只要制作者用心讲好故事，那就是足够优秀的作品。凭着这样稳扎稳打的主线剧情，P5R 就确保了下限，而追加的第三学期更是拉高了上限，为了不剧透，还是请各位读者实际游玩进行体验吧。&lt;/p>
&lt;p>说到人物，那值得一提的东西可太多了，对于王道 JRPG 而言，主角及其同伴的成长是绕不开的话题，像 P5R 这样以心灵为主题的游戏更是如此，只不过我把这个放到了潜意识理论一章进行说明，在这里我更想谈论的是 P5R 广受赞誉的社群（cooperation）系统，社群系统中包括其他怪盗团成员在内的人无疑是主角完成“更生”的最大助力，当主角在这个系统中推进进度时，我们也会感觉到情况在一步步地变好。简单来说，这个系统就是主角与他人的联结的体现，当你花费时间推进与某一人物的关系时，会为你解锁新的能力或帮助，其中的人无一例外地都与主角一样陷入了人生的低谷，因为主角能为他们提供特定的帮助而与主角达成了互相帮助的协议。在关系进展的最后，得益于主角的帮助，这些人都从低谷中走了出来并实现了自我的成长，在这过程中也给予了主角很大的帮助。由于现代社会分工明确，所以这些人都拥有不同的技能特长，在这样的情况下，总共二十多位人物所给予的帮助可是十分丰富的。例如下图的“塔”，主角花费时间与其练习带来的就是射击能力的全面增强，甚至可以让主角的子弹能对射击无效的敌人造成伤害。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Persona5TheRoyal/P5R_Tower.jpg"
width="3840"
height="2160"
loading="lazy"
alt="P5R 塔的能力"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>从上图也可以看出每个人都对应了一张塔罗牌，其经历、性格特征或能提供的帮助也与塔罗牌的名称有所关联。例如，“塔”的正位代表勇敢克服挫折，逆位则代表得意忘形与沉溺在自大的想象中，在游戏中这种象征确实与“塔”的经历相关，各位可以据此猜测一下具体情形；另一个例子，“死神”可以提供探图时最需要的药品，获得其售卖的气功贴后我们的续航能力可以迎来质的飞跃，从这点来看“死神”与医生是不是很配呢。&lt;/p>
&lt;p>除了因塔罗牌而附上的一丝神秘性、丰富的解锁能力、优秀的配角剧情外，社群系统值得称道的还有拟真的时间安排，社群系统中的要么是学生要么是社畜，都有各自的工作生活，只有他们有空的时候才会联系主角，这就使得前中期的日程安排成了一个问题，而到了后期比较有空的时候，还可以约其中的人去各个值得游览的地方玩，另外，陪伴他人度过时光时还可能会有一些意外之喜。所以说，社群系统是 P5R 除了战斗外的一大核心系统，也是王道 JRPG 强调的人与人之间的联结的最好体现。&lt;/p>
&lt;h2 id="洗脑的音乐">洗脑的音乐
&lt;/h2>&lt;p>P5R 的音乐不止是有自己的特色，而且还十分的洗脑，让人忍不住想循环播放。音乐特色从风格上就可略见一斑，一般来说，RPG 中的音乐都是以古典乐为主，原因有很多，最主要的理由就是能让人沉浸在史诗感中以及可以有效规避版权问题，但 P5R 却在很多地方用了现代风格的音乐（如摇滚、电音与爵士乐）。我现在都还记得在东京闹市闲逛时的&lt;a class="link" href="https://share.viflythink.com/music/P5R/Tokyo%20Daylight.mp3" target="_blank" rel="noopener"
>《Tokyo Daylight》&lt;/a>，它很好的烘托出了闹市街头的热闹与繁华，甚至于有的时候我会听着这首乐曲站在游戏中的大街上什么都不干，就单纯地看着人来人往，仿佛自己也来到了东京街头驻足观赏；还有在静谧的场景出现的&lt;a class="link" href="https://share.viflythink.com/music/P5R/Beneath%20the%20Mask.mp3" target="_blank" rel="noopener"
>《Beneath the Mask》&lt;/a>，&lt;a class="link" href="https://share.viflythink.com/music/P5R/Beneath%20the%20Mask%20-rain%2C%20instrumental%20version-.mp3" target="_blank" rel="noopener"
>其雨天纯音乐版&lt;/a>在我工作时经常充当播放的背景音乐，静下心来聆听时很快就会浸入独自一人在雨天漫步，雨水为街景披上了一层薄纱的想像当中。&lt;/p>
&lt;p>日常的背景音乐尚且如此令人印象深刻，战斗时的音乐更不用说，与普通阴影战斗时播放的&lt;a class="link" href="https://share.viflythink.com/music/P5R/Take%20Over.mp3" target="_blank" rel="noopener"
>《Take Over》&lt;/a>，每次听到这激昂的开头我就有种能跟敌人再大战三百回合的错觉；还有&lt;a class="link" href="https://share.viflythink.com/music/P5R/Last%20Surprise.mp3" target="_blank" rel="noopener"
>《Last Surprise》&lt;/a>，只要听到它就会觉得自己只要轻松地打个响指，敌人就会灰飞烟灭。而在 Boss 战前赶往秘宝所在处时响起的&lt;a class="link" href="https://share.viflythink.com/music/P5R/Life%20Will%20Change.mp3" target="_blank" rel="noopener"
>《Life Will Change》&lt;/a>也值得一提，一听到这个旋律内心就会涌起不幸的现状一定会被改变的信念。至于每个殿堂特有的背景音乐（主要是 Boss 战），那就更是各有特色，&lt;a class="link" href="https://share.viflythink.com/music/P5R/Blooming%20Villain.mp3" target="_blank" rel="noopener"
>《Blooming Villain》&lt;/a>、&lt;a class="link" href="https://share.viflythink.com/music/P5R/Keeper%20of%20Lust.mp3" target="_blank" rel="noopener"
>《Keeper of Lust》&lt;/a>以及&lt;a class="link" href="https://share.viflythink.com/music/P5R/Rivers%20In%20the%20Desert.mp3" target="_blank" rel="noopener"
>《Rivers In the Desert》&lt;/a>等都是既符合殿堂主题又令人印象深刻的曲子，听到它们就会自然地想起与 Boss 战斗时的情景。&lt;/p>
&lt;p>上面这些音乐覆盖范围已经很广泛了，但还有没有即非日常又非战斗时的洗脑音乐呢，每次进入天鹅绒房间时都会听到的&lt;a class="link" href="https://share.viflythink.com/music/P5R/The%20Poem%20of%20Everyone%E2%80%99s%20Souls.mp3" target="_blank" rel="noopener"
>《The Poem of Everyone’s Souls》&lt;/a>就是一个突出的存在，其开头的旋律有一种幽深的感觉，就像是从表意识一路下沉到潜意识一样，随后开始出现的人声咏唱又以逐渐升高的音调给我带来一种灵魂从底部开始不断升华的感觉。试想一下，当这首曲子飘荡在耳边时，你在天鹅绒房间又规划好了一个人格面具的合成路线，此时是不是有一种自己的灵魂也进行了一番蜕变的感觉呢。&lt;/p>
&lt;h2 id="令人怀念的高中生活">令人怀念的高中生活
&lt;/h2>&lt;p>最后，我还想再夸一夸 P5R 不遗余力营造的高中生活。不知道是不是人老了就特别怀念年轻时候的生活（笑），当我在游戏中重回高中时莫名有了一种十分怀念的感觉：每天早起挤地铁上学，路上偶遇朋友闲聊几句，上课虽说也会听课，但玩手机、睡觉、看课外书也是不缺席的，放学后参与社团活动或当一个独行侠干自己的事情，游戏中的上学日都几乎会经历这些事情，这些日常看上去都如此的平淡，但对于已经在经受社会毒打的人来说却会觉得十分幸福，所以我总觉得 P5R 并不是给还处于中二时期的玩家准备的，而是献给与制作人一样已经步入社会的人的一个特别的纪念品。毫无疑问，P5R 的高中生活与我经历过的高中生活存在着不少差别，对我来说，游戏中的高中生活显然更为轻松与美好，甚至不禁设想这要是真的那该多好。&lt;/p>
&lt;p>然而，在这平淡的日常背后别忘了你可是怪盗团的一员，如何在保持高中生正常生活的情况下开展怪盗活动无疑是一件具有挑战性的事情。想想看吧，作为怪盗团一员的你除了偶尔潜入殿堂/印象空间让人悔改外，平常还要作为一个正常的高中生进行活动，每一天都要决定是否去图书馆学习以确保自己的学习成绩，要决定是否为了未来的潜入而健身或制作道具，要决定是否为了加深牵绊而陪伴社群系统中的一员，要决定是否为了锻炼自己的能力而去打工，等等这些都是高中生与怪盗团成员双重身份下特有的烦恼。由于异世界的特殊性质，不少高中生可以做到的事情都可以在潜入中发挥作用，例如现实中的模型枪可以在异世界中当真枪使用，画家临摹的纸片可以成为技能卡片，购买的药物甚至可以让人满血复活，在这样的前提下，原本微小的事情也变得很有意义，不值一提的幻想也在此时变成了现实。如此一来，平淡的上学时光与紧张的怪盗团生涯便交杂在一起，构成了精彩的高中生活。&lt;/p>
&lt;p>以及，俗话说得好，劳逸结合是最好的。在忙碌的日常之余，放长假时与同伴相约参加烟花大会，以及修学旅行一起去夏威夷游玩，光是隔着屏幕都能感受到其中洋溢着的欢快气息，这才是充满欢笑与美好回忆的青春该有的样子啊。&lt;/p>
&lt;p>尽管以上所说的内容与我真实的高中经历相去甚远，但在这漫长的游戏过程中，我开始有了一种虚幻与真实的高中生活交织在一起的错觉，在通关后甚至感觉自己真的作为日本高中生在东京的高中上了一年多的学，并在此期间留下了不少宝贵的回忆，为此不得不感慨真的好久没遇到过这样能让我完全沉浸的游戏了。&lt;/p>
&lt;h1 id="潜意识理论">潜意识理论
&lt;/h1>&lt;p>女神异闻录系列的人格面具等概念都是基于荣格的潜意识理论，正好我对此也有一些了解，所以这里不那么专业地介绍一下 P5R 哪些地方涉及到了该理论。在开始之前需要说明一下，潜意识理论并非科学理论，所以在属于现代社会科学的心理学分支下并没有它的一席之地，其所属的精神分析流派主要应用于心理咨询等领域（还记得吗，丸喜作为心理咨询师在上课时也提到过潜意识），它也流行于文学、电影等艺术领域，无数创作者基于该理论发挥他们的想象力创造了不少奇诡到令人印象深刻的作品，以及，由于荣格从神秘学借鉴了不少东西，所以神秘学也把这套理论吸纳回去了，从这点来说它与女神异闻录（乃至真女神转生）系列的风格可太搭了。&lt;/p>
&lt;p>潜意识理论最早由弗洛伊德提出，而荣格作为弗洛伊德的学生，对该理论进行了不少的改造（并因此和老师闹翻了）。两人的理论有一些共同点，包括都认为在我们的表层思维（例如思考时出现在脑海内的声音）之下还有潜藏的意识，做梦就是一个潜意识活动的绝佳例子；与潜意识相比，表意识只是冰山一角（见下图）；潜意识与表意识的冲突会导致精神疾病的发生；一直以来我们都只顾着研究表意识而忽视了潜意识的重大作用，对其的探究不仅可以治疗精神疾病，还可以让我们对人类心灵有更深入的理解。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Persona5TheRoyal/%E6%84%8F%E8%AF%86%E4%B8%8E%E6%BD%9C%E6%84%8F%E8%AF%86.jpg"
width="888"
height="551"
loading="lazy"
alt="意识与潜意识"
class="gallery-image"
data-flex-grow="161"
data-flex-basis="386px"
>&lt;/p>
&lt;h2 id="集体潜意识">集体潜意识
&lt;/h2>&lt;p>所谓的集体潜意识是荣格首创的一个概念，也是与弗洛伊德的潜意识理论最大的一个分歧点。弗洛伊德专注于通过梦境等方式分析个人被长久压抑的情绪与想法（或者说情结，例如著名的恋母情结），而荣格从对个体精神分析的共同点出发，提出了集体潜意识并以此构建了自己的理论体系。在荣格看来，集体潜意识就像大树潜藏在地下的根系一样，规模庞大且任由地上风吹雨打也俨然不动，这正是其具有种种伟力的体现，它的存在就是对宗教中的奇迹的最好解释。&lt;/p>
&lt;p>在游戏中，印象空间就是对集体潜意识的一种扩展的想像，事实上原版的理论并没有明确指出集体潜意识是什么样子的，而游戏将理论扩展为每个人的潜意识互相连接组成了印象空间这一实体，对印象空间的个体潜意识进行修改可以确实影响他人的思想。另外，游戏显然为主角团队开了金手指，例如印象空间还是太有秩序了，如果潜意识真的存在，那么对应的印象空间应该更为混沌无序才对，而且如何在集体潜意识中区分个体也应当是很难的，不应该随便开车转几层就能找到（印象空间分层也是有序的体现），至于殿堂这种个人欲望强到足以扭曲现实的情况就更不用说了，都是游戏设定。&lt;/p>
&lt;p>那么游戏中还有哪些地方明显与集体潜意识理论有关系呢，很显然，人格面具与阴影的形象也是该理论的体现。尽管这些形象是来自于不同文化不同地区的神话人物，但其经历总是有相似之处，可以被概括为不同的抽象主题（如死亡与新生）。按荣格的说法，这些传说都是在原型的基础上加工而成的，同时由于集体潜意识的作用，我们都可以轻松地接受这些神话传说。&lt;/p>
&lt;h2 id="人格面具与阴影">人格面具与阴影
&lt;/h2>&lt;p>人格面具与女神异闻录的英文名都是同一个单词：persona，从这点来看可以说理解了人格面具的概念就等于大致理解了女神异闻录的主题。根据定义，人格面具是个体用于适应世界的价值理念或迎合他人的方法，换句话说，它就是伪装用的面具（mask），在不同的人面前展现出不同的样子。人格面具固然可以让我们能与他人交流得很愉快，但在多个人格面具之下却会有迷失自我的风险，也就是说面具戴久后忘记自己的本来面目了。在游戏中，与社群系统的人进行活动时佩带相符的人格面具可以获得更高的牵绊值，这正是不同种类的人需要不同的方式来交流的体现。&lt;em>PS：有关于人是怎么被分类的，由于涉及到人格原型这一概念，此处略过。&lt;/em>&lt;/p>
&lt;p>至于阴影，则是一种（常见于梦中的）拟人形象，是看上去负面的东西，可以认为是个体潜意识的一部分。它具有多种形象，可以代表不同的东西，除了大家瞬间会想到的被压制的欲望以外，还可以是自己觉得尴尬丢人的一面，也可以是目前生活状态的叛逆与反思。阴影既可以是我们的敌人也可以是我们的朋友，当它与我们的表意识一直发生严重冲突时，是敌人，会导致多种精神疾病；当我们勇敢地面对与接纳阴影时，是朋友，做到这点后我们将来到一个新的人生阶段。在游戏中这体现在与阴影对战时可以通过语言沟通让其主动退出战斗或成为主角的人格面具。&lt;/p>
&lt;h2 id="心灵成长">心灵成长
&lt;/h2>&lt;p>终于来到了个人认为最重要的一节，前面谈了那么多概念，其实都是为心理学中的重要议题：心灵的成长而铺路的。对于王道 JRPG 而言，成长也是一个经久不衰的主题。很多人都曾想过，要符合什么条件才能说一个人是成熟的，心灵成长的过程中发生了什么，上面这些问题在精神分析流派看来都是与潜意识有密切关联的。当我们还是儿童时，思维是以自我为中心的，在接受教育后，我们逐渐认识到了这个世界并不是围着自己转的，并开始在与他人的接触中带上了一张张形式各异的面具。显然，能见人说人话，见鬼说鬼话并不一定是成熟的表现，我们在人格面具中越陷越深，以至于迷失了童真时期尚有的自我。成熟并不意味着处事圆滑，也不意味着每个人成熟后都是千篇一律的形态，事实上，每个人都有其独特之处，所以真正的成长之路第一步就是照照镜子，认识到自己所佩戴的人格面具是如此的厚重，随后勇敢地开始尝试脱去这些面具来直面独特的自我。如果这样做了，此时我们首先会遇到的就是自己的阴影。在游戏中，我们总能轻松地打败阴影，可在现实中该如何面对它呢？在我们需要改变时，阴影会以一种模糊的形状在我们的梦中出现，这就是潜意识对自我的一种启示。为了成长，我们首先需要大胆思考阴影的含义，了解它为什么会以那样的形态出现，在挖掘出背后的原因后，反思对应的背景，并勇敢地采取行动。面对阴影的方法就是如此的简单而又困难，面对自己一直回避的东西的勇气是如此的难得，我们总能找到各种借口继续逃避，所以这是相当艰难的一步。在跨过阴影后，我们将进入广袤的集体潜意识，此时依然存在迷失自我的风险，但与之前不同，之前是对自我的压抑，现在则是由于集体潜意识的广阔而找不到自我的独特之处。最后，通过在集体潜意识中发掘到自我意识（用原话来说应是生命中心）从而完成成长。总结来说，成长就是从认识到人格面具的存在开始，通过直面自我的阴影，一步步从狭隘的境地进入广阔的集体潜意识，最后发掘自我以到达成熟的境界，在荣格看来，这也是所谓的超脱之路。&lt;/p>
&lt;p>对于初次接触荣格理论的人来说，以上内容是如此的抽象，所以在荣格的著作中其使用了大量的病例来说明（弗洛伊德也很喜欢用病例举例子）。有关这点除了怪盗团个人的心灵成长外，游戏中还有哪些地方有所体现呢？下面我将通过同样深受荣格影响的&lt;a class="link" href="https://douban.com/book/subject/1775691/" target="_blank" rel="noopener"
>《少有人走的路：心智成熟的旅程》&lt;/a>一书并结合第二学期末的 Boss 来说明心灵的成长。&lt;/p>
&lt;details>
&lt;summary>存在对第二学期的剧透&lt;/summary>
回想起在狮童悔改后，民众依然执迷不悟地认为狮童是绝对正义的，为此怪盗团不得不在十二月二十四号这天铤而走险闯进了印象空间的最深处，尝试盗取大众的心灵以让他们悔改。一路上看到的景象已经透露出了这背后的原因，普通民众与之前怪盗团击败的殿堂主人都自愿把自己关到了牢笼当中，认为在牢笼是最适合他们待的地方，狮童直言其原来统治世界的宏大计划不如被关在牢笼放弃思考来得舒服；最后遭遇的圣杯（统御之神）更是直接点出了这一点：因为人们想逃避责任，所以期待一个无所不能的存在来解决问题，这就是印象空间深处中孕育出了这一存在的原因。&lt;em>PS：圣杯：为什么我每次出场都要被砸。&lt;/em>按《少有人走的路》的说法，这都是懒惰的体现，而懒惰无疑是成长的最大阻碍，懒惰驱散了动力，所以人们逃避责任，逃避选择的权力，逃避自我改变。对于原来的殿堂主人来说，尽管有怪盗团这样的外力击败了阴影，但之后他们并没有在成长的路上更进一步，没有发掘自我，所以依然会被懒惰控制。这些被懒惰所控制的人，尽管外表是成熟的，但其心灵却比小孩子好不了多少，用时髦一点的说法来说，这就是巨婴。若想击败统御之神这样由懒惰而孕育出来的存在，面对它的人必须拥有足够成熟的心灵，而怪盗团的成员在天鹅绒房间走出牢房这一行为完全可以解读为他们通过其对应的经历克服了懒惰，从而获得了成长。可以说，游戏的前半段就是怪盗团的心智成熟之旅，所有成员都认识到并经历了现实的痛苦，并在面对苦痛的过程中获得了心态的改变，觉醒了人格面具（这就是和自己的阴影成为朋友的体现），完成了从青年到成年的成长之路。例如，杏认识到了面对好友遭受侵害时的痛苦与无力感，并为了这样的事情不再发生而变得坚强。尽管荣格比较喜欢强调阴影与潜意识自我等概念，但在普遍的成长过程中，痛苦这一概念更为突出。人们都是先认识到了现实的痛苦，随后在痛苦的刺激下开始成长。痛苦有大有小，并不是说必须像怪盗团成员这样有沉重的经历才能成长，痛苦是一个契机，也是刺激对抗懒惰的动力，若是一昧逃避痛苦，那么出现统御之神这样依托于懒惰的存在可以说是必然的。
&lt;/details>
&lt;h2 id="备注">备注
&lt;/h2>&lt;p>再次强调，潜意识理论并非科学理论，与之相关的催眠等种种传说请自行判断真假。因为只是简单介绍且有些观点在今天来看相当过时，所以这里并没有介绍原型、人格、阿妮玛、阿尼姆斯等内容，对此感兴趣的请自行翻阅荣格的著作。另外，上面谈论心灵成长时掺杂了一点发展心理学的内容，对心灵的成长感兴趣的话，我想以上提到的《少有人走的路：心智成熟的旅程》是一本很好的指南，其本身面向非心理学背景的读者，将论述内容集中在了心灵成长而非荣格的其它观点上，更为易读与消化。&lt;/p>
&lt;h1 id="对第三学期的一点看法剧透警告">对第三学期的一点看法（剧透警告！）
&lt;/h1>&lt;details>
&lt;summary>存在对第三学期的全面剧透，如果你还没通关，请不要展开下面的内容&lt;/summary>
在第三学期（或者说从元旦开始），主角突然发现整个世界都变了，怪盗团之前所失去的亲人奇迹般的复原，其它主要心愿也都达成了，经过一番调查后主角和明智发现这一切的幕后黑手就是在十一月时离开学校的顾问官丸喜拓人。在这里，我觉得称呼丸喜为幕后黑手或敌人都是不太合适的，与之前所面临的狮童等人不同，丸喜并不是为了自己的私欲或野心而利用异世界来改变现实，而是出于一个（至少）看起来非常崇高的理想：创造出一个人们能幸福生活的世界。实际上他也不愿意和怪盗团敌对，希望能通过对话达成一致，在游戏中我们也确实可以选择接受丸喜所创作的世界，若是选择接受，那么怪盗团的各个成员死去的亲人都会复活，之前所经历的重大挫折也会被抹平，从此生活在一个理想幸福的世界。面对这一充满诱惑的选择，我也犹豫了好一会儿，要知道这个选择比之前的统御之神所规划的世界好多了，而且我也不太愿意和之前帮助过怪盗团的丸喜交战。
&lt;p>不得不说，这是一个很困难的抉择问题，也是一个很有思考乐趣的问题，以下我将大致讲述拒绝丸喜的提案的理由，欢迎各位读者对此发表不同的看法。&lt;/p>
&lt;p>一般来说，让人们变得幸福这个目的是没错的，目的没有问题，那么需要考量的就只剩下手段了。丸喜的做法的确是只能利用印象空间才能达成的非常规手段：通过集体潜意识的力量达成人们的心愿，例如重现原本已经死亡的亲朋好友；对人们的思想进行修改，包括但不限于让人直接放弃原来的目标，对明显有问题的想法（例如嫉妒）进行修正。从这些手段上可以看出丸喜在掌握了印象空间后也不是无所不能的，有如此强大的能力为何还要让人放弃原来的想法呢，我想是因为有些目标确实无法达成，举个例子，多个人想取得比赛第一，那总会有些人无法达成，所以只能放弃。至于只有在让他人痛苦时才能快乐的人，恐怕只有修正其思想才能让他能感到幸福吧。对于直接达成心愿的作法我想不到什么问题，但对于其它手段我觉得就很有讨论价值了。&lt;/p>
&lt;p>首先，让人放弃目标这一点看上去严重性不是很大，但我依然想对此提出质疑。本质上说，放弃目标就是一种逃避，其带来的轻松感是短期的，是不可持久的。若想一直保持幸福感，那么只会是不断放弃一个又一个目标，也就是一直在逃避。在我看来，若目标过于困难/失败的打击过于沉重，那短期来说逃避虽可耻却有用。逃避是给了人短暂修整的机会，这并不意味着一直逃避对人是有用的。从心理咨询的原则来说，对于这些因目标无法达成而感到沮丧的人，丸喜作为心理咨询师唯一应做的事情就是在这些沮丧的人想从泥泞的大坑中爬出来时拉一把手，而不是劝说对方待在坑里也很舒服，也不是直接把坑弄没。上面的“心灵成长”一节也已经说明，一直逃避只会剥夺人成长的可能性，所以说丸喜的作法看上去虽然让人变得幸福了，可让人放弃原来的想法以收获幸福这怎么看都是自欺欺人，大量滥用这一手段恐怕会让人的心灵变得畸形。这样充满畸形的心灵的世界真的是一个理想的世界吗，大量懒惰的人不会导致第二个统御之神的出现吗，我对此只能给出悲观的回答。&lt;/p>
&lt;p>对于修正想法这点，很容易让人想到无数作品中反派进行的洗脑活动，只不过丸喜的目的与反派截然相反罢了。但是我们必须注意到一点，丸喜的这一作法从本质上来说和怪盗团的悔改没什么区别，只不过他的行动规模更大，针对的人群范围更广。所以我想据此引申出一个尖锐的问题，即我们所扮演的主角，怪盗团的所作所为是否正义。很显然，怪盗团的行动成功让为数不少的人避免受到或继续受到伤害，所以至少符合结果正义这一种正义。但是，虽然怪盗团成功让坏人悔改，然而这从本质上来说属于侵犯他人的思想自由，阻止不义的行为与直接改变他人观念是两码事，让人直接悔改总是会令其他人觉得可怕。就像欧美超级英雄题材的作品经常对英雄提出的质问那样，拥有这么强大力量的怪盗团是否有必要存在，他们有权力自主决定悔改的目标吗，虽然游戏尽力淡化了这些问题，但在现实中无疑对此存在很多争议，我在这里也想不到一个明确的回答。从老生常谈的“能力越大，责任越大”这点出发，丸喜想做的事情远比怪盗团更为激进，他并不只是想阻止明显的侵害他人的行为，还希望在别人没做出不义行为前修正其思想，为此他在拥有了远超怪盗团的能力后，能保证未来不会成为第二个统御之神吗，他真的能承担如此巨大的责任吗？&lt;/p>
&lt;p>当然，我们也可以从其它角度出发对丸喜的作法进行评判。在一人拥有如此强大的能力的情况下，世界的多样性会受到怎样的影响。要知道世界上如此多独特的自我共同组成了广袤无垠的集体潜意识，在丸喜“刮削”掉了他认为不合适的部分后，占据着重要地位的集体潜意识的范围还会那么广阔吗，这一改变会带来什么后续影响恐怕谁都不知道。&lt;/p>
&lt;p>总而言之，丸喜的作法更像是热血上头的中二青年一样不顾后果地乱来，尽管他的目标是如此的崇高，但我认为阻止他才是一件正确的事情。&lt;/p>
&lt;p>在我看来，第三学期十分的独特，它的存在使得 P5R 的剧情从优秀进化到了神作级别。若说 P5 本身的主题是对作恶多端的大人的反叛，那 P5R 追加的第三学期就是对自身题材的反叛：P5 本来的正义打倒邪恶这一简单二元冲突被颠覆了，这里再进一步对更深入的内容进行了挖掘。在以往的王道 JRPG 作品中，很少有丸喜这样为人善良却因目标的分歧而成为敌人的存在，更别提丸喜所用的能力还隐含了对怪盗团让人悔改的合理性的质疑，当对选择产生犹豫时我就知道这确实给我抛出了不少棘手的问题：我该如何确保我所做的事情是正义的呢？我与丸喜有什么区别？英雄的目标是打倒邪恶，可是这里有邪恶存在吗，让人变得幸福不应该是值得赞同的吗？可以说这些问题对以往理所当然的一些概念进行了解构，令人忍不住想思考这是否有些更好的答案。另外，第三学期还对游戏本身的心灵成长主题进行了一次升华：丸喜给我们的选择恰恰说明了成长之路的复杂，成长可并不是简单地直面痛苦就能达成的，面对种种诱惑我们能想到自己真正想要的是什么吗。在通关游戏后，除了美好的高中生活回忆外，在我看来这些思考就是最宝贵的收获，对此不一定要有一个明确的答案，思考的过程本身就是一笔财富。&lt;/p>
&lt;/details>
&lt;h1 id="总结">总结
&lt;/h1>&lt;p>P5R 在每个方面都是如此的完美，整体又是如此的天衣无缝，受限于本人的文笔，这篇博文恐怕并没有把这些完全展现给大家。总之，百闻不如一见，强烈建议各位读者购买游戏或通过 XGP 实际游玩 P5R 以感受其特有的韵味，相信各位在通关后也会跟我一样大喊：P5R 天下第一！&lt;/p></description></item><item><title>从 Material 主题迁移到 Stack 主题</title><link>https://viflythink.com/Migrate_from_Material_to_Stack/</link><pubDate>Sun, 21 Nov 2021 00:00:00 +0800</pubDate><guid>https://viflythink.com/Migrate_from_Material_to_Stack/</guid><description>&lt;img src="https://viflythink.com/Migrate_from_Material_to_Stack/show.jpg" alt="Featured image of post 从 Material 主题迁移到 Stack 主题" />&lt;p>在用了近三年的 Material 主题后，我终于决定更换博客主题了。尽管当初在我开始写博客时，Material 主题已经不维护了，但我暂时没找到更符合自己需求的主题，于是便一直用到了现在。当然，那时的我已经有将来会更换主题的预感了，而如今这一只脚终于落地，给博客换上了看上去更现代的 &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener"
>Stack 主题&lt;/a>，也从 Hexo 迁移到了 Hugo，于是写下本文说明一下相关的经过（&lt;del>又能水一篇博文了&lt;/del>）。&lt;/p>
&lt;h1 id="为什么要换主题">为什么要换主题
&lt;/h1>&lt;p>先列举一下旧方案的不足吧。Material 主题并不差，设计风格符合我的胃口，而且提供了我所需要的功能，但用了这么久，由于各种原因令我想更换博客主题：&lt;/p>
&lt;ol>
&lt;li>Material 主题已经不再维护，这是最核心的一个问题，考虑到前端圈在兼容性方面的“良好”名声，我毫不怀疑哪天 NodeJS 或 Hexo 一个更新就会令我的博客没法构建&lt;/li>
&lt;li>感觉当年的 Material Design 有点跟不上时代了（喜新厌旧），另外展开侧边栏时的动画给我的感觉有点慢，令我不禁怀疑在 PC 端模拟 Android 端 Material Design App 的必要性&lt;/li>
&lt;li>友链界面的卡片有一点问题，当描述较长时会出现文字溢出的问题，原本打算咬咬牙修一下 CSS 的，但想到迟早要换博客主题，不如赶快换了主题再处理友链界面&lt;/li>
&lt;/ol>
&lt;p>另外，还是由于偏主观的对前端工具链的不信任，我不想再用 Hexo 了，切换到使用自己更熟悉的语言写的静态站点生成器至少能确保在出现问题时我可以尝试自己动手修一修，所以 Python 写的 Pelican 和 Go 写的 Hugo 对我来说都是不错的选择（&lt;em>PS：为什么还没人用 Rust 造一个强大的静态站点生成器&lt;/em>）。&lt;/p>
&lt;h1 id="迁移过程">迁移过程
&lt;/h1>&lt;p>先根据 &lt;a class="link" href="https://gohugo.io/getting-started/quick-start/" target="_blank" rel="noopener"
>Hugo 官方的 Quick Start&lt;/a> 下载 Stack 主题并建了一个简单的站点，然后一步步把博客原有的配置迁移过去。&lt;/p>
&lt;p>其中比较费时间且烦琐的步骤包含把 Stack 主题提供的示例配置从 YAML 改为了 &lt;a class="link" href="https://github.com/vifly/blog/blob/main/config.toml" target="_blank" rel="noopener"
>TOML 文件&lt;/a>（非必要，个人偏好原因）；以及更改文章源码的结构：原本我是把所有的 Markdown 文件放在一个文件夹下，文章配图放到另外的文件夹，现在由于 Stack 主题的要求我给每篇博文新建了文件夹并把配图移动到对应博文所在的文件夹。&lt;/p>
&lt;p>由于不是新建博客而是迁移博客，所以我还处理了 URL 相关的问题，确保原来的 URL 不变。在迁移前，我的 Markdown 文件都是像 Python_GIL_and_concurrency.md 这样首字母与专有名词大写，下划线用于分割单词的格式，Hexo 生成的博文 URL 与文件名相同；迁移后，每篇博文都放到单独的文件夹，具体存放结构可在&lt;a class="link" href="https://github.com/vifly/blog/tree/main/content/post" target="_blank" rel="noopener"
>此&lt;/a>查看，按照 Stack 主题给的示例所生成的博文 URL 则是 viflythink.com/&amp;lt;文章标题&amp;gt;/ 的格式，我经过谷歌后按照 &lt;a class="link" href="https://gohugo.io/content-management/urls/" target="_blank" rel="noopener"
>Hugo 文档&lt;/a>设定了新的 URL 生成规则：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Toml" data-lang="Toml">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">permalinks&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">post&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;/:filename/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">page&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;/:slug/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样就会用文件夹的名字而不是用文章标题来生成 URL，当然，这还不够，生成的 URL 虽然用的是文件夹的名字，但全都转为了小写字母，然后找到&lt;a class="link" href="https://www.jvt.me/posts/2019/11/10/hugo-case-sensitive-urls/" target="_blank" rel="noopener"
>这篇文章&lt;/a>让 Hugo 生成大小写敏感的 URL：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Toml" data-lang="Toml">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">disablePathToLower&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>除了博文的 URL 外，还需要处理 RSS 订阅地址，原来的 RSS 地址是 viflythink.com/atom.xml ，可 Stack 默认产生 viflythink.com/index.xml 作为 RSS 地址，于是我又谷歌找到了 &lt;a class="link" href="https://discourse.gohugo.io/t/how-can-i-change-the-rss-url/118" target="_blank" rel="noopener"
>Hugo 论坛的讨论&lt;/a>，据此修改了设置：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Toml" data-lang="Toml">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">outputFormats&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">RSS&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">mediatype&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;application/rss&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">baseName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;atom&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>除了让 URL 保持不变外，我还设法把原本对 Material 主题修改的代码也迁移了过来。这里不得不提到 Hugo 的 &lt;a class="link" href="https://gohugo.io/hugo-modules/theme-components/" target="_blank" rel="noopener"
>Theme Components&lt;/a>，通过它我可以在站点根目录下建立同样结构的目录以覆盖主题的部分内容，也就是说不用直接对主题的源代码动手了，接下来的修改都是靠这个特性完成的，具体可参考 &lt;a class="link" href="https://docs.stack.jimmycai.com/zh/modify-theme/" target="_blank" rel="noopener"
>Stack 的修改主题章节&lt;/a>。&lt;/p>
&lt;p>与 Material 主题相同，Stack 没有提供 Isso 评论系统，新建 &lt;a class="link" href="https://github.com/vifly/blog/blob/main/layouts/partials/comments/provider/isso.html" target="_blank" rel="noopener"
>layouts/partials/comments/provider/isso.html&lt;/a> 并按 &lt;a class="link" href="https://posativ.org/isso/docs/configuration/client/" target="_blank" rel="noopener"
>Isso 文档&lt;/a>的说明把内容复制粘贴进去就能添加对 Isso 的支持了（别忘了修改站点配置文件）。&lt;/p>
&lt;p>还有验证网站所有权与插入 Matomo 统计代码都是通过新建与修改 &lt;a class="link" href="https://github.com/vifly/blog/blob/main/layouts/partials/head/custom.html" target="_blank" rel="noopener"
>layouts/partials/head/custom.html&lt;/a> 完成的。设置 CNAME 让 viflythink.com 指向 vifly.github.io 则是新建 &lt;a class="link" href="https://github.com/vifly/blog/blob/main/static/CNAME" target="_blank" rel="noopener"
>/static/CNAME&lt;/a>来完成（来源：&lt;a class="link" href="https://gohugo.io/hosting-and-deployment/hosting-on-github/#use-a-custom-domain" target="_blank" rel="noopener"
>Hugo 文档&lt;/a>）。&lt;/p>
&lt;p>我还想把随机标语也迁移到 Stack 上，这就需要覆盖主题原本的文件了。把 themes/hugo-theme-stack/layouts/partials/sidebar/left.html 复制为 &lt;a class="link" href="https://github.com/vifly/blog/blob/main/layouts/partials/sidebar/left.html" target="_blank" rel="noopener"
>layouts/partials/sidebar/left.html&lt;/a>，然后对 &lt;code>&amp;lt;h2 class=&amp;quot;site-description&amp;quot;&amp;gt;{{ .Site.Params.sidebar.subtitle }}&amp;lt;/h2&amp;gt;&lt;/code> 进行修改，具体的修改可看我的代码，如何在 Gist 存放标语内容则请看我的旧博文&lt;a class="link" href="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog_advanced/" target="_blank" rel="noopener"
>《使用 GitHub Pages 和 Hexo 搭建个人博客(进阶篇)》&lt;/a>中的“Material 主题实现随机显示标语（slogan）”一节。&lt;em>PS：翻阅旧博文后发现自己的前端技术也有所进步了，当年只会用一下 jQuery，而现在直接用 Vanilla JS 的 fetch 就完成了相同的任务&lt;/em>。为了让修改后的侧边栏看上去更好，我还微调了一下样式，具体可看 &lt;a class="link" href="https://github.com/vifly/blog/tree/main/assets/scss/partials" target="_blank" rel="noopener"
>assets/scss/partials&lt;/a> 下的内容。&lt;/p>
&lt;p>最后，我还把博客的构建从本地搬到了 GitHub Actions 上，当我把修改的 Markdown 文件推送到 GitHub 后博客就会自动更新，再也不用在本地构建好后再推送到 GitHub Page 所在的仓库了。&lt;/p>
&lt;p>总的来说，迁移过程可说是十分平滑，没有踩到任何大坑就把博客迁移到 Stack 主题了，甚至没花多少时间就把对 Material 主题的修改也搬了过来。&lt;/p>
&lt;h1 id="新变化">新变化
&lt;/h1>&lt;p>既然把博客主题换了，那肯定要对比一下迁移前后的外观。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Migrate_from_Material_to_Stack/material_blog_homepage.png"
width="1877"
height="940"
loading="lazy"
alt="使用 Material 主题的博客主页"
class="gallery-image"
data-flex-grow="199"
data-flex-basis="479px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Migrate_from_Material_to_Stack/stack_blog_homepage.jpg"
width="1886"
height="900"
loading="lazy"
alt="使用 Stack 主题的博客主页"
class="gallery-image"
data-flex-grow="209"
data-flex-basis="502px"
>&lt;/p>
&lt;p>Stack 主题是典型的三栏式布局，右侧栏包括了常用的跳转链接与搜索功能，而 Material 则是单栏，侧边栏需要点击按钮才会出现。至于在主页上的博文呈现，两者都是卡片风格，同样是由上到下的图片 -&amp;gt; 标题 -&amp;gt; 文章摘要这样组成的布局。可以看出两者给人的感受有所区别，迁移到 Stack 主题后我博客的风格更柔和多彩了，这得益于圆角的使用与主题配色的区别，当然，也与我在迁移后给文章配上精心挑选的图片有关，之前的文章图片都是由 Material 自动生成的，虽然也挺好看，体现了 Material Design 的简洁风格，但看久了后总会觉得千篇一律。&lt;/p>
&lt;p>除了外观上的变化，细心的读者可能会发现，在迁移后，我的博客左侧栏多了 &lt;a class="link" href="https://viflythink.com/service/" target="_blank" rel="noopener"
>Service&lt;/a> 这一个链接，这是我新增的一个页面，用于列举出目前我提供的服务，当提供的服务有改动时，该页面也会有所说明。&lt;/p>
&lt;p>另外，有一个读者无法察觉但我能明显感知到的变化：迁移后的博客构建速度变快了。这大概是 Hugo 的优化比 Hexo 好导致的，不过并不能完全怪罪 Hexo，因为这也与我之前用的 Material 主题许久不更新，没有用上 Hexo 的用于加快构建速度的新特性有关。总之目前的构建速度终于让我感到舒服了，能够快速看到改动效果这一点在修改主题样式时十分有用。&lt;/p>
&lt;h1 id="总结">总结
&lt;/h1>&lt;p>我有时会开玩笑说：“写博客哪有写博客主题有意思”，包括这次迁移在内，看着刚开始时一个标准的模板被改造成了自己想要的样子的确很有意思，但我认为开设博客的目的更应该是写一些自己觉得有价值的内容，博客的外观与设计的确很重要，体现了博主的品位与喜好，但世界上并不缺少能展示自己品位的方法，写博客却是少有的通过文字阐述观点、分享知识与经历的行为，所以我会尽量不让与网站相关的博文占到总博文数的一半，而是尽量让博客涉及更多领域的内容，这也是我为本博客所设立的一个目标。&lt;/p>
&lt;p>最后，希望本次愉快的博客迁移能激励我提高一些更新博客的频率（咕咕咕）。最近来说，我也有了一些新想法，考虑到不是所有的新想法都有必要写一篇博文来说明，未来我可能会在其它平台（Twitter、Telegram 频道等）或另一个自建站点记录与说明这些东西，也可能会考虑建立公开 Wiki 知识库这样的站点来分享自己的知识，总之这些都是目前只存在于脑海中的想法，这些任务就交给未来的自己了。本篇水文到此结束，谢谢有耐心读到最后的读者。&lt;/p></description></item><item><title>2021 中科大信息安全大赛题解</title><link>https://viflythink.com/Hackergame_2021_writeups/</link><pubDate>Sun, 31 Oct 2021 00:00:00 +0800</pubDate><guid>https://viflythink.com/Hackergame_2021_writeups/</guid><description>&lt;img src="https://viflythink.com/Hackergame_2021_writeups/show.jpg" alt="Featured image of post 2021 中科大信息安全大赛题解" />&lt;p>这是我第二次参加 Hackergame 了，今年依然玩的很开心，感受到了来自不同领域的考验，更开心的是与&lt;a class="link" href="https://viflythink.com/Hackergame_2020_writeups/" target="_blank" rel="noopener"
>上一年&lt;/a>的 800 分相比，今年拿到了 1150 分，有所进步😂。虽然我做出来的题并不多，而且有些简单的题目没解决，但还是写一篇博文提供题解吧。希望本篇博文能够帮到想参加 Hackergame 的各位小伙伴。&lt;/p>
&lt;h1 id="签到">签到
&lt;/h1>&lt;p>与上一年的签到题相比，今年连脑筋急转弯都不需要了，每次点 Next 时间都会 +1s（唐突玩梗），我们只要把时间翻到本届大赛的举办日期即可，也就是说需要计算出 1970 年 1 月 1 日与 2021 年 CTF 大赛的时间差（用秒表示）。这里随便选取一个符合 2021 年 CTF 大赛时间范围的时间，然后使用 Python 计算：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">datetime&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">today&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">datetime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2021&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">25&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">begin&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">datetime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1970&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">delta&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">today&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">begin&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">delta&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">total_seconds&lt;/span>&lt;span class="p">()))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>由于可选的范围较大，所以代码不考虑时区且日期只精确到日，最后得到 1635120000 这个数字，那么访问 http://202.38.93.111:10000/?page=1635120000 就可以得到 flag 了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2021_writeups/get_2021_qiandao_flag.png"
width="1432"
height="894"
loading="lazy"
alt="成功获取签到题的 flag"
class="gallery-image"
data-flex-grow="160"
data-flex-basis="384px"
>&lt;/p>
&lt;h1 id="进制十六参上">进制十六——参上
&lt;/h1>&lt;p>X 同学不会退出 Vim 着实让我笑了（&lt;a class="link" href="https://devhumor.com/content/uploads/images/June2018/vim.jpg" target="_blank" rel="noopener"
>知名勒索软件&lt;/a>），只不过这题虽说标题含有十六进制，但主要考验的是参赛者对 ASCII 码的了解。看到图片中的数字时很容易就会猜测这是不是 ASCII 码，通过 &lt;code>man ascii&lt;/code> 查十六进制表进行对比，发现前面的几个数字与图片右侧开头的字母完全能对上，那么接下来就简单了。先找到 flag 的开头，查表得“flag{”对应的十六进制 ASCII 码是 66 6C 61 67 7B，而末尾的“}”对应的是 7D，把图中属于这部分的数字手打出来，然后用 Python 进行转换即可，这里我直接抄 &lt;a class="link" href="https://stackoverflow.com/a/49400923" target="_blank" rel="noopener"
>stackoverflow 上的回答&lt;/a>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">s&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;66 6C 61 67 7B 59 30 55 5F 53 48 30 55 31 44 5F 6B 6E 30 77 5F 48 30 57 5F 74 30 5F 43 30 6E 76 33 72 74 5F 48 45 58 5F 74 6F 5F 54 65 78 54 7D&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">l&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">16&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">()))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">chr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">l&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="猫咪问答-pro-max">猫咪问答 Pro Max
&lt;/h1>&lt;p>与上一年的猫咪问答++一样，都是考验参赛者的信息搜索能力，我依然是采用了部分问题搜寻答案，另一些问题用脚本暴力破解的方案（脚本都没怎么改）。&lt;/p>
&lt;p>第一小题有点意思，题目提到信息安全俱乐部的域名（sec.ustc.edu.cn）已经无法访问，如何找到现在已经无法访问的网页上的资源呢？答案当然是网页快照，这里我选择了 &lt;a class="link" href="https://web.archive.org" target="_blank" rel="noopener"
>Wayback Machine&lt;/a>，其记录的&lt;a class="link" href="https://web.archive.org/web/20170515053637/http://sec.ustc.edu.cn/doku.php/codes" target="_blank" rel="noopener"
>社团章程页面&lt;/a>正文第一句话就是“本章程在 2015 年 5 月 4 日，经会员代表大会审议通过。”，所以答案是 20150504。&lt;/p>
&lt;p>&lt;em>PS：对于中国大陆的网民来说，想看的文章 404 是一个家常便饭的事情，感谢 Wayback Machine 等提供网页快照的组织，他们让互联网的记忆不再转瞬即逝，避免了互联网内容的永久消失，运营这些服务所耗费的资源是巨大的，如果你有能力的话，可以考虑&lt;a class="link" href="https://archive.org/donate" target="_blank" rel="noopener"
>向 Wayback Machine 捐款&lt;/a>以支持他们继续运营下去。&lt;/em>&lt;/p>
&lt;p>第三小题可以通过搜索“中国科学技术大学 Linux 用户协会 西区活动室”这些关键词找到对应的&lt;a class="link" href="https://news.ustclug.org/2016/06/new-activity-room-in-west-library/" target="_blank" rel="noopener"
>新闻稿&lt;/a>，其中就有现场照片，翻看图片得到 Development Team of Library 这个答案。&lt;/p>
&lt;p>第五小题又是一个愚人节玩笑，谷歌找到对应的 &lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc8962" target="_blank" rel="noopener"
>RFC&lt;/a>，在 Table of Contents 中看到存在 Reporting Offenses 这一章节，跳到该章节看到“Send all your reports of possible violations and all tips about wrongdoing to /dev/null.”这一句话提供了答案，所以本题的答案是 /dev/null。&lt;/p>
&lt;p>剩下的题目就是靠&lt;a class="link" href="https://gist.github.com/vifly/d98bae07c3fffdbd44a908152afb1b75#file-cat_answers_pro_max-py" target="_blank" rel="noopener"
>脚本&lt;/a>解决了。&lt;/p>
&lt;h1 id="卖瓜">卖瓜
&lt;/h1>&lt;p>不知道是不是每一届 Hackergame 的 Web 类都会有一道题目涉及数值运算。6 斤与 9 斤的瓜在放整数个的情况下是不可能凑够 20 斤的，我一开始以为这题需要想办法弄出浮点数，但经过了多次尝试后发现这题需要利用数值溢出来解决。随意尝试输入一个很大的数字，可以发现当输入的数值超过一定范围时正数会溢出变为负数。试了好一会后发现添加 5944674407370955162 个 9 斤的瓜后会导致记录变为 -1838162554790060032 斤，我们的目标是 20 斤，-1838162554790060032 加 20 正好是 9 的倍数，(-1838162554790060032 + 20) / 9 得到 -204240283865562228 这个数字，这个数并不会导致溢出，也就是说先加 5944674407370955162 个 9 斤的瓜，然后再加 204240283865562228 个 9 斤的瓜就可以凑够 20 斤了。前面进行尝试时顺便写了个&lt;a class="link" href="https://gist.github.com/vifly/d98bae07c3fffdbd44a908152afb1b75#file-sell_lemon-py" target="_blank" rel="noopener"
>脚本&lt;/a>，于是最后用它提交并获得了 flag。&lt;/p>
&lt;h1 id="amnesia">Amnesia
&lt;/h1>&lt;h2 id="轻度失忆">轻度失忆
&lt;/h2>&lt;p>这是我第一次成功解决了一道 binary 类的题目！尽管只是完成了第一小题，但依然觉得非常开心。在轻度失忆的情况下，.data 和 .rodata 段会被清除，要知道这两个段是什么东西呢，就需要对 ELF 格式有所了解。一般来说，Linux 下我们编译后得到的产物都是 ELF 格式的，例如 &lt;code>file /bin/bash&lt;/code> 会告诉我们 bash 是一个 x86 体系架构的 ELF 格式的可执行文件，ELF 要求把编译产物的不同部分放到不同的分段（section，不是 segment），具体有哪些分段，它们分别存放什么，&lt;a class="link" href="https://wiki.osdev.org/ELF#File_Structure" target="_blank" rel="noopener"
>OSDev Wiki&lt;/a> 提供了一个表格进行说明。从表格可以得知 .data 段用来存放已初始化的全局变量等数据，而 .rodata 段用来存放不变的数据。既然这两个段会被清空，那么我们就不能把含有 “Hello, world!” 的字符串放到这些地方，在这里不得不说到 C 语言的一个麻烦点：C 语言中并不存在真正的字符串，所谓的字符串本质上是字符指针或字符数组。使用字符指针的话，在初始化时该指针会指向包含“Hello, world!”的只读数据（在 .rodata 段），所以字符指针不能用在这里，只能采用字符数组进行储存。同时也不能把该字符串放到函数外作为全局变量。另外，为了防止 printf(&amp;quot;%s&amp;quot;, s); 中的 %s 被清除，这里使用 putchar 函数把字符一个个输出。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-C" data-lang="C">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;stdio.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">char&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34;Hello, world!&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">13&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">putchar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在编译完成后可以通过 &lt;code>objdump -s -j .rodata hello_world&lt;/code> 与 &lt;code>objdump -s -j .data hello_world&lt;/code> 检查 .data 和 .rodata 段是否还存在数据（自行替换文件名）。&lt;/p>
&lt;h1 id="图之上的信息">图之上的信息
&lt;/h1>&lt;p>GraphQL 是一种新颖的接口设计方案，也是我目前还不了解的一个玩意，只不过我根据题目位置和分值推测这道题目的难度应该较低，抱着试一试的心态用谷歌搜寻了 GraphQL 的常见安全问题，然后发现&lt;a class="link" href="https://blog.yeswehack.com/yeswerhackers/how-exploit-graphql-endpoint-bug-bounty/" target="_blank" rel="noopener"
>这篇博文&lt;/a>，还以为需要多动手试验几次，可没想到用文章里推荐的 &lt;a class="link" href="https://github.com/swisskyrepo/GraphQLmap" target="_blank" rel="noopener"
>GraphQLmap&lt;/a> 就直接扒出了整个接口可用的字段（需要先靠浏览器 F12 找到接口地址和其它信息）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2021_writeups/get_GraphQL_schema.png"
width="759"
height="624"
loading="lazy"
alt="获取所有字段"
class="gallery-image"
data-flex-grow="121"
data-flex-basis="291px"
>&lt;/p>
&lt;p>知道字段后就好办了，只要没有另外的身份验证，接下来直接查询 admin 的邮箱即可。根据 &lt;a class="link" href="https://graphql.org/learn/queries/" target="_blank" rel="noopener"
>GraphQL 官方文档&lt;/a>写出查询语句，此处需要填入参数，由于 guest 账号的 id 为 2，所以猜测 admin 的 id 为 1。在 GraphQLmap 内执行 {user(id: 1) {privateEmail}}，好了，黑客扒库成功。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2021_writeups/get_GraphQL_flag.png"
width="837"
height="277"
loading="lazy"
alt="成功获取图之上的信息的 flag"
class="gallery-image"
data-flex-grow="302"
data-flex-basis="725px"
>&lt;/p>
&lt;p>&lt;em>PS：我后来才发现服务端没禁用 &lt;a class="link" href="https://graphql.org/learn/introspection/" target="_blank" rel="noopener"
>introspection&lt;/a>，例如我查询 users 时服务端会提示是不是想查询 user，所以其实不用 GraphQLmap，执行以下查询就可以取得所有字段了。&lt;/em>&lt;/p>
&lt;pre tabindex="0">&lt;code>{__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description locations args{...InputValue}}}}fragment FullType on __Type{kind name description fields(includeDeprecated:true){name description args{...InputValue}type{...TypeRef}isDeprecated deprecationReason}inputFields{...InputValue}interfaces{...TypeRef}enumValues(includeDeprecated:true){name description isDeprecated deprecationReason}possibleTypes{...TypeRef}}fragment InputValue on __InputValue{name description type{...TypeRef}defaultValue}fragment TypeRef on __Type{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name}}}}}}}}
&lt;/code>&lt;/pre>&lt;p>&lt;img src="https://viflythink.com/Hackergame_2021_writeups/query_when_enable_introspection.png"
width="587"
height="323"
loading="lazy"
alt="当 introspection 被启用时的查询结果"
class="gallery-image"
data-flex-grow="181"
data-flex-basis="436px"
>&lt;/p>
&lt;h1 id="加密的-u-盘">加密的 U 盘
&lt;/h1>&lt;p>与上一年 Hackergame 的&lt;a class="link" href="https://github.com/USTC-Hackergame/hackergame2020-writeups/blob/master/official/%E5%AE%A4%E5%8F%8B%E7%9A%84%E5%8A%A0%E5%AF%86%E7%A1%AC%E7%9B%98/README.md" target="_blank" rel="noopener"
>室友的加密硬盘&lt;/a>一样都涉及到了 LUKS，上一年的题目由于没想到冷启动攻击这种高级玩意没做出来，今年的题目相比起来就简单多了，对 LUKS 的原理有一定了解的前提下可以很快找到思路。LUKS 有一个很有趣的设计，当我们解密使用 LUKS 加密的分区时，首先会使用我们输入的密码解密位于加密分区头部的主密钥（master key），此时使用的算法在设计上故意令运算速度非常慢（LUKS1 默认的是 PBKDF2），以此给尝试暴力破解的攻击者增加时间成本，然后才会使用这个主密钥解密分区内容，而此时算法的性能就非常好（默认采用 aes-xts-plain64，使用 &lt;code>cryptsetup luksFormat&lt;/code> 创建加密分区时可以通过 &amp;ndash;cipher 参数指定其它算法，通过 &lt;code>cryptsetup benchmark&lt;/code> 查看各种算法的性能指标），不会让用户觉得访问速度慢。&lt;/p>
&lt;p>知道了上述这一点后，我们可以想一想 day1 与 day2 这两个镜像会有什么区别，虽然 day2 的密码已经被改了，可两者之间是否有什么东西没有改变呢？答案就是主密钥没变。利用这点，我们可以从能够解密的 day1.img 中提取出主密钥，然后用主密钥解密 day2。&lt;/p>
&lt;p>先根据&lt;a class="link" href="https://unix.stackexchange.com/a/504234" target="_blank" rel="noopener"
>该回答&lt;/a>把两个镜像文件附到（attach）Loop 设备，并解密挂载 day1.img：&lt;/p>
&lt;pre tabindex="0">&lt;code>sudo losetup -P /dev/loop1 ./day1.img
sudo losetup -P /dev/loop2 ./day2.img
sudo cryptsetup open /dev/loop1p1 day1
mkdir /tmp/day1
sudo mount /dev/mapper/day1 /tmp/day1
&lt;/code>&lt;/pre>&lt;p>接下来原以为按 &lt;a class="link" href="https://access.redhat.com/solutions/1543373" target="_blank" rel="noopener"
>RedHat 的文章&lt;/a>操作就行了，没想到 &lt;a class="link" href="https://gitlab.com/cryptsetup/cryptsetup/-/issues/453" target="_blank" rel="noopener"
>LUKS2 不支持通过 dmsetup 获得 masterkey&lt;/a>，那么只能先用 cryptsetup 导出主密钥了：&lt;/p>
&lt;pre tabindex="0">&lt;code>sudo cryptsetup luksDump /dev/loop1p1 --dump-master-key
&lt;/code>&lt;/pre>&lt;p>这会警告你输出的信息很敏感，需要另外输入大写的 YES 进行确认才能继续。获得输出后复制 MK dump 里的一长串十六进制数，并用 xxd 进行转换，最后解密 day2.img：&lt;/p>
&lt;pre tabindex="0">&lt;code>echo &amp;#34;be 97 db 91 5c 30 47 ce 1c 59 c5 c0 8c 75 3c 40 72 35 85 9d fe 49 c0 52 c4 f5 26 60 af 3e d4 2c ec a3 60 53 aa 96 70 4d f3 f2 ff 56 8f 49 a1 82 60 18 7c 58 d7 6a ec e8 00 c1 90 c1 88 43 f8 9a&amp;#34; | xxd -r -p &amp;gt; masterkey
sudo cryptsetup open /dev/loop2p1 day2 --master-key-file ./masterkey
mkdir /tmp/day2
sudo mount /dev/mapper/day2 /tmp/day2
cat /tmp/day2/flag.txt
&lt;/code>&lt;/pre>&lt;p>这是一种比较难以利用的破解 LUKS 的方式，需要取得已无效的密码与该密码有效时的 LUKS 分区。当然，这也提醒我们旧的加密数据最好不要随便公开，没准攻击者就靠这点破解了新的加密数据呢。&lt;/p>
&lt;h1 id="赛博厨房">赛博厨房
&lt;/h1>&lt;h2 id="level-0">Level 0
&lt;/h2>&lt;p>第零天的菜谱是“0,1”，而写好指令后第一天变成了“1,1”，那只能更改指令重新学习后执行。指令如下：&lt;/p>
&lt;pre tabindex="0">&lt;code>向右 2 步
拿起 2 个物品
向下 1 步
向左 2 步
放下 1 个物品
放下 1 个物品
&lt;/code>&lt;/pre>&lt;p>&lt;img src="https://viflythink.com/Hackergame_2021_writeups/cybercook_level0_flag.png"
width="1537"
height="909"
loading="lazy"
alt="Level 0 flag"
class="gallery-image"
data-flex-grow="169"
data-flex-basis="405px"
>&lt;/p>
&lt;h2 id="level-1">Level 1
&lt;/h2>&lt;p>看到菜谱包含了这么多的 0，先复制并用 Python 统计总共有多少个 0，得到 73 这个数字。&lt;/p>
&lt;p>接下来又是拿起物品并放下的流程了，别忘了一次只能放下一个物品，所以我们需要 73 次放下，这就需要构造一个循环语句了，如果把循环展开为 73 行“放下 1 个物品”，题目会由于行数过大而不予通过，具体指令如下（使用 goto 实现循环可真难受）：&lt;/p>
&lt;pre tabindex="0">&lt;code>向右 1 步
拿起 73 个物品
向下 1 步
向左 1 步
放下 1 个物品
如果手上的物品大于等于 0 向上跳转 1 行
&lt;/code>&lt;/pre>&lt;p>&lt;img src="https://viflythink.com/Hackergame_2021_writeups/cybercook_level1_flag.png"
width="1427"
height="905"
loading="lazy"
alt="Level 1 flag"
class="gallery-image"
data-flex-grow="157"
data-flex-basis="378px"
>&lt;/p>
&lt;h1 id="一些没做出来的题目">一些没做出来的题目
&lt;/h1>&lt;p>不知道为什么，今年与上一年类似，一些题目看似很简单，可我就是没做出来，赛后看到题解时差点吐血，原来我就只差一点点就解决了。&lt;/p>
&lt;p>“去吧！追寻自由的电波”，这题我已经发现 &lt;code>ffmpeg -i radio.mp3 -af &amp;quot;atempo=0.5,asetrate=22050&amp;quot; res.mp3&lt;/code> 可以输出能让人听清楚读音的音频，而且也找到了无线电领域所使用的&lt;a class="link" href="https://zh.wikipedia.org/wiki/%E5%8C%97%E7%BA%A6%E9%9F%B3%E6%A0%87%E5%AD%97%E6%AF%8D" target="_blank" rel="noopener"
>字母表&lt;/a>，可惜就差 November 这一个字母没听出来。&lt;/p>
&lt;p>“透明的文件”，当我写 &lt;a class="link" href="https://github.com/vifly/rgrcat" target="_blank" rel="noopener"
>rgrcat&lt;/a> 这个项目时（咕咕咕）已经了解到了 &lt;a class="link" href="https://en.wikipedia.org/wiki/ANSI_escape_code" target="_blank" rel="noopener"
>ANSI 转义序列&lt;/a>，知道它可以让程序的输出附上颜色，所以我在每个“[”前加上了“\033”，唯一没想到的是还需要把所有空格替换为其它字符，不然会看不到输出。&lt;/p>
&lt;p>“Easy RSA”，看到题目说“你还获得了构造 p 和 q 的方式”，我还以为今年终于能解决一道 math 类的题目，可惜自己的数理基础太差，连怎么计算 p 都没想到，只能明年继续努力了。&lt;/p>
&lt;p>“FLAG 助力大红包”，还以为需要在应用层以下的协议栈当中寻找伪造 IP 的办法，没想到反代服务器存在 X-Forwarded-For 欺骗漏洞，可以很轻松地伪造 IP，这也提醒我以后在涉及 IP 识别的代码中不要信任 X-Forwarded-For 头标。&lt;/p>
&lt;h1 id="总结">总结
&lt;/h1>&lt;p>今年的中科大信息安全大赛依然很好玩，就算没解决题目，很多题目的描述看了以后都让我发出了笑声。虽然做出来的题目不多，但面对每道题目都绞尽脑汁寻找解题方法时的感觉非常美妙。今年也是我第一次做出来一小道 binary 类题目，当然，我在这方面的基础还是不够，只能多学习点东西，在明年的 Hackergame 尝试再进一步。&lt;/p></description></item><item><title>使用 Vercel 与 OneDrive 自建软件源</title><link>https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/</link><pubDate>Sat, 04 Sep 2021 00:00:00 +0800</pubDate><guid>https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/</guid><description>&lt;img src="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/show.jpg" alt="Featured image of post 使用 Vercel 与 OneDrive 自建软件源" />&lt;p>&lt;em>2021.11.12.更新：增加了关于 GPG 签名的说明。&lt;/em>
&lt;em>2024.11.23.更新：现在支持上传软件包到其它 Rclone 支持的云存储了，所以更新了对 uploadToOneDrive job 的说明。&lt;/em>&lt;/p>
&lt;p>大家好，又是本鸽子久违的博客更新。之前的&lt;a class="link" href="https://viflythink.com/Use_GitHubActions_to_build_AUR" target="_blank" rel="noopener"
>《GitHub Actions 打造 AUR 打包下载一条龙服务》&lt;/a>已经折腾出了完全白嫖的编译机，可好景不长，当我的机器数量变多后，我发现在每台机器上运行脚本下载软件包的确有点麻烦，与其自己写一个脚本下载软件包，还不如直接自建一个软件源呢。经过一番研究后，我盯上了 OneDrive 与 Vercel 这两个可以免费使用的服务，通过它们实现了自建一个完全免费而且在国内外都可高速访问的软件源。&lt;/p>
&lt;p>本文是对&lt;a class="link" href="https://viflythink.com/Use_GitHubActions_to_build_AUR" target="_blank" rel="noopener"
>《GitHub Actions 打造 AUR 打包下载一条龙服务》&lt;/a>的扩展，如果你还没有读过该博文，请先读完它再回来阅读本文。通过前文与本文，你可以在没有服务器，不花一分钱的情况下搭建一个基于 OneDrive 的高速自建软件仓库，体验到白嫖与折腾 Linux 的双重快乐。&lt;/p>
&lt;p>为了得到一个公开的软件仓库，只是把软件包构建出来并放到 GitHub Release 是不够的，没有软件包数据库，软件包管理器可不知道如何获取这些软件包，更不用提校验与安装等等。生成软件包数据库只需要一行 &lt;code>repo-add ./reponame.db.tar.gz *.tar.zst&lt;/code>，然后把它们都放到 GitHub Release 这样的免费存储后端就可以解决分发问题了，实际上有一个 &lt;a class="link" href="https://github.com/Brx86/repo" target="_blank" rel="noopener"
>arch-build 的 fork&lt;/a> 就是这样做的。当然，我对其还是不够满意，通过 fastgit 等 GitHub 反代服务的确可以加速 GitHub Release 的下载速度，但还是不够稳定；还有一个更重要的原因，我不仅想要自建 Arch 软件源，也想要自建 Debian 软件源，而 GitHub Release 的路径不够灵活，无法构造像 yourrepo.com/debian/pool/main/n/nginx/ 这样的 URL，没法满足自建 Debian 软件源的需求。所以嘛，只能自己再造一个轮子了。&lt;/p>
&lt;p>在看下文之前，不妨先打开&lt;a class="link" href="https://archrepo.viflythink.com/" target="_blank" rel="noopener"
>我的自建仓库&lt;/a>页面查看最终效果，如果想使用我的自建软件源，需要先执行以下指令导入 GPG 公钥：&lt;/p>
&lt;pre tabindex="0">&lt;code>wget -O /tmp/vifly-repo.key &amp;#39;https://share.viflythink.com/arch-repo.key&amp;#39; &amp;amp;&amp;amp; sudo pacman-key --add /tmp/vifly-repo.key
sudo pacman-key --lsign-key viflythink@gmail.com
&lt;/code>&lt;/pre>&lt;p>然后在 /etc/pacman.conf 末尾添加下面几行后执行 pacman -Syu：&lt;/p>
&lt;pre tabindex="0">&lt;code>[vifly]
Server = https://archrepo.viflythink.com
&lt;/code>&lt;/pre>&lt;p>尽管本文最终提供的成品目前只用在 Arch 自建源上，但对脚本稍加修改后也可以用来自建其它 Linux 发行版的软件仓库。&lt;/p>
&lt;h1 id="上传到-onedrive">上传到 OneDrive
&lt;/h1>&lt;p>之前配置的 GitHub Actions 已经可以把软件包上传到 Release 上，现在只需要对原来的配置文件稍加改造就可以让 GitHub Actions 把软件包也上传到 OneDrive，为了尽量不重复造轮子，在这里我选择了 Rclone 进行上传，它提供了非常完善的文件传输功能，例如上传或下载时遇到文件内容相同的情况会自动跳过。&lt;/p>
&lt;h2 id="在-azure-创建应用">在 Azure 创建应用
&lt;/h2>&lt;p>根据 &lt;a class="link" href="https://rclone.org/onedrive/#getting-your-own-client-id-and-key" target="_blank" rel="noopener"
>Rclone 的官方文档&lt;/a>操作即可，以下给出图文操作步骤。尽管 Rclone 官方认为这是可选的，但在 Vercel 部署直链下载应用时也需要在 Azure 创建应用获取 Token，所以便把相关步骤放这里了，另外基于权限最小化的原则，这里我们创建的是一个拥有读写权限的应用，而在 Vercel 部署时则是创建一个具有只读权限的应用，两个应用用在不同的地方，这样可有效提升安全性。&lt;/p>
&lt;p>打开 &lt;a class="link" href="https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade" target="_blank" rel="noopener"
>Azure 的应用管理页面&lt;/a>，点击 New registration。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/azure_applist.png"
width="1914"
height="786"
loading="lazy"
alt="Azure 应用列表"
class="gallery-image"
data-flex-grow="243"
data-flex-basis="584px"
>&lt;/p>
&lt;p>在打开的界面中输入应用的名字（这里我用了 rclone 这个名字），Supported account types 这一项选择图中的第二项（也是包含范围最广泛的那项），在 Redirect URI (optional) 这一项选择 Web 并在右边的输入框里输入 http://localhost:53682/ 这一网址。完成后点击 Register。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/azure_app_register.png"
width="1904"
height="878"
loading="lazy"
alt="Azure 注册应用"
class="gallery-image"
data-flex-grow="216"
data-flex-basis="520px"
>&lt;/p>
&lt;p>此时应用已经注册完成，记录下 Application (client) ID 的值，这就是下文会用到的 client id。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/azure_get_client_id.png"
width="1918"
height="876"
loading="lazy"
alt="Azure 获取 client id"
class="gallery-image"
data-flex-grow="218"
data-flex-basis="525px"
>&lt;/p>
&lt;p>点击左侧菜单的 Certificates &amp;amp; secrets，然后点击 New client secret，在 Description 一栏随便填点什么，把 Expires（过期时间）设为最长的 24 months（两年后记得更新 client secret），点击 Add。最后记录下新增的 client secret 的值（在图中标注的 Value）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/azure_get_client_secret.png"
width="1918"
height="897"
loading="lazy"
alt="Azure 获取 client secret"
class="gallery-image"
data-flex-grow="213"
data-flex-basis="513px"
>&lt;/p>
&lt;p>到此为止就完成了。有些同学可能会感到奇怪：Rclone 文档中不是还有第 4 与第 5 步设置权限吗？根据我的实测，这两步并没有必要执行，所以这里不会附上这两步操作的示意图。&lt;/p>
&lt;h2 id="获取-token">获取 Token
&lt;/h2>&lt;p>在本地安装 Rclone（&lt;code>pacman -S rclone&lt;/code>），运行 rclone config 进入交互式配置流程，接着&lt;a class="link" href="https://rclone.org/onedrive" target="_blank" rel="noopener"
>一步步地按照提示操作&lt;/a>，当程序询问 Microsoft App Client Id 和 Microsoft App Client Secret 时，填入上一小节中记录的对应值。&lt;/p>
&lt;p>完成配置后 Rclone 会把配置数据存放在 ~/.config/rclone/rclone.conf，使用 &lt;code>cat ~/.config/rclone/rclone.conf&lt;/code> 查看。如下所示，下文的 RCLONE_CONFIG 就需要把这些东西复制粘贴进去。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-INI" data-lang="INI">&lt;span class="line">&lt;span class="cl">&lt;span class="k">[xxx]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">onedrive&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">client_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">xxx&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">client_secret&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">xxx&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">global&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">drive_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">personal&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">token&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">{&amp;#34;access_token&amp;#34;:&amp;#34;xxx&amp;#34;,&amp;#34;token_type&amp;#34;:&amp;#34;Bearer&amp;#34;,&amp;#34;refresh_token&amp;#34;:&amp;#34;xxx&amp;#34;,&amp;#34;expiry&amp;#34;:&amp;#34;xxx&amp;#34;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">drive_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">xxx&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="配置-github-actions">配置 GitHub Actions
&lt;/h2>&lt;p>首先 fork &lt;a class="link" href="https://github.com/vifly/arch-build" target="_blank" rel="noopener"
>arch-build 仓库&lt;/a>，如果你在之前已经使用了它，记得同步到最新版本。与前文所给的例子相比，现在的 workflow 文件（.github/workflows/build.yml）多了 uploadToOneDrive 这一个 job，虽然我这里用的是 OneDrive，但其实你也可以用它把软件包上传到其它 Rclone 支持的云存储上。&lt;/p>
&lt;p>${{ secrets.xxx }} 这样的变量都是需要在 GitHub 的项目配置中的 Secrets 一栏设置的私密变量，打开项目的 Settings，找到下图所示的界面，然后点击 New repository secret，并填入 RCLONE_CONFIG 这个变量的值（把上一小节的 rclone.conf 内容复制粘贴进去即可）。具体的操作也可参考 &lt;a class="link" href="https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository" target="_blank" rel="noopener"
>GitHub 官方文档&lt;/a>。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/github_actions_add_secret.png"
width="1752"
height="1026"
loading="lazy"
alt="GitHub Actions 添加私密变量"
class="gallery-image"
data-flex-grow="170"
data-flex-basis="409px"
>&lt;/p>
&lt;p>接着回来修改 workflow 文件，dest_path 是 OneDrive 上传的目的地路径（如果该路径不存在，Rclone 会自动创建），以 Linux 文件路径的形式填写即可，不建议使用根路径，因为接下来将会把这个路径下的所有东西公开，各位肯定不希望别人打开你的软件仓库页面时还看到其它乱七八糟的文件；repo_name 是你的自建软件仓库的名字，&lt;a class="link" href="https://wiki.archlinux.org/title/Pacman/Tips_and_tricks#Custom_local_repository" target="_blank" rel="noopener"
>它用于 repo-add 的参数&lt;/a>。&lt;/p>
&lt;p>为了安全，建议为自己的软件源添加 GPG 签名，不签名的话，pacman.conf 中的仓库配置需要加上 &lt;code>SigLevel = Never&lt;/code> 禁用签名校验才能使用。如果你想为自己的软件源添加 GPG 签名的话，建议先生成一个单独的 GPG 密钥对（不要设置密码），而不是使用原有的密钥对，并导出私钥：&lt;/p>
&lt;pre tabindex="0">&lt;code>gpg --gen-key
gpg --armor --export-secret-keys your_keyid &amp;gt; private.key
&lt;/code>&lt;/pre>&lt;p>回到 GitHub 的项目配置新增 Secrets，Name 为 gpg_private_key，Value 则是导出的私钥内容。最后在 workflow 文件的 uploadToOneDrive job 的参数加上 gpg-privatekey: ${{ secrets.gpg_private_key }}（即 dest_path、repo_name 等配置所在的位置）。现在得到的软件源将具有 GPG 签名，需要按以下步骤导入公钥才能使用：&lt;/p>
&lt;pre tabindex="0">&lt;code>gpg --armor --export your_keyid &amp;gt; public.key
sudo pacman-key --add public.key
sudo pacman-key --lsign-key your_keyid
&lt;/code>&lt;/pre>&lt;p>为了节省存储空间，这个 job 只会在 OneDrive 存储最新版本的软件包，不像 Arch 官方软件仓库那样还提供了归档。如果你对使用 Rclone 同步到 OneDrive 或构建软件包数据库的细节感兴趣，那么可以查看 &lt;a class="link" href="https://github.com/vifly/arch-build/blob/master/create-db-and-upload-action/entrypoint.sh" target="_blank" rel="noopener"
>entrypoint.sh&lt;/a> 脚本了解细节，不到三十行便完成了这些工作（其实是因为我把复杂的逻辑用 Python 实现了）。&lt;/p>
&lt;h1 id="在-vercel-部署直链下载应用">在 Vercel 部署直链下载应用
&lt;/h1>&lt;p>上面我们已经把软件包成功放到了 OneDrive 中，OneDrive 本身也有分享功能，可是它的分享链接地址没有任何的规律，Pacman 可不知道一个软件包对应的下载地址与 OneDrive 分享地址的联系，它只认 yourrepo.com/package 这样的下载地址（Apt 等包管理器认的 URL 更复杂，但依然有明显的规律），所以我们需要一个应用来实现链接的转换，这就是直链下载应用要干的事情。&lt;/p>
&lt;p>GitHub 上已经有不少 onedrive index 项目实现 OneDrive 的直链下载，我嫌它们提供的功能太多了（没忍住自造轮子的冲动），所以也用 Python 造了一个非常简陋的应用 &lt;a class="link" href="https://github.com/vifly/urepo" target="_blank" rel="noopener"
>urepo&lt;/a>，支持在 Vercel 上部署，也支持直接在 VPS 上部署，它和 Rclone 一样采用了微软官方提供的 API 实现提取文件下载链接的功能。下文将使用 urepo 实现直链下载，如果你之前已经部署了其它的 onedrive index 应用，那参照下文继续用原来的应用也是可以的。&lt;/p>
&lt;h2 id="获取访问令牌">获取访问令牌
&lt;/h2>&lt;p>既然 urepo 和 Rclone 一样采用了微软官方提供的 API，那么它同样也要像使用 Rclone 那样获取访问令牌。回到上面的“在 Azure 创建应用”这一小节，按同样的步骤再创建一个应用，只是这次的 Redirect URI (optional) 应输入 http://localhost/ ，完成后得到 client id 与 client secret。&lt;/p>
&lt;p>与上面依靠 Rclone 获取 Token 不同的是，这次则是使用一个脚本获取 Token，它不会像 Rclone 那样申请写入权限。下载我写好的&lt;a class="link" href="https://github.com/vifly/urepo/blob/main/client-tools/get_deploy_config.py" target="_blank" rel="noopener"
>获取 Token 脚本&lt;/a>与&lt;a class="link" href="https://github.com/vifly/urepo/blob/main/client-tools/config.py.example" target="_blank" rel="noopener"
>配置示例&lt;/a>，并确保已经安装了 Python 的 Requests 库（&lt;code>pacman -S python-requests&lt;/code>），然后把配置示例（config.py.example）重命名为 config.py，并填入 CLITENT_ID 与 CLITENT_SECRET。&lt;/p>
&lt;p>运行脚本：&lt;/p>
&lt;pre tabindex="0">&lt;code>cd downloadpath
python3 get_deploy_config.py
&lt;/code>&lt;/pre>&lt;p>根据提示操作，最后得到 code 与 refresh_token。此时我们已经获得 client id、client secret、code、refresh_token 这四项配置。至于下文中需要用到的 path，那就是在“配置 GitHub Actions”这一小节中的 dest_path。&lt;/p>
&lt;h2 id="部署到-vercel">部署到 Vercel
&lt;/h2>&lt;p>&lt;a class="link" href="https://vercel.com/" target="_blank" rel="noopener"
>Vercel&lt;/a> 是一个免费的应用部署平台，主要用来测试和部署 Serverless 应用，通过它，我们可以零成本地部署直链下载应用。Vercel 提供了两种部署方式，从下面两种方式任选其一执行即可。&lt;/p>
&lt;h3 id="打开链接部署推荐">打开链接部署（推荐）
&lt;/h3>&lt;p>注册或登录你的 Vercel 账号，然后打开我创建的&lt;a class="link" href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvifly%2Furepo&amp;amp;env=code,path,client_secret,client_id,refresh_token" target="_blank" rel="noopener"
>部署链接&lt;/a>，会出现如下界面，在 GitHub、GitLab、Bitbucket 这三个 Git 平台中选择一个进行连接，Vercel 会把 urepo 仓库复制到连接的平台上。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/vercel_import_repo.png"
width="1204"
height="893"
loading="lazy"
alt="Vercel 导入仓库"
class="gallery-image"
data-flex-grow="134"
data-flex-basis="323px"
>&lt;/p>
&lt;p>接下来的 Create a Team 只要点击 Skip 跳过就行，然后就是环境变量的设置，urepo 会首先尝试从环境变量中读取这些私密信息，无法找到对应的信息时才会去读取项目根目录下的 auth.json 获取配置，与把访问令牌写在配置文件相比，利用环境变量配置可以避免自己不小心把私密信息公开，根据上文填写这五个环境变量后就完成部署了。&lt;/p>
&lt;h3 id="从本地上传部署">从本地上传部署
&lt;/h3>&lt;p>本方法需要安装 NodeJS 相关的工具链，我不太想和这些工具打交道，但 Vercel 本来是一个部署前端应用的平台，所以官方的客户端使用 JS 编写是很正常的事情，如果不想安装这些软件，那可以使用上面的部署方法。&lt;/p>
&lt;p>首先安装 Yarn 或其它 NodeJS 包管理器：&lt;code>pacman -S yarn&lt;/code>，由于 JS 应用总是喜欢在用户的家目录乱丢东西，所以为了让 Yarn 遵循&lt;a class="link" href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html" target="_blank" rel="noopener"
> XDG 目录规范&lt;/a>，我们可以执行 &lt;code>yarn config set prefix ~/.local&lt;/code>。然后执行下面的指令全局安装 vercel 应用，这会把它安装到你的家目录（~/.local/bin，记得让你的 $PATH 包含这个路径）：&lt;/p>
&lt;pre tabindex="0">&lt;code>yarn global add vercel
&lt;/code>&lt;/pre>&lt;p>安装完成后执行下面的指令进行登录，在打开的浏览器窗口注册或登录你的 Vercel 账号并进行验证：&lt;/p>
&lt;pre tabindex="0">&lt;code>vercel login
&lt;/code>&lt;/pre>&lt;p>下载 urepo 源码：&lt;/p>
&lt;pre tabindex="0">&lt;code>git clone git@github.com:vifly/urepo.git
&lt;/code>&lt;/pre>&lt;p>把 urepo 根目录下的 auth.json.example 重命名为 auth.json，然后把对应的配置填入里面。或者，也可以在上传部署后到 Vercel 的项目面板中设置 code、path、client_secret、client_id、refresh_token 这五个环境变量。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/vercel_set_env.png"
width="1236"
height="902"
loading="lazy"
alt="Vercel 设置环境变量"
class="gallery-image"
data-flex-grow="137"
data-flex-basis="328px"
>&lt;/p>
&lt;p>最后就是上传部署：&lt;/p>
&lt;pre tabindex="0">&lt;code>cd urepo
vercel .
&lt;/code>&lt;/pre>&lt;h2 id="使用自己的域名可选">使用自己的域名（可选）
&lt;/h2>&lt;p>尽管 Vercel 会为部署的应用分配一个二级域名（xxx.vercel.app），但自建源使用自己的域名无疑是一个更好的选择。根据&lt;a class="link" href="https://vercel.com/docs/custom-domains#subdomains" target="_blank" rel="noopener"
>官方文档&lt;/a>，首先需要打开项目的域名管理界面，添加自己想使用的域名。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/vercel_add_domain.png"
width="1319"
height="873"
loading="lazy"
alt="Vercel 添加域名"
class="gallery-image"
data-flex-grow="151"
data-flex-basis="362px"
>&lt;/p>
&lt;p>假设各位和我一样使用了自己的子域名，那么在自己的 DNS 解析服务提供商管理面板添加一条 CNAME 解析记录即可，我使用的是 cloudflare，还需要把代理状态设为“仅限 DNS”以确保不会使用 cloudflare 的反代。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/cloudflare_set_vercel_cname.png"
width="1102"
height="154"
loading="lazy"
alt="在 cloudflare 设置指向 Vercel 的 CNAME"
class="gallery-image"
data-flex-grow="715"
data-flex-basis="1717px"
>&lt;/p>
&lt;h1 id="有待改进的地方">有待改进的地方
&lt;/h1>&lt;p>尽管这一流程已经能工作了，但有些地方还是可以再改进一下的。&lt;del>首先，目前并没有数字签名，添加这一自建软件源时需要禁用对此的签名校验，在乎安全性的同学可能会对此表示不爽，所以日后有必要加上对软件包签名的支持。其次，urepo 应该无需修改就可以用于分发其它发行版的软件包与数据库（它的本质就是一个简陋的 onedrive index），但前面的 GitHub Actions 只支持 Arch，我未来肯定会加上对 Debian/Ubuntu 的支持，具体什么时候搞定这个，就要看我什么时候有需求了，对其它发行版的支持也是同样的😂&lt;/del>。目前已支持 GPG 签名，也新增了 &lt;a class="link" href="https://github.com/vifly/debian-build" target="_blank" rel="noopener"
>debian-build&lt;/a> 用于构建 deb 软件包。&lt;/p>
&lt;p>另外，我编写的 urepo 与 GitHub Actions 脚本的报错信息并不够用户友好，由于大量采用了 Python 进行编写，假如出现错误的话对于没学过 Python 的同学来说可能难以根据输出的错误信息解决问题。这个问题也是留待日后解决。&lt;/p></description></item><item><title>Python GIL 和并发编程</title><link>https://viflythink.com/Python_GIL_and_concurrency/</link><pubDate>Sat, 08 May 2021 00:00:00 +0800</pubDate><guid>https://viflythink.com/Python_GIL_and_concurrency/</guid><description>&lt;img src="https://viflythink.com/Python_GIL_and_concurrency/show.jpg" alt="Featured image of post Python GIL 和并发编程" />&lt;p>各位读者好，又是本鸽子久违的更新。最近在应聘后端开发岗位的过程中为了应对面试官各种奇怪的问题，特意整理了自己为 Python 并发编程所做的笔记，一看内容已经足够填满一篇博文了，那就作为新的一篇博文发布吧。&lt;/p>
&lt;p>由于并发（concurrency）与并行（parallelism）这两个词的意义总是纠缠不清，以至于有时会看到“Python 不支持并发”这样让我哭笑不得的说法，所以先明确一下在本文中这两个词的定义：根据&lt;a class="link" href="https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/" target="_blank" rel="noopener"
>还在疑惑并发和并行？&lt;/a>的说明，我们把并行定义为&lt;strong>同时&lt;/strong>有多个单位工作，这指的是运行时的&lt;strong>状态&lt;/strong>，而并发则是一种&lt;strong>程序结构设计&lt;/strong>，能够实现&lt;strong>一段时间&lt;/strong>内执行逻辑上存在区别的多个操作。在应用程序层面，并发是并行的必要条件。&lt;/p>
&lt;h1 id="python-的-gil">Python 的 GIL
&lt;/h1>&lt;p>说起 Python 的并发，那么总是绕不开 GIL（全局解释器锁）这个东西，这就是导致我们采用的很多种并发手段无法实现并行的罪魁祸首，要注意的是并不是说所有并发手段都无法实现并行，例如多进程就可以。为了破解 Python 的并行难题，下面就让我们先解析一下这个让无数 Python 开发者怨声载道的 GIL。&lt;/p>
&lt;h2 id="gil-是什么">GIL 是什么
&lt;/h2>&lt;p>全局解释器锁（Global Interpreter Lock），顾名思义是一种锁，与我们平常在多线程环境下用到的锁不同，GIL 确保的是在同一时刻，在一个 Python 解释器中只有&lt;strong>一个线程&lt;/strong>能够处于执行状态。回想一下我们写的 Python 代码是如何被执行的：在不考虑优化的情况下，Python 解释器一行一行地读取我们写的代码，然后把代码翻译为机器指令（准确来说是 bytecode）执行。GIL 就是对这个解释器上了一把锁，让它在任何时刻都只能执行一条指令。对于 GIL 的实现细节，可以在阅读完本文后参阅 &lt;a class="link" href="https://cyrusin.github.io/2016/04/27/python-gil-implementaion/" target="_blank" rel="noopener"
>GIL 的实现细节&lt;/a>一文加深理解。&lt;/p>
&lt;p>需要注意的是，GIL 仅存在于 Python 官方的 CPython 实现和 PyPy 中，使用其它语言实现的 Python 解释器（如 Jython）并不存在这个限制，所以本文涉及 GIL 时所指的 Python 都是 CPython 实现。&lt;/p>
&lt;h2 id="为什么需要-gil">为什么需要 GIL
&lt;/h2>&lt;p>对于其它的主流语言（C/C++ 和 Java 等），它们并不会阻止在同一时刻多个线程的存在，为什么 Python 这么特殊，非要给解释器上一个锁，令我们写的多线程代码没法并行呢。&lt;/p>
&lt;p>不少人会回答这是为了解决 Python 的内存管理问题而引入 GIL。且慢，GIL 和内存管理有什么关系？准确来说 GIL 和内存管理中的&lt;a class="link" href="https://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29" target="_blank" rel="noopener"
>垃圾回收&lt;/a>（Garbage Collection，简称 gc）有关，假如你只学过 C 语言，那你可能在刚开始写 Python 时感到有点疑惑：为什么大家的代码在创建新对象后都不手动销毁，不会内存泄漏吗。然后你知道了 Python 有 gc，可以自动帮你回收不用的内存，像 Java、Go 等语言也有 gc，所以大家都不担心自己忘记销毁对象。可为什么其它具有 gc 的语言不存在 GIL 呢？这个问题可以从 Python 的起源获得一部分解答，Python 本身可以说是一个为了好玩而产生的项目，所以采用简单的方式实现 gc 是一个很合理的选择，这个简单的方式就是引用计数（学过 C++ 的同学是否觉得这个名词很熟悉）。&lt;em>经热心群友提醒，Python 的 gc 实现不只是简单的引用计数，还用到了&lt;a class="link" href="https://devguide.python.org/garbage_collector/#optimization-generations" target="_blank" rel="noopener"
>分代 gc&lt;/a> 作为优化手段，该 gc 实现并不是线程安全的&lt;/em>。Python 中的所有东西都是对象，每个对象都有一个引用计数值，当一个对象的引用计数归零时（代表已经没有任何地方用到这个对象了），Python 就会销毁对象并回收对应的内存，以此实现自动管理内存。你可以通过以下方式查看对象的引用计数：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">a&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getrefcount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>上文说到了引用计数对于 gc 的作用，接下来有个重要的问题：我们该如何确保在多线程环境下对象的引用计数是正确的呢？对并发有点了解的人肯定会回答，这就是一个竞态冲突问题，每次读写线程间共享的对象的引用计数前上锁就行了。的确，这样可以解决上述问题，但是，这可能导致死锁，此外频繁获取与释放锁会带来严重的性能问题，所以 Python 官方没有采用这个方案，而是采用了 GIL（主角登场了）。GIL 的思路很简单：与其对多个对象上锁，不如只对解释器上一个锁，有效避免上多个锁的问题。对于 Python 官方来说，既然已经采用了简单的方法实现 gc，那也采用简单的方法解决引用计数的竞态冲突问题是很合理的，当然，代价就是 Python 的多线程无法实现并行。&lt;/p>
&lt;p>在当年来说，使用 GIL 并没有什么问题，可是当 Python 逐渐被用于后端开发后，不少开发者都开始抱怨 GIL，而且正好 Python3 带来了一堆不兼容 Python2 的改变，为什么此时不干掉 GIL 呢？&lt;/p>
&lt;p>去除 GIL 有很多种办法，例如更改 gc 的实现，使其不再依赖引用计数，或者采用其它解决引用计数的竞态冲突问题的方案，等等。可惜的是，这些方案要么太难以实现，要么会降低单线程应用和多线程 I/O 密集型应用的性能，所以&lt;a class="link" href="https://www.artima.com/weblogs/viewpost.jsp?thread=214235" target="_blank" rel="noopener"
>移除 GIL 并不简单&lt;/a>。&lt;/p>
&lt;p>除此以外，还有一个原因，那就是我们提到 Python 的优点时经常列举的一点：Python 可以轻易集成使用 C/C++ 语言编写的库，它们极大丰富了 Python 的生态，调用这些库时也不用担心性能问题。虽然这些 C 库铸就了 Python 的辉煌，但也存在一个问题：这些 C 库本身不一定是线程安全的，在多线程环境下可能会出现各种奇怪的问题，这点应该不少用 C 语言写过应用的人都深有体会，要想兼容这些线程不安全的 C 库，GIL 是最简单（也许是唯一）的方案。&lt;/p>
&lt;h2 id="python-的多线程真的没用吗">Python 的多线程真的没用吗
&lt;/h2>&lt;p>虽然大家都在吐槽 Python 的多线程约等于单线程，但其实在某些情况下 Python 的多线程依然是有用的。首先我们需要区分 I/O 密集型应用与计算密集型应用这两种情况，前者经常等待 I/O 操作，例如读写数据库，上传/下载文件，后者在运行时会消耗掉所有分配给它的 CPU 资源进行计算，例如图像处理。&lt;/p>
&lt;p>对于计算密集型应用来说，Python 的多线程反倒是一个累赘：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">threading&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Thread&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">COUNT&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">50000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">countdown&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">n&lt;/span> &lt;span class="o">-=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">countdown&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">COUNT&lt;/span>&lt;span class="o">//&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">countdown&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">COUNT&lt;/span>&lt;span class="o">//&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">end&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">end&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">COUNT&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">50000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">countdown&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">n&lt;/span> &lt;span class="o">-=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">countdown&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">COUNT&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">end&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">end&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>实际运行后会发现上面的多线程版本居然比单线程要慢一点，原因就是多线程不能并行，与单线程相比，多线程版本中线程间的上下文切换还浪费了一些时间。&lt;/p>
&lt;p>那么 I/O 密集型应用的情况呢（使用 time.sleep 模拟 I/O 操作）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">threading&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Thread&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">fake_io&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">fake_io&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">fake_io&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">t2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">end&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">end&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">fake_io&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">fake_io&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">fake_io&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">end&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">end&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这回多线程版本大获全胜，仅用了约2秒结束运行，而单线程版本用了约4秒。&lt;/p>
&lt;p>看到这里，有些同学可能有点奇怪，为什么此时多线程看上去像是没有 GIL 那样以并行状态运行，只用了2秒就结束。其实这里并没有并行，Python 始终只使用了一个 CPU 核心，只是 Python 对 I/O 操作做了一个优化：当进行 I/O 或 time.sleep 这样会阻塞当前线程的操作前会主动释放 GIL，自己等待导致阻塞的任务完成，由于 GIL 被释放所以其它的线程能够运行，完成后该线程再获取 GIL。这个优化并不能实现并行，因为在同一时刻依然只有一个单位在工作，但得益于它，Python 的 I/O 密集型应用在多线程下的表现要比单线程好。所以，Python 的多线程并不是完全没用的。&lt;/p>
&lt;h2 id="为什么依然需要线程锁">为什么依然需要线程锁
&lt;/h2>&lt;p>当我第一次知道 Python 的 GIL 对多线程的影响后，就产生了一个挥之不去的问题：既然都有 GIL 了，为什么多线程编程还需要上线程锁呢？现在想来，我应该是被相关文章里的“Python 多线程约等于单线程”这个说法给误导了：既然等于单线程，那就不需要用于多线程的线程锁吧？本质上来说，这属于对定义不够了解所产生的问题。从运行时的状态来说，即使采用了多线程编程，Python 在同一时刻也的确只有一个线程在运行，可是这并不代表我们可以忽略线程安全问题。如果对 GIL 和线程锁的定义和作用有足够的了解，那么就不会存在这个问题，显然，假如有了 GIL 后在多线程环境下可以不用线程锁，那 GIL 就必须提供与线程锁相同的功能。从这点出发，上文已经提到，GIL 是作用于解释器的，确保同一时刻只能存在一个线程，而线程锁作用于多线程编程里的临界区，或者说是对应代码里的共享数据，确保不会发生竞态冲突。前者并不能实现后者的功能，举个多线程下的例子：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">threading&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">foo&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">n&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">n&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">threads&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">threading&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">foo&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">threads&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">threads&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">threads&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>有时候这段没有使用线程锁的代码不一定能输出100这个值，具体原因就是 GIL 并不保证执行完成一个线程里的操作后才切换到另一线程，也就是说不加线程锁可能会出现：线程 A 读取了变量 n（假设此时是 10），线程 B 读取变量 n（此时是 10），线程 B 修改了变量 n（n = 10 + 1），线程 A 修改变量 n（n = &lt;strong>10&lt;/strong> + 1）。此时线程 A 对变量 n 的修改会导致错误的结果（它修改的是过时的值）。这与其它语言中的线程安全问题是相似的，&lt;a class="link" href="https://opensource.com/article/17/4/grok-gil" target="_blank" rel="noopener"
>Grok the GIL: How to write fast and thread-safe Python&lt;/a> 一文从原子操作的角度解释了为什么 Python 依然需要线程锁。&lt;/p>
&lt;p>尽管仍然需要线程锁，但是 GIL 还是为 Python 多线程编程带来了一个好处：无需像其它语言那样考虑锁的颗粒度，上粗颗粒度的锁并没有任何问题，只需确保上线程锁的那部分代码不存在 I/O 等会释放 GIL 的操作，不然的话会导致性能下降，原因是：在当前线程进行 I/O 时，GIL 被自动释放，一般情况下会自动切换到另一线程，但是如果此时线程锁未被释放，那将导致另一线程无法进入临界区，不得不等待持有线程锁的线程完成 I/O。&lt;/p>
&lt;h1 id="如何实现并发">如何实现并发
&lt;/h1>&lt;p>在存在 GIL 的情况下，该如何实现并发编程并且让 Python 能在同样的时间内处理更多的事情呢？大致有以下几种思路。&lt;/p>
&lt;h2 id="多线程协程">多线程/协程
&lt;/h2>&lt;p>之所以把多线程和协程放在一起，是因为这两者都无法实现并行。&lt;a class="link" href="https://en.wikipedia.org/wiki/Coroutine" target="_blank" rel="noopener"
>协程（Coroutine）&lt;/a>也是在遇到 I/O 等阻塞操作时主动让出 CPU 的控制权让其它协程能够运行，思路都是让 CPU 单核不要浪费时间在等待阻塞操作上，只不过与多线程相比协程的花销更小，现在越来越多的强调性能的 Python 框架开始采用协程，如 &lt;a class="link" href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener"
>FastAPI&lt;/a>、&lt;a class="link" href="https://github.com/encode/httpx" target="_blank" rel="noopener"
>HTTPX&lt;/a> 等。历史上 Python 存在多种实现协程的方式，如 Gevent、yield 等，现在 Python 官方推荐的是通过 &lt;a class="link" href="https://docs.python.org/zh-cn/3/library/asyncio-task.html" target="_blank" rel="noopener"
>async/await&lt;/a> 关键字实现。这两者都适合在 I/O 密集型应用中使用。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">say_after&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">delay&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">what&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">delay&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">what&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_task&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">say_after&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;hello&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_task&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">say_after&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;world&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;started at &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strftime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="si">%X&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">task1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">task2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">end&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;finished at &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strftime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="si">%X&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Time taken in seconds -&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">main&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="多进程">多进程
&lt;/h2>&lt;p>这是能让 Python 实现并行的一个方案，原因是 GIL 是针对单个解释器的，既然如此，多开几个解释器不就能同时运行多个工作了吗。当然，考虑到进程上下文切换的代价要比线程大，这个方案比较适合计算密集型应用。&lt;/p>
&lt;p>Python 内置的 &lt;a class="link" href="https://docs.python.org/zh-cn/3/library/multiprocessing.html" target="_blank" rel="noopener"
>multiprocessing 库&lt;/a>提供了对应的支持。值得一提的是，&lt;a class="link" href="https://m.douban.com/book/subject/26337939/" target="_blank" rel="noopener"
>《七周七并发模型》&lt;/a>的第三章“函数式编程”中提到 Clojure 语言提供了 pmap 函数实现对 map 的并行化，Python 通过进程池也可做到这点：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">multiprocessing&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">multiprocessing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Pool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">f&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">x&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">Pool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">multiprocessing&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cpu_count&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="用-c-语言重写耗时的代码">用 C 语言重写耗时的代码
&lt;/h2>&lt;p>最后的杀手锏，嫌弃多进程消耗资源大又想并行怎么办，答案就是把相关代码&lt;a class="link" href="https://docs.python.org/zh-cn/3/extending/extending.html" target="_blank" rel="noopener"
>用 C 语言重写&lt;/a>。C 库中的代码并不受 GIL 限制，而且一般来说 C 语言编写的代码执行速度要比 Python 快不少，还可以充分利用 CPU 的并行能力（如 SIMD），像 &lt;a class="link" href="https://numpy.org/" target="_blank" rel="noopener"
>NumPy&lt;/a> 这种科学计算库就是很好的例子。虽然性能很诱人，但是用 C 语言重写其实是一个非常麻烦的事情，没有足够技术力的情况下最好不要考虑这个方案。&lt;/p>
&lt;h1 id="结语">结语
&lt;/h1>&lt;p>Python 的 GIL 给想要实现并行的程序员带来了一定的挑战，同时由于 Python 作为解释型语言的先天劣势，其性能在面对短时间内高流量的情况时有些无力，当然，虽说如此，不少能人还是探索了相当多的解决方案，使得 Python 的性能不至于太差，让 Python 在后端开发中依旧占据一席之地。&lt;/p>
&lt;p>当然，如果你实在受不了 GIL，还可以考虑使用其它语言的 Python 实现，只是，是否能使用 C 库和不同实现的细节差异所带来的坑使得并没有什么人选择这样做，改用其它语言的后端框架也是一种选择。当对高性能有所要求时，不要为难自己，换一门语言海阔天空。&lt;/p></description></item><item><title>中科大信息安全大赛初体验</title><link>https://viflythink.com/Hackergame_2020_writeups/</link><pubDate>Mon, 09 Nov 2020 00:00:00 +0800</pubDate><guid>https://viflythink.com/Hackergame_2020_writeups/</guid><description>&lt;img src="https://viflythink.com/Hackergame_2020_writeups/show.jpg" alt="Featured image of post 中科大信息安全大赛初体验" />&lt;p>半年以来的第一篇新博文！九月底的时候放弃考研，然后尝试的秋招都凉了，处于颓废期的博主正好看到第七届中科大信息安全大赛（Hackergame 2020）即将举办，于是便去参加这个 CTF 比赛转换心情。作为一个非信安专业的学生，这是我第一次参加信息安全大赛，虽然之前也看过往年中科大信息安全大赛的题解，但自己真正参与时才发现自己与专业人士的差距。专业 CTF 选手轻松占据了排行榜前列，不过本菜鸡也玩的很开心，打完比赛后不得不说比赛的题目设置都相当有趣，对信安有点兴趣的人来这个比赛玩玩保证不会后悔。&lt;/p>
&lt;p>很菜的只拿到了 800 分，但下面还是写一下成功解决的题目的题解吧。&lt;/p>
&lt;h1 id="签到">签到
&lt;/h1>&lt;p>为了鼓励参与而设置的送分题，既然是 Web 类的题目那先在浏览器按 F12 打开开发者工具准没错，随便拉一下，点击提取，果然不能拿到 flag，不过看到浏览器发送了一个 GET 请求，其中的参数 number 就是刚才拉到的数字。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2020_writeups/try_qiandao.png"
width="1725"
height="927"
loading="lazy"
alt="尝试获取签到题的 flag"
class="gallery-image"
data-flex-grow="186"
data-flex-basis="446px"
>&lt;/p>
&lt;p>那么我们试试提&lt;strong>一个&lt;/strong> flag 吧。把 number 参数的值改为 1，就成功拿到签到题的 flag 了！&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2020_writeups/get_qiandao_flag.png"
width="1723"
height="905"
loading="lazy"
alt="成功获取签到题的 flag"
class="gallery-image"
data-flex-grow="190"
data-flex-basis="456px"
>&lt;/p>
&lt;h1 id="猫咪问答">猫咪问答++
&lt;/h1>&lt;p>这题很明显是考验参赛者使用搜索引擎的能力。不过嘛，看到 &lt;a class="link" href="https://github.com/ustclug/hackergame2018-writeups/blob/master/official/ustcquiz/README.md" target="_blank" rel="noopener"
>2018 年猫咪问答题解&lt;/a>后我想到一些题目也可以靠脚本暴力尝试进行破解，第一和第四小题搜索起来都比较麻烦，所以这两题就靠&lt;a class="link" href="https://gist.github.com/vifly/751221f27ba89f670b8f2c56b96c24a7#file-cat_answers-py" target="_blank" rel="noopener"
>脚本&lt;/a>破解好了。&lt;/p>
&lt;p>第二小题的答案可以在&lt;a class="link" href="https://tools.ietf.org/html/rfc1149" target="_blank" rel="noopener"
> RFC1149 文档&lt;/a>上找到，原文是“A typical MTU is 256 milligrams.”，所以答案是 256。&lt;/p>
&lt;p>第三小题的答案可以在&lt;a class="link" href="https://news.ustclug.org/2019/09/2019-sfd-ustc/" target="_blank" rel="noopener"
>中国科学技术大学 Linux 用户协会新闻站&lt;/a>上找到，原文提到“最后一项是李文睿同学介绍了开源游戏 Teeworlds”，所以得到 9 这个数字。&lt;/p>
&lt;p>第五小题的答案依然可以在&lt;a class="link" href="https://news.ustclug.org/2019/12/hackergame-2019/" target="_blank" rel="noopener"
>中国科学技术大学 Linux 用户协会新闻站&lt;/a>上找到，答案是 17098。&lt;/p>
&lt;p>对于第一小题，我先通过粗略估计得出答案至少为 6 的结论，接着就是毫无技巧的脚本暴力破解时间，最后得出第一小题答案是 12，第四小题答案是 9。&lt;/p>
&lt;h1 id="2048">2048
&lt;/h1>&lt;p>非常有趣的 2048 游戏，但是，既然这是打 CTF，那这题肯定不是考验我们玩 2048 的技术，首先像签到题那样胡乱尝试直到在开发者工具看到一个 GET 请求：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2020_writeups/try_2048.png"
width="1920"
height="920"
loading="lazy"
alt="尝试游玩 2048"
class="gallery-image"
data-flex-grow="208"
data-flex-basis="500px"
>&lt;/p>
&lt;p>看来这题想要取得 flag 就需要找到出题者喜欢的水果，虽然可以靠脚本暴力尝试所有常见的水果名，不过通过分析网页源代码我们可以找到 2048 获胜时将会发送的网络请求：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2020_writeups/2048_source_code.png"
width="709"
height="624"
loading="lazy"
alt="2048 的源代码分析"
class="gallery-image"
data-flex-grow="113"
data-flex-basis="272px"
>&lt;/p>
&lt;p>好，水果名就藏在其中，只需在开发者工具的控制台里输入 (&amp;lsquo;b&amp;rsquo;+&amp;lsquo;a&amp;rsquo;+ +&amp;lsquo;a&amp;rsquo;+&amp;lsquo;a&amp;rsquo;).toLowerCase() 就能得到“banana”这个答案了（神奇的 JavaScript 语法），发送这个网络请求即可获得 flag。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2020_writeups/get_fruit.png"
width="656"
height="124"
loading="lazy"
alt="获得水果名称"
class="gallery-image"
data-flex-grow="529"
data-flex-basis="1269px"
>&lt;/p>
&lt;h1 id="一闪而过的-flag">一闪而过的 Flag
&lt;/h1>&lt;p>在 Windows 下双击下载的 exe 文件发现黑窗一闪而过，那么很自然的想到在 exe 文件所在的目录下按住 Shift 键并点击右键选择“在此处打开 PowerShell”，输入 ./Untitled01.exe，然后可执行文件就输出了 flag{Are_you_eyes1ght_g00D?_can_you_dIst1nguish_1iI?} 这个答案（我还以为会有其它障碍）！本题难度非常低，基本上在终端里执行过可执行文件的人都知道先尝试这样做。&lt;/p>
&lt;h1 id="从零开始的记账工具人">从零开始的记账工具人
&lt;/h1>&lt;p>中文大写数字转阿拉伯数字？这个需求想必很常见吧。我找到了一个能实现这个功能的 &lt;a class="link" href="https://pypi.org/project/cn2an/" target="_blank" rel="noopener"
>Python 库&lt;/a>，接下来就是写一个&lt;a class="link" href="https://gist.github.com/vifly/751221f27ba89f670b8f2c56b96c24a7#file-bills-py" target="_blank" rel="noopener"
>脚本&lt;/a>来帮我们进行计算了。&lt;/p>
&lt;p>为了减少工作量（懒得写读取 xlsx 文件的代码），我用 MS Excel 导出文本文件，然后进行计算。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2020_writeups/bills_txt.png"
width="962"
height="604"
loading="lazy"
alt="导出的账单文件"
class="gallery-image"
data-flex-grow="159"
data-flex-basis="382px"
>&lt;/p>
&lt;p>写脚本时需要注意 Excel 导出的 txt 文件采用的是 GBK 编码。根据计算结果得到 flag{11118.23}。&lt;/p>
&lt;h1 id="233-同学的-docker">233 同学的 Docker
&lt;/h1>&lt;p>注意到题目描述里说明“写了一行命令删掉这个文件”，对 Docker 有点了解的人应该已经想到这个文件至少在某一层依然存在。&lt;/p>
&lt;p>首先尝试&lt;a class="link" href="https://gist.github.com/dasgoll/476ecc7a057ac885f0be" target="_blank" rel="noopener"
>使用 Docker history 回滚&lt;/a>到未删除 flag.txt 文件时的版本，执行 docker history 8b8d3c8324c7/stringtool 后输出：&lt;/p>
&lt;pre tabindex="0">&lt;code>IMAGE CREATED CREATED BY SIZE COMMENT
be6d023618d1 2 weeks ago /bin/sh -c #(nop) ENTRYPOINT [&amp;#34;/bin/sh&amp;#34; &amp;#34;-c… 0B
&amp;lt;missing&amp;gt; 2 weeks ago /bin/sh -c rm /code/flag.txt 0B
&amp;lt;missing&amp;gt; 2 weeks ago /bin/sh -c #(nop) COPY dir:c36852c2989cd5e8b… 1.19kB
&amp;lt;missing&amp;gt; 6 weeks ago /bin/sh -c #(nop) WORKDIR /code 0B
&amp;lt;missing&amp;gt; 6 weeks ago /bin/sh -c mkdir /code 0B
&amp;lt;missing&amp;gt; 6 weeks ago /bin/sh -c #(nop) ENV PYTHONUNBUFFERED=1 0B
&amp;lt;missing&amp;gt; 6 weeks ago /bin/sh -c pip3 install pipenv 37.5MB
...
&lt;/code>&lt;/pre>&lt;p>这里执行的 rm /code/flag.txt 就是题目所说的删掉了 flag.txt，然而除了最新的 tag 外其它层的 ID 都是 &lt;code>&amp;lt;missing&amp;gt;&lt;/code>，看来上面的教程并不适用。不过没关系，这里还有&lt;a class="link" href="https://www.pushbeta.com/2019/04/10/stripping-the-layers-how-secure-is-your-docker-image/" target="_blank" rel="noopener"
>一篇教程教你如何寻找机密信息&lt;/a>。执行 docker save 8b8d3c8324c7/stringtool &amp;gt; out.tar 得到 dump 出来的文件。解压 out.tar，在 a39ee53cb7d2d86ef0&amp;hellip;（省略）这个文件夹下解压 layer.tar 即可得到 flag.txt。&lt;/p>
&lt;h1 id="狗狗银行">狗狗银行
&lt;/h1>&lt;p>不得不说这题难倒我几天了，看完规则就可以确定在&lt;strong>正常&lt;/strong>情况下我们绝对赚不到钱，想要让净资产达到两千以上只能靠寻找漏洞了。刚开始时我以为需要用到整数溢出，没想到前端还对转账数额上限进行了限制，那就靠 F12 找到网络请求刷吧。没想到虽然网页显示的净资产已经超过 2000，但还是没法取得 flag。接着我又尝试了负数，浮点数等各种奇怪的输入，都没有办法取得成果，我被题目卡住了。&lt;/p>
&lt;p>直到两天后组委会发布公告（仅截取重要内容）：&lt;/p>
&lt;blockquote>
&lt;p>公告 1：本题前端计算存在浮点数导致的计算误差，数字特别极端时显示可能不正确。但后端采用大整数精确计算，只有净资产确实高于 2000 时才会给出 flag。&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>公告 3：本题新增限制：每个用户最多 1000 张卡，每张卡最多 100000 条交易&amp;hellip;&amp;hellip;&lt;/p>
&lt;/blockquote>
&lt;p>嗯&amp;hellip;公告 1 直接说明我之前的解题思路不对，不过公告 3 倒是给了我灵感：本题需要开多张卡才能解决。那么，也许是利息计算存在误差？我先做了一个测试，得出一张储蓄卡需要至少 167 元才能获得 1 元的利息这个结论。注意到储蓄卡的规则是“利率每日 0.3%“，1 / 0.003 = 333.33，也就是说正常情况下应当存入 334 元才能获得 1 元的利息，但这里只需 167 元即可。然后让我们看看信用卡的规则：利率每日 0.5%，最低 10 元。那么在每日增加 10 元负债的情况下我们最多能从一张信用卡中拿到多少钱呢，答案是 2099 元。2099 / 167 = 12.5689，这意味着我们终于发现了发家致富的好办法，因为此时从信用卡获取的贷款居然能给我们带来比负债利息更大的收益。
于是具体的赚钱方法就是：开一张信用卡，然后开 12 张储蓄卡，用信用卡给每张储蓄卡转帐 167 元，那么我们每日的净收益就是 12 - 10 = 2 元。不停重复上述步骤直到题目规定的上限，然后吃饭结束一天。具体代码可通过&lt;a class="link" href="https://gist.github.com/vifly/751221f27ba89f670b8f2c56b96c24a7#file-hack_bank-py" target="_blank" rel="noopener"
> gist &lt;/a>查看。运行脚本后打开网页可看到 flag{W0W.So.R1ch.Much.Smart.52f2d579}。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Hackergame_2020_writeups/get_bank_flag.png"
width="997"
height="887"
loading="lazy"
alt="成功获取狗狗银行的 flag"
class="gallery-image"
data-flex-grow="112"
data-flex-basis="269px"
>&lt;/p>
&lt;h1 id="一些没做出来的题目">一些没做出来的题目
&lt;/h1>&lt;p>先说一说令我觉得遗憾（感觉差点就能做出来）的题目吧。看了&lt;a class="link" href="https://github.com/USTC-Hackergame/hackergame2020-writeups/" target="_blank" rel="noopener"
>官方/非官方题解&lt;/a>后，不得不说有些题目真的只是差一点就能做出来了，请容我在这里倒下苦水。&lt;/p>
&lt;p>“从零开始的 HTTP 链接”，我已经找到&lt;a class="link" href="https://daniel.haxx.se/blog/2014/10/25/pretending-port-zero-is-a-normal-one/" target="_blank" rel="noopener"
>一篇文章提到 curl 可以连接零号端口&lt;/a>，然而 Arch 上的 curl 版本过高，没法做到这一点，本博主居然忘了自己可以编译一个旧版本的 curl 尝试连接，错失了这一道题。除了 curl 外，这题&lt;a class="link" href="https://github.com/USTC-Hackergame/hackergame2020-writeups/blob/master/players/mcfx/writeup.md#%E6%9D%A5%E8%87%AA%E4%B8%80%E6%95%99%E7%9A%84%E5%9B%BE%E7%89%87" target="_blank" rel="noopener"
>也可以通过 Python 解决&lt;/a>。&lt;/p>
&lt;p>“来自一教的图片”，作为一个学过图像处理的人士，居然没想到可以使用 &lt;a class="link" href="https://numpy.org/doc/stable/reference/generated/numpy.fft.fft2.html#numpy.fft.fft2" target="_blank" rel="noopener"
>np.fft.fft2&lt;/a> 得到答案，真是非常惭愧啊（狗头保命）。&lt;/p>
&lt;p>“生活在博弈树上”，虽然知道可以靠 C 语言 gets 函数的安全缺陷进行栈溢出攻击跳转到输出 flag 的位置，但是不会构造 payload，所以只能放弃这题。&lt;/p>
&lt;p>除了上面这些让我遗憾的题目外，还有一些有趣的题目值得一提。&lt;/p>
&lt;p>“室友的加密硬盘”，什么，居然有 512 位 AES 加密？看完题解后发现这不是重点，并不需要猜测这里的加密实现是否存在缺陷，使用冷启动攻击才是正道。这也提醒我们全盘加密并不能 100 % 确保数据不会泄露，遇到懂得在 dump 的内存中寻找密钥的攻击者还是有可能被破解的。&lt;/p>
&lt;p>“超简易的网盘服务器”，很有趣的一点是这题与上一题产生了联动，数据放本地不安全，那么放在云端就能确保万事大吉了吗？为了解决这题我特意去研究了 Nginx 的 location 匹配规则，虽然得知针对本题的 nginx.conf 访问 php 文件可以绕过认证，但对于如何访问 private 文件夹下的 flag 还是一筹莫展，尝试了 ../../ 这样的路径，但可惜 h5ai 并没有这么容易被攻破。比赛结束后发现自己吃了没认真阅读 h5ai 源代码的亏，没想到在没认证的情况下可以通过 h5ai 的下载功能把全部文件下载。不得不说，这题成功吓到了我，让我也赶快去检查自己的 Nginx 配置，避免出现同样由于忽略匹配优先级而导致的漏洞。&lt;/p>
&lt;h1 id="总结">总结
&lt;/h1>&lt;p>谢谢中科大信息安全大赛，让我体验到了久违的解决问题的乐趣，我已经很久没体会过躺在床上时依然在思考比赛题目的感觉了，这次比赛成功做到了这点，对我来说这就足够了。名次并不重要，重要的是解题的乐趣。&lt;/p>
&lt;p>如果明年还有中科大信息安全大赛，那我肯定会参加，当然，与今年没什么准备就仓促上阵不同，至少我会先看下 &lt;a class="link" href="https://ctf-wiki.github.io/ctf-wiki/" target="_blank" rel="noopener"
>CTF Wiki&lt;/a> 再来解题，争取更好的名次。也强烈安利各位还没参加过 CTF 的童鞋尝试一下中科大的信息安全大赛，并不需要多少专业技能也可解题，既可以体会到这种充满乐趣的过程，顺便还能学到一点冷门的东西。&lt;/p></description></item><item><title>GitHub Actions 打造 AUR 打包下载一条龙服务</title><link>https://viflythink.com/Use_GitHubActions_to_build_AUR/</link><pubDate>Tue, 28 Apr 2020 00:00:00 +0800</pubDate><guid>https://viflythink.com/Use_GitHubActions_to_build_AUR/</guid><description>&lt;img src="https://viflythink.com/Use_GitHubActions_to_build_AUR/show.png" alt="Featured image of post GitHub Actions 打造 AUR 打包下载一条龙服务" />&lt;p>&lt;em>2021.2.2.更新：受 &lt;a class="link" href="https://www.aloxaf.com/2020/06/build_aur_with_github_actions/" target="_blank" rel="noopener"
>Aloxaf 的博文&lt;/a>启发新增使用自己的 PKGBUILD 进行构建的说明。&lt;/em>&lt;/p>
&lt;p>尽管目前博主我还在考研，但最近还是经不住折腾 Arch 的诱惑，抽空对使用 Arch 以来一直觉得体验不够好的安装 AUR 软件包流程进行改造，最终的结果就是搭建了这一个自动化的 AUR 编译打包下载安装一条龙服务，并写下本文向各位安利。要问为什么我想折腾这个东西，当然是因为使用 AUR 助手安装 AUR 的软件包存在如下缺点：&lt;/p>
&lt;ol>
&lt;li>下载速度慢，由于很多时候都需要从 GitHub 下载文件，所以每秒 10 KB 的下载速度是很常见的（虽然这点可以通过设置 http_proxy 环境变量让 Yay 等 AUR 助手使用代理来解决）&lt;/li>
&lt;li>编译需要时间，如果你只是需要几个小软件包那可以无视这点&lt;/li>
&lt;li>给别人分享已打好的软件包有点麻烦，每次更新你都需要通过某种方式传输文件给对方（凑够三点 ／人◕‿‿◕人＼）&lt;/li>
&lt;/ol>
&lt;p>为了解决以上问题，本文使用免费的 GitHub Actions 与 Cloudflare Workers，手把手教你搭建一个自动化 AUR 软件构建流程，只需一次配置，你就可以享受船新的&lt;del>白嫖&lt;/del> AUR 使用体验。另外，这也是我第一次实际使用 CI（持续集成），通过配置整个工作流，我算是学习了一把 CI 的使用（&lt;em>PS：这才是真正的目的&lt;/em>），所以你也可以把这篇文章当作我的 CI 学习笔记。&lt;/p>
&lt;h1 id="github-actions-简介">GitHub Actions 简介
&lt;/h1>&lt;p>首先，GitHub Actions 是 GitHub 在 2019 年推出的一项 CI 服务。如果你没听说过 CI，那这里我尝试用一句话来解释，CI 就是对新的项目更改进行自动化构建，在本文的场景下，新的更改指的是 AUR 上的 PKGBUILD 文件发生变更（实际上我为了偷懒，选择了设置定时任务而不是监测 PKGBUILD 的变更），自动化构建就是自动编译加打包以及上传（如果是闭源软件那就不是编译而是拆包等操作）。嗯，就是这么简单，如果想知道 CI 的详细定义，可以看下&lt;a class="link" href="https://www.redhat.com/zh/topics/devops/what-is-ci-cd" target="_blank" rel="noopener"
>红帽的文章&lt;/a>。&lt;/p>
&lt;p>GitHub Actions 的特点是支持的触发条件种类数非常多，而且与 GitHub 的集成很好，学习难度也不高，只要简单地写一个配置文件即可，还可轻松调用别人写好的操作步骤，对于开源项目作者来说最大的好处就是可以白嫖 GitHub 的机器用来为不同的平台编译。如果你已经对 GitHub Actions 感到心动，那么不妨阅读&lt;a class="link" href="https://help.github.com/en/actions/getting-started-with-github-actions" target="_blank" rel="noopener"
>官方文档&lt;/a>来学习一下用法，或者靠&lt;a class="link" href="https://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html" target="_blank" rel="noopener"
>阮一峰的介绍文&lt;/a>快速上手。想要更简短的介绍？没问题，来看看下面的讲解吧。&lt;/p>
&lt;p>想要使用 GitHub Actions，那首先需要在项目根目录下的 .github/workflows 文件夹下创建一个以 yml 为后缀名的 workflow 文件（如 build.yml），在这个 YAML 文件中写入我们的配置。那么配置该怎么写呢，让我们看一个示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-YAML" data-lang="YAML">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Greeting from Mona&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">push&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># ================== 第一个 job，这只有一个 job ==================&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">my-job&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">My Job&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ================== 第一个 step，执行单个命令 ==================&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Print a greeting&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MY_VAR&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Hi there! My name is&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">FIRST_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Mona&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MIDDLE_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">The&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">LAST_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Octocat&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">echo $MY_VAR $FIRST_NAME $MIDDLE_NAME $LAST_NAME.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ================== 第二个 step，使用别人的 action ==================&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ================== 第三个 step，执行多个命令 ==================&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Install the dependencies&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> sudo apt-get update
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> sudo apt-get install pkg-config gettext&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>按从上到下的顺序来看，name 对象应该无需解释了，值得注意的是 &lt;a class="link" href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#on" target="_blank" rel="noopener"
>on&lt;/a> 对象，可以填入单个事件或事件数组作为触发条件，当满足条件时便执行这个 YAML 文件里的内容（一个项目可以存在多个 workflow 文件），在示例中的 push 指的是 git push，即每次推送代码都会触发这个 workflow，完整的事件支持列表可通过&lt;a class="link" href="https://help.github.com/en/articles/events-that-trigger-workflows" target="_blank" rel="noopener"
>官方文档&lt;/a>获知。&lt;/p>
&lt;p>接下来就是 jobs 了，在这里我们只创建了一个名为 my-job 的 job，一般而言 jobs 是 workflow 文件的主体，一个 job 由若干个 step 组成，这些 step 会按顺序执行，为了便于阅读，我用分割线将各个 step 分开了。&lt;/p>
&lt;p>在解释 step 前我们不妨先看下每一个 job 中都要填写的 &lt;a class="link" href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on" target="_blank" rel="noopener"
>runs-on&lt;/a> 对象，它指定了该 job 的工作系统环境，目前可选的系统有 windows-latest、ubuntu-latest、ubuntu-16.04、macos-latest，这覆盖了主流的操作系统平台，为不同平台的编译提供了便利。&lt;/p>
&lt;p>最后，就是每个 job 中必须存在的 step 了，每个 step 都代表一个单独的操作步骤，既可以在 &lt;a class="link" href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun" target="_blank" rel="noopener"
>run&lt;/a> 对象内填入你需要执行的 Shell 命令，也可以在 &lt;a class="link" href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses" target="_blank" rel="noopener"
>uses&lt;/a> 对象里填入对应的配置以使用别人的 action（在 &lt;a class="link" href="https://github.com/marketplace?type=actions" target="_blank" rel="noopener"
>Marketplace&lt;/a> 中浏览全部 action）。&lt;a class="link" href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsenv" target="_blank" rel="noopener"
>env&lt;/a> 对象用来设置环境变量，这个对象存在一个非常有趣的应用场景：如果你的 step 需要使用不宜公开的 Token，那你可以&lt;a class="link" href="https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets" target="_blank" rel="noopener"
>在项目设置中设置该 Token&lt;/a>，然后在 env 对象中使用 super_secret: $ 将这个 Token 设为一个环境变量，并在自己的 step 中读取该环境变量以取得 Token，这样就能避免在 workflow 文件中硬编码 Token。当你再次看到 $ 这样的变量时，你就应该明白这是一个私密变量，是不能公开的。&lt;/p>
&lt;h1 id="开始构造打包工作流">开始构造打包工作流
&lt;/h1>&lt;p>经过上文的介绍，各位应该对 GitHub Actions 有了一定的了解，接下来就让我们开始白嫖 GitHub Actions 吧。注意，本节中的说明均与最新版本的 &lt;a class="link" href="https://github.com/vifly/arch-build" target="_blank" rel="noopener"
>arch-build&lt;/a> 存在差异，仅用作描述思路，想要抄作业的请直接跳到“最终成品与配置”一节。&lt;/p>
&lt;h2 id="自动编译-aur-的软件包">自动编译 AUR 的软件包
&lt;/h2>&lt;p>一般而言，我们都是在 Arch Linux 上构建 AUR 上的软件包，但是上文提到的 runs-on 对象可以填入的系统并不包括 Arch，那该怎么办呢？答案是使用基于 Arch 的容器，在容器内构建。这里要感谢 &lt;a class="link" href="https://github.com/Qv2ray/Qv2ray" target="_blank" rel="noopener"
>Qv2ray&lt;/a> 的一位开发者 &lt;a class="link" href="https://www.ducksoft.site/" target="_blank" rel="noopener"
>DuckSoft&lt;/a> 提供了这个思路，而且编写了 &lt;a class="link" href="https://github.com/DuckSoft/build-aur-action" target="_blank" rel="noopener"
>build-aur-action&lt;/a> 这个 action 用来编译打包 AUR 上的软件。现在，我们需要考虑的就是如何自动进行编译，从 CI 的正常使用方式来说，我们应该在 on 对象中设定这样一个触发条件：当 AUR 特定的软件包更新时自动进行编译。不过这个方案还需要写检测更新的代码，为了偷懒，我选择设置定时任务来编译，由于 on 对象支持&lt;a class="link" href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#onschedule" target="_blank" rel="noopener"
>通过 Cron 语法设定定时任务&lt;/a>，所以这个问题能被轻松解决。就这样，我们完成了一个简单的 workflow 文件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-YAML" data-lang="YAML">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">BUILD&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schedule&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">cron&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;1 */8 * * *&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">DuckSoft/build-aur-action@master&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">repo-name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">osu-lazer&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>提醒一下，使用别人的 action 时可能需要使用 with 对象输入一些变量，在这里我们输入的就是想要构建的 AUR 软件的软件包名。&lt;/p>
&lt;h2 id="上传到-github-releases">上传到 GitHub Releases
&lt;/h2>&lt;p>在上一步中我们已成功地构建了想要的软件包，接下来需要解决的就是如何把软件包取出来这个问题了。最好的解决方案莫过于将构建出来的软件包上传到 Releases，GitHub 官方提供了 &lt;a class="link" href="https://github.com/actions/upload-release-asset" target="_blank" rel="noopener"
>upload-release-asset&lt;/a> 来完成这个操作，但我看了说明文档后觉得这个太麻烦了，它不支持通过 Unix 终端规则（例如 “*.zst”）筛选要上传的文件，而且需要先创建 Releases 后才能上传文件。经过一番谷歌，我找到了 &lt;a class="link" href="https://github.com/ncipollo/release-action" target="_blank" rel="noopener"
>release-action&lt;/a> 这个替代品，与 upload-release-asset 相比，这个 action 的配置明显更简单，从下面这段配置即可看得出来：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-YAML" data-lang="YAML">&lt;span class="line">&lt;span class="cl">- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ncipollo/release-action@v1.7.3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">allowUpdates&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tag&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;packages&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">artifacts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;./*/*.zst&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.GITHUB_TOKEN }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>把这段配置添加到 workflow 中，然后我们先看看 with 对象中输入的变量。token 是上传文件到 Releases 时必需的一项变量，从它的形式就可以看出这是一个私密变量，不过这个私密变量是内置的，我们不需要在项目中手动设置这个变量，直接使用即可；接着就是 tag 了，为了方便管理，我们在仓库的 Releases 页面创建一个 tag，然后将 tag 的名字填入其中。完成配置后这个 action 就会把构建的软件包上传到指定 tag 下的 Releases，我们也可以下载该软件包了。&lt;/p>
&lt;h2 id="使用-matrix-进行改进">使用 matrix 进行改进
&lt;/h2>&lt;p>虽然目前我们的 workflow 已经能用了，但是需要编译多个 AUR 的软件时需要多次复制粘贴上面的 step，这可太难看了，是否存在更优雅的方法呢？答案是有的，经 DuckSoft 的提醒，可以使用 &lt;a class="link" href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix" target="_blank" rel="noopener"
>matrix&lt;/a>，它基于单个 job 中定义的 steps 并行运行多个 job，多个 job 之间的差异就是特定变量的差异，这些变量以数组的形式存在。一般来说，matrix 的用处就是为不同平台采取同样的步骤进行构建，在本文的场景下就是采用同样的步骤构建不同的 AUR 软件。虽然听着有点抽象，但看一下这个例子你就应该能明白了：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-YAML" data-lang="YAML">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">strategy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matrix&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">repos&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">osu-lazer, mpv-mpris]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fail-fast&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">DuckSoft/build-aur-action@master&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">repo-name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ matrix.repos }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>与之前的 workflow 不同，我们先创建了 repos 数组，并在其中填入需要构建的软件名。如果不把 fail-fast 设置为 false，在并行 job 中出现一个运行失败的 job 时会导致其它的 job 被终止。另外，使用 build-aur-action 时我们没有直接输入 repo-name，而是以 $ 的形式输入，数组中的变量会自动应用到对应的 job 中。&lt;/p>
&lt;h2 id="使用自己的-pkgbuild可选">使用自己的 PKGBUILD（可选）
&lt;/h2>&lt;p>上述版本已经很完美了，不过还存在一个问题：我需要的某个软件包虽然在 AUR 中存在，但对应的 PKGBUILD 写的太烂了/无法构建成功，此时我写了一个 PKGBUILD，希望能白嫖 GitHub Actions 进行构建，该怎么办呢。这个需求也是早有人想到了，只需使用 &lt;a class="link" href="https://github.com/edlanglois/pkgbuild-action" target="_blank" rel="noopener"
>pkgbuild-action&lt;/a> 就可解决，它还可以解决打的包还依赖了其它的 AUR 包的问题。为此，我们需要再添加一个 job，checkout 目前仓库获取 PKGBUILD，然后使用 pkgbuild-action 进行构建，需要的 pkgdir 参数就是 PKGBUILD 所在的路径（父文件夹），最后依然是使用 release-action 根据 pkgbuild-action 返回的构建产物路径将其上传到 GitHub Releases。&lt;/p>
&lt;h1 id="最终成品与配置">最终成品与配置
&lt;/h1>&lt;p>最终版的 workflow 可以在&lt;a class="link" href="https://github.com/vifly/arch-build/blob/master/.github/workflows/build.yml" target="_blank" rel="noopener"
>这&lt;/a>查看，只需 fork &lt;a class="link" href="https://github.com/vifly/arch-build" target="_blank" rel="noopener"
>arch-build&lt;/a>，然后按下面的说明修改一下 workflow 文件即可食用。注意，经过一段时间的改进，最终版的配置已与上文存在一定区别，其中的 uploadToOneDrive 是&lt;a class="link" href="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo" target="_blank" rel="noopener"
>《使用 Vercel 与 OneDrive 自建软件源》&lt;/a>中所需的 job，如果你不需要建立一个可公共访问的软件源请删掉它。&lt;/p>
&lt;p>如果只是需要构建上传 AUR 包，那只需修改 buildAUR 这个 job 中的内容，根据自己的需要修改其 matrix 内的软件包名，buildNonAUR 的内容则可以删掉。&lt;/p>
&lt;p>如果想使用自己的 PKGBUILD 进行构建，那还需要修改 buildNonAUR 这个 job，依然需要修改其 matrix 内的软件包名，另外还需要在仓库的根目录下新建以软件包名命名的文件夹，在其中存放对应的 PKGBUILD 文件和其它构建过程中所需的资源文件。经过我的修改后的 pkgbuild-action 还支持一个新功能：如果想构建的软件包依赖某个 AUR 软件包而你不想使用 AUR 上的 PKGBUILD，那么你可以新建一个子文件夹，在其中放入自己的 PKGBUILD。结构如下所示：&lt;/p>
&lt;pre tabindex="0">&lt;code>├── foo
│   ├── PKGBUILD
│   └── bar (dependences of foo)
│   ├── PKGBUILD
│   └── baz (dependences of bar)
│   └── PKGBUILD
&lt;/code>&lt;/pre>&lt;p>到此为止，借助 GitHub Actions，我们拥有了一个 24 小时可用的编译机以及公开的软件包存储库，解决了本文开始提到的第二与第三点问题。这些都是全自动且免费的，为了更好的体验，下文将介绍如何让安装软件包也实现自动化。&lt;/p>
&lt;h1 id="简单的自动更新仓库">简单的自动更新仓库
&lt;/h1>&lt;p>打开浏览器，从 Releases 页面下载软件包，然后执行 pacman -U xxx.pkg.tar.zst 安装软件包，这些操作实在太麻烦了，为何不建立一个软件源，令每次执行 pacman -Syu 时自动安装最新版本的软件呢。自建软件源听上去十分高大上，但其实只是建立一个本地软件源并不难，&lt;a class="link" href="https://wiki.archlinux.org/index.php/Pacman/Tips_and_tricks#Custom_local_repository" target="_blank" rel="noopener"
>Arch WiKi 几段文字&lt;/a>便说明白了，当然，如果想建立一个在线的公用软件源会麻烦一些，所以这里只说明如何建立一个自动更新的本地软件源。想要建立公用软件源的话请跳过下文，阅读我的新博文&lt;a class="link" href="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo" target="_blank" rel="noopener"
>《使用 Vercel 与 OneDrive 自建软件源》&lt;/a>。&lt;/p>
&lt;h2 id="使用自动化脚本">使用自动化脚本
&lt;/h2>&lt;p>作为一个爱偷懒的人，我肯定希望能自动化下载软件包与更新软件仓库的操作，为此我写了一个脚本来完成这些事情，各位只需打开&lt;a class="link" href="https://github.com/vifly/helper" target="_blank" rel="noopener"
>项目地址&lt;/a>，下载代码并根据说明进行操作即可。我在这里说明一下 conf.py 的配置：UserName 和 GitHubRepoName 并不难理解，举个例子，我的白嫖仓库地址是 &lt;a class="link" href="https://github.com/vifly/arch-build" target="_blank" rel="noopener"
>https://github.com/vifly/arch-build&lt;/a> ，那么需要填写的 UserName 便是 vifly，GitHubRepoName 则是 arch-build；ProxyURL 在下一小节会提到，这里先不说；DownloadPath 是从 Releases 下载的软件包的存储路径；末尾的 ArchRepoDBPath 和 ArchRepoName 是用于生成本地软件源的数据库的，脚本会调用 repo-add 根据 DownloadPath 中的软件包生成路径为 ArchRepoDBPath/ArchRepoName.db.tar.gz 的数据库。&lt;br>
当你成功运行脚本建立了一个本地软件源后，还需要修改 /etc/pacman.conf 文件，在末尾添加以下配置以让 Pacman 同步你的软件源的数据库（自行替换 ArchRepoName 与 ArchRepoDBPath）：&lt;/p>
&lt;pre>&lt;code>[ArchRepoName]
SigLevel = Optional TrustAll
Server = file://ArchRepoDBPath
&lt;/code>&lt;/pre>
&lt;p>另外，为了让本地的软件仓库保持最新，我们可以通过 &lt;a class="link" href="https://wiki.archlinux.org/index.php/cron" target="_blank" rel="noopener"
>Cron&lt;/a> 设置一个定时任务自动运行这个脚本。现在，我们得到了一个完全免费、自动更新的个人软件仓库，快执行 pacman -Syu 开始享受白嫖的快乐吧。&lt;/p>
&lt;h2 id="使用-cloudflare-workers-反代加速下载可选">使用 Cloudflare Workers 反代加速下载（可选）
&lt;/h2>&lt;p>为了解决在国内 GitHub 下载速度慢的问题，让我们继续发扬白嫖的精神，使用 &lt;a class="link" href="https://workers.cloudflare.com/" target="_blank" rel="noopener"
>Cloudflare Workers&lt;/a>（不需要拥有域名） 来加快下载速度。广大白嫖党早已发现可以使用免费的 Cloudflare Workers 部署 serverless 应用反代国内无法访问的网络资源，在这里我们也使用这种方式加速下载。刚开始时我使用了 &lt;a class="link" href="https://github.com/hunshcn/gh-proxy" target="_blank" rel="noopener"
>gh-proxy&lt;/a> 这个加速 GitHub 下载的项目，不过后来 Arch 群的 &lt;a class="link" href="https://nichi.co/" target="_blank" rel="noopener"
>NickCao&lt;/a> 同学推荐了他写的更通用的反代应用，感谢 NickCao，接下来我们便开始部署反代应用吧。 &lt;br>
首先，打开&lt;a class="link" href="https://workers.cloudflare.com/" target="_blank" rel="noopener"
>官网&lt;/a>，注册或登录你的 Cloudflare 帐号，点击 Start building，选择免费方案并创建一个专属的子域名（是 workers.dev 的子域名），进入主页后点击 Create a Worker，复制&lt;a class="link" href="https://gitlab.com/NickCao/experiments/-/blob/master/workers/r.js" target="_blank" rel="noopener"
>这份代码&lt;/a>，像下图这样粘贴到编辑框内：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_GitHubActions_to_build_AUR/create_cloudflare_workers.png"
width="1918"
height="835"
loading="lazy"
alt="创建 Cloudflare Workers 应用"
class="gallery-image"
data-flex-grow="229"
data-flex-basis="551px"
>&lt;/p>
&lt;p>注意，页面中间的域名是 Cloudflare Workers 分配给你的一个专属子域名（形如 xxx.xxx.workers.dev），复制这个域名并用它替换左侧代码中的两处域名，完成修改后点击 Save and Deploy 保存并部署，这样我们就得到了一个可以反代网络资源的应用了。&lt;/p>
&lt;p>最后，还记得在上一小节中被忽略的 ProxyURL 配置项吗，这里各位只需填入刚刚得到的 xxx.xxx.workers.dev 域名就可以了，脚本会使用这个反代域名高速下载 GitHub Releases 的软件包。&lt;/p></description></item><item><title>永恒才是你的敌人——《永久记录》读后感</title><link>https://viflythink.com/Permanent_Record/</link><pubDate>Fri, 28 Feb 2020 17:11:57 +0800</pubDate><guid>https://viflythink.com/Permanent_Record/</guid><description>&lt;img src="https://viflythink.com/Permanent_Record/show.jpg" alt="Featured image of post 永恒才是你的敌人——《永久记录》读后感" />&lt;p>我在博客的简介中说过这个博客会记录我的折腾与感想，不过到目前为止，本博客的归档里全都是技术折腾类的文章。对于我来说，写折腾记录是一件比写论述文章轻松的事情，后者需要具有明确的写作目的与结构安排，而且通用性更广，而前者则不需要管这么多。虽说如此，我早就想写论述类文章了，毕竟能看到自己的想法以一种规整的形式出现在屏幕前是一种让人十分满足的事情。在好不容易战胜长期宅在家导致的拖延症后，我终于提笔写下了本博客的第一篇论述类文章（笑）。这里顺带说一下，本文标题化用自本人最喜欢的 RPG &lt;a class="link" href="https://store.steampowered.com/app/466300/Planescape_Torment_Enhanced_Edition/" target="_blank" rel="noopener"
>《异域镇魂曲》&lt;/a>中的一句话，原话是「时间不是你的敌人，永恒才是」。 &lt;br>
虽然之前已经得知《永久记录》出版了，不过在这漫长的假期中，我才想到去阅读它。对我来说，这本书里的内容并不算新鲜，但对于很多我接触到的人来说，隐私是个很奇怪的问题，每次我想解释清楚时总发现三言两语是不足够的，既然如此，我就在此借《永久记录》来谈谈我对隐私的看法吧，废话就到此了，下面正式进入正文。&lt;/p>
&lt;p>不知道各位是否记得 2013 年的“棱镜门”事件呢，这与今天要说到的《永久记录》有着密切的联系。前段时间，“棱镜计划”等大型监视项目的揭露者斯诺登出版了这本名为《永久记录》的回忆录（由于内地出版社愚蠢地阉割了书中内容，&lt;a class="link" href="https://twitter.com/Snowden/status/1226906278817124364" target="_blank" rel="noopener"
>斯诺登决定免费提供未经阉割的简体中文版本&lt;/a>，下载请戳&lt;a class="link" href="https://a.temporaryrecord.com/" target="_blank" rel="noopener"
>这&lt;/a>，也可选择&lt;a class="link" href="https://1drv.ms/b/s!AnrGd4m50K7QbXw8iv10BXzVsDo?e=wU0ecv" target="_blank" rel="noopener"
>我在 OneDrive 上传的副本&lt;/a>），在其中讲述了当年为何决定曝光一系列监视丑闻的动机，也讲述了紧张刺激的资料收集与逃亡过程。如果你还没看过这本书，那么我强烈建议你去把它看完，读这本书就像是读一个非常流畅的故事，只是与故事书相比最大的区别就是我们知道这是真的，而不是一个虚构故事（很多时候我更希望这只是一个虚构故事）。如果你还不清楚“棱镜计划”等项目是什么，没关系，下面是对此的一些描述。&lt;/p>
&lt;h1 id="大规模监控项目">大规模监控项目
&lt;/h1>&lt;p>这一切也许源于所谓的总统监控计划（President’s Surveillance Program，PSP），其含糊的措辞给了情报机构极大的权力，特别是其允许国安局不必取得外国情报监控法院的搜查令便能实施监控，该计划的核心部分被称为 STLW（STELLARWIND，星风）。&lt;/p>
&lt;p>PSP 扫清了法律上的障碍后，就需要解决技术上的问题了。为了实现大规模监控，美国情报机构尽其所能与盟国的情报机构构建了两大网络监视计划：棱镜计划（PRISM）和上游收集（Upstream Collection），前者从谷歌、微软、苹果等服务商的云端所有内容中收集资料，后者从网络基础设施，如全球网络流量的转换器与路由器、经由太空卫星和高容量海底光纤电缆等地方直接抓取资料。&lt;/p>
&lt;p>棱镜计划的危险性应该无需说明，为了说明上游收集的能力，这里提供一个示例：在你打开（不加密的 HTTP）网站时，网络请求会经过国安局的“乱流”（Turbulence），其中有“混乱”（Turmoil）和“涡轮”（Turbine）两大组件，前者根据 metadata（元数据）判断该流量是否可疑，如果是，那么转给涡轮，涡轮会将合适的恶意程序注入到流量中以对你进行窃取信息的操作。&lt;/p>
&lt;p>总之，这些丧心病狂的项目可以用以下几句话概括：&lt;/p>
&lt;blockquote>
&lt;p>无所不嗅，无所不知，无所不收集，无所不处理，无所不利用，无所不合伙。&lt;/p>
&lt;/blockquote>
&lt;p>至于收集到的信息是如何被使用的呢。为此存在着一个被称为“XKeyscore”的项目，情报人员可以通过该项目搜索到一个人的私人电邮、聊天记录、档案等等一切最为隐蔽的隐私资料，也包括目标的所有线上活动记录。&lt;/p>
&lt;p>为了实施这些项目，美国政府采用多种方式绕过法律和公众监督：&lt;/p>
&lt;ol>
&lt;li>美国政府重新诠释“取得”（acquire）与“获得”（obtain）的定义：从原本描述情报资料进入数据库的过程，被扭曲成某人（或某个算法）未来某时刻查询并取得资料的行为。如此一来便大幅扩充了执法机关的权力。&lt;/li>
&lt;li>令公众放下警惕的地方在于，情报部门记录的是 metadata 而不是实际的通信内容，这可以说是实际技术实力不足以储存所有内容导致的，但通过 metadata 也足以分析出一个人的具体行动（这只需要收集你昨晚入睡与今早起床的时间、每天逛了哪些地方、在哪里待了多久，以及你接触过的对象有谁，谁又与你联系过），而且获取与存储 metadata 的难度都是很低的。&lt;/li>
&lt;li>美国国家安全局认为，你已经将手机里的记录“分享”给了“第三方”——你的电信商，因此你已经失去了宪法保障的隐私权。他们坚持认为，只有在分析师主动调查已经自动收集来的资料时，才算是搜查或扣押，而算法并不算。&lt;/li>
&lt;/ol>
&lt;p>觉得发生在美国的事情太遥远？来看看国内各大互联网公司的情况吧。还记得年底时各家公司推送的年度总结报告吗，这些年度报告都会告诉你你几点还在使用它们。看上去好像没什么，但这意味着它们记录了你的最晚使用时间，对于很多人来说，微信的最晚使用时间就等于准备睡觉的时间，最晚使用时间就属于上文提到的 metadata，除此以外，这些公司也详细记录了你的浏览行为（如在哪个页面停留了很长时间），诸如此类的信息足以勾画出一个人的行踪（无论是线上还是线下）与性格爱好，仅仅这点便足以让人感到不安了，而且这些信息看上去人畜无害，商业公司总是以“改善服务质量”为名收集这些信息，就算你阅读了隐私条款，也不会立刻想到收集的信息是如何被使用的，当然，也许单个公司收集的这些信息并不全面，并不足以将你的一切勾画出来，可是当政府能随意访问这些信息时，上面提到的全面画像就是轻而易举的事情，更别说政府还可以通过后门等访问你的通讯内容，在这种情况下，你的一切都暴露无遗。这是非常不幸的事情，互联网在飞速发展，但目前来说我们并没有迈向预想中的光明的未来，而是进入了接近于反乌托邦场景的监视资本主义。&lt;/p>
&lt;h1 id="为什么隐私是值得重视的">为什么隐私是值得重视的
&lt;/h1>&lt;p>对上文提到的情报机构无限制地侵犯个人隐私这一现状感到失望的斯诺登想要采取措施扭转这一局面，他选择了冒险进行公开爆料。我们都知道斯诺登此时在俄罗斯接受政治避难，美国政府暂时拿这位名人没什么办法，但在当时，斯诺登可不确定是否会被人发现他在收集机密资料，也不知道如何应对公开爆料后美国执法机构对他的拘捕。假如当年出了什么意外，那历史肯定会被改写。想到当年我对整个事件的看法无非就是，“哇，美国国安局居然有个牛人把这些事情抖了出来，且看美国这回怎么处理”，现在不由得为当时的想法感到羞愧。对于现在的我来说，当时的我只是抱有一种看热闹的心态，而完全不清楚曝光这种大项目的难度，也不清楚这样做的意义。时隔多年，当我通过这本书重温当年的事件后，才开始了解到了曝光监视项目的困难与意义。&lt;/p>
&lt;p>首先，让我们看看公开机密情报的难度有多大吧。若是没怎么关注此事，那你可能只会记得记者公开斯诺登提供的文件，而这些机密文件的获取难度则被忽视了，想当然的认为作为内部人员，泄密肯定不难吧。实际上，取得这些机密资料就等于潜入敌方情报机构窃取情报，作为窃取情报的能手，CIA 等部门肯定也会采取各种手段严防情报泄露。作为其中的雇员，斯诺登直接大规模查找这些信息肯定会被怀疑（内部肯定会有访问记录），而且，成功获取资料后还需要设法将其&lt;strong>分批&lt;/strong>带出禁止携带存储设备入内的军事基地。就算取得足够的资料并带走，斯诺登还需要考虑如何将它们公布出去，这其中的资料太多了，让公众一个个翻阅肯定是一件不现实的事情，而且，斯诺登也不愿意牺牲美国的国家安全（斯诺登在书中明确说明自己是爱国的），所以他只能与记者合作对这些资料进行筛选并将筛选出来的与公众利益密切相关的资料公开，与记者的联络也是一个问题：需要考虑对方是否可信（道德与技术方面）。总而言之，历经九九八十一难后，这些文件才呈现在我们的面前。这些情节都可以在书中找到，部分甚至可以当作现实版的谍战小说来看，如果你还没读过，不妨现在去看看吧。&lt;/p>
&lt;p>上面提到公开爆料的风险是很大的，那么斯诺登为什么要冒如此大的险来公开信息呢？仅仅是对隐私受到侵犯的不满吗，在我看来，并不全是，按照书中的说法，斯诺登应是为了阻止大规模监控对美国的民主体制的侵蚀而挺身而出。有的同学可能会问，原本的主题不是隐私吗，怎么现在说起民主体制了。隐私与民主体制有什么关系呢，我们先理清这两者的关系吧。&lt;/p>
&lt;p>众所周知，美国的民主体制核心是三权分立，之所以这样设计，其原因就是为了实现分权思想，避免权力的膨大，对政府权力的限制，或许可以在“若无法律许可政府不能做任何事”这一句话中完全体现出来。美国情报机构的秘密的大规模监控项目很明显是不受约束的，它是秘密的，以至于不可能受到公众的监督和约束。另外，权力是会不断膨胀的，在斯诺登爆料前，情报机构只是拥有不受限制的收集隐私信息的权力，但是若没有人揭露这一问题，那么拥有这种能力的情报机构取得其它权力也不是不可能的事，举个典型的例子，在美国大选中各方总是会抨击对手的不良行为，假如情报机构向某一方提供对手的隐私信息（不一定是丑闻），那选举结果就会受到影响，情报机构的权力也能进一步扩大。能不受限制地进行监控等于拥有不受限制的权力，这样的存在无疑是对民主体制的一种威胁，这就是美国宪法中存在要求保障隐私权的条文的原因。《永久记录》提到了隐私与个人自由的关系，不过复述书中这部分内容并没有什么趣味，我就只说这个了。&lt;/p>
&lt;p>尽管如此，我猜不少人此时可能依然对隐私不以为意，“隐私这种东西我暂时也不需要，说这么多大道理也没用”，换句话说，既然隐私与我有关，那我愿意牺牲自己的隐私也是可以接受的？是否放弃隐私当然属于个人自由，但请注意，你是否在意隐私不仅会影响到自己的隐私，也会影响到别人的隐私，由于你不在意隐私而导致朋友或亲人的隐私信息泄露是可以接受的吗？&lt;/p>
&lt;blockquote>
&lt;p>你可能因为怕麻烦而放弃此权利，或者你和多数人想法一样，认为只有做不光明的事才需要隐私保护。但是，声称自己不需要或不想要隐私，因为没有什么事好隐瞒的这种说法，是假定所有人都不该或不能隐瞒任何事情，比如他们的移民身份、失业历程、财务状况与健康记录等。你假定，所有人（包括你在内）都乐于与他人分享宗教信念、政党倾向与性生活，就如同有些人随意透露自己的电影、音乐品位与阅读偏好一样。&lt;/p>
&lt;/blockquote>
&lt;p>我强烈反对将自己的观念强加于别人身上的行为，即使不在意自己的隐私，也应当对别人保护隐私的行为保持最基本的尊重，这就是我反对在 Telegram 上称呼一些人是“隐私怪”这样的行为的原因，尽管我知道被称为“隐私怪”的那些人的确对隐私存在一定的误解以致于有时显得不合常理（也许其中一些人需要的是匿名），很多时候称呼别人为“隐私怪”也只是在开玩笑，但我们为什么要嘲笑保护隐私的行为呢？保护隐私并没有错，有错的只是对隐私的误解，若是能好好沟通的话，指出对方在哪存在对隐私的误解不是更好吗。&lt;/p>
&lt;p>即使仅看个人利益（假设不存在大型监控项目），隐私依然是一项值得重视的权益。很多人爱说“用隐私换取便利”，这说法隐含了隐私可以用来进行交易的含义，假如这是交易，那么我们不妨来衡量一下这其中的得与失。在拥有足够的个人信息的前提下，语音助手等产品能变得非常“智能便捷”，查天气、找联系人、操控智能家居，甚至网购下单，在方便的同时它也掌握了你的家庭住址、手机号码等敏感的个人信息，语音助手以外的服务也是同样的，当你欣然同意授权时是否想过这些信息若是被泄露会带来什么后果呢，我想诈骗份子肯定会非常高兴，而你对此并不会察觉到什么不对。在“交易”中光是不清楚自己到底付出了什么就已经很要命了，而且更重要的是，个人信息不是实体，实体若是被盗会很快被发现，但你难以察觉到个人信息是否被窃取，而当你后悔当初付出了隐私时，你也没法撤销这种“交易”。也请不要抱有太大的侥幸心理，认为个人信息被窃取是概率很低的事情，近年来经常出现的&lt;a class="link" href="https://www.solidot.org/story?sid=59295" target="_blank" rel="noopener"
>个人信息泄露事件&lt;/a>就是很好的说明案例，而且即使发生的概率低，你也需要考虑到个人信息被泄露的后果，如果自认为后果严重，哪怕这种坏事的发生概率很低也应该有所准备，打个比方，个人信息泄露就像癌症，碰到它们的概率都很低，但若是真碰上了那会怎样呢。所以，下次打算“用隐私换取便利”前不妨先想想这点吧。&lt;/p>
&lt;h1 id="从隐私到永久记录">从隐私到永久记录
&lt;/h1>&lt;p>仔细一想，隐私涉及的个人信息范围真广：从无论是谁都不希望泄露的身份证号码到绝大多数人不希望被看到的私人聊天内容，再到不起眼的 IP 地址，如果二十年前有人跟我说他能把这些信息全部收集起来，那我肯定会认为他在开玩笑；而在 2013 年，斯诺登告诉我们，美国的情报机构还真的可以做到这一点，他们花费了巨大的人力和物力来构建一个现代大型监控项目，无所不入地收集信息，还建立了一套完善的储存和搜索系统来使用这些信息，当然，这都是以打击恐怖主义的名义进行的。&lt;/p>
&lt;p>这一切的缔造者不仅包括政府部门，也包括了商业公司，他们互相勾结，构成了所谓的监视资本主义，在此环境下个人数据被当作宝贵的资源，大数据的作用被不断鼓吹，“智能”的服务背后是靠大量个人数据所堆积出来的效果，当然也少不了“国家安全”的需要，而用户甚至不明白这有什么问题。我们在网上的一切被通过各种途径所记录下来，政府与商业公司像对待金子一样小心地储存它们，生怕这些信息丢失，毫不客气地说，这些数据甚至有可能保存到人类文明尽头。我以前曾经想过，若是能把自己的数据长久地记录下来该多好，那样就可以随时回顾自己做过的事情了，现在，数据的确是被记录下来了，我们成为了“永恒”，但这些数据却不在自己的手上，而是在别人的存储设备当中，这些人能随意地翻阅你的信息，而你却毫不知情，不知道这是不是一件非常讽刺的事情呢。&lt;/p>
&lt;p>我们之所以在意隐私，就是因为想要在这监视资本主义下掌握自己的数据，无论这些数据是过去、现在还是将来。若是所有的数据都在我们无法控制的情况下被永久记录，从出生伴随到死亡，那意味着任何人都不应该犯下错误，因为你犯的错误会一直存在，没有“撤销”可言，成为人生中的一个污点并因此受到歧视。在我最悲观的预测中，大型监控项目继续发展所带来的未来就是这样一个害怕犯错的世界，想必那也是一个十分无趣的世界。&lt;/p>
&lt;p>个人信息的永久记录，这个看似科幻作品才会出现的场景如今已化作现实。上文我也提到了，信息被复制传播是一件难以察觉的事情，因此，即使你删除了某些信息，也没法确定在哪个地方是否依然保留了副本，当我们使用互联网时，信息不断产生，该如何让这些信息得到妥善的处理，这已经成为了一个大问题。此前我们从未想过数据竟有如此大的能量，也没想过数据能用来做那么多邪恶的事情，斯诺登的这本书之所以命名为“永久记录”，就是希望读者能认识到这一现实。即使我们暂时不知道这些信息的最佳处理办法，但我们至少知道以“国家安全”为由将其永久记录是一件错误的事情。我也希望各位读者能够花时间思考应该如何处理个人信息，因为：&lt;/p>
&lt;blockquote>
&lt;p>雪崩时， 没有一片雪花觉得自己有责任。&lt;/p>
&lt;/blockquote>
&lt;h1 id="总结">总结
&lt;/h1>&lt;p>最后，作为一个注重个人隐私的人，我看完这本书后最大的感慨就是无论是保护个人隐私还是信息安全，&lt;strong>人永远都是最重要的一环&lt;/strong>。美国固然有历史悠久的保护公民隐私的法律，可上文提到的这些人依然有办法绕开这个限制（通过混淆“取得”与“获得”的定义），稍微转换一下角度，对于想要保护自己隐私的人来说，只有自己重视隐私，投入一定的时间和精力，才能在当今这疯狂追求大数据的环境下有效保护自己的隐私，我们永远都不应该指望一两部法律能做到这一点，因为若没人确保法律的实行，那么法律就只是空谈。这也是本博客使用自建的 Matomo 统计分析服务和 Isso 评论系统的原因，只要愿意付出时间和金钱，总是会找到保护隐私的方案。我喜欢把“保护隐私”与“把事情做得更好”混为一谈，把事情做得更好需要付出额外的时间或金钱，保护隐私也是一样的，通过一定的付出，我们得以保护自己的隐私；另外，“把事情做得更好”与“保护隐私”同样属于思维方式，喜欢将工作完成的尽可能完美的人会在做事情时不由自主的想到如何把细节做得更好，而想要保护隐私的人想做一些事情时（如选择软件或服务、在社交媒体分享等）会自然而然的想到“这对我的隐私有什么影响呢”，所以无需担忧需要每时每刻都在想着会不会泄露隐私。如果你看完本文后想采取行动保护隐私，以下的网站值得查看（感谢“棱镜门”事件，在此之后不少隐私保护项目如雨后春笋那样冒了出来）：&lt;br>
&lt;a class="link" href="https://prism-break.org/zh-CN/" target="_blank" rel="noopener"
>逃离棱镜&lt;/a>，听名字就知道是专门针对棱镜计划的项目，推荐了各种商业软件/服务的安全替代品，有中文&lt;br>
&lt;a class="link" href="https://cybermagicsec.github.io/privacytools-zh/" target="_blank" rel="noopener"
>隐私工具 - 加密安全对抗全球大规模监控&lt;/a>，提供保护隐私的工具推荐，也有中文&lt;br>
&lt;a class="link" href="https://panopticlick.eff.org/" target="_blank" rel="noopener"
>Panopticlick&lt;/a>，来测试你的浏览器指纹多么独特（易被跟踪）&lt;br>
&lt;a class="link" href="https://program-think.blogspot.com/" target="_blank" rel="noopener"
>编程随想的博客&lt;/a>，信安大牛的中文博客，科普文多，涉及信息安全、保护隐私、匿名术&lt;/p>
&lt;h1 id="摘录">摘录
&lt;/h1>&lt;ol>
&lt;li>
&lt;p>法律因国家而异，科技则不是。每个国家都有自己的法律，但计算机程序码却是相同的。科技跨越边境，持有几乎所有国家的护照。随着时间的流逝，我越来越明白，通过立法改革我出生国的监控机制，未必会对我流亡国的记者或异议人士有所帮助，但加密手机就帮得上忙。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>只因为我分享我的知识，并不意味着任何人必须认同我的意见。并非每个反对隐私遭侵犯的人，都可能准备好采用 256 位的加密标准，或是全面停止使用网络。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>从当年的宪法日到现在，已经超过了一个世纪，云端、计算机、手机已经变成了我们的家，如同实际的房子那样隐秘、私人化。如果你不认同这句话，那么请回答我这个问题:你愿意让你的同事一个人待在你家一个小时，还是愿意让他看你已经解锁的手机，就算只是十分钟而已？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不过，删除帖文的可能后果让我心烦意乱，那么做只会强化网络生活中的一些最腐蚀人心的训诫：没人有犯错空间，凡是犯错者，都要一辈子为自己的错误负责。我在意的倒不是文字记录是否完美无缺，而是灵魂的完整性。我不想活在一个人人必须假装完美的世界里，那样的世界没有我和朋友的容身之处。抹掉网上的评论，等于抹杀了我是谁，我从哪里来，我走了多远。否定年少时的我，等于否定现在的我的合法性。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>所谓长大，代表的是你体会到你的存在受制于成套的规范、模棱两可的规则以及毫无根据的常规。这些规定未经过你的同意便强加在你身上，而且随时随地都有可能改变，甚至在你违反规定时，你才意识到它们的存在。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>美国这个国家已变成买新机器取代故障机器比找专家修理来得便宜，而且一定比自己去找零件设法修理来得便宜。单凭这项事实，便几乎保证会出现科技暴政，助纣为虐的不是科技本身，而是每天使用却不了解机器的所有人。&lt;/p>
&lt;/li>
&lt;/ol></description></item><item><title>将 KDE 改造为 Windows 10</title><link>https://viflythink.com/KDE_to_Windows10/</link><pubDate>Wed, 01 Jan 2020 10:08:32 +0800</pubDate><guid>https://viflythink.com/KDE_to_Windows10/</guid><description>&lt;img src="https://viflythink.com/KDE_to_Windows10/show.jpg" alt="Featured image of post 将 KDE 改造为 Windows 10" />&lt;p>&lt;em>2023.6.18.更新：为适配新版本的 KDE 以及找到了更好的方案，本文大部分内容已被更新。注意旧的一些配图依然有所保留，所以不要对截图风格不统一感到奇怪。&lt;/em>&lt;/p>
&lt;p>2020 年已然到来，一些博主已经发表了年度总结，总结了不少经验，我想了想，觉得自己好像没什么可以总结的经验，只好写一篇最近的 KDE 折腾记录给各位读者当新年礼物了。想必有不少刚开始使用 Linux 的新手总想着美化自己的桌面吧，我在开始使用 KDE 后也想着折腾美化，随便搜索了一下美化教程后发现不少都是将 KDE 改造为 Mac 风格的，之前听闻 KDE 粉说过“你可以将 KDE 捏成任何形状”，然而很多人都是将它捏成 Mac 的样子，那么，能把 KDE 捏成 win10 的形状吗？我决定挑战一下这个问题，当然，在这里我要承认这有点标题党的嫌疑，我只是想将我认为 win10 做得好的部分转嫁到 KDE 上，并不是将 KDE 完全变为 win10 的样子，后者是一件费力不讨好的事情，而且，在经过一波折腾后，我发现有些细节还暂时无法做到像 win10 那样优秀，只能寄希望于未来了。另外，本文只适用于 Arch，我尚未在其它发行版进行测试。&lt;/p>
&lt;p>先上一张图片看看改造后的效果（图中标注了下文用到的名词所指代的东西）：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/my_kde_desktop.png"
width="1920"
height="1080"
loading="lazy"
alt="我的桌面展示"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>觉得很漂亮吧？那么，接下来就开始我们的改造吧。&lt;/p>
&lt;h1 id="底部栏">底部栏
&lt;/h1>&lt;p>在没有打开任何窗口时，底部栏是与 win10 相差最远的一个地方，所以我们的改造工作先从底部栏开始。可以先看看改造完成后的底部栏效果图：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/my_bottom.png"
width="2558"
height="52"
loading="lazy"
alt="底部栏"
class="gallery-image"
data-flex-grow="4919"
data-flex-basis="11806px"
>&lt;/p>
&lt;h2 id="开始菜单">开始菜单
&lt;/h2>&lt;p>喜欢 win10 那简洁的开始菜单吗，在 KDE 下只需安装一个挂件（widgets）即可拥有同样的体验。下载 &lt;a class="link" href="https://store.kde.org/p/1160672/" target="_blank" rel="noopener"
>Tiled Menu&lt;/a>，并在底部栏空白处单击右键，然后点击“添加挂件”，按照下图所示进行安装。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/install_widget.png"
width="288"
height="880"
loading="lazy"
alt="安装挂件"
class="gallery-image"
data-flex-grow="32"
data-flex-basis="78px"
>&lt;/p>
&lt;p>想要使用的话有两种方法：在你左下角的开始菜单处鼠标右键单击，选择“显示替代方案”，选择 Tiled Menu；在底部栏右键单击，选择“编辑面板”，鼠标移到原来的开始菜单，选择移除，然后选择“添加挂件”，选择 Tiled Menu。你可以在弹出的开始菜单右上角按住 ALT 和 鼠标右键进行拖拽以更改菜单大小。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/set_start_menu.png"
width="1920"
height="232"
loading="lazy"
alt="将 Tiled Menu 设为开始菜单"
class="gallery-image"
data-flex-grow="827"
data-flex-basis="1986px"
>&lt;/p>
&lt;h2 id="任务管理器挂件">任务管理器挂件
&lt;/h2>&lt;p>依然是在底部栏右键单击，选择“编辑面板”，将鼠标移动到底部栏空白处，点击“显示替代方案”，选择图标任务管理器（Icon-Only Task Manager），然后你就可以看到类似于 win10 的底部窗口了，这里说明一下，图标任务管理器是自带的一个挂件，无需安装。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/set_task_manager.png"
width="1920"
height="250"
loading="lazy"
alt="设置图标任务管理器"
class="gallery-image"
data-flex-grow="768"
data-flex-basis="1843px"
>&lt;/p>
&lt;h2 id="快速查看已打开的窗口">快速查看已打开的窗口
&lt;/h2>&lt;p>win10 可点击左下角的按钮快速查看已打开的窗口，在安装了 &lt;a class="link" href="https://store.kde.org/p/1181039" target="_blank" rel="noopener"
>Present Windows Button&lt;/a> 这个挂件后，我们也可以做到这一点。这个挂件的安装方法与 Tiled Menu 时相同，在安装完成后把这个挂件放在开始菜单和任务管理器之间。这个挂件产生的点击效果与在 Gnome 下将鼠标移动到屏幕左上角触发的效果差不多，KDE 也可设置这样的触发角，不过我觉得设置这样的一个按钮没什么实际意义，可能最大的好处就是没事点一下能有效消遣无聊吧（雾）。这里还有一个能让底部栏变得更美观的 Tips，在底部栏右键单击，选择“编辑面板”后点击“添加间距”，以此添加两个间距，将其缩到最小后对其右键取消勾选“设置可变大小”，根据实际情况可能还需要把间距的空隔宽度设为非零值，然后将这两个间距拖到 Present Windows Button 两边，这能令 Present Windows Button 两边不会显得拥挤，从而变得美观。&lt;/p>
&lt;h2 id="其它">其它
&lt;/h2>&lt;p>我知道存在 &lt;a class="link" href="https://store.kde.org/p/1167558/" target="_blank" rel="noopener"
>Winux10&lt;/a> 之类的图标主题可以将默认的图标替换为 win10 图标，但我经过尝试后发现其覆盖不全面，用以截图假装自己在使用 win10 是可以的，但日常使用会感到违和，所以还是推荐使用默认的图标主题。还有一个细节，那就是右下角的时钟，想让它像 win10 那样双栏显示日期和时间并不难：在底部栏空白处右键单击，选择“编辑面板”，编辑“面板高度”的值，稍微调高一些后，右键单击时钟，选择“配置数字时钟”，勾选“显示日期”，并将时间显示改为24小时制。&lt;/p>
&lt;h1 id="样式大改">样式大改
&lt;/h1>&lt;h2 id="桌面整体样式">桌面整体样式
&lt;/h2>&lt;p>仅仅只是桌面布局接近 win10 是不够的，还有相当一部分的桌面细节可以继续改进。这一部分主要是对桌面面板的图标以及挂件的样式进行修改，基于 &lt;a class="link" href="https://github.com/vinceliuice/Fluent-kde" target="_blank" rel="noopener"
>Fluent-kde&lt;/a> 这一项目来实现。Fluent-kde 包含了应用于各个方面的 Fluent 风格主题，由于我不打算折腾 Kvantum，所以仅使用其提供的 Plasma Desktop Themes。克隆 Fluent-kde 到本地后，进入项目目录并运行：&lt;/p>
&lt;pre>&lt;code>cp -r ./plasma/desktoptheme/* ~/.local/share/plasma/desktoptheme
&lt;/code>&lt;/pre>
&lt;p>然后就可以在系统设置中的“外观”-&amp;gt;“Plasma 视觉风格”中找到新增加的几个主题了，选择自己喜欢的 Fluent 风格主题并点击“应用”即可。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/set_plasma_desktop_themes.png"
width="1465"
height="1024"
loading="lazy"
alt="设置 Plasma 视觉风格"
class="gallery-image"
data-flex-grow="143"
data-flex-basis="343px"
>&lt;/p>
&lt;h2 id="应用程序内部风格">应用程序内部风格
&lt;/h2>&lt;p>经过上面的折腾后，桌面截图已经足够接近 win10 了，但一旦随便打开一个应用就会露馅，所以需要也让应用程序的风格向 win10 靠拢。首先我们需要安装 &lt;a class="link" href="https://github.com/Luwx/Lightly" target="_blank" rel="noopener"
>Lightly&lt;/a> 主题，该主题虽然并非完全向 Fluent 靠拢，但也足够现代。由于目前其并不在积极维护状态，所以仅在 AUR 上有 PKGBUILD，可以在终端输入指令安装（需要安装 Yay 这个 AUR 助手）：&lt;/p>
&lt;pre>&lt;code>yay -S lightly-git
&lt;/code>&lt;/pre>
&lt;p>在安装完成后重启系统，打开系统设置，在侧栏点击“外观”，再点击“应用程序风格”，选择“Lightly”并应用更改；此外为还建议点击那个笔状的图标调整这个主题，把透明度改为下图中的数值。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/use_lightly.png"
width="1467"
height="1026"
loading="lazy"
alt="应用 Lightly 主题"
class="gallery-image"
data-flex-grow="142"
data-flex-basis="343px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/set_lightly_transparency.png"
width="731"
height="649"
loading="lazy"
alt="设置 Lightly 透明度"
class="gallery-image"
data-flex-grow="112"
data-flex-basis="270px"
>&lt;/p>
&lt;p>完成上面的操作随便打开一个 Qt 应用（如 Dolphin）都会发现其风格变得与 win10 应用很像，但 GTK 应用（如我使用的 lollypop）依然死性不改，原因就在于 GTK 应用与 Qt 应用所采用的主题是不一样的，为此我们需要安装同一个作者出品的 &lt;a class="link" href="https://www.gnome-look.org/p/1574551" target="_blank" rel="noopener"
>Fluent round gtk theme&lt;/a>。打开该主题页面后选择并下载一个自己喜欢的 Fluent 主题，然后将其解压到 ~/.themes 下（如该路径不存在则自行创建），重回刚才设置应用程序风格的界面，点击“配置 GNOME/GTK 应用程序风格”，选择刚安装的主题并点击“应用”。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/set_gtk_app_theme.png"
width="1465"
height="1025"
loading="lazy"
alt="设置 GTK 应用风格"
class="gallery-image"
data-flex-grow="142"
data-flex-basis="343px"
>&lt;/p>
&lt;p>此时可以看到 lollypop 的风格也向 win10 靠拢了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/lollypop.png"
width="1299"
height="957"
loading="lazy"
alt="lollypop"
class="gallery-image"
data-flex-grow="135"
data-flex-basis="325px"
>&lt;/p>
&lt;h2 id="窗口装饰">窗口装饰
&lt;/h2>&lt;p>KDE 的窗口装饰指的是打开的应用程序窗口的顶部部分（就是包含了最小化、最大化、关闭按钮的那一栏）。我在 Google 搜索如何让 KDE 变得像 win10 时发现了一个非常新的 KDE 主题 &lt;a class="link" href="https://github.com/fauzie811/Breeze10" target="_blank" rel="noopener"
>Breeze10&lt;/a>，从 Github 页面上的图片可以看出这个主题可以完美地将窗口装饰变为 win10 的风格。&lt;del>由于目前（2019年12月）还没人打包，所以需要按照其 Github 页面上的操作步骤进行编译安装&lt;/del>。AUR 上已有 PKGBUILD，所以我们可以在终端输入指令安装（需要 Yay）：&lt;/p>
&lt;pre>&lt;code>yay -S breeze10-kde-git
&lt;/code>&lt;/pre>
&lt;p>此外，我的&lt;a class="link" href="https://viflythink.com/Use_Vercel_and_OneDrive_to_setup_your_repo/" target="_blank" rel="noopener"
>个人源&lt;/a>里也有该软件包，可以&lt;a class="link" href="https://viflythink.com/service/#arch-%E8%BD%AF%E4%BB%B6%E6%BA%90" target="_blank" rel="noopener"
>添加我的 Arch 软件源&lt;/a>后安装：&lt;/p>
&lt;pre>&lt;code>pacman -S breeze10-kde-git
&lt;/code>&lt;/pre>
&lt;p>在安装完成后重启系统，打开系统设置，在侧栏点击“外观”，再点击“窗口装饰元素”，选择“Breeze10”并应用更改；此外还可以点击那个笔状的图标调整这个主题，例如把字体设置变大，完成后你的应用程序窗口会显得更为美观大方。最后，把“窗口边框大小”设为“无边框”。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/set_breeze10.png"
width="1462"
height="1027"
loading="lazy"
alt="使用 Breeze10"
class="gallery-image"
data-flex-grow="142"
data-flex-basis="341px"
>&lt;/p>
&lt;h1 id="其它细节">其它细节
&lt;/h1>&lt;p>当你习惯性地用 ALT + TAB 键想要切换窗口时，就会发现在默认设置的情况下窗口列表将在左侧显示，我个人更喜欢 win10 或 Gnome 那样在切换窗口将列表显示在屏幕中间，为了做到这一点，打开系统设置，在侧栏点击“窗口管理”，然后点击“任务切换器”，在“可视化”处选择大图标，可看下图：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/KDE_to_Windows10/set_task_switch.png"
width="1093"
height="754"
loading="lazy"
alt="设置任务切换器"
class="gallery-image"
data-flex-grow="144"
data-flex-basis="347px"
>&lt;/p>
&lt;p>我并不喜欢每次点击关机按钮后都要进行确认，而是希望像 win10 那样直接关机，这也是稍微修改系统设置即可做到的事情，打开系统设置，在侧栏点击“开机和关机”，然后点击“桌面会话”，取消勾选“注销屏幕”一栏的“显示”。&lt;/p>
&lt;h1 id="总结">总结
&lt;/h1>&lt;p>经过这么多的折腾后，我总算是大概了解 KDE 了，“你可以将 KDE 捏成任何形状”毕竟只是一句用来吹嘘的话，除非动手改源代码，否则可自定义的部分总是有极限的，例如，在完成上述改造后，我对图标任务管理器并不完全满意，因为其显示的程序图标还是偏大，导致图标之间的间距不足，无法模拟 win10 底部栏的美观大方的感觉，当然，还有其它地方的间距设置也不尽人意，这些都难以通过安装主题等手段进行改造。当然，我个人认为没必要为此下结论说开源项目都处于美工下线的状态，其实无论是 KDE，亦或是 Gnome，它们的整体外观水平已经是与 Windows、Mac 这些商业公司开发的系统持平了，Linux 用户同样能有不差的桌面体验，KDE 等桌面所欠缺的只是一些审美细节，由于我也不懂设计，这里就不多说了。如果有兴趣的话，还可以多翻翻系统设置里的选项，其中包含了大量与桌面相关的自定义选项，这已经提供了非常大的改造空间。最后，在新的一年里，祝各位折腾愉快。&lt;/p></description></item><item><title>【译】椭圆曲线密码介绍</title><link>https://viflythink.com/translate_elliptic_curve_cryptography_explained/</link><pubDate>Sat, 14 Dec 2019 00:00:00 +0800</pubDate><guid>https://viflythink.com/translate_elliptic_curve_cryptography_explained/</guid><description>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/show.jpg" alt="Featured image of post 【译】椭圆曲线密码介绍" />&lt;p>前段时间我看到了一篇标题为 Elliptic Curve Cryptography Explained 的既通俗易懂又较为全面的介绍椭圆曲线密码的英语博文，可以说是非常优秀的一篇科普文了。看到目前在中文互联网上介绍非对称加密算法中的 RSA 加密算法的高质量文章有很多，而介绍同样属于非对称加密算法的椭圆曲线密码的高质量文章并不多，所以我将该文章翻译为中文并在我的博客上发表。原文链接是 &lt;a class="link" href="https://fangpenlin.com/posts/2019/10/07/elliptic-curve-cryptography-explained/" target="_blank" rel="noopener"
>https://fangpenlin.com/posts/2019/10/07/elliptic-curve-cryptography-explained/&lt;/a> ，已获得原作者授权翻译。由于作者仅要求我注明原文链接，所以&lt;strong>这篇译文的文字部分依然按本博客的默认授权协议 CC-BY-NC-SA 4.0 进行授权&lt;/strong>。对于普通读者而言，这篇文章基本不需要数学知识就可以理解，当然，还是需要了解一些对称加密和非对称加密的基础概念的，为了便于读者理解，我还在一些地方添加了译者注。下面，我们便开始对椭圆曲线密码的介绍吧。&lt;/p>
&lt;p>最近，我正在学习椭圆曲线密码（Elliptic Curve Cryptography）的工作原理（译者注：为了少打点字，下文统一使用 ECC 这一缩写指代椭圆曲线密码）。我在互联网上搜索相关内容，发现了很多解释它的文章和视频。其中大多数仅涵盖了 ECC 中的一部分内容，有一些跳过了许多关于你如何能从这处到达另一处的关键步骤。最后，我找不到真正的能以直观的方式从头到尾解释它的文章。考虑到这一点，我想写一篇解释 ECC 的文章，其内容从基础知识到密钥交换，加密和解密。&lt;/p>
&lt;p>为了绘制本文所需要的曲线，且了解 ECC 的运作方式，我写了两个 Jupyter Notebook 用于使用 Python 进行曲线绘制和计算，用到的绘图库是 &lt;a class="link" href="https://matplotlib.org/" target="_blank" rel="noopener"
>matplotlib&lt;/a>。另外，如果你想随意操作椭圆曲线，并自己体验一下其运作方式，那你很幸运！我&lt;a class="link" href="https://github.com/fangpenlin/elliptic-curve-explained" target="_blank" rel="noopener"
>在 GitHub 上开源了源代码&lt;/a>，一个&lt;a class="link" href="https://github.com/fangpenlin/elliptic-curve-explained/blob/master/elliptic-curve.ipynb" target="_blank" rel="noopener"
>适用于实数&lt;/a>，还有一个&lt;a class="link" href="https://github.com/fangpenlin/elliptic-curve-explained/blob/master/elliptic-curve-on-finite-field.ipynb" target="_blank" rel="noopener"
>适用于有限域&lt;/a>：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/jupyter-notebook.png"
width="2304"
height="1788"
loading="lazy"
alt="jupyter-notebook"
class="gallery-image"
data-flex-grow="128"
data-flex-basis="309px"
>&lt;/p>
&lt;p>你可以在 Jupyter Notebook 中找到大多数本文用到的图表。&lt;/p>
&lt;p>请注意，本文并&lt;strong>不是为了说明如何安全地实现 ECC&lt;/strong>，我们在此使用的示例只是为了使你和我自己便于理解或使用（译者注：警告，除非你是专家，否则不要在软件项目中自己实现加密算法，而应当使用现有的成熟的加密算法库）。我们也不想在数学这个兔子洞挖得太深，我只想集中精力了解它的本质的运作方式。因此，我们将剔除许多数学细节，仅提供参考资料供感兴趣的读者阅读。（译者注：本文存在不少星球大战的梗）&lt;/p>
&lt;p>现在，我们开始吧？&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/star-trek-into-darkness.gif"
width="500"
height="175"
loading="lazy"
alt="star-trek-into-darkness"
class="gallery-image"
data-flex-grow="285"
data-flex-basis="685px"
>&lt;/p>
&lt;h1 id="让我们先来玩个游戏">让我们先来玩个游戏
&lt;/h1>&lt;p>一个椭圆曲线是由 $y^{2} = x^{3} + a x + b$ 定义的曲线。 &lt;br>
举个例子，让 a = −3 和 b = 5，然后当你绘制这条曲线时，它看起来像这样：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve.png"
width="927"
height="621"
loading="lazy"
alt="elliptic-curve"
class="gallery-image"
data-flex-grow="149"
data-flex-basis="358px"
>&lt;/p>
&lt;p>现在，让我们玩一个游戏。随机选取曲线上 x 值不相同的两个点，并用一条直线连接这两个点，这两个点我们称为 A 和 B。然后你会注意到直线在除了 A 与 B 外的第三点与曲线接触。让我们找到第三个点并将其 y 值翻转到 x 轴的另一侧（译者注：也就是说以 x 轴为对称轴，将第三个点翻转到另一侧），我们将翻转后的点称为 A + B。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-game.png"
width="927"
height="621"
loading="lazy"
alt="elliptic-curve-game"
class="gallery-image"
data-flex-grow="149"
data-flex-basis="358px"
>&lt;/p>
&lt;p>点 A + B 是 A 与 B 的和。你可以认为此过程是某种太空旅行。想象有敌人正紧跟着你的飞船。要摆脱你的敌人，你可以在航线上走直线捷径，到达航线上的另一点，一旦到达第三个点，就会迅速弹跳到路线的另一侧。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/star-war.gif"
width="500"
height="213"
loading="lazy"
alt="star-war"
class="gallery-image"
data-flex-grow="234"
data-flex-basis="563px"
>&lt;/p>
&lt;p>好吧，敌人仍然跟着你，让我们再来一次。这次我们从最新的点 A + B 开始，到达另一点 C。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-game02.png"
width="927"
height="621"
loading="lazy"
alt="elliptic-curve-game02"
class="gallery-image"
data-flex-grow="149"
data-flex-basis="358px"
>&lt;/p>
&lt;p>如你所见，只要新增的线不是垂直的，我们就可以通过添加新的点来重复相同的技巧，以此跳到一个新的数字。&lt;/p>
&lt;p>随着时间的流逝，你意识到寻找一个新的弹跳落点很麻烦。为了使这个技巧更直观，更容易重复使用，现在让我们沿当前位置 P 的切线走捷径，它看起来像这样：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-2p.png"
width="927"
height="621"
loading="lazy"
alt="elliptic-curve-2p"
class="gallery-image"
data-flex-grow="149"
data-flex-basis="358px"
>&lt;/p>
&lt;p>考虑前面提到的两点式跳跃技巧，就像你看到点 A 和点 B 在 P 处彼此无限靠近，这实际上是相同的技巧。因此我们可以应用前面的规则，称 P 和 P 的结果之和为 P + P，即 2P。&lt;/p>
&lt;p>同样的，我们可以再一次重复执行相同的步骤以摆脱我们的敌人，这一次，我们从 2P 开始回到起始点 P：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-3p.png"
width="927"
height="621"
loading="lazy"
alt="elliptic-curve-3p"
class="gallery-image"
data-flex-grow="149"
data-flex-basis="358px"
>&lt;/p>
&lt;p>对于结果，我们将它称为 P + 2P 或 3P。显然，我们可以多次这样做以到达 NP。现在，问题来了，给定点 NP 的坐标，你能找出 N 值吗？换句话说，像下图这样，我们从 P 到 NP 跳了多少次呢？&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-np.png"
width="927"
height="621"
loading="lazy"
alt="elliptic-curve-np"
class="gallery-image"
data-flex-grow="149"
data-flex-basis="358px"
>&lt;/p>
&lt;p>我可以告诉你，在上图中的 NP 点的 N 值为 13。我很容易说出来，因为我选择了这个数字。但你很难找出答案，因为没有已知的简单而又有效率的方法来计算 N 值。&lt;/p>
&lt;p>就是这样，你已经了解了 ECC 的基础！曲线和起始点 P 是每个人都知道并同意使用的共享值，终点 NP 是你的&lt;strong>公钥&lt;/strong>，分享给任何人都是安全的。你跳了多少步，所对应的值 N 是你的&lt;strong>私钥&lt;/strong>。正如我们上面所说的，只知道 NP 和 P 的人很难推断出你的私钥，因为众所周知这是一个很难解决的问题。&lt;/p>
&lt;blockquote>
&lt;p>没这么快！&lt;/p>
&lt;/blockquote>
&lt;p>我听到你这样对我大喊。&lt;/p>
&lt;blockquote>
&lt;p>要到达 NP 点，并不意味着你需要进行 N 次这样的操作。如果你可以在合理的时间内完成该操作，那么我是否可以做同样的事情，即一步一步前进，直到遇到相同的点 NP，这不就确切地发现了需要走多少步了吗？&lt;/p>
&lt;/blockquote>
&lt;p>这是一个好问题，实际上我在网上阅读了许多文章后也产生了相同的疑问，但是我发现其中一些文章可以清楚地解释这一点。因此，接下来，我们来讨论在太空中如何真正地使你的敌人无法对你进行跟踪。&lt;/p>
&lt;h1 id="以曲速前进">以曲速前进
&lt;/h1>&lt;p>我们提到了用沿曲线跳跃的技巧以摆脱敌人，然而以缓慢的速度使用这个技巧是不明智的，因为它很容易被追踪。你的敌人可以简单地做同样的事情，直到他们弄清楚到达目的地需要跳跃多少次。为了真正使你无法被追踪，你需要以曲速前进。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/star-trek.gif"
width="471"
height="266"
loading="lazy"
alt="star-trek"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="424px"
>&lt;/p>
&lt;p>对于求和运算，或者我们在椭圆曲线上使用的技巧存在着一个有趣的特点，这个有趣的特点在于，曲线上的点与其求和运算都遵循&lt;a class="link" href="https://en.wikipedia.org/wiki/Group_%28mathematics%29" target="_blank" rel="noopener"
>群律&lt;/a>。其主要想法是，你可以对一个组中的元素进行某些操作，在这里我们称其为“相加”，进行该操作后它们仍将留在这个组中，而且，该操作具有一些特殊的属性。其中被称为关联性（associativity）的一种特殊属性是像这样的：&lt;/p>
&lt;p>(A + B) + C 与 A + (B + C) 是相同的&lt;/p>
&lt;p>这个概念背后的数学证明实际上并不简单，如果你感兴趣，可以在&lt;a class="link" href="https://www.andrew.cmu.edu/user/tnayak/papers/EllipticCurves.pdf" target="_blank" rel="noopener"
>这&lt;/a>或&lt;a class="link" href="https://math.rice.edu/~friedl/papers/AAELLIPTIC.PDF" target="_blank" rel="noopener"
>这&lt;/a>阅读相关资料。虽然很难证明，但是当你画出曲线和直线时很容易看出这一点。让我们来看一个例子。如你所见，我们在上面已得到一个点 (A + B) + C，根据关联性，我们应该能够先执行 B + C，然后再执行 A + (B + C)（译者注：也就是说将 A 与上一步 B + C 的结果相加），并且执行这两步后应该到达相同的终点。下图是 B + C：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-bc-first.png"
width="927"
height="621"
loading="lazy"
alt="elliptic-curve-bc-first"
class="gallery-image"
data-flex-grow="149"
data-flex-basis="358px"
>&lt;/p>
&lt;p>接下来，让我们执行 A +（B + C）：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-a-plus-bc.png"
width="927"
height="621"
loading="lazy"
alt="elliptic-curve-a-plus-bc"
class="gallery-image"
data-flex-grow="149"
data-flex-basis="358px"
>&lt;/p>
&lt;p>看下终点，它与（A + B）+ C 完全相同。不相信我吗？这是我编写的程序中输出的值：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ab_c&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ab&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">c&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">a_bc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">bc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="n">ab_c&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a_bc&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>(Point(0.9531851331698311, 1.733918191357413),
Point(0.9531851331698316, 1.7339181913574133))
&lt;/code>&lt;/pre>&lt;p>如你所见，ab_c 与 a_bc 几乎是相同的。其中的差异是由浮点运算的舍入误差造成的。这实际上是一个&lt;strong>大&lt;/strong>问题，我们将在后面讨论这一点。&lt;/p>
&lt;p>类似地，对于单点情况，群律允许我们以不同的顺序进行求和以到达相同的位置。这一点很关键，还记得我们如何通过 P + 2P 到达 3P 吗？现在我想告诉你 P + 3P = 4P 与 2P + 2P = 4P 是相同的。&lt;/p>
&lt;p>首先，让我们看一下愚蠢的计算方式，即只是继续将 P 与 3P 相加。这是计算 P + 3P 的最后一步：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-p-plus-3p.png"
width="927"
height="621"
loading="lazy"
alt="elliptic-curve-p-plus-3p"
class="gallery-image"
data-flex-grow="149"
data-flex-basis="358px"
>&lt;/p>
&lt;p>然后，让我们尝试将 2P 与自身相加，这恰好是我们之前使用的技巧：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-2p-plus-2p.png"
width="927"
height="621"
loading="lazy"
alt="elliptic-curve-2p-plus-2p"
class="gallery-image"
data-flex-grow="149"
data-flex-basis="358px"
>&lt;/p>
&lt;p>看到没有？两种方式都产生了相同的结果。就这样，我们可以轻松地将一个点加倍(double)，并且进行相加操作的顺序无关紧要。我们可以通过不断对一个点进行加倍操作来“作弊”，从而快速生成我们想要的值，不再需要将其一一加起来。&lt;/p>
&lt;p>让我们尝试通过求和到达一个更大的数字，例如 227P，我们首先将其转化为二进制数字，以便获得其两个成分的幂。其二进制表示为 11100011。换句话说，将两个值的所有幂加起来就是：
$$2^{7}P + 2^{6}P + 2^{5}P + 2^{1}P + 2^{0}P$$ &lt;br>
也就是：128P + 64P + 32P + 2P + P。&lt;/p>
&lt;p>所以我们需要的操作步骤是（译者注：原作者在这里说的很简单，为了让译文更易懂，这里参考 &lt;a class="link" href="https://zhuanlan.zhihu.com/p/36326221" target="_blank" rel="noopener"
>https://zhuanlan.zhihu.com/p/36326221&lt;/a> 添加了一点对操作步骤的补充）：&lt;/p>
&lt;ol>
&lt;li>将 P 与 0 相加，同时 P 加倍得到 2P&lt;/li>
&lt;li>将 2P 与 P 相加得到 3P，同时 2P 加倍得到 4P&lt;/li>
&lt;li>由于在二进制表示中从右到左的第三位为 0，所以不将 4P 与 3P 相加（以此类推，下面不再说明不相加的原因），只是 4P 加倍得到 8P&lt;/li>
&lt;li>不将 8P 与 3P 相加，只是 8P 加倍得到 16P&lt;/li>
&lt;li>不将 16P 与 3P 相加，只是 16P 加倍得到 32P&lt;/li>
&lt;li>将 32P 与 3P 相加得到 35P，同时 32P 加倍得到 64P&lt;/li>
&lt;li>将 64P 与 35P 相加得到 99P，同时 64P 加倍得到 128P&lt;/li>
&lt;li>将 128P 与 99P 相加得到 227P，同时 128P 加倍得到 256P&lt;/li>
&lt;/ol>
&lt;p>这仅需 8 步，与 227 步相比，这种方法快得多了。此方法被称为 &lt;a class="link" href="https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Double-and-add" target="_blank" rel="noopener"
>double and add&lt;/a>。它使我们可以快速地在一个椭圆曲线上跳跃以到达所需的点。在此示例中的数字 227 是很小的，我们可以在 $O(\log{}n)$ 的时间复杂度下到达我们期望的数字，就算这个数字是与宇宙中原子的数量一样大（一般而言是 $10^{82}$，约等于 $2^{275}$），此方法仍可以在 275 次 double and add 操作后完成计算。&lt;/p>
&lt;p>现在我们知道了如何以曲速在椭圆曲线上向前跳跃，因此，我们可以轻松地向前跳跃数十亿次。虽然这个操作对我们来说很容易，但是对于攻击者而言，要准确地找出我们跳了多少次是极其困难的，此问题等价于：在给定 NP 与 P 且 N 足够大的情况下，找出 N 值。这被称为椭圆曲线离散对数问题，如果你想了解有关此主题的更多信息，可以自己去网上搜索。&lt;/p>
&lt;h1 id="让我们在一个秘密的地方会面">让我们在一个秘密的地方会面
&lt;/h1>&lt;p>我们之前一直在谈论如何以光速在愚蠢的椭圆曲线上跳跃，但是加密呢？别急，我们这就介绍它。在此之前，让我们首先谈下密钥交换。&lt;/p>
&lt;p>想想看，Alice 和 Bob 正在太空旅行，他们将交换反抗军新总部的位置。突然，他们发现帝国的无人机正在尾随他们并拦截他们飞船之间的通信。为了安全地交换信息，他们同意在只有他们两个都知道的秘密坐标下会面。但是，如果敌人正在窃听，他们如何交换这个秘密坐标呢？现在，ECC 在这里为他们提供帮助。下面是 Alice 和 Bob 要做的事情：&lt;/p>
&lt;p>Alice：&lt;/p>
&lt;blockquote>
&lt;p>嘿，Bob，让我们将 P 作为起始点，这是我的公钥 NP。&lt;/p>
&lt;/blockquote>
&lt;p>Bob：&lt;/p>
&lt;blockquote>
&lt;p>以 P 为起始点对我来说听起来不错，而我的公钥是 MP。&lt;/p>
&lt;/blockquote>
&lt;p>在这里，按照我们之前的定义，NP 是 P 经过 N 次相加运算后得到的点。同样的，MP 就是 P 经过 M 次相加运算后得到的点。&lt;/p>
&lt;p>接下来，Alice 得到 Bob 的 MP 值，对 MP 自加 N 次：&lt;/p>
&lt;p>$$\underbrace{MP + MP + &amp;hellip; + MP}_\text{N times} = N \times MP$$&lt;/p>
&lt;p>对于 Bob，那就变成了取得 Alice 的公钥 NP 后将此点自加 M 次：&lt;/p>
&lt;p>$$\underbrace{NP + NP + &amp;hellip; + NP}_\text{M times} = M \times NP$$&lt;/p>
&lt;p>嗯，M 和 N 都很大，因为我们不希望敌人轻易地找到它。显然，上面的自加操作并不是真的一一相加，而是通过使用我们刚刚介绍的 double and add 技巧来作弊。最终，他们将会在一个只有他们知道的秘密坐标 SK 上会面：&lt;/p>
&lt;p>SK = (N × M)P = (M × N)P&lt;/p>
&lt;p>想想看，对于 Alice，每次跳跃的值是 P 点的 M 倍，而她跳跃了 N 次。对于 Bob 而言，每次跳跃的值是 P 点的 N 倍，而他跳跃了 M 倍。假设 Alice 一次跳跃 4 光年，而她总共跳跃了 3 次；Bob 一次跳跃 3 光年，他跳跃了 4 次。对应的运算分别是 4 × 3 与 3 × 4，它们都将在 12 这里结束：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/ecdh-jump.png"
width="803"
height="566"
loading="lazy"
alt="ecdh-jump"
class="gallery-image"
data-flex-grow="141"
data-flex-basis="340px"
>&lt;/p>
&lt;p>对于窃听者，他们需要找出 N 或 M 才能获得相同的坐标。通过一步一步运算，最终也会到达终点。但是，正如我们前面提到的，鉴于数字足够大，以致没有简单的方法可以做到这一点，因此我们可以确定只有 Alice 和 Bob 才能知道这个秘密坐标。这种密钥交换协议被称为&lt;a class="link" href="https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman" target="_blank" rel="noopener"
>椭圆曲线 Diffie–Hellman 密钥交换&lt;/a>。&lt;/p>
&lt;h1 id="加密">加密
&lt;/h1>&lt;p>现在让我们谈谈加密。Alice 想要安全地向 Bob 发送信息，他们需要首先进行我们上面刚刚提到的椭圆曲线 Diffie–Hellman 密钥交换：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/ecdh-encryption-key-exchange.png"
width="803"
height="425"
loading="lazy"
alt="ecdh-encryption-key-exchange"
class="gallery-image"
data-flex-grow="188"
data-flex-basis="453px"
>&lt;/p>
&lt;p>这里请注意，Alice 需要能够验证公钥 MP 真的是属于 Bob 的。否则，冒名顶替者可以向 Alice 提供自己的公钥，并声称自己是 Bob，然后，Alice 将与攻击者交换共享密钥，再然后，攻击者就可以执行&lt;a class="link" href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack" target="_blank" rel="noopener"
>中间人攻击&lt;/a>。 要解决该问题，就需要另一个概念，它被称为&lt;a class="link" href="https://en.wikipedia.org/wiki/Public_key_infrastructure" target="_blank" rel="noopener"
>公钥基础架构&lt;/a>，因为这个属于离题范围，如果你感兴趣，可以搜索相关资料。&lt;/p>
&lt;p>由于今天我们只想专注于 ECC，所以在此假设 Alice 已经取得 MP 并不加思索地相信它来自 Bob，而 Bob 也得到了 Alice 的公钥。现在，在交换密钥之后，他们最终得到了相同的共享秘密坐标，我们可以得到 x 值作为密钥。一旦我们拥有一个共享的密钥，一切就很简单了。得到共享密钥后，我们可以在任何安全的对称加密算法中使用共享密钥 SK 对我们的机密数据进行加密（译者注：出于性能因素的考量，通常只使用 ECC 等非对称加密算法交换对称加密算法所需的共享密钥，此后的通信使用对称加密而不是非对称加密算法进行加密，在这里就是这样做的）。假设我们在这里使用 &lt;a class="link" href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard" target="_blank" rel="noopener"
>AES256&lt;/a>。接收者 Bob 可以使用相同的共享密钥 SK 解密经过加密的消息。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/ecdh-encryption-encrypt.png"
width="803"
height="614"
loading="lazy"
alt="ecdh-encryption-encrypt"
class="gallery-image"
data-flex-grow="130"
data-flex-basis="313px"
>&lt;/p>
&lt;p>太好了，现在即使帝国的无人机窃听了所有通信，Alice 仍可以安全地将反抗军的新总部位置分享给 Bob。同样的，Bob 可以使用相同的共享密钥 SK 加密消息，并将其发送回 Alice。&lt;/p>
&lt;p>当 Alice 和 Bob 彼此认识时，我们知道可以使用 ECC 安全地发送消息。但是，如果在某些情况下我们想安全地向某人发送消息，但他们不知道并且可能不在乎你是谁，该怎么办？简单，我们在这举个例子，假设 Bob 已经知道 Alice 的公钥，如果 Alice 知道 Bob 的公钥 MP，她可以使用她自己的私钥 N 生成相同的共享密钥 SK，加密数据并将纯文本形式的公钥 NP 与经过加密的数据一起发送出去。一旦 Bob 收到消息，他就可以使用 NP 和他的私钥 M 创建相同的密钥 SK 并解密经过加密的数据。但是，由于信息中附带的公钥 NP 可能属于任何人，因此 Bob 将无法确认消息是来自谁的。如果 Bob 不在乎发送者是谁，Alice 也可选择为同一操作创建一个临时的新密钥对。&lt;/p>
&lt;h1 id="浮点数的问题">浮点数的问题
&lt;/h1>&lt;p>到目前为止，我们一直在讨论在实数范围进行计算的 ECC。我们在这里使用实数的原因是，它更易于解释和理解。在现实世界中，这实际上并不是我们进行加密的方式。使用实数会带来很多问题，我们之前展示的一个大问题就是会出现计算错误。还有另一个问题，在某些极端情况下，该数字可能会非常大，而浮点数可能无法容纳它。&lt;/p>
&lt;p>要回答你可能会问的这个问题，我们给出的答案是在&lt;a class="link" href="https://en.wikipedia.org/wiki/Finite_field_arithmetic" target="_blank" rel="noopener"
>有限域&lt;/a>，或者更精确地来说是在对整数 p 取模（p 为质数）的有限域上进行计算。同样的，我们也不想将兔子洞挖得太深，因此，如果你对此有兴趣，可以阅读 &lt;a class="link" href="https://andrea.corbellini.name/2015/05/23/elliptic-curve-cryptography-finite-fields-and-discrete-logarithms/" target="_blank" rel="noopener"
>Elliptic Curve Cryptography: finite fields and discrete logarithms&lt;/a>，或者观看 &lt;a class="link" href="https://www.youtube.com/watch?v=mFVKuFZ29Fc&amp;amp;list=PLN9KZDpNfsHMd7d7PX87JGesGY_Qzyb3V&amp;amp;index=2" target="_blank" rel="noopener"
>Trustica 的系列视频&lt;/a>及&lt;a class="link" href="https://trustica.cz/en/category/ecc/page/3/" target="_blank" rel="noopener"
>其相关文章&lt;/a>。&lt;/p>
&lt;p>要解释数学中的位于对整数 p 取模的有限域的椭圆曲线：&lt;/p>
&lt;p>$$y^{2} \equiv x^{3} + a x + b\pmod{p}$$&lt;/p>
&lt;p>先让我们取 p = 19，a = −3，b = 5，然后画出来：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-on-finite-field.png"
width="922"
height="621"
loading="lazy"
alt="elliptic-curve-on-finite-field"
class="gallery-image"
data-flex-grow="148"
data-flex-basis="356px"
>&lt;/p>
&lt;p>这看起来似乎不太像是一条“曲线”，但这确实是在有限域上的椭圆曲线。基本上，$y^{2}\pmod{p}$ 仅在特定整数点上等于 $x^{3} + a x + b\pmod{p}$。以点(11,7)为例，对于 y：&lt;/p>
&lt;p>$$y^{2} \equiv 7^{2} \equiv 49 \equiv 11 \pmod{19}$$&lt;/p>
&lt;p>以及对于 x：&lt;/p>
&lt;p>$$x^{3} + a x + b \equiv 11^{3} - 3 \times 11 + 5 \equiv 1303 \equiv 11 \pmod{19}$$&lt;/p>
&lt;p>由于两者在被 19 相除后得到相同的余数 11，所以这确实是曲线上的一个点。&lt;/p>
&lt;p>虽然它看起来不像是一条曲线，但它确实与在实数范围的椭圆曲线一样遵循相同的群律。让我们看一个例子，假设点 A = (3,2)，B = (5,18)，并使用相同的相加操作来计算 A + B：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-on-field-a-plus-b.png"
width="937"
height="623"
loading="lazy"
alt="elliptic-curve-on-field-a-plus-b"
class="gallery-image"
data-flex-grow="150"
data-flex-basis="360px"
>&lt;/p>
&lt;p>是的，在有限域上的该曲线有些特殊，当一条线到达边界时，实际上可以弯曲到另一端，因为取模操作就是在绕来绕去的。这条线会碰到第三点，就像是在实数上的曲线一样。&lt;/p>
&lt;p>在此给出我们的例子，(18,8)是我们要到达的点。然后将 y 值 8 翻转为 −8 并将其除以 19 取余，这将会得到(18,11)。 因此，A 和 B 的和为(18,11)。&lt;/p>
&lt;p>为了让这更容易被理解，我个人非常喜欢将其以甜甜圈的形状呈现在 3D 空间中，就像 &lt;a class="link" href="https://www.youtube.com/watch?v=mFVKuFZ29Fc&amp;amp;list=PLN9KZDpNfsHMd7d7PX87JGesGY_Qzyb3V&amp;amp;index=2" target="_blank" rel="noopener"
>Trustica 的关于 ECC 的视频系列&lt;/a>那样：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-on-field-donut.png"
width="1358"
height="1056"
loading="lazy"
alt="elliptic-curve-on-field-donut"
class="gallery-image"
data-flex-grow="128"
data-flex-basis="308px"
>&lt;/p>
&lt;p>但是考虑到我正在写一篇文章，所以在此将其以简单的 2D 图像呈现出来更加容易。&lt;/p>
&lt;p>现在，让我们向 A + B 点添加一个新点 C = (10,14)：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-on-field-ab-plus-c.png"
width="937"
height="621"
loading="lazy"
alt="elliptic-curve-on-field-ab-plus-c"
class="gallery-image"
data-flex-grow="150"
data-flex-basis="362px"
>&lt;/p>
&lt;p>接下来，让我们看看群律的关联性是否仍适用于有限域，这一次我们首先添加 B + C 点：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-on-field-b-plus-c.png"
width="922"
height="623"
loading="lazy"
alt="elliptic-curve-on-field-b-plus-c"
class="gallery-image"
data-flex-grow="147"
data-flex-basis="355px"
>&lt;/p>
&lt;p>然后，我们将 A 加到 B + C 点：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/translate_elliptic_curve_cryptography_explained/elliptic-curve-on-field-a-plus-bc.png"
width="922"
height="621"
loading="lazy"
alt="elliptic-curve-on-field-a-plus-bc"
class="gallery-image"
data-flex-grow="148"
data-flex-basis="356px"
>&lt;/p>
&lt;p>是的，它们最终都在同一个点(14,16)：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">a_b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">b_c&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">c&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="n">a_b&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b_c&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;pre tabindex="0">&lt;code>(Point(p=19, x=14, y=16), Point(p=19, x=14, y=16))
&lt;/code>&lt;/pre>&lt;p>现在你可能会问，在有限域上如何对一个点进行自加操作？是的，对一个点进行自加操作也是遵循与实数域相同的规则，即用有限域上的切线连接第三点。正如我们已经展示的它如何处理实数一样，我们不想在这里重复一遍，或者说实际上是因为我很懒😅。&lt;/p>
&lt;p>最后，由于有限域仅在整数上进行运算，所以我们不会损失任何精度。这令它更适合用于密码学。&lt;/p></description></item><item><title>从 Debian 迁移到 Arch Linux</title><link>https://viflythink.com/Try_Arch_Linux/</link><pubDate>Sun, 03 Nov 2019 14:34:27 +0800</pubDate><guid>https://viflythink.com/Try_Arch_Linux/</guid><description>&lt;img src="https://viflythink.com/Try_Arch_Linux/show.jpg" alt="Featured image of post 从 Debian 迁移到 Arch Linux" />&lt;p>&lt;em>2023.8.8.更新：写了一篇&lt;a class="link" href="https://viflythink.com/New-Install-Arch/" target="_blank" rel="noopener"
>新 Arch 的安装随手记&lt;/a>，推荐与本文对比阅读。&lt;/em>&lt;/p>
&lt;p>在用了将近两年的 Debian 后，我打算尝试另一个与 Debian 存在较大差别的发行版，做了一番比较后（&lt;del>并没有&lt;/del>）选择了相比 Debian 激进许多（经常需要滚包）的 Arch Linux。其实我在刚开始使用 Debian 时便听说过 Arch Linux 了，这都要归功于活跃的&lt;a class="link" href="https://www.archlinuxcn.org/" target="_blank" rel="noopener"
> Arch Linux 中文社区&lt;/a>，里面的人整天忙着安利 Arch Linux（&lt;del>传教&lt;/del>），而且，&lt;a class="link" href="https://wiki.archlinux.org/index.php/Main_page_%28%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87%29" target="_blank" rel="noopener"
> Arch Linux 的 Wiki &lt;/a>也是非常优秀的文档，我在 Debian 上遇到问题时也会参考 Arch Linux 的 Wiki，久而久之，便产生了尝试 Arch Linux 的想法，此外，对于现在的我而言，&lt;a class="link" href="https://wiki.archlinux.org/index.php/Arch_Linux_%28%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87%29#%E5%8E%9F%E5%88%99" target="_blank" rel="noopener"
>Arch Linux 的哲学&lt;/a>也非常有意思，其中提到：&lt;/p>
&lt;blockquote>
&lt;p>Arch 适用于乐于自己动手的用户，他们愿意花时间阅读文档，解决自己的问题。&lt;/p>
&lt;/blockquote>
&lt;p>这完全符合我想折腾 Linux 的想法！当然，我认为 Arch Linux 对 Linux 新手来说并不合适，因为光是第一步的使用命令行安装系统（Arch Linux 官方没有提供图形化安装界面）恐怕就能劝退不少人了，不过对于接触过 Linux 的人，通过理解 Arch Linux 安装过程中所需要输入的指令的含义，能体会到一种完全掌握自己的系统的快感（&lt;del>误入邪教&lt;/del>）。总而言之，对于喜欢折腾的人来说，尝试 Arch Linux 是绝对不会后悔的决定。&lt;/p>
&lt;p>由于 Arch Linux 的激进策略，安装教程很容易过时，我也不打算费力不讨好地写具体的安装步骤了，本文主要分享我在 Arch Linux 下使用的软件，希望能安利更多人使用（提到的不少软件都是跨平台的，即使不使用 Arch Linux 也可使用这些软件）。先在这里说一下我挑选软件的原则：通用性是最重要的，无论在哪个平台上使用都具有近乎一致的体验，为此没有利用单个平台的特性也是可接受的；数据可无障碍导出与导入，尊重用户的选择自由；简单易用且具备可扩展性（例如可安装扩展增强功能），但我也不排斥“一次配置，终身受用”这样需要折腾的软件；当然，开源是最好的。能达到这些要求的软件实属少数派，我在下文仅仅推荐几个，有空再补充。&lt;/p>
&lt;h1 id="安装">安装
&lt;/h1>&lt;p>考虑到 Arch Linux 经常变动，所以最好的安装指南应该是官方的&lt;a class="link" href="https://wiki.archlinux.org/index.php/Installation_guide" target="_blank" rel="noopener"
> Installation Guide&lt;/a>，我另外也参考了两篇博文，一个是萌狼的&lt;a class="link" href="https://blog.yoitsu.moe/arch-linux/installing_arch_linux_for_complete_newbies.html" target="_blank" rel="noopener"
>给 GNU/Linux 萌新的 Arch Linux 安装指南 rev.B&lt;/a>，另一个是&lt;a class="link" href="https://www.viseator.com/2017/05/17/arch_install/" target="_blank" rel="noopener"
>以官方Wiki的方式安装ArchLinux&lt;/a>。对于我这样存在多系统的情况，执行了 grub-mkconfig 后最好检查一下/boot/grub/grub.cfg 是否包括了所有的系统。 &lt;br>
有关于桌面环境的选择，鉴于之前总是看到各位大佬吹 Arch Linux 的 KDE 桌面的美观，而我一直在 Debian 下使用 Gnome，这回便决定尝试 KDE（&lt;del>其实是为了在出问题时更容易找到大佬求救&lt;/del>），在安装了 kde-applications 后，开始嫌弃如此多的用不上的应用了（说的就是教育与游戏分类下的那堆东西），所以花了点时间写了一个&lt;a class="link" href="https://gist.github.com/vifly/33d1a4f63b0b7319c6db9af9d3bdbdb0" target="_blank" rel="noopener"
>简单的 Python 脚本&lt;/a>删除这些软件（&lt;strong>需要 root 权限，使用需谨慎&lt;/strong>）。安装完成后重启进入桌面，我不得不表示默认的 KDE 桌面比 Gnome 漂亮多了，相比之下，Gnome 的塑料风格看着实在是让我难受。另外，KDE 全家桶之间的配合也令我十分满意，统一的设计风格，美观的特效，让我忍不住想吹爆 KDE 了。有一个值得一提的细节，在 KDE 下的鼠标单击等于其它桌面环境下的鼠标双击（例如在其它桌面环境下打开文件需要双击），一开始我并不习惯这种设置，觉得不便于选中单个文件，但用多了以后发现这种操作明显更轻松，因为平常使用鼠标时双击的频率比单击要高，而双击肯定比单击累，将双击替换为单击肯定可以减缓疲劳，对于需要选中单个文件的情况，右键也能满足需求，这又成了一个我喜欢 KDE 的原因。&lt;br>
除了桌面环境外，首先需要熟悉的还有 Arch Linux 的软件包管理器 Pacman，它的命令行参数与 apt 完全不一样，开始使用时经常需要查看其&lt;a class="link" href="https://wiki.archlinux.org/index.php/Pacman" target="_blank" rel="noopener"
> Wiki 页面&lt;/a>，值得一提的是，得益于&lt;a class="link" href="https://wiki.archlinux.org/index.php/Arch_User_Repository" target="_blank" rel="noopener"
> AUR(Arch User Repository) &lt;/a>的存在以及 Arch Linux 打包的低门槛，Arch Linux 拥有数量庞大的软件包，考虑到可能会使用 AUR 里的软件包，所以我安装了&lt;a class="link" href="https://github.com/Jguer/yay" target="_blank" rel="noopener"
> Yay &lt;/a>这个&lt;a class="link" href="https://wiki.archlinux.org/index.php/AUR_helpers" target="_blank" rel="noopener"
> AUR 助手&lt;/a>（Yay 完全兼容 Pacman 的命令行参数）帮我节省输入 makepkg 等指令的步骤，下文涉及到安装软件的指令既有可能使用 Pacman，也有可能使用 Yay。&lt;/p>
&lt;h1 id="中国大陆用户所需的东西">中国大陆用户所需的东西
&lt;/h1>&lt;h2 id="中文设置">中文设置
&lt;/h2>&lt;p>我直接根据&lt;a class="link" href="https://szclsya.me/zh-cn/posts/fonts/linux-config-guide/" target="_blank" rel="noopener"
> Linux 下的字体调校指南&lt;/a>一文进行调教，在这里我想说一下该博文中提到的“archlinuxcn required”，这意味着需要&lt;a class="link" href="https://www.archlinuxcn.org/archlinux-cn-repo-and-mirror/" target="_blank" rel="noopener"
>添加 archlinuxcn 源&lt;/a>，上面介绍已经提到了 Arch Linux 中文社区，而这个社区维护着一个非官方软件仓库，被称为 Arch Linux 中文社区仓库（archlinuxcn 源），该仓库包括了很多中文用户会用到的已编译好的软件包，而 AUR 提供的是 PKGBUILD 打包脚本（这就是为什么你可通过 AUR 安装不少明确禁止二次分发的闭源软件的原因，因为 AUR 分发的是打包脚本而不是软件本体），需要下载后进行编译打包安装，如果你懒得自己打包的话，建议添加 archlinuxcn 源。在配置完成中文字体的显示后，在 KDE 的系统设置中将语言设置为中文就行了。另外，强烈建议阅读官方的&lt;a class="link" href="https://wiki.archlinux.org/index.php/Arch_Linux_%E4%B8%AD%E6%96%87%E5%8C%96" target="_blank" rel="noopener"
> Arch Linux 中文化&lt;/a>页面。&lt;/p>
&lt;h2 id="翻墙">翻墙
&lt;/h2>&lt;p>折腾 Linux 总是会遇到各种问题，这种时候便需要 Google 了，让我们先解决使用无法使用 Google 的问题（此处使用 V2Ray 作为例子，在官方软件仓库有 V2Ray 的软件包真是太好了）：&lt;/p>
&lt;pre>&lt;code>yay -S v2ray
&lt;/code>&lt;/pre>
&lt;p>安装后修改/etc/v2ray/config.json 的配置，然后：&lt;/p>
&lt;pre>&lt;code>sudo systemctl enable v2ray.service
sudo systemctl start v2ray.service
&lt;/code>&lt;/pre>
&lt;p>如果想让桌面应用走代理，可以在 KDE 的系统设置中点击“网络”中的设置，然后点击“代理”，选中“使用系统代理服务器配置”，填入对应的代理信息，示例如下：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Try_Arch_Linux/set_kde_proxy.png"
width="1074"
height="736"
loading="lazy"
alt="KDE 设置系统代理"
class="gallery-image"
data-flex-grow="145"
data-flex-basis="350px"
>&lt;/p>
&lt;p>另外，V2Ray 支持 ShadowSocks 协议，可根据&lt;a class="link" href="https://www.v2ray.com/chapter_02/protocols/shadowsocks.html" target="_blank" rel="noopener"
> V2Ray 官方文档&lt;/a>写出配置文件，也可使用&lt;a class="link" href="https://www.veekxt.com/utils/v2ray_gen" target="_blank" rel="noopener"
>在线工具&lt;/a>生成；如果你使用 SSR 翻墙，AUR 中有&lt;a class="link" href="https://aur.archlinux.org/packages/electron-ssr/" target="_blank" rel="noopener"
> electron-ssr&lt;/a>，也有&lt;a class="link" href="https://aur.archlinux.org/packages/shadowsocksr/" target="_blank" rel="noopener"
> shadowsocksr&lt;/a>，但需要注意的是 electron-ssr 无法在 KDE 下自动设置代理（它使用了 gsetting 设置系统代理，不支持 KDE）。总之，安装好翻墙软件后终于能在电脑上使用 Google 查问题了。&lt;/p>
&lt;h1 id="终端模拟器与-shell">终端模拟器与 Shell
&lt;/h1>&lt;p>既然在 Linux 下，那么肯定免不了与终端打交道，既然如此，我们就需要一个美观、实用的终端（模拟器）。要说美观的话，KDE 自带的 Konsole 已经足够漂亮了，透明背景这一点让一直使用 Gnome Terminal 的我感到非常舒服，只需要稍微调整一下，就可以做到&lt;a class="link" href="https://kirikira.moe/post/28/#3" target="_blank" rel="noopener"
> kiri 大佬这样的效果&lt;/a>，让自己一整天都保持心情愉悦。不过在实用性方面我开始时遇到了一点问题，Konsole 使用的 Shell 是 Bash，而 Arch Linux 本身的 Bash 并没有自动补全配置，想要自动补全的话需要安装 bash-completion：&lt;/p>
&lt;pre>&lt;code>sudo pacman -S bash-completion
&lt;/code>&lt;/pre>
&lt;p>想要更高级的 Shell 体验的话（不知道终端模拟器与 Shell 有什么区别？请看&lt;a class="link" href="https://www.ihewro.com/archives/933/" target="_blank" rel="noopener"
>这&lt;/a>），也可以安装 zsh 加 oh-my-zsh 这样一整套的懒人包（或者自己配置 zsh？），只不过这里有一个小坑，在 AUR 中的 oh-my-zsh-git 并不会在 home 目录下生成 .zshrc，查找后发现在 /usr/share/oh-my-zsh 下有 zshrc 文件，我直接复制到 home 目录了，这里贴出安装懒人包的操作命令（将 username 改为你的用户名）：&lt;/p>
&lt;pre>&lt;code>yay -S zsh
sudo chsh -s /bin/zsh username
yay -S oh-my-zsh-git
cp /usr/share/oh-my-zsh/zshrc ~/.zshrc
&lt;/code>&lt;/pre>
&lt;p>*更新：博主已经放弃启动速度慢的 oh-my-zsh，转向 Zinit 这个神器的怀抱了，另外，2021 年 11 月 Zinit 的原作者删除代码库，目前由 zdharma-continuum 组织接手进行维护，请注意 URL 的变化。*Zinit 不仅轻松可以使用 oh-my-zsh 的各种插件，还拥有 Turbo mode 这个大幅减少插件加载时间的大杀器。如果你心动的话，请看&lt;a class="link" href="https://www.aloxaf.com/2019/11/zplugin_tutorial/" target="_blank" rel="noopener"
>加速你的 zsh —— 最强 zsh 插件管理器 zplugin/zinit 教程&lt;/a>一文。仅仅是照抄文末的示例配置，我也在保留 oh-my-zsh 体验的前提下感受到了起飞的加载速度，所以请无视上面的 oh-my-zsh，使用以下指令体验顺滑如丝的 Zinit（这里用了没什么配置难度的 proxychains-ng 翻墙下载 GitHub 片段，也可使用其它手段）：&lt;/p>
&lt;pre>&lt;code>yay -S zsh proxychains-ng
git clone https://github.com/zdharma-continuum/zinit.git ~/.zinit/bin
# 在 .zshrc 中添加 source ~/.zinit/bin/zinit.zsh 以及其它配置，可参考我的配置
nano .zshrc
# 配置 proxychains-ng，在最后一行添加类似 socks5 127.0.0.1 1080 的内容即可，自行谷歌了解配置
sudo nano /etc/proxychains.conf
# 启动 zsh，由于 .zshrc 已加载 Zinit，所以 zsh 首次启动时会自行下载 GitHub 上的片段
proxychains zsh
# 下载片段完成后退出执行这条指令更改默认 Shell，重启后见效果
sudo chsh -s /bin/zsh username
&lt;/code>&lt;/pre>
&lt;p>你可以在&lt;a class="link" href="https://github.com/vifly/dotfiles/blob/master/zsh/.zshrc" target="_blank" rel="noopener"
> GitHub &lt;/a>查看我的 zsh 配置，不过请记得根据自己的需求进行修改。&lt;/p>
&lt;h1 id="输入法">输入法
&lt;/h1>&lt;p>前面搞定了中文字体的显示，但是还没解决输入中文这个问题，在这里我选择了与使用 Debian 时同样的方案：基于 Fcitx 框架的 Rime 输入法。先贴一波安装指令（其它基于 Fcitx 框架的输入法请看&lt;a class="link" href="https://wiki.archlinux.org/index.php/Fcitx" target="_blank" rel="noopener"
> Wiki 页面&lt;/a>）：&lt;/p>
&lt;pre>&lt;code>sudo pacman -S fcitx fcitx-im fcitx-rime
&lt;/code>&lt;/pre>
&lt;p>为了确保能输入中文，修改一下/etc/profile，在开头加上：&lt;/p>
&lt;pre>&lt;code>export XMODIFIERS=&amp;quot;@im=fcitx&amp;quot;
export GTK_IM_MODULE=&amp;quot;fcitx&amp;quot;
export QT_IM_MODULE=&amp;quot;fcitx&amp;quot;
&lt;/code>&lt;/pre>
&lt;p>另外，需要更改一下输入法配置，操作步骤是右键点击托盘中的输入法图标，选择“配置”，修改后的配置如下图所示（按 Shift 键可切换中英文）：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Try_Arch_Linux/set_fcitx_1.png"
width="683"
height="820"
loading="lazy"
alt="输入法配置图1"
class="gallery-image"
data-flex-grow="83"
data-flex-basis="199px"
>&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Try_Arch_Linux/set_fcitx_2.png"
width="681"
height="816"
loading="lazy"
alt="输入法配置图2"
class="gallery-image"
data-flex-grow="83"
data-flex-basis="200px"
>&lt;/p>
&lt;p>最后，将我已经在 Debian 上调校好的 Rime 输入法配置文件拷贝过来（调校 Rime 的教程太多了，这里懒得贴了～），就能畅快地输入中文了。&lt;/p>
&lt;h1 id="多媒体">多媒体
&lt;/h1>&lt;p>这里选择在 Debian 上非常熟悉的 MPV 和 Rhythmbox 作为视频和音频播放器，之所以选择 MPV 是因为我已经有了一套&lt;a class="link" href="https://github.com/vifly/dotfiles/blob/master/mpv/.config/mpv/mpv.conf" target="_blank" rel="noopener"
>配置方案&lt;/a>，没必要选择其它播放器了，如果你还没使用过 MPV，那么&lt;a class="link" href="https://vcb-s.com/archives/7594/comment-page-1" target="_blank" rel="noopener"
>这里&lt;/a>有一篇相当不错的配置教程。而 Rhythmbox 支持不少插件，例如，在 KDE 桌面下，Rhythmbox 无法在关闭窗口时隐藏到托盘继续播放，可以通过安装 rhythmbox-tray-icon 插件解决：&lt;/p>
&lt;pre>&lt;code>yay -S rhythmbox-tray-icon
&lt;/code>&lt;/pre>
&lt;p>安装好插件后记得点击 Rhythmbox 右上角的设置按钮-&amp;gt;“插件”，在弹出的窗口中勾选刚安装的插件以激活插件效果，如下图：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Try_Arch_Linux/rhythmbox_plugin.png"
width="778"
height="598"
loading="lazy"
alt="Rhythmbox 插件"
class="gallery-image"
data-flex-grow="130"
data-flex-basis="312px"
>&lt;/p>
&lt;p>除此以外，我还推荐 rhythmbox-equalizer 插件，安装后可调整 EQ。&lt;/p>
&lt;h1 id="生产力">生产力
&lt;/h1>&lt;p>属于生产力的工具有很多，我在这里只选择分享几个比较重要的工具。如果平常使用的生产力工具没有 Linux 客户端，或许可用网页版代替客户端（连网页版都没有的话，折腾下 wine 或放弃在 Linux 下使用吧）。&lt;/p>
&lt;h2 id="浏览器">浏览器
&lt;/h2>&lt;p>都说程序猿是面向 Google 编程的，既然如此，怎能缺少一个趁手的浏览器用于查资料呢。直接安装我在 Debian 上一直在使用的 Firefox 与 Chromium（Google Chrome 的开源部分）：&lt;/p>
&lt;pre>&lt;code>yay -S firefox-i18n-zh-cn chromium
&lt;/code>&lt;/pre>
&lt;p>这两个浏览器都有云同步机制，可直接将在其它平台上的浏览器资料同步过来，不想使用云同步的话，也可以手动复制用户资料以进行数据备份和迁移，Chromium 的用户资料在~/.config/chromium/Default/，浏览器扩展及其数据存放在这个目录下带有“Extensions”的子目录中；Firefox 有些不同，它轻松支持多个用户配置，你可以打开&lt;a class="link" href="about:profiles" > about:profiles &lt;/a>页面查看用户配置文件路径，显示“正在使用此配置文件，因而不能删除。”的就是当前的用户配置文件。 &lt;br>
对于我来说，选择这两个浏览器的一个重要原因就是可以安装扩展改善各种功能，例如禁用 JS 的 NoScript/ScriptSafe，拦截广告的 uBlock，为网页注入实用 JS 的 Greasemonkey，对于 Firefox 用户，还可参考编程随想的&lt;a class="link" href="https://program-think.blogspot.com/2016/10/custom-firefox-theme-without-extension.html" target="_blank" rel="noopener"
>无需任何插件或扩展，定制 Firefox 外观&lt;/a>和&lt;a class="link" href="https://program-think.blogspot.com/2019/07/Customize-Firefox.html" target="_blank" rel="noopener"
>扫盲 Firefox 定制——从“user.js”到“omni.ja”&lt;/a>进行更高级的定制。另外，从安全补丁的及时性这一角度来说，我也更推荐这两个浏览器，而不是基于这两者的衍生版。&lt;/p>
&lt;h2 id="代码编辑器与ide">代码编辑器与IDE
&lt;/h2>&lt;p>要问对程序猿而言最重要的生产力工具是什么，回答肯定是代码编辑器或 IDE。目前在 Linux 下我喜欢的编辑器就是 Visual Studio Code（简称 VS Code）了，虽然这是微软出品的（别跟 VS 搞混了，两者之间的差别非常大），不过用了以后还是要说一句“真香！”。它可以胜任多种需求，常见的 Python、C/C++等完全不在话下，也可以用作 Markdown 写作，像本文就是在 VS Code 下完成的，当然，值得一提的还有美观的界面，开箱即用的设置，这都令它在短时间内打动了我，再配合各种扩展，带来的是十分舒适的体验。对于 VS Code，我目前推荐 TabNine 以及 Markdown Preview Enhanced 这两个扩展，前者带来优秀的主流编程语言自动补全，后者带来更高级的 Markdown 预览体验（例如查看 LaTex）。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Try_Arch_Linux/vs_code.png"
width="1920"
height="1043"
loading="lazy"
alt="VS Code 图"
class="gallery-image"
data-flex-grow="184"
data-flex-basis="441px"
>&lt;/p>
&lt;p>如果需要一个 IDE 的话，我推荐由 JetBrains 出品的 IDE，应该有不少人用过它家的 PyCharm 了，除此以外，Clion (C/C++) 与 IntelliJ IDEA (Java) 也是非常优秀的 IDE，至少在目前来说，Clion 对 Cmake 项目的支持可比 VS 好多了。另外，配合 Github 的学生认证可以白嫖 JetBrains 的产品，在此强烈推荐学生党尝试一下 Clion。&lt;/p>
&lt;h2 id="笔记">笔记
&lt;/h2>&lt;p>作为一个程序猿，总是会有记录笔记的需求，我目前有相当一部分的笔记资料储存在 EverNote 这个云笔记上，而它并没有 Linux 官方客户端，不过，得益于它的开放 API，早就有开发者做了一个在 Linux 下的客户端：NixNote（原名 Nevernote），Arch Linux 官方仓库有这个软件包：&lt;/p>
&lt;pre>&lt;code>sudo pacman -S nixnote2
&lt;/code>&lt;/pre>
&lt;p>&lt;del>只不过我遇到了在已设置应用程序使用语言为中文的情况下，菜单依然为英文的问题，Google 后找到一篇&lt;a class="link" href="https://blue-leaf81.net/archives/nixnote-translate-jp/" target="_blank" rel="noopener"
>让 NixNote 显示日语的教程&lt;/a>，受到这篇教程的启发，我查看了一下/usr/share/nixnote2/translations/目录，发现其中只有 nixnote2_cs_CZ.qm 文件，看来想要让菜单显示中文，就必须在这个目录下添加中文翻译。具体来说，先前往 GitHub 仓库下载&lt;a class="link" href="https://github.com/baumgarr/nixnote2/blob/master/translations/nixnote2_zh_CN.ts" target="_blank" rel="noopener"
>中文翻译源文件&lt;/a>，接着使用 Qt Linguist 打开下载回来的文件，然后点击左上角“File”-&amp;gt;&amp;ldquo;Release As&amp;quot;导出到/usr/share/nixnote2/translations/nixnote2_zh_CN.qm。重启 NixNote 便可以看到中文菜单了&lt;/del>。更新：2020年5月的更新已带上中文翻译，无需再按上面折腾。 &lt;br>
当然，EverNote 在 Linux 下还有&lt;a class="link" href="https://itsfoss.com/evernote-on-linux/" target="_blank" rel="noopener"
>几个非官方客户端&lt;/a>，我选择 NixNote 的原因在于它是使用 C++ QT 开发的，而不是类似于&lt;a class="link" href="https://github.com/klauscfhq/tusk" target="_blank" rel="noopener"
> Tusk &lt;/a>等使用前端技术开发的套壳 Web 应用，但对于 EverNote 的高级用户，我建议使用 EverNote 的网页版，而不是使用 NixNote，因为网页版的编辑功能比 NixNote 更优秀。&lt;/p>
&lt;h2 id="虚拟机">虚拟机
&lt;/h2>&lt;p>我有时候会有使用虚拟机运行 Windows 或其它 Linux 发行版的需求，这个时候就需要用到虚拟机了，VirtualBox 是一个操作简单且免费开源的虚拟机软件，根据&lt;a class="link" href="https://wiki.archlinux.org/index.php/VirtualBox" target="_blank" rel="noopener"
> Wiki 页面&lt;/a>进行安装（安装时会要求选择内核模块，没有更换默认内核的话，选择 virtualbox-host-modules-arch，不然选择 virtualbox-host-dkms）：&lt;/p>
&lt;pre>&lt;code>yay -S virtualbox virtualbox-ext-oracle virtualbox-guest-iso
&lt;/code>&lt;/pre>
&lt;p>假如你没有用过 VirtualBox，那么这里提醒一句，拖放文件和共享粘贴板等功能需要在运行中的虚拟机窗口上方点击“设备”-&amp;gt;“安装增强功能”才可使用。&lt;/p>
&lt;h1 id="后记">后记
&lt;/h1>&lt;p>得益于我对软件的通用性的要求，可以说是无痛从 Debian 迁移到了 Arch Linux，不少软件只需简单地复制粘贴配置文件即可（前提是已经有配置文件了），而且 Arch Linux 的系统安装过程也并不像我之前想象的那样复杂。折腾完这堆东西后最大的感触就是之前折腾积累的东西（如相关知识与配置）并没有浪费，若是没有相关的积累，面对安装 Arch Linux 以及安装完成后做什么这些问题恐怕会一头雾水，浪费不少时间，从节约时间的角度来说，编程随想所说的&lt;a class="link" href="https://program-think.blogspot.com/2013/10/personal-it-infrastructure.html" target="_blank" rel="noopener"
>重视个人 IT 基础设施的改善&lt;/a>是很有道理的。总之，安装好 Arch Linux 的我就像是一个刚得到新玩具的小孩子，正迫不及待地想要探索这个新玩具的有趣之处，更多有趣的软件留待日后补充好了。&lt;/p></description></item><item><title>致刚入门深度学习的我——作为过来人的一点经验分享</title><link>https://viflythink.com/Sharing_experience_about_Deep_Learning/</link><pubDate>Fri, 04 Oct 2019 22:44:08 +0800</pubDate><guid>https://viflythink.com/Sharing_experience_about_Deep_Learning/</guid><description>&lt;img src="https://viflythink.com/Sharing_experience_about_Deep_Learning/show.jpg" alt="Featured image of post 致刚入门深度学习的我——作为过来人的一点经验分享" />&lt;p>前几天突然有了一个很有趣的想法，假如能与一年前刚入门深度学习的我进行交流，那么此时的我会有什么经验想分享给过去的我呢？想到入门深度学习以来踩的各种坑以及经历过的迷茫，我决定写一篇文章，从过来人（入门一年？）的角度说一下对新手的一些建议。由于我的水平有限，所以文章肯定会有遗漏与错误之处，望各位大佬轻拍。&lt;/p>
&lt;h1 id="常见问题解答faq">常见问题解答（FAQ）
&lt;/h1>&lt;ol>
&lt;li>
&lt;p>问：网上好多深度学习入门教程啊，该选择哪个呢？&lt;br>
答：随便在 Google 上搜一下“深度学习入门”，就能找到不少高质量的回答以及学习资源分享。我并不想在这推荐什么学习资源，因为在这方面的好文章太多了，我只想强调一点：&lt;strong>无论你选择了哪个入门教程（书籍），都请专注于该教程&lt;/strong>，不要总是被五花八门的教程扰乱自己，任何教程都是殊途同归的，学成后的效果总是一致的。其实最关键的是需要有一个明确的目标，我会在正文突出学习目标，当你在学习时只需关注&lt;strong>如何达成学习目标&lt;/strong>即可。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>问：我的数学/英语水平低，会不会难以入门？&lt;br>
答：其实不必那么担心。在我看来，入门深度学习对数学并没有太高的要求，入门阶段只需要知道导数与积分，矩阵等几个概念即可；至于英语，我只能说即使是天天翻阅英语文档的程序员也不见得能熟练地使用英语，想学习深度学习，能记住英语术语即可。如果一定要说入门有什么要求的话，我觉得能使用 Google 查找资料和不畏惧英语阅读这两点是必需的，当然，还需要一点编程能力。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>问：学习深度学习需要懂编程吗？&lt;br>
答：实战部分需要（可能有些教程在讲解理论时也会贴一段 Python 代码），但是要求不高。目前（2019 年）学术界的主流编程语言依然是 Python，假如你有编程基础，那么只需花 3 天时间学习 Python 基础语法即可，深度学习并不需要 Python 的高级语法知识；如果没有编程基础，请先老老实实地找个 Python 入门教程学习，不必担心难以学习，Python 作为一门小学生都能学会的编程语言，相信你用半个月到一个月的时间足以学会它的基础语法。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h1 id="理论">理论
&lt;/h1>&lt;p>首先，回答一个问题：你是为了什么而决定学习深度学习呢？是需要与老师做深度学习相关的科研项目？开发软件项目时需要用到深度学习？还是觉得近几年深度学习很火，想学习一下？如果只是软件项目中需要深度学习，那么可以考虑直接使用腾讯等大厂提供的 API，这些 API 可以实现图片分类等许多实用的功能。对于我来说，我是在跟着老师做计算机视觉（CV）科研项目时开始学习深度学习的，此前并没有相关经验。在这种情况下如何学习一个新的技术呢？完全可以根据&lt;a class="link" href="https://program-think.blogspot.com/2009/02/study-technology-in-three-steps.html" target="_blank" rel="noopener"
>学习技术的三部曲：WHAT、HOW、WHY&lt;/a>中提到的 WHAT、HOW、WHY 这三个步骤进行学习，不过要注意的一点是在深度学习中想达到 WHY 这一步非常困难（学到后面就会发现神经网络基本是一个黑盒，所以训练模型也被戏称为炼丹）。先说下如何知道 WHAT 吧，想要知道 WHAT，你就必须知道深度学习中常见的名词的含义，例如卷积、梯度下降、神经网络、前向传播、反向传播等，最重要的是，知道神经网络的训练流程是怎样的。这一步比较简单，我推荐观看吴恩达的&lt;a class="link" href="https://www.coursera.org/specializations/deep-learning" target="_blank" rel="noopener"
>Deep Learning Specialization&lt;/a>系列视频，上面有 5 门课程，每门课程都安排了 4 周的学习时间，但实际上我们不必全部看完，只需观看课程 1、2 即可，凡是需要写代码的练习全部跳过，因为我们只需要了解概念，等下我会说明如何进行实战练习。跳过课程 3 是因为入门阶段可以无视机器学习。准备进入计算机视觉（CV）领域的请另外看课程 4 的第一周的视频，了解卷积神经网络（CNN）的概念；准备进入自然语言处理（NLP）领域的请另外观看课程 5 的第一周的视频，了解循环神经网络（RNN）的概念。（PS：因为好奇而学习深度学习，不知道 CV 和 NLP 是干什么的？先去查下这两个领域有什么应用吧，看看哪些是你感兴趣的或者未来工作可能会用到的）&lt;/p>
&lt;p>除了吴恩达的深度学习系列视频，你也可以通过看书完成这一步，只不过我没有仔细看过深度学习入门的书籍，所以不做推荐。无论如何，在这一个阶段，你必须对深度学习有&lt;strong>整体&lt;/strong>的认识。检验是否达成这一目标的方法是回答一个问题：深度学习中的训练是怎么一回事？尝试用尽可能少的专业术语进行简短的描述，思考出答案后与我的回答进行对比看看。&lt;/p>
&lt;p>对于上面的问题，我的回答就是构建一个神经网络，不断输入数据与标签，其根据数据与标签之间的对应关系调整自己的参数以令输出与标签一致（只限于监督学习）。说到这里，你可能会问：“诶？那么反向传播的实现这类问题呢？”。这已经是属于 HOW 这一范畴的问题了，你如果能清楚知道这类问题的答案那当然是一件非常好的事情，不过只记得大概的描述也没关系，知道 WHAT 以后，我们就已经可以使用深度学习框架进行实战了，只不过若是想要继续发展，请一定要花时间了解神经网络的实现细节，&lt;strong>程序员可以不了解使用的技术的细节，但搞科研的必须了解细节&lt;/strong>。说了这么多关于了解概念的经验，接下来该说下肯定有不少人关注的实战部分了，对于 IT 行业的人来说，这部分会比较轻松。&lt;/p>
&lt;h1 id="实战">实战
&lt;/h1>&lt;p>首先，我们先来聊点与编程有关的东西。想要进行实战，那么必须懂得 Python 基础语法，只是用 Python 如何实现在理论教程中吹了那么久的神经网络呢？先选择一个深度学习框架吧，我推荐选择 Keras 或者 Pytorch，这两个在目前是主流，而且简单易用（都 9102 年了，Tensorflow 该让位给 Keras 了）。为什么我推荐先学习使用深度学习框架而不是按照一些教程所说的先使用 Numpy 等库实现一个简单神经网络的训练呢？原因很简单，理论部分中，我已经说过在入门阶段只需对神经网络有一个&lt;strong>整体&lt;/strong>的认识即可，实战必须与理论相结合，在理论学习中没有完全搞清楚实现细节，在实战部分通过不使用框架实现神经网络就可以弄清楚细节了吗？很难。当然，如果你很牛，在理论部分已经搞清楚细节了，例如前向/反向传播的具体过程，那么可以先使用 Numpy 等库实现一个简单神经网络。对于大多数人而言，在入门阶段，只需要掌握大概即可，以后还有很多时间了解细节呢。为什么只选择一个框架呢？未来的实战肯定不可能只使用一个框架，甚至有可能需要自己实现框架，但是，在入门阶段，一个足够了，框架隐藏了实现细节，让我们能够专注于神经网络的架构（在这里是优点），而且，在学会使用一个框架后，再学习另一个框架会事半功倍。&lt;/p>
&lt;p>选好框架后，快来选择一个经典的模型上车吧。以我所在的计算机视觉领域举例来说，经典的模型包括 LeNet 5、AlexNet、VGG 等，各个框架都自带了这些经典模型（Pytorch 的在 torchvision 中），先根据官方文档提供的 demo 实际训练一下模型，看一下 CNN 在图片分类上的效果（选择图片分类任务是因为目前该领域最成熟），这里提供 Pytorch 的一个&lt;a class="link" href="https://github.com/pytorch/examples/blob/master/imagenet/README.md" target="_blank" rel="noopener"
>demo&lt;/a>。稍微了解了训练流程后，接下来便应该读使用的 demo 中创建神经网络的代码了，例如可以看 Pytorch 官方的&lt;a class="link" href="https://github.com/pytorch/vision/blob/master/torchvision/models/vgg.py" target="_blank" rel="noopener"
>VGG模型实现代码&lt;/a>。应该如何阅读这部分的代码呢？我建议与原论文或原论文的解析文章进行对照，&lt;strong>将模型结构图与模型代码联系起来&lt;/strong>，因为以后很有可能需要根据模型结构图与描述使用框架构建对应的模型，或者根据模型代码深入理解模型结构，强烈建议在入门阶段便开始锻炼这方面的能力，这与刚入门编程时敲很多代码以熟悉语法和编程有点相似，不同的是：在编程领域这是为了熟悉从现实抽象出来的问题与代码之间的联系，而在这里是为了熟悉模型整体架构与构建出的模型代码之间的联系。当然，急着应用深度学习到实际项目的话可以放低一点要求，知道如何修改现有模型代码的输入层与输出层就可以了。假如觉得代码太长，不够直观，那么直接输出模型结构看看（Pytorch）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-Python" data-lang="Python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torchvision.models&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">models&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">models&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">vgg16&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Keras 也可以进行这个操作，使用 model.summary()即可。先不理会 demo 的其它部分代码，好好消化构建模型的方法，这是我觉得入门阶段最适合的方案，因为我觉得入门阶段就接触核心向的东西（深度学习比较简单，所以能在起始阶段便接触偏核心的问题，其它领域不一定喔）能避免以后可能遇到的不少弯路，&lt;del>假如重来一遍，我也不知道这样是不是能保证一切顺利&lt;/del>。虽然我在上文当中都是以计算机视觉举例，不过相信想学习 NLP 的读者也可以从中举一反三，知道该如何通过实战进行入门。&lt;/p>
&lt;h1 id="下一步">下一步？
&lt;/h1>&lt;p>我们直击重点的入门教程算是结束了，&lt;del>虽然我在开头说过自己踩了不少坑，但是写完这篇文章后发现好像并没有怎么提到自己踩过的坑&lt;/del>，我知道很多人会问接下来该干什么？在这里我真的可以说一说下一步该干什么。还记得上面的实战部分并没有提到关于读取数据等方面的内容吗，在日后的实战当中，读取数据并输入神经网络其实也是一个重要的部分，除非你确定以后只需使用主流的公开数据集，不然这部分知识就是必需的。接下来你可以先尝试在自己已能成功运行的 demo 上更换负责读取数据集的代码（更换后的代码不应该使用框架提供的常见数据集的 API，如读取 Imagenet 的 API），让模型使用另一个数据集进行训练完成相同的任务（如图片分类），这部分工作可能会花上几天时间，但是完成以后你应该就能真正理解如何向神经网络中输入数据了，除此以外，你有可能会顺便了解到一些数据预处理的简单操作，在实战当中，使用的数据集与模型的原作者使用的数据集总是不同，无论如何，&lt;strong>一定要参考原作者的数据预处理步骤&lt;/strong>，不然有可能出现很大的指标差异。&lt;/p>
&lt;p>弄完了上面提到的东西，接下来真的可以说是海阔天空了，接下来的路就是无数个分叉口，入门后的你可以尝试使用神经网络做一个简单的实战项目，例如：用户上传图片，服务器返回分类结果（早已有大厂提供这个 API 了）；也可以找老师开始下一步的科研；当然也可以继续自学，只不过需要自己把握学习方向了。总之，未来就在脚下，&lt;del>祝各位在炼丹的路上一切顺利&lt;/del>。&lt;/p></description></item><item><title>Debian 安装 Matomo (Piwik) 开源统计分析服务</title><link>https://viflythink.com/Install_Matomo_on_Debian/</link><pubDate>Sun, 18 Aug 2019 00:00:00 +0800</pubDate><guid>https://viflythink.com/Install_Matomo_on_Debian/</guid><description>&lt;img src="https://viflythink.com/Install_Matomo_on_Debian/show.jpg" alt="Featured image of post Debian 安装 Matomo (Piwik) 开源统计分析服务" />&lt;h1 id="前言">前言
&lt;/h1>&lt;p>之前我在&lt;a class="link" href="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog_advanced/#%E6%95%B0%E6%8D%AE%E7%BB%9F%E8%AE%A1%E4%B8%8E%E5%88%86%E6%9E%90" target="_blank" rel="noopener"
>使用Github Pages和Hexo搭建个人博客(进阶篇)&lt;/a>这一篇博文中已经提到了不考虑使用大型公司提供的网站统计分析服务了，只不过网站统计分析服务还是有必要的，至少能看到有多少人浏览过自己的网站。之所以不采用商业公司提供的分析服务是因为这等于助纣为虐，帮助这些公司建立更精准的用户画像，这些公司可以利用遍布于大半个互联网的自家的跟踪代码对读者进行浏览痕迹的跟踪，从而建立精准的用户画像，我无法接受这种侵犯用户隐私的行为，所以只能考虑自己搭建统计分析服务了。在 Google 上搜了一下后决定采用 Matomo（原名为 Piwik）这个开源的网站统计分析服务。本文主要参考了&lt;a class="link" href="https://my.oschina.net/u/3944788/blog/2874366" target="_blank" rel="noopener"
>在Debian 9上安装Matomo Analytics&lt;/a>这一个教程，只不过很不巧的是目前 Debain 10 已经发布，这篇教程里的 php7.0 已经过时，但是没关系，下文中提供的安装 php 的指令并没有指定版本，所以对于 Debian 9/10 的用户都是可行的。&lt;/p>
&lt;h1 id="需求">需求
&lt;/h1>&lt;p>1.基本的 Linux 终端操作经验&lt;br>
2.一个安装了 Debian 9/10 的服务器（VPS），理论上来说 Ubuntu 18 也可以（并没有实测过）&lt;br>
3.一个属于自己的域名，并且已经将其 DNS 解析指向自己的服务器&lt;/p>
&lt;h1 id="操作">操作
&lt;/h1>&lt;p>先安装必须的库：&lt;/p>
&lt;pre>&lt;code>sudo apt install unzip apt-transport-https curl wget dirmngr php php-fpm php-curl php-gd php-cli php-mysql php-xml php-mbstring
&lt;/code>&lt;/pre>
&lt;p>安装 MySQL 的替代品 MariaDB，这里必须提到的一点是，从 Debian9 开始，&lt;a class="link" href="https://mariadb.org/debian-9-released-mariadb-mysql-variant/" target="_blank" rel="noopener"
>软件包仓库中的 MySQL 实际上已经全被 MariaDB 取代了&lt;/a>：&lt;/p>
&lt;pre>&lt;code>sudo apt install mariadb-server
&lt;/code>&lt;/pre>
&lt;p>运行 mysql_secure_installation 脚本以改进 MariaDB 安装的安全性：&lt;/p>
&lt;pre>&lt;code>sudo mysql_secure_installation
&lt;/code>&lt;/pre>
&lt;p>作为数据库 root 用户登录到 MariaDB（注意，必须使用 root 权限才可以作为数据库 root 用户登录到 MariaDB，数据库的 root 用户与系统中的 root 用户不是同一个东西）：&lt;/p>
&lt;pre>&lt;code>sudo mysql -u root -p
&lt;/code>&lt;/pre>
&lt;p>假如没有一个用于 Matomo 的数据库用户的话，先执行以下指令新建数据库用户，localhost 意味这个用户只可以本地登录（&lt;em>PS：记得将username和password替换为自己准备设置的用户名和密码，下同&lt;/em>）：&lt;/p>
&lt;pre>&lt;code>CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
&lt;/code>&lt;/pre>
&lt;p>创建后请记住用户名和密码。&lt;br>
创建一个新的 MariaDB 数据库并授权：&lt;/p>
&lt;pre>&lt;code>CREATE DATABASE db_name;
GRANT ALL ON db_name.* TO 'username' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;
quit;
&lt;/code>&lt;/pre>
&lt;p>安装 nginx：&lt;/p>
&lt;pre>&lt;code>sudo apt install -y nginx
&lt;/code>&lt;/pre>
&lt;p>新建 Nginx 配置文件：&lt;/p>
&lt;pre>&lt;code>sudo nano /etc/nginx/sites-available/matomo
&lt;/code>&lt;/pre>
&lt;p>在其中填入（将 your_domain 替换为你的域名，例如 stats.viflythink.com，fastcgi_pass 的内容请根据自己的版本进行填写，你可以通过 ls /run/php/ 看到对应的 sock 文件）：&lt;/p>
&lt;pre>&lt;code>server {
listen 80;
server_name your_domain;
root /var/www/html/matomo;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
&lt;/code>&lt;/pre>
&lt;p>通过建立软链接将刚写好的配置文件对应的网站设置为可访问的：&lt;/p>
&lt;pre>&lt;code>sudo ln -s /etc/nginx/sites-available/matomo /etc/nginx/sites-enabled/
&lt;/code>&lt;/pre>
&lt;p>测试配置：&lt;/p>
&lt;pre>&lt;code>sudo nginx -t
&lt;/code>&lt;/pre>
&lt;p>创建 matomo 目录：&lt;/p>
&lt;pre>&lt;code>sudo mkdir -p /var/www/html/matomo
&lt;/code>&lt;/pre>
&lt;p>下载和解压 matomo：&lt;/p>
&lt;pre>&lt;code>cd /var/www/html/matomo
wget https://builds.piwik.org/piwik.zip
unzip piwik.zip
rm piwik.zip
mv piwik/* .
rmdir piwik
&lt;/code>&lt;/pre>
&lt;p>更改该目录的所有权，确保访问者可以访问这些页面文件：&lt;/p>
&lt;pre>&lt;code>sudo chown -R www-data:www-data /var/www/html/matomo
&lt;/code>&lt;/pre>
&lt;p>重新加载 Nginx 以让配置生效：&lt;/p>
&lt;pre>&lt;code>sudo systemctl reload nginx.service
&lt;/code>&lt;/pre>
&lt;p>接下来使用浏览器打开 Nginx 配置文件中填写的域名，按照指引完成 matomo 的安装。&lt;/p></description></item><item><title>作为大学生的我如何申请外币信用卡并用于境外支付</title><link>https://viflythink.com/How_to_apply_for_non_UnionPay_credit_and_shopping_as_college_student/</link><pubDate>Sun, 28 Jul 2019 00:00:00 +0800</pubDate><guid>https://viflythink.com/How_to_apply_for_non_UnionPay_credit_and_shopping_as_college_student/</guid><description>&lt;img src="https://viflythink.com/How_to_apply_for_non_UnionPay_credit_and_shopping_as_college_student/show.png" alt="Featured image of post 作为大学生的我如何申请外币信用卡并用于境外支付" />&lt;h1 id="前言">前言
&lt;/h1>&lt;p>之前在购买 VPS 和域名时发现很多国外商家只支持 VISA，MASTERCARD 这两家的信用卡或者 PayPal，虽然国区 PayPal 可以绑定银联的借记卡/信用卡，但是除了这些以外我还有在 Google Play 购物的需求，未来也会有更多的境外支付的需要，目前我已经忍受不了每次去 Google Play 购物时都要先去淘宝买个礼品卡这个操作了，所以决定采用一个一劳永逸的办法：办理一张外币信用卡（准确来说是走非银联结算通道的信用卡）。我原本以为作为大学生办理一张外币信用卡会比较麻烦，不过想到之前在少数派看过《&lt;a class="link" href="https://sspai.com/post/38960" target="_blank" rel="noopener"
>给普通大学生的境外支付指南&lt;/a>》和《&lt;a class="link" href="https://sspai.com/post/45933" target="_blank" rel="noopener"
>学生党 Google Play 剁手经验分享&lt;/a>》，所以决定照此进行尝试。&lt;em>PS：本文都是我的个人经验分享，不一定完全有效，同时也可能具有时效性，仅作参考。&lt;/em>&lt;/p>
&lt;h1 id="大学生申请外币信用卡的方法">大学生申请外币信用卡的方法
&lt;/h1>&lt;p>按照我的估计，很多大学生都没有考虑过申请信用卡，日常消费只需要借记卡绑定微信支付和支付宝就没问题了，有关于信用卡与借记卡的区别，是否应该申请信用卡，google 一下就能找到不少靠谱的回答，我就不多说了。不过大约十几年前大学生申请信用卡真的很难，由于申请信用卡时需要经过银行的资产审核，而大学生又没有工作，所以对于很多大学生来说是几乎不可能申请到信用卡。不过近年来已经有相当多的银行推出了大学生专属信用卡，上述的问题已经不复存在。然而别以为随便申请一张大学生信用卡就可以在 Google Play 之类的国外商店上购物了，想要在 Google Play 上购物，你需要一张&lt;strong>非银联&lt;/strong>的信用卡（借记卡也行，但我只记得中国银行的 EMV 卡属于这一类）。注意，必须是&lt;strong>非银联&lt;/strong>的卡，因为很多国外付款方式都不支持银联。除此以外，借记卡/信用卡还有单币/双币/全币的区别，只不过 VISA 和 MASTERCARD 基本上都是使用美元结算的，所以我们几乎不需要关心这个差别。在满足非银联的这个条件下，我们来选择一间银行的信用卡，参考学姿势的《&lt;a class="link" href="https://www.xuezishi.net/%E5%A4%A7%E5%AD%A6%E7%94%9F%E4%BF%A1%E7%94%A8%E5%8D%A1%E7%94%B3%E8%AF%B7%E5%A7%BF%E5%8A%BF" target="_blank" rel="noopener"
>大学生信用卡申请姿势&lt;/a>》，里面对比了各个银行的大学生信用卡的区别，我们主要关注的是是否有 VISA 或 MasterCard 等非银联的卡，当然我也建议最好看下学姿势的其它文章，以后想要靠信用卡享受各种福利（俗称薅羊毛）的话可先要熟悉各家银行的信用卡的区别哦。&lt;br>
以我申请的民生银行 more 世界卡为例，我先参考了《&lt;a class="link" href="https://www.xuezishi.net/the-guidance-of-cmbc-student-cc-application" target="_blank" rel="noopener"
>民生银行大学生信用卡申请指引&lt;/a>》，然后通过民生银行官网进行申请（&lt;em>请注意是否为大学生版，大学生版是不需要填职业等信息的，如果不确定的话，点击学姿势的&lt;a class="link" href="https://link.xuezishi.net/cmbc-student" target="_blank" rel="noopener"
>民生银行学术卡申请通道&lt;/a>&lt;/em>），在申请页面上如实填写必填的个人信息，在这当中，亲属手机号应该是最麻烦的，因为银行会根据这个手机号给你的家长发送短信，如果你的家长不同意你办信用卡，那么这里提供两个办法：&lt;br>
1.假如你有一个手机号是你的家长的手机号的附属号码，那可以直接填这个手机号，然后在本人手机号填入一个使用自己的身份证注册的手机号。&lt;br>
2.在家长的手机上设置短信骚扰拦截（每个国内 ROM 应该都有这个功能吧），根据短信内容设置关键字，短信的具体内容可看下方，例如可以设置拦截“民生银行”。&lt;/p>
&lt;blockquote>
&lt;p>【民生银行】尊敬的A，您好！您的亲属B已向民生银行提交信用卡申请，我行预审通过，预计为其核发卡片的信用额度为5000.00元，具体申请事宜请您联系申请人核实。如您对本次申请存在异议，请于Y年M月D日24点前短信回复“QXSQ”，我行将终止本次申请。超过上述时间未回复则视为您同意本次申请。感谢您的支持！&lt;/p>
&lt;/blockquote>
&lt;p>搞定这些麻烦的申请步骤后，就是等待银行进行资料审核了。不出意外的话，大约一个星期后你就能收到通过信用卡申请的短信，假如不幸被拒的话，请回忆下自己是否填错了一些个人信息，或者之前是否曾有过欠钱没还的不良行为（例如花呗忘了还），或者学校不在某些银行的白名单当中。&lt;br>
在发送短信告诉申请者通过信用卡申请后，银行会通过邮寄方式将信用卡寄给申请者，稍微等个两天，信用卡就到了！&lt;br>
不过很可惜的是，刚到手的信用卡还不能被使用，需要进行激活后才可正常使用，有一些银行可以网上激活，然而民生银行是要求持卡人必须到柜台进行当面激活的，假如你在比较偏远的城市，这个就不太好办了。另外，我很想吐槽一下民生银行的初始信用卡密码设置手续，这 TM 居然需要我打电话进行设置，就完全没有考虑过安全性的问题吗？信用卡激活后，申请信用卡的整个过程总算是结束了，恭喜各位打开了新世界的大门。&lt;/p>
&lt;h1 id="外币信用卡到手后">外币信用卡到手后
&lt;/h1>&lt;p>既然有了外币信用卡，那么很多国外商家的大门已经对我们敞开了。Google Play，美区 App Store，美区/日区亚马逊等商店都可以让我们疯狂剁手，还可以订阅 Spotify，Netflix 等流媒体服务。读到这里的读者可能都没有使用信用卡购物的经验，不过没关系，使用外币信用卡购物并不麻烦，一般而言只需填入信用卡卡号，CVC 码（信用卡背面的一个三位数），有效日期就可以了，以 Google Play 为例，说一下如何添加外币信用卡作为付款方式。&lt;/p>
&lt;h2 id="在google-play上添加外币信用卡作为付款方式">在Google Play上添加外币信用卡作为付款方式
&lt;/h2>&lt;p>与申请信用卡相比，这节内容可以说是简单多了，直接根据图片操作即可（我已经添加了付款地址，如果之前尚未添加，那么在要求输入付款地址时可以随便输入相关信息，亲测姓名和地址等信息可以与信用卡上的信息不同；另外，确保信用卡里有大于等于 1 美元的余额/额度）：&lt;br>
1.打开 Google Play，在侧边栏找到“付款方式”并点击&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/How_to_apply_for_non_UnionPay_credit_and_shopping_as_college_student/payment_method.jpg"
width="1071"
height="1834"
loading="lazy"
alt="付款方式"
class="gallery-image"
data-flex-grow="58"
data-flex-basis="140px"
>&lt;/p>
&lt;p>2.点击“添加信用卡或借记卡”，按照要求输入信用卡卡号，CVC 码，有效日期（别填错了！！！）等&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/How_to_apply_for_non_UnionPay_credit_and_shopping_as_college_student/add_card.jpg"
width="1059"
height="1835"
loading="lazy"
alt="添加信用卡或借记卡"
class="gallery-image"
data-flex-grow="57"
data-flex-basis="138px"
>&lt;/p>
&lt;p>3.假如一切顺利的话，现在即可使用信用卡在 Google Play 上购买东西了，出现问题的话可参考《&lt;a class="link" href="https://sspai.com/post/45933" target="_blank" rel="noopener"
>学生党 Google Play 剁手经验分享&lt;/a>》中的“添加卡片并购物”一节进行解决，懒得与客服沟通的话可以先试着申请美区 PayPal 帐号，然后选择美区 PayPal 作为付款方式。&lt;/p>
&lt;h2 id="一些小贴士">一些小贴士
&lt;/h2>&lt;p>1.可以考虑使用外币信用卡申请美区 PayPal 帐号，使用 Paypal 付款更安全。 &lt;br>
2.请善用 Google Play 中的心愿单功能，不急着买但想买的东西可放入心愿单中，等待打折，在黑色星期五的时候，很多 App 都会有幅度非常大的折扣。 &lt;br>
3.外币信用卡建议设置自动购汇还款，一般在银行 App 中可设置此项，能免去每月手动购汇还款的麻烦，而且不用担心自己忘了还款。&lt;br>
4.&lt;strong>记得还款&lt;/strong>，各家银行的最后还款期限不尽相同，在每个月的最后还款期限前请确认一下自己是否已还款。&lt;br>
5.再次强调，&lt;strong>请注意用卡安全&lt;/strong>，信用卡背面的信息不要随便泄漏给别人。&lt;/p></description></item><item><title>使用 GitHub Pages 和 Hexo 搭建个人博客(进阶篇)</title><link>https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog_advanced/</link><pubDate>Mon, 15 Jul 2019 00:00:00 +0800</pubDate><guid>https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog_advanced/</guid><description>&lt;p>&lt;em>2019.8.18.更新：增加了如何搭建自己的统计分析服务的说明。&lt;/em>&lt;br>
&lt;em>2019.9.13.更新：增加了更多的 SEO 内容，对一些内容进行修改。&lt;/em>&lt;br>
&lt;em>2019.11.9.更新：增加了 Material 主题添加随机标语（slogan）的方法。&lt;/em>&lt;br>
在&lt;a class="link" href="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog" target="_blank" rel="noopener"
>上一篇博文&lt;/a>当中我已经说完了使用 GitHub Pages 和 Hexo 搭建博客的基础操作了，只是这个刚搭建好的博客还缺了不少经常能在别人的博客上看到的东西，例如 RSS 订阅，评论区等功能。同时，不知道各位有没有发现一个问题，在搜索引擎当中搜索自己的站点时，搜索引擎返回的结果中并不会出现你的站点。本篇教程将会说明如何解决这些问题，如果还有更多的问题，请在评论区留言。&lt;/p>
&lt;h1 id="额外的功能与服务">额外的功能与服务
&lt;/h1>&lt;p>&lt;em>下面提到的这些功能与自己使用的 Hexo 主题相关，存在某些主题并没有提供某项功能的可能性&lt;/em>&lt;/p>
&lt;h2 id="rss-订阅">RSS 订阅
&lt;/h2>&lt;p>想要提供 RSS 订阅这个功能，各位需要查看自己使用的 Hexo 主题的文档进行操作，以我使用的 Material 主题为例，&lt;a class="link" href="https://github.com/viosey/material-theme-docs/blob/master/services-zh-cn.md" target="_blank" rel="noopener"
>官方文档&lt;/a>简单地说明了一下，其实就是在安装了&lt;a class="link" href="https://github.com/hexojs/hexo-generator-feed" target="_blank" rel="noopener"
> hexo-generator-feed &lt;/a>插件后，修改 Material 主题下的配置文件（_config.yml）中的 rss:的值：&lt;/p>
&lt;pre>&lt;code>url:
rss: atom.xml
&lt;/code>&lt;/pre>
&lt;p>按照这样配置的话，你的博客的 RSS 订阅地址就是 &lt;a class="link" href="https://%e4%bd%a0%e7%9a%84%e5%8d%9a%e5%ae%a2%e5%9f%9f%e5%90%8d/atom.xml" target="_blank" rel="noopener"
>https://你的博客域名/atom.xml&lt;/a>，例如我的博客的 RSS 订阅地址就是 &lt;a class="link" href="https://vifly.github.io/atom.xml" target="_blank" rel="noopener"
>https://vifly.github.io/atom.xml&lt;/a>。&lt;/p>
&lt;h2 id="评论区">评论区
&lt;/h2>&lt;p>依然使用 Material 主题举例子，&lt;a class="link" href="https://github.com/viosey/material-theme-docs/blob/master/services-zh-cn.md" target="_blank" rel="noopener"
>官方文档&lt;/a>也说明了如何设置评论系统，为了能让读者畅所欲言，我否决了全部的国内评论系统，同时为了不翻墙的读者的体验以及匿名评论的需求，我最后选择了 Material 主题提供的 &lt;a class="link" href="https://disqus.com/" target="_blank" rel="noopener"
>Disqus&lt;/a> 加自建 &lt;a class="link" href="https://posativ.org/isso/docs/" target="_blank" rel="noopener"
>Isso&lt;/a> 评论系统（这里要感谢 &lt;a class="link" href="https://alynx.one" target="_blank" rel="noopener"
>Alynx Zhou&lt;/a> 提供的多评论系统前端代码，话说周老师不去当前端工程师是不是太浪费了鸭≧▽≦）。&lt;/p>
&lt;h2 id="数据统计与分析">数据统计与分析
&lt;/h2>&lt;p>继续贴&lt;a class="link" href="https://github.com/viosey/material-theme-docs/blob/master/services-zh-cn.md" target="_blank" rel="noopener"
>官方文档&lt;/a>。基于对读者隐私的考虑，我直接排除了百度和 CNZZ 的分析系统，至于 Google 分析，考虑到谷歌近年来的名声与未翻墙用户的体验，最终也决定不予采用，那么就只剩下一条路了，自己搭建分析系统。&lt;del>这个暂时也咕咕咕了，若是能成功搭建的话，再写一篇博文进行叙述。&lt;/del> 目前已经成功使用 Matemo 搭建了自己的网站统计分析服务，具体操作请看&lt;a class="link" href="https://viflythink.com/Install_matomo_on_debian/" target="_blank" rel="noopener"
> Debian 安装 Matomo (Piwik) 开源统计分析服务&lt;/a>。&lt;/p>
&lt;h1 id="搜索引擎优化seo">搜索引擎优化（SEO）
&lt;/h1>&lt;p>假如你在搜索引擎中输入 site:xxxxx.github.io（自己的博客网址） 后发现没有出现自己的网站，那么请赶快看一下下面的“提交网站站点地图”小节。&lt;/p>
&lt;h2 id="验证网站所有权">验证网站所有权
&lt;/h2>&lt;p>为了便于以后在各个搜索引擎当中管理和查看我们的博客，我强烈建议在各个搜索引擎验证网站所有权，完成这一步后即可使用站长工具查看数据了，而且这也是完成后面某些步骤的必要前提。假如你将自己的域名（指的是 *.github.io 以外的域名）用作博客的域名，那么便可以通过添加 DNS 记录的方式验证所有权（&lt;em>PS：每家搜索引擎对这个验证方法有不同的称呼，谷歌称为“域名提供商”，而 Bing 称为“手动向 DNS 添加 CNAME 记录”&lt;/em>），不采用下面提到的方法了。&lt;/p>
&lt;h3 id="google">Google
&lt;/h3>&lt;p>前往 Google 搜索的&lt;a class="link" href="https://search.google.com/search-console/ownership" target="_blank" rel="noopener"
>控制台&lt;/a>添加自己的博客地址即可，Google 提供了几种验证方式:&lt;br>
&lt;img src="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog_advanced/google_add_site.png"
width="627"
height="735"
loading="lazy"
alt="Google 的网站所有权验证"
class="gallery-image"
data-flex-grow="85"
data-flex-basis="204px"
> &lt;br>
对于 Material 主题，请使用 HTML 标记或 Google 分析（假如你的博客使用了 Google 分析）进行验证，具体操作只需看&lt;a class="link" href="https://github.com/viosey/material-theme-docs/blob/master/intro-zh-cn.md" target="_blank" rel="noopener"
>官方文档&lt;/a>。&lt;/p>
&lt;h3 id="bing必应">Bing（必应）
&lt;/h3>&lt;p>前往 Bing 的&lt;a class="link" href="https://www.bing.com/toolbox/webmaster/" target="_blank" rel="noopener"
>网站管理员工具&lt;/a>进行验证。很不幸的是，Material 主题官方并没有提供 Bing 的站点所有权验证，那只能自己动手修改源代码了，经过一番查找，终于找到了用于在 HTML 页面当中标记所有权的部分，其代码位于 themes/material/layout/_partial/head.ejs 中，CTRL+F 查找“site_verification”发现在约 142 行的位置就是我们要找的代码，在下方照葫芦画瓢地加上：&lt;/p>
&lt;pre>&lt;code>&amp;lt;% if(theme.head.site_verification.bing) { %&amp;gt;&amp;lt;meta name=&amp;quot;msvalidate.01&amp;quot; content=&amp;quot;&amp;lt;%= theme.head.site_verification.bing %&amp;gt;&amp;quot; /&amp;gt;&amp;lt;% } %&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>注意，我感觉这里的 meta name 不一定对于每个人都适用，请观察一下 Bing 提供的 HTML 代码后再修改 head.ejs。&lt;/strong>&lt;br>
&lt;img src="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog_advanced/bing_add_site.png"
width="1023"
height="785"
loading="lazy"
alt="Bing 的网站所有权验证"
class="gallery-image"
data-flex-grow="130"
data-flex-basis="312px"
>
在复制了“&amp;lt;meta name=&amp;ldquo;msvalidate.01&amp;rdquo; content=&amp;ldquo;xxxxxxxxxx&amp;rdquo; /&amp;gt;”中的 xxxxxxxxxx 对应的值后，在主题的_config.yml 中进行修改：&lt;/p>
&lt;pre>&lt;code>site_verification:
bing: xxxxxxxxxx
google:
baidu:
&lt;/code>&lt;/pre>
&lt;h2 id="提交网站站点地图">提交网站站点地图
&lt;/h2>&lt;p>这一步就是告诉搜索引擎你的网站有哪些链接，提交后搜索引擎就会自动顺着站点地图中的链接爬取你的站点内容了，若是没有这一步，在搜索引擎当中直接搜索自己的博客地址是没有任何结果的。要完成这一步骤，需要在博客目录下输入以下指令安装&lt;a class="link" href="https://github.com/hexojs/hexo-generator-sitemap" target="_blank" rel="noopener"
> hexo-generator-sitemap &lt;/a>插件：&lt;/p>
&lt;pre>&lt;code>npm install hexo-generator-sitemap --save
&lt;/code>&lt;/pre>
&lt;p>这个插件默认站点地图生成路径为 &lt;a class="link" href="https://%e4%bd%a0%e7%9a%84%e5%8d%9a%e5%ae%a2%e5%9f%9f%e5%90%8d/sitemap.xml" target="_blank" rel="noopener"
>https://你的博客域名/sitemap.xml&lt;/a>，在使用 hexo g 重新生成网址内容后，将此网址提交到各个搜索引擎就可以了，以 Google 为例子，只需要发送 &lt;a class="link" href="http://www.google.com/ping?sitemap=https://" target="_blank" rel="noopener"
>http://www.google.com/ping?sitemap=https://&lt;/a>你的博客域名/sitemap.xml 这个网络请求即可，也可以在&lt;a class="link" href="https://search.google.com/search-console/" target="_blank" rel="noopener"
>Google 搜索控制台&lt;/a>中的 sitemaps（站点地图）一栏提交自己的站点地图。而 Bing 则是需要在其 Web 面板中添加 sitemap。&lt;/p>
&lt;h2 id="提交网页地址收录新页面">提交网页地址（收录新页面）
&lt;/h2>&lt;p>虽然搜索引擎会自动顺着站点地图爬取网页内容，但是我就是想让它赶快收录刚发布的文章（因为不知道这个这个自动流程需要多少时间），这种时候该怎么办呢？我从&lt;a class="link" href="http://www.guxiaobei.com/submit-your-content-of-google.html" target="_blank" rel="noopener"
>“SEO 技巧！如何最快时间让 Google 收录你的页面”&lt;/a>中了解到了如何让 Google 及时收录新发布的博文，然而 Google 的控制台页面已经进行了改版，经过一番搜索，在&lt;a class="link" href="https://support.google.com/webmasters/answer/9012289" target="_blank" rel="noopener"
> Google 官方的帮助文档&lt;/a>找到了新的提交新页面的方法，打开&lt;a class="link" href="https://www.google.com/webmasters/tools" target="_blank" rel="noopener"
>https://www.google.com/webmasters/tools&lt;/a>，转到网址检查，输入你的新页面的网址，这就可以及时提交新的网页了。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog_advanced/submit_new_address.png"
width="1414"
height="756"
loading="lazy"
alt="网址检查"
class="gallery-image"
data-flex-grow="187"
data-flex-basis="448px"
>&lt;/p>
&lt;h2 id="seo-进阶">SEO 进阶
&lt;/h2>&lt;p>为了让搜索引擎更好地爬取我们的网页，还需要使用一些技巧。这一部分主要参考了 &lt;a class="link" href="https://hoxis.github.io/Hexo&amp;#43;Next%20SEO%E4%BC%98%E5%8C%96.html" target="_blank" rel="noopener"
>Hexo 博客 Next 主题 SEO 优化方法&lt;/a>这一博文，虽然 Material 主题与 Next 主题不同，不过有不少地方还是共通的。&lt;/p>
&lt;h3 id="修改文章链接地址">修改文章链接地址
&lt;/h3>&lt;p>Hexo 默认的文章链接形式为 domain/year/month/day/postname，这种四级 url 形式对于搜索引擎而言并不友好，我们可以修改为 domain/postname 这种形式。打开 blog 根目录下的_config.yml（下面统称为&lt;strong>站点配置文件&lt;/strong>），找到 permalink 部分，将 permalink: :year/:month/:day/:title/改为 permalink: :title.html。&lt;/p>
&lt;pre>&lt;code>#permalink: :year/:month/:day/:title/
permalink: :title.html
&lt;/code>&lt;/pre>
&lt;h3 id="出站链接添加-nofollow-标签">出站链接添加 nofollow 标签
&lt;/h3>&lt;p>博文中总是会引用其它的文章，为了不让搜索引擎跳转到其它的网站，专注于我们的网页，我们需要为这些站外链接设置 nofollow 标签，可以通过安装&lt;a class="link" href="https://github.com/liuzc/hexo-autonofollow" target="_blank" rel="noopener"
> hexo-autonofollow &lt;/a>自动完成这一个步骤。&lt;/p>
&lt;pre>&lt;code>npm install hexo-autonofollow --save
&lt;/code>&lt;/pre>
&lt;p>然后在&lt;strong>站点配置文件&lt;/strong>中添加（更多配置请看 GitHub 页面）：&lt;/p>
&lt;pre>&lt;code>nofollow:
enable: true
&lt;/code>&lt;/pre>
&lt;h3 id="添加文章描述">添加文章描述
&lt;/h3>&lt;p>由于在 Material 主题中，文章的 tags 等于 keywords，所以无需再添加关键字了。不过我们可以为每篇博文添加描述，添加方法与添加 tags 等相似，在博客文章 markdown 文件开头添加 description：&lt;/p>
&lt;pre>&lt;code>---
title: 标题
date: year-month-day xx:xx:xx
tags: [tag01,tag02]
description: 你的博文描述
---
&lt;/code>&lt;/pre>
&lt;h1 id="个性化">个性化
&lt;/h1>&lt;h2 id="启用自己的域名">启用自己的域名
&lt;/h2>&lt;p>千篇一律的 xxxxxxx.github.io 总是让人觉得厌烦，说到个性化，怎么能缺少使用自己喜欢的域名作为博客网址这一项呢？使用自定义的域名更有可能让人记住你的博客，而且只要不是前往 GoDaddy 这种价格偏高的网站购买域名，一个普通的 com 顶级域名每年也只需花费 10 美刀左右就可以拥有。在这么多的域名服务商当中，我选择了国外的&lt;a class="link" href="namecheap.com" > namecheap&lt;/a>，主要的好处就是以下几点：&lt;/p>
&lt;ol>
&lt;li>价格便宜（从网站名字就可以看出来了）&lt;/li>
&lt;li>老牌商家，服务可靠。别看这个网站的页面长得不好看，而且价格也比较便宜，但这个网站真的是老牌服务商，我并没有找到它坑客户的案例，基本可以放心。&lt;/li>
&lt;li>提供免费的 WhoisGuard 服务，保护你的隐私。&lt;/li>
&lt;li>不用备案，选择国外的域名服务商的话都能享受到这点。&lt;/li>
&lt;/ol>
&lt;h3 id="自定义的域名与-github-page-的绑定">自定义的域名与 GitHub Page 的绑定
&lt;/h3>&lt;p>买好了域名，接下来要做的事就是让我们的域名能够指向自己的博客啦。这个操作流程十分简单，只需前往自己的域名服务商的控制面板，然后参考这条&lt;a class="link" href="https://www.zhihu.com/question/31377141/answer/87541858" target="_blank" rel="noopener"
>知乎回答&lt;/a>进行操作，只需在域名解析当中添加两条 CNAME 记录让域名（以我的域名为例就是 viflythink.com 和 &lt;a class="link" href="https://www.viflythink.com" target="_blank" rel="noopener"
>www.viflythink.com&lt;/a> ）指向 xxxxxx.github.io，大概就是下面这样： &lt;br>
&lt;img src="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog_advanced/set_dns.png"
width="621"
height="173"
loading="lazy"
alt="设置DNS解析"
class="gallery-image"
data-flex-grow="358"
data-flex-basis="861px"
>&lt;br>
然后在 source 目录下新建一个叫 CNAME 的文本文件，里面填入你的域名（如 viflythink.com），与不少网上的操作不同，我们这里并不需要绑定 GitHub 的 IP，因为有时候 IP 地址会变化，所以绑定 xxxxxx.github.io 会更好。&lt;/p>
&lt;h3 id="启用-https-加密">启用 HTTPS 加密
&lt;/h3>&lt;p>为了保证读者的隐私，防止 ISP 的广告植入，稍微提高自己的网站在搜索引擎中的权重，我们需要为自己的域名启用 HTTPS 加密，当你成功启用 HTTPS 加密后读者访问你的网站时就会看到浏览器上出现一个绿色小锁头了，整个步骤也不难，这里参考了&lt;a class="link" href="https://tzhou2018.github.io/2018/04/%E4%B8%BAGitHub-Pages%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9F%9F%E5%90%8D%E5%B9%B6%E6%B7%BB%E5%8A%A0SSL-%E5%BC%80%E5%90%AFHTTPS%E5%BC%BA%E5%88%B6/" target="_blank" rel="noopener"
>为 GitHub Pages 自定义域名并添加SSL-开启https强制&lt;/a>这一篇博文，使用 cloudflare 提供的免费服务，这里必须提醒一句，使用 cloudflare 的 &lt;em>Universal SSL&lt;/em> 并不意味着全程加密，这个服务其实是通过 cloudflare 自己的服务器进行中转，读者到 cloudflare 的服务器这一段路是加密的，而 cloudflare 到 GitHub 的这一段并没有加密，另外，我在部署后出现“重定向次数过多”的错误，将 cloudflare 的加密由 Flexible 转为默认的 FULL 后就可以了，暂不知道原因。&lt;/p>
&lt;h2 id="material-主题的进阶设置">Material 主题的进阶设置
&lt;/h2>&lt;h3 id="material-主题实现随机显示标语slogan">Material 主题实现随机显示标语（slogan）
&lt;/h3>&lt;p>是否觉得仅仅一句标语（格言）不足以说明什么呢？我想要在博客首页的标语更能表达我自己，使用随机显示的标语是一个很好的主意，这样就可以使用多个名言警句了，但是 Google 了一番后并没有找到为 Material 主题设置随机标语的方案，那么只能自己动手了（&lt;del>在改源码的道路上越走越远&lt;/del>）。这里主要参考了萌狼的&lt;a class="link" href="https://blog.yoitsu.moe/pelican/new_yoitsu_birth_notes.html" target="_blank" rel="noopener"
>新约伊兹的萌狼乡手札诞生全过程伪实录&lt;/a>里的“实现动态格言”部分，由于我不懂前端，所以最后只能做到在每次刷新网页后显示随机的标语，而不能动态刷新。这里记录下我的折腾过程，欢迎前端大佬帮忙改进。&lt;br>
首先找到 Material 主题负责显示标语的代码，其路径是 themes/material/layout/_partial/daily_pic.ejs，可以看到相关代码：&lt;/p>
&lt;pre>&lt;code>&amp;lt;div class=&amp;quot;mdl-card__media mdl-color-text--grey-50&amp;quot; style=&amp;quot;background-image:url(&amp;lt;%= url_for(theme.img.daily_pic) %&amp;gt;)&amp;quot;&amp;gt;
&amp;lt;p class=&amp;quot;index-top-block-slogan&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;&amp;lt;%= theme.url.daily_pic %&amp;gt;&amp;quot;&amp;gt;
&amp;lt;% if(theme.uiux.slogan) { %&amp;gt;
&amp;lt;% if(Array.isArray(theme.uiux.slogan)) { %&amp;gt;
&amp;lt;%- theme.uiux.slogan.join('&amp;lt;br&amp;gt;') %&amp;gt;
&amp;lt;% } else { %&amp;gt;
&amp;lt;%- theme.uiux.slogan %&amp;gt;
&amp;lt;% } %&amp;gt;
&amp;lt;% } %&amp;gt;
&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>这部分最后会被转换为 HTML 文本，然而我们需要每次加载时都能显示不同的标语，该怎么办呢？答案是使用 document.addEventListener() 设置一个监听器，加载多个标语后随机选择一个显示，这就是我写的代码的思路，当然，由于我是一个前端小白，所以借鉴了萌狼的代码，期间遇到了“$ is not defined”的错误，按照查到的&lt;a class="link" href="https://stackoverflow.com/questions/10779148/javascript-jquery-is-not-defined-function-error" target="_blank" rel="noopener"
> stackoverflow 上的回答&lt;/a>成功解决了（说的简单，其实为了读懂相关的代码花了不少时间），最后写出的能用的代码如下（使用下面的代码替换上面贴出的代码）：&lt;/p>
&lt;pre>&lt;code>&amp;lt;div class=&amp;quot;mdl-card__media mdl-color-text--grey-50&amp;quot; style=&amp;quot;background-image:url(&amp;lt;%= url_for(theme.img.daily_pic) %&amp;gt;)&amp;quot;&amp;gt;
&amp;lt;p id=&amp;quot;myslogan&amp;quot; class=&amp;quot;index-top-block-slogan&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;&amp;lt;%= theme.url.daily_pic %&amp;gt;&amp;quot;&amp;gt;
无法加载 gists 上的标语
&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;!-- 动态显示标语 --&amp;gt;
&amp;lt;script src=&amp;quot;js/jquery.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
document.addEventListener(&amp;quot;DOMContentLoaded&amp;quot;, function() {
jQuery.getJSON( &amp;quot;https://api.github.com/gists/7a04e2188185ddb19cbd19d8217b9400&amp;quot;, function(data) {
slogans = JSON.parse(data.files[&amp;quot;slogans.json&amp;quot;].content);
randomSlogan = slogans[ Math.floor( Math.random() * slogans.length ) ];
jQuery(&amp;quot;#myslogan&amp;quot;).html(randomSlogan.content);
});
});
&amp;lt;/script&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>可以看出，使用了 jQuery 来解析 json 文件和提取标语内容，也用来更改页面中的标语，用 myslogan 这个 ID 标记标语所在区域。另外，想要使用这个代码的话，需要在&lt;a class="link" href="https://gist.github.com/" target="_blank" rel="noopener"
> gist &lt;/a>上创建一个 json 文件，其格式类似于这样（与萌狼不同的是，我的代码并没有显示名言作者，所以这里的 json 文件并不需要填写 author）：&lt;/p>
&lt;pre>&lt;code>[
{
&amp;quot;content&amp;quot;:&amp;quot;example1&amp;quot;
},
{
&amp;quot;content&amp;quot;:&amp;quot;example2&amp;quot;
},
]
&lt;/code>&lt;/pre>
&lt;p>然后将得到的类似于&lt;a class="link" href="https://gist.github.com/7a04e2188185ddb19cbd19d8217b9400" target="_blank" rel="noopener"
>https://gist.github.com/7a04e2188185ddb19cbd19d8217b9400&lt;/a>这样的网址里的&amp;quot;gist.github.com&amp;quot;替换为&amp;quot;api.github.com/gists&amp;quot;，再用这个替换后的网址替换上面代码中的 gist 网址。完成后就能实现打开首页时随机显示标语了。&lt;/p></description></item><item><title>GPG key</title><link>https://viflythink.com/gpg/</link><pubDate>Tue, 28 May 2019 00:00:00 +0800</pubDate><guid>https://viflythink.com/gpg/</guid><description>&lt;p>&lt;em>2022.2.25.更新：更新了我的公钥，旧公钥已不再使用。&lt;/em>&lt;/p>
&lt;p>如果你想与我进行足够安全的通信，那么可以使用 GPG 加密信息，以下是我的 GPG 公钥：&lt;/p>
&lt;pre tabindex="0">&lt;code>-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGIYzI4BEACor5KDr4jKWiv6hO5B/3/1tDh1ussz0zHPcJiPyl8oYPX899xK
gLQo2FpvZMGZ9oqT4JAdPAb3qRt5PC52PbuCQsE1rOLHafG7y+g35H0aGtISvQEK
iM227DJm9VNo1T/ea33qzSGk0v1iQ9SdONO0A4Qki+82RV6cT3zg+RHS/ZVTd6x2
ibS2N37TDCu213XqJhKZR7GVGb2utg51dR5HfqODSrcHU29qEQ6zeFV6hjg1t9Y+
WCxLSvz0+iCwGgI1o9o045Wjv9NUlP/MAOHgyaqLpT4/FAco/cIkRUWBDMANtiUo
M98KO2/i6rbZnSvHluNmkbQqOrqwmG7ckevIasWLu9XgkCExizHlgZ8V49D1r2MC
xaSDzZ6KC3YiXBccZDsVLk+oJL8TvExT9jC+pmtPBPxrlBTSedz07VkoHerPqVG7
v9fn7DrAOfNmSBJ6yrkBWMGMa0yyCwIm4u1hfCercM5hukbxNHYDmCYReYDSqSTa
XajDFq8rFZp7m1PV0dkhaYpZcx5HfNOcbD741uS6+Y365fpJlxhM68Rn1tNIj6qU
8H8DHTp1K9wJ5AVk9OLc7Lnbd+RxrIOWLX1g6xQBC6g40yK4uS5d54g9c5wEjGaW
V5y+LzWLS7NOApeR8hg6OyElOaUxt3TO8A5HEZNiHk2Dig/YZLZGSlH7XwARAQAB
tBxWaWZseSA8dmlmbHl0aGlua0BnbWFpbC5jb20+iQJMBBMBCgA2FiEEdilC2Iud
z0ZbBlSYxMPRRfx0MQQFAmIYzI4CGw8ECwkIBwQVCgkIBRYCAwEAAh4BAheAAAoJ
EMTD0UX8dDEE1SUP/1CnwA+Pbb5pHuIQvqZ/LXwRbR+3fG0+4DlQW0/Ai16RpxZC
cny4MchnozAV1cnJEOrdVLx1i4GD92dSm9urIpXMo6ijimJF0LxDkNONQptm5lyB
QBGrzQaTLGd43ke1s4tasrSuSPtLokxD98KniYEANQQKBnIk8483gP0wuoC6INxx
LyiXwHa6t6kRRzNaxEEM5KZoYs3MlW8oTHa5q6sO7wtz7Q6LGAzNZC6cjQJzy1sQ
kl/zlwD2mLRjrJZJa9NUn7DhstAEclemIh2Q+IWcN6T/2RBOPxfKIQVGHytYliEl
0OBTHvfTJ5ADOqkjhkQ14BJdT70o7tIJcLcwuqGXSdS6oxpyoFBTwIhzyWbetfBz
24Bes0KciSKiKTqvjbiOk4+Y5HhK2de5aC7/WiYZRs7xM7MGGVpqopK/RnKEKUAA
WHIouPRpKvWFX0/LYRAgDv0NV39UHNLSMXX42r3bno1/chX+uBW1RVg/kXn6dlA5
qCpiwleWxPJzlfkhb3tYM2HwZRdmyG+n4hsFgNm+0muZXVmLeT5JGYt4OMAiUdle
QKPeqkeZnyI1nPVRvJ22z4sDiNLwe5ZimUvUO72gSQl+Zsjig2JCHEUBiqQbqmzi
tciFnnXwsof5/RoBfCx8VLQOScd2jCtkZZjcLw+h+9OmvHua1YhYIp3Zwx7auQIN
BGIYzM0BEACoy/mm6NW6Gprqazrtr1DsMyVR3An5yAUjuTqiBRkCrFejl1lj+mwI
nsYnOFu7B4aSO3tsiY0/4GB63I1IKhxgDPLFB+Nzcd+jbv9nGEuwCy9oK8jZsPRr
DNEbE7PRGvl4GmulW0QLevEn+QIx/EDkF3d6nAPjHi0LqT8sinfFTmpR4BDb9BFc
xfvbmyN87jQTIAfDUV+4XJerAad9fdxbjBt3lntGQVD6ivj9riimKW9+tf1vIJLK
y8Pcb8CBSoIBgicVpaSWeGyLvdQOyeCyr9VjWyV/9WKchcfuGDaqZRsOu30jUphs
rNC4SArAYGZbfl8DNj3HJWvEC0XIyEpXKDsLY6yhZ8pInh48B5dRFW1qOSODu5Pt
DxNQWObkCq8NCFzluMCuflBG0P8ILgMQ7NTL6l57Q0fY5SyXY7m2fMbiaJwghj3B
aET1B1puVQB0ISEvXlBRrFF/aSrbelxwZDNX+DcmRlf+dfJNtnXZkyp7lCzLJ3Vp
fZg1nzsVc5ECkZRSZ1FQEiJivmpesJsdD0dBEAmLyFyyUI4Un0zzBV6Pk/IVulI8
fUbBter/aK3aUKeYVfMWI8O8niR//tiSvGe724Urf55ObLb9E9HZvFL7w4hJeHKu
rBRGBvAcVZLzaV2b00MBlcOgerkZ/h3HQDzSiegPhYPoGdgoZYyg7wARAQABiQRs
BBgBCgAgFiEEdilC2Iudz0ZbBlSYxMPRRfx0MQQFAmIYzM0CGwICQAkQxMPRRfx0
MQTBdCAEGQEKAB0WIQT1cX2K+YDvd4ZVjXRMVcvFI7usSwUCYhjMzQAKCRBMVcvF
I7usS4QOD/9QFMrnEj7CqPRameY9MpuG36DSznKzqFSSJeN04D9REPQEaezWYRnO
mO3Brvq9APmgohNNhiS096VhpimI5204LIzTqYkXT30dja7ixNX8XF+pEmek9gPQ
Rdg7AYbY/nERvjnhyJWH/t3G0IaM5qG1SZKXTqF4+xb+yRs1XEFMjefs/d4znOTy
x5zyeGJGDsRilm5h1r1JP5TmMMG2Y6ITLxhPdhurAq7Uu2hJcardlUJzs7hoaGo7
NW3Ul95xBsZPTi/oycPCtedaq0cHlxJgolWM+RHUW2bVhCYwWxZPyjW3APBFmjLx
qyxhKpD7Z5//8enB88VmRwJi1/v/cJybqATDk8vrKpVC+msvR25eLYO3A1qN53q2
TUbi0rbVRzMw20smu/FYoSXZWmcfLDMl95wCUz11lqhv06U8m/I9jhE3CLT9j8n3
0iiF+GI77338104HuaIJXD0I1H0X1yDLty5FCWJBq/XlcXi4vYmeViPsKGkl/ch8
E13TDdW4chHtugzIZqoWNMCAw4AEmdc5OpKsksJf3ioibA+RZggr4VZRK/VtCwcC
qU0PoC69pHtF+w2SbN64ZHxrLGwMN9UPsb8+EDwYm5xagU5KVbdCPVOkk9ksrsh9
LUVZHPEp4TRrmxlZctooHeG8fTg9lrlymkUhoaNEMqLB12BGtRdb3HabD/0Y3Ax5
5yX235+7FXF5LtCz6WWp4ZA9FMHXW1iryVffUkvn6wmBiSS/1kkTMUkBmTjDBfIm
+BYhfhnl0scI8f/375oEfW21Q0ho7HVFb8kMmpPVWEhbuJRxLnYoZ0KepoArWZvu
aTThy49K17KYHcwdWM8G+Slafh0Vzacg6BNTU29S/W/wLNcfWklIHaFKnL32wMP3
M+OE3UjWiw1eRMX1E9ZS2JkNXER5sI1dqUfegPMeER6hOUUq2AcmszXNZi5Bal9Y
e/uL4KMFP3rdwZubU6lNm5rmnFWjfGv0WdMMqD8TWDI8qDAlWpSRJjfWEnYqYVo5
VkN2EXrmlUc3ZCapMTxSzlVs0hFyMSUyXaGGPHnjtPjOQ1yhJb/uqzJBxbZcpGDC
GjtIUllykAH386scQ2AIVrHp2+b1kT5k3r4xF30y//jqi6auQUjm3a4DK9F9dHBn
J5I50Hdu1LDUU7WY+7x9njd0Eio2IYRVf+uVV8I0BreS2ycffBtwp941Rl7ceRy+
Drqm7y/Nk5CNT71QtsC+SY24aPDlUHdDzQQVHjRSzOpEUw7yxZDjOjnVu1Ij73Hh
VwU+PK60jxlfv3H6ih+uOC/u5KZ8vZQiKG1vGXJo45vrAvXnhLgM0vPjncPIzSRA
3oXVM7wsvgenOJbNufWIZSw9dE/T1fPcWNZOFbkCDQRiGMztARAAxL4G/RGMZISI
T+WKVHj4hBwS4G4atHTfK2roBZG2NwfLcM3d0n5AwM/REEYgJO6wODqkY9v0aaI1
VPhD4HwB+s4ykAbZvEsAq4kEQcbKksWcV2G7mHg3+IXMaGUfq4yI4C4TPupZdxhJ
BrnpYo5XoxlbzhcAiLWRBvVP5UrOjZGhnMO7g2ZwzWcdBd+iLIkh/AJ1L9h4ZftY
nrEdkm1dN4HQC9p3NFFXfnXqQR1gpsov8l5nVyIvUa3Hj1ZPpMRPvVDEfo5ke2zv
WvXYnvgr/iMsTBP1kACMbuEbzePt2SNwQ6TGhLp1X+XFxmZ+DrgwziZFIo5952Zd
8Jb63zYRti6BEdO3shPUtAgTaoU0PahJwnJTJpbDc1p/FbPA3pvg9kD/5wrDwH42
q+Rpu3Vjy63shlksI2IhFABM7n/tHtluh7/x7yLFl8xcOQiiXQc29Ggph7I2gOGM
CTaBbhozU/KwWFdUWkFA6Er0p1NghDxoWPL6tjYNhMXQQfneIh3A5Tf3IOTDvAI1
RLPIhV4XLKNLuaAylCBccuolX9xmtbq0Ocos++AxJIrVElBSE7rd4P/P3jWVNwhm
bui08+BxAbyY1WxsJLTRk9DFueSThnD6K16Ruw3dMay4vL2cmyIyODqsTmX+OkF6
0EQSTO/l4VcusP2NpvFKs5jLFEafgMcAEQEAAYkCNgQYAQoAIBYhBHYpQtiLnc9G
WwZUmMTD0UX8dDEEBQJiGMztAhsMAAoJEMTD0UX8dDEEvrAP/3PWE2UF3lVlkJbw
1u/rx1Ld/MlYZozRkKv0gCG0mMGzfj27czVZ16DvTYssmi6CdTdxFLPSzlwcXgDW
UO6lVRXRi48pO68PhVg4lb/Wda22nUn3k73BshFhuSR/2aNTVdAMIUXWHE9VUl9k
GvtyCqDc/ss3H+RyQHpDsxo5Igwjxuo4in6qK2VuhpfC1tSSpsDEigCz3EeA1ABw
qFYDXVU3JNgswmJ44u0VHmJI0i15r1TyE9Z7+uisuD+gKtxsq7bHjJRWRp9d7qHf
B0Pzmaofg2bGE0OfFzgvUp96KdahMLLmV8GdJHvmZx/1HaiO4w3J91FYIQh/xILB
zZgMqyVcSZZ4KOB+mAFWA9MxKmJGHwV/nzgY0/xmIOzyEycH6jSQm4TBXlgZXjTT
cIMh1j3b4wxcxzkUY4BuPiwfBMsJlGuqiHzuQA7rig02rBQzGSJCKC8xUqI7OUMA
KUTFRZlIVttUPWSp4lYALNpAdU5UHziU8ecfLMErheasGY1PRItGDAZxN6nRjMk/
6vjOCyJBU7s5+EjrtFdt3Xyx2XjxjkOj2TVi+BtKIX19hsI0o+nTx+/yp8Xa1NXD
ckeWWzU4laYOOMnu32rantOSBhVwENgdnlpOTP/wljKqHq3Gh2/uK+VyO6WOjcHP
b86acHDXYLGfWIz7L0qJih48FmOVuQINBGIYzQgBEAC4mpmNj3brWu8TMmveW+5F
PXNtX0v+8cV95zgKgpHiqfQ6NuOjA+pI1QmCDjM/bcRX6ldFoZ/zDdu6Hrc+97KD
mNML/ew3X6chj3ruYU/KIRL0iD/4tdnIqwAbIhkJQSX02wswLq9juIld++9nOXBv
EE4WceMrdAyNQxpqn1Nx7IE+QAva4tc3k80NmRZ8iaVWfx0VZ2tQ4y8BoAqrYoK4
hw7Cdqy85OgHYVaf4pFg0zPI1IOWtnxZ6RslCCNuXzbzuPuJ3sxxOvvDNLmsYTyT
JgawxO/MEYm2I7tXkdLt1aNih/1dOGFOnscHPnBZ0/oCDpGGur0C8MS2EHjmPywd
yCns4u+Dp/dTeRrBQ32PoAuClxOUCOjgUiuUb0hrJRSubQoHFWRsfZFGFYYyRVwo
ntgU/NccUaG50XK1UzaddhSEyUthBuhI1VuqJVTpbMm/lb2t40H90CeBF/ep2gKN
v+PGgTMFMFsndleXQ16CvnxF6tifZWTqFkmJ/5IkLjHPBySldCEpgRu8ioidKzGB
4RxAa/M5Q8Dqsuc1rWdhJ86qoTVyB0p11tEu3VoMO81mSnJZqjKeVz8pwY+Rh+OW
vzlmQDj3JcLK+X6QcKakbcn4JAw4fzxya6vYSX4F8ambhzEZpruwmDGgt+s6hunN
WIly+9tkqKyQhHE+XvJY9wARAQABiQI2BBgBCgAgFiEEdilC2Iudz0ZbBlSYxMPR
Rfx0MQQFAmIYzQgCGyAACgkQxMPRRfx0MQQbrw/9HsZAdWOg/oaxHI0c9uhizcHU
XvM12nA9C5Xa+dP4Qcj2wIJdzpqZD+wfZvWUgASsos866qtKIW8TEFP8Vw22H7mI
rB/YK+GEuMJqPrLpl06yJZuqYOY52nUaPCqeRcR0Gmf35l4bOXO5T5773OldrJ8U
dVmJuj9mrZwZRxh/CuF22baot7bd1unoFzzM3p8Y20NC+sSvgNvRrS7zJZTRL85h
YtyfGLWYpv0JB3MWqETWIbq222fu0B7XnGk9DiOb7w3Utm/5pcqf6rJQmEeVpyB3
YxOuZ2gUdP91xSv+bIATj41mEM0IIPocRukdjFLm4KwFZmVCKVsVOsDdJk5ylrJE
M85S+H2jscEPDF+xqYVYFjR02tJbsjSrWFls1y9qWLGn8p0Go7Dfl55HwRZVPv3k
ElszO/FgyR4rbuxq9FC9UIYWLkm35t9JvCs4jfvEvgX3+ibhgLJrkfAW01YKDsae
+NpKjocO8QCtTvdFL7r5jygCqV07SSO//PFl8K7eDUfHdSYWhXa9hV/ilkJdLFai
lln7XxpvbypvOo1u/b/eOprEbDzTc5qFc7wjqT3tWpK/JKucl19H4vtfmilds9cO
xZTBfHajZzav44YT3HC0wjnGnM0imznFUHNQSXs6Pkb6FGVUCgckvAgXUB15Sxaa
Q9jbTWanOkjFlitDSmg=
=qWTW
-----END PGP PUBLIC KEY BLOCK-----
&lt;/code>&lt;/pre>&lt;p>将上面以&lt;code>-----BEGIN PGP PUBLIC KEY BLOCK-----&lt;/code>作为开头，以&lt;code>-----END PGP PUBLIC KEY BLOCK-----&lt;/code>作为结尾的文本保存到文本文件中，然后使用 &lt;code>gpg --import public.txt&lt;/code> （假设你把这段文本保存到 public.txt）导入我的公钥。在导入公钥后，请核对密钥指纹：&lt;/p>
&lt;p>7629 42D8 8B9D CF46 5B06 5498 C4C3 D145 FC74 3104&lt;/p></description></item><item><title>Service</title><link>https://viflythink.com/service/</link><pubDate>Tue, 28 May 2019 00:00:00 +0800</pubDate><guid>https://viflythink.com/service/</guid><description>&lt;p>目前，除了博客以外，我还有一些公共可用的服务，本页面用于列出这些服务并配上相应的说明。&lt;/p>
&lt;h1 id="arch-软件源">Arch 软件源
&lt;/h1>&lt;p>&lt;a class="link" href="https://archrepo.viflythink.com" target="_blank" rel="noopener"
>https://archrepo.viflythink.com&lt;/a>&lt;/p>
&lt;p>这是我自用的 Arch 软件源，国内外均可高速访问，里面主要包含根据 AUR 的 PKGBUILD 进行打包的软件，质量没有足够的保证，有相关的问题可以在 &lt;a class="link" href="https://github.com/vifly/arch-build/issues" target="_blank" rel="noopener"
>GitHub Issues&lt;/a> 中提出。&lt;/p>
&lt;h2 id="使用方法">使用方法
&lt;/h2>&lt;p>首先导入并信任我的签名公钥，如果将来有一天软件包签名失效的话，那有可能是我更换了签名用的密钥，重新执行以下步骤就可以解决：&lt;/p>
&lt;pre tabindex="0">&lt;code>wget -O /tmp/vifly-repo.key &amp;#39;https://share.viflythink.com/arch-repo.key&amp;#39; &amp;amp;&amp;amp; sudo pacman-key --add /tmp/vifly-repo.key
sudo pacman-key --lsign-key viflythink@gmail.com
&lt;/code>&lt;/pre>&lt;p>接着就是像添加 archlinuxcn 等第三方软件源一样在 /etc/pacman.conf 末尾添加下面几行：&lt;/p>
&lt;pre tabindex="0">&lt;code>[vifly]
Server = https://archrepo.viflythink.com
&lt;/code>&lt;/pre>&lt;h1 id="debian-软件源">Debian 软件源
&lt;/h1>&lt;p>&lt;a class="link" href="https://debianrepo.viflythink.com" target="_blank" rel="noopener"
>https://debianrepo.viflythink.com&lt;/a>&lt;/p>
&lt;p>除了 Arch 软件源外，我还有为 Debian 准备的软件源，同样是国内外均可高速访问，其中包含 fcitx5 百万肥猫词典（中文维基百科包含的词语）、萌娘百科词典等桌面用户所需的软件包，未来也会添加在服务器使用的软件。如有问题可在 &lt;a class="link" href="https://github.com/vifly/debian-build/issues" target="_blank" rel="noopener"
>GitHub Issues&lt;/a> 提出。注意，本软件源目前只确保在最新的 Debian 稳定版（stable）可用，如果你使用旧稳定版/测试版/不稳定版 Debian，那么请自行解决报错。&lt;/p>
&lt;h2 id="使用方法-1">使用方法
&lt;/h2>&lt;p>与使用其它第三方软件源相同，首先导入签名公钥。由于 apt-key 会在 Debian 12 被废弃，所以这里不使用 apt-key 导入公钥。&lt;/p>
&lt;pre tabindex="0">&lt;code>wget -qO - &amp;#39;https://share.viflythink.com/debian-repo.key&amp;#39; | gpg --dearmor | sudo tee /usr/share/keyrings/vifly-keyring.gpg &amp;amp;&amp;gt; /dev/null
&lt;/code>&lt;/pre>&lt;p>接着在 sources.list.d 添加软件源，记得把 bookworm（对应 Debian 12）改为最新的稳定版系统代号（codename）。&lt;/p>
&lt;pre tabindex="0">&lt;code>echo &amp;#39;deb [signed-by=/usr/share/keyrings/vifly-keyring.gpg] https://debianrepo.viflythink.com/ bookworm main&amp;#39; | sudo tee /etc/apt/sources.list.d/vifly.list
&lt;/code>&lt;/pre>&lt;h1 id="文件分享">文件分享
&lt;/h1>&lt;p>&lt;a class="link" href="https://share.viflythink.com" target="_blank" rel="noopener"
>https://share.viflythink.com&lt;/a>&lt;/p>
&lt;p>还有一个存在感较低的文件分享服务，目前仅用于分发上面所需的签名公钥。&lt;/p>
&lt;p>其中的文件都存放在我的服务器上。&lt;/p></description></item><item><title>About</title><link>https://viflythink.com/about/</link><pubDate>Sun, 28 Apr 2019 00:00:00 +0800</pubDate><guid>https://viflythink.com/about/</guid><description>&lt;h1 id="about-me">About me
&lt;/h1>&lt;p>Hello, world! &lt;br>
我是谁呢？这个问题并不是那么容易回答，如果是从现实角度来说，我目前是一个程序猿，除了写代码以外也会搞深度学习（&lt;del>炼丹&lt;/del>），除了技术以外的日常爱好也不少。
但是，这短短几句话不足以说明我是谁，我还有很多东西没说，最起码，我应该说清楚我喜欢什么与讨厌什么：&lt;/p>
&lt;p>&lt;strong>喜欢的东西：&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>开源/自由软件（FOSS）&lt;/li>
&lt;li>能带来改变的思想与文字&lt;/li>
&lt;li>能与他人理性沟通的平台与机会&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>讨厌的东西：&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>封闭的环境&lt;/li>
&lt;li>无处不在的权威&lt;/li>
&lt;li>懒惰&lt;/li>
&lt;/ol>
&lt;p>在这里我想要解释一下为什么我讨厌懒惰，根据《少有人走的路：心智成熟的旅程》中所说的内容，懒惰是一种原罪，阻止人们寻求真相，阻止人们改变自我等等。而从我喜欢的东西可以看出，我是一个喜欢包容开放的环境的人，懒惰无疑会带来固执与封闭等等这些对开放社会（环境）有害的东西，而且，人们总是难以意识到自己的懒惰（包括我自己），所以我必须声明我讨厌懒惰，给予懒惰足够的重视。&lt;/p>
&lt;p>想要更直观地了解我的立场？可以看看我在&lt;a class="link" href="https://www.politiscales.net/zh_CN/" target="_blank" rel="noopener"
> PolitiScale 在线政治测试&lt;/a>上的测试结果（仅供参考）：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/about/my_politi.jpg"
width="628"
height="841"
loading="lazy"
alt="政治测试结果"
class="gallery-image"
data-flex-grow="74"
data-flex-basis="179px"
>&lt;/p>
&lt;p>目前，在政治立场上对我影响较大的应该就是&lt;a class="link" href="https://program-think.blogspot.com/" target="_blank" rel="noopener"
>编程随想&lt;/a>了（从我选择的 ID 就可以看出这一点）。因为同样想说些什么，所以我开通了博客。要问本博客的主题是什么？我只能说博客偏向技术主题，当然，还有其它东西，偏向技术主题是因为作为程序猿的我同样相信技术能够改变世界，不过，仅仅拥有技术是不够的，还有很多方面需要考虑，所以博客上也会有一些我的感想与论述。&lt;/p>
&lt;p>最后，路还很长，有话慢慢说。欢迎各位与我交流，我的邮箱是 &lt;a class="link" href="mailto:viflythink@gmail.com" >viflythink@gmail.com&lt;/a>，你也可以通过&lt;a class="link" href="https://t.me/viflythink" target="_blank" rel="noopener"
> Telegram &lt;/a>联系我，或者使用&lt;a class="link" href="https://twitter.com/viflythink" target="_blank" rel="noopener"
> Twitter &lt;/a>（较少使用）私信我。&lt;/p>
&lt;h1 id="交换友链">交换友链
&lt;/h1>&lt;p>欢迎各位与我交换博客友链，想要交换友链的话，可以通过上面的联系方式联系我，或者直接在任意博文下留言，以下是一些可能用到的信息：&lt;/p>
&lt;p>博客简述：Vifly 的自留地，既包括技术折腾记录，也有对于各种东西的感想。&lt;br>
头像：https://viflythink.com/img/avatar.png&lt;/p></description></item><item><title>Archives</title><link>https://viflythink.com/archives/</link><pubDate>Sun, 07 Apr 2019 00:00:00 +0800</pubDate><guid>https://viflythink.com/archives/</guid><description/></item><item><title>Tags</title><link>https://viflythink.com/tags/</link><pubDate>Sun, 07 Apr 2019 00:00:00 +0800</pubDate><guid>https://viflythink.com/tags/</guid><description/></item><item><title>使用 Github Pages 和 Hexo 搭建个人博客</title><link>https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog/</link><pubDate>Sun, 07 Apr 2019 00:00:00 +0800</pubDate><guid>https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog/</guid><description>&lt;h1 id="简介">简介
&lt;/h1>&lt;h2 id="github-pages是什么">Github Pages是什么
&lt;/h2>&lt;p>先看看维基百科的说法:&lt;/p>
&lt;blockquote>
&lt;p>GitHub Pages是GitHub提供的一个网页寄存服务，于2008年推出。可以用于存放静态网页，包括博客、项目文档甚至整本书。&lt;/p>
&lt;/blockquote>
&lt;p>&lt;a class="link" href="http://www.ruanyifeng.com/blog/2012/08/blogging_with_jekyll.html" target="_blank" rel="noopener"
>阮一峰的这篇文章&lt;/a>的说法更为简短：&lt;/p>
&lt;blockquote>
&lt;p>github Pages可以被认为是用户编写的、托管在github上的静态网页。&lt;/p>
&lt;/blockquote>
&lt;p>根据维基百科的介绍，我们可以得知 Github Pages 可用来存放博客，而且是免费且无限存储容量的，只不过它只支持静态网页，也就是说无法使用 WordPress 等工具建站。&lt;/p>
&lt;h2 id="hexo是什么">Hexo是什么
&lt;/h2>&lt;p>先上一个官方的介绍：&lt;/p>
&lt;blockquote>
&lt;p>Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown（或其他渲染引擎）解析文章，在几秒内，即可利用靓丽的主题生成静态网页。&lt;/p>
&lt;/blockquote>
&lt;p>从官方介绍中我们可以得知&lt;strong>Hexo 是用来生成静态网页的&lt;/strong>。
由于 Github Pages 上只能发布静态网页，所以我们需要找到一个生成静态网页的软件来让我们能快速发布博文，Hexo 就是其中的一个。github 官方是推荐使用 Jekyll 来生成和发布的，然而 Hexo 有许多好看的主题，为此我选择了 Hexo。&lt;/p>
&lt;h1 id="本地搭建">本地搭建
&lt;/h1>&lt;h2 id="安装与运行">安装与运行
&lt;/h2>&lt;p>Windows 下请自行下载安装 nodejs 与 git，Linux 请根据自己所使用的发行版安装 nodejs，npm，git。&lt;br>
安装好后输入以下指令安装 hexo-cli（g 参数代表全局，请无视运行过程中出现的错误）：&lt;/p>
&lt;pre>&lt;code>npm install hexo-cli -g
&lt;/code>&lt;/pre>
&lt;p>然后在博客目录下输入以下指令：&lt;/p>
&lt;pre>&lt;code>npm install hexo --save
&lt;/code>&lt;/pre>
&lt;p>完成后可输入 hexo -v 验证是否安装成功。&lt;br>
接着输入以下指令进行初始化：&lt;/p>
&lt;pre>&lt;code>hexo init
&lt;/code>&lt;/pre>
&lt;p>然后输入以下指令安装依赖：&lt;/p>
&lt;pre>&lt;code>npm install
&lt;/code>&lt;/pre>
&lt;p>搞定后就可以运行一下测试效果了，生成静态网页：&lt;/p>
&lt;pre>&lt;code>hexo g
&lt;/code>&lt;/pre>
&lt;p>运行本地服务器：&lt;/p>
&lt;pre>&lt;code>hexo s
&lt;/code>&lt;/pre>
&lt;p>根据输出信息使用浏览器打开&lt;a class="link" href="http://localhost:4000" target="_blank" rel="noopener"
>http://localhost:4000&lt;/a>，即可看到效果&lt;/p>
&lt;h2 id="写文章">写文章
&lt;/h2>&lt;p>可以使用命令行或手动创建方法新建博文。
使用命令行：&lt;/p>
&lt;pre>&lt;code>hexo new &amp;lt;title&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>手动创建：在博客根目录下 source -&amp;gt; _posts 新建以 .md 为后缀的文件。&lt;/p>
&lt;h1 id="主题选择">主题选择
&lt;/h1>&lt;p>就我个人而言，查看技术类博文时总是看到写博文的博主使用了 Next 主题，虽然很简洁，但我总感觉过度简单了，在 16：9 的屏幕上左右两侧的空白太多了。经过一番查找，我选择了在集成服务和美观程度上成功打动了我的 Material 主题。&lt;/p>
&lt;h2 id="使用material主题后运行报错">使用Material主题后运行报错
&lt;/h2>&lt;p>使用 Material 主题后，运行 hexo s 后打开网页报错：&lt;/p>
&lt;pre tabindex="0">&lt;code>Unhandled rejection TypeError: /home/blog/themes/Material/layout/layout.ejs:3
1| &amp;lt;!DOCTYPE html&amp;gt;
2| &amp;lt;html style=&amp;#34;display: none;&amp;#34; &amp;lt;% if(config.language !== null) { %&amp;gt;lang=&amp;#34;&amp;lt;%- config.language.substring(0,2) %&amp;gt;&amp;#34;&amp;lt;% } %&amp;gt;&amp;gt;
&amp;gt;&amp;gt; 3| &amp;lt;%- partial(&amp;#39;_partial/head&amp;#39;) %&amp;gt;
4|
5| &amp;lt;% if(page.layout === &amp;#39;gallery&amp;#39;) { %&amp;gt;
&lt;/code>&lt;/pre>&lt;p>在官方仓库的 issues 中找到了&lt;a class="link" href="https://github.com/viosey/hexo-theme-Material/issues/686" target="_blank" rel="noopener"
>解决方法&lt;/a>，需要对 layout/_widget/dnsprefetch.ejs 进行修改：&lt;br>
将&lt;/p>
&lt;pre>&lt;code>&amp;lt;% } else if(theme.comment.use.startsWith(&amp;quot;disqus&amp;quot;)) { %&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>改为&lt;/p>
&lt;pre>&lt;code>&amp;lt;% } else if(theme.comment.use &amp;amp;&amp;amp; theme.comment.use.startsWith(&amp;quot;disqus&amp;quot;)) { %&amp;gt;
&lt;/code>&lt;/pre>
&lt;h1 id="配置">配置
&lt;/h1>&lt;h2 id="站点配置">站点配置
&lt;/h2>&lt;p>首先打开 blog 根目录下的_config.yml（下面统称为&lt;strong>站点配置文件&lt;/strong>），按照以下示例进行修改：&lt;/p>
&lt;pre>&lt;code>title: 你的站点名称
author: 你的名字
language: zh-CN
&lt;/code>&lt;/pre>
&lt;p>注意冒号后必须有空格，如果你不喜欢默认主题的话，可自行寻找 Hexo 主题，按照对应的主题的说明文档进行安装，记得修改 theme 内容：&lt;/p>
&lt;pre>&lt;code>theme: 新主题名字
&lt;/code>&lt;/pre>
&lt;h2 id="material主题配置">Material主题配置
&lt;/h2>&lt;p>这里按照&lt;a class="link" href="https://github.com/neko-dev/Material-theme-docs/" target="_blank" rel="noopener"
>Material主题官方文档&lt;/a>配置即可，选择 Material 主题的一个重要原因就是这个主题提供了很多对第三方服务的支持（前端小白的福音），所以看看有什么需要的第三方服务吧（RSS，评论区，访问统计等等）,可以参考我写的&lt;a class="link" href="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog_advanced" target="_blank" rel="noopener"
>进阶篇&lt;/a>。&lt;/p>
&lt;h2 id="material主题文档的一个小坑">Material主题文档的一个小坑
&lt;/h2>&lt;p>在配置过程中遇到了一个问题，不知道如何在侧边栏添加独立页面的入口（比如关于，友链之类的），添加后点击入口却无法进入对应的页面，官方文档说明如下：&lt;/p>
&lt;blockquote>
&lt;p>link 的参数为相对路径，对应 hexo 目录下的 source 文件夹内的相应文件夹。&lt;/p>
&lt;/blockquote>
&lt;p>然而我已经按照说明创建了文件夹，为什么还是不行呢？最后在&lt;a class="link" href="https://github.com/viosey/hexo-theme-Material/wiki/%E5%88%9B%E5%BB%BA%E3%80%8C%E5%8F%8B%E6%83%85%E9%93%BE%E6%8E%A5%E3%80%8D%E9%A1%B5%E9%9D%A2" target="_blank" rel="noopener"
>这个页面&lt;/a>找到了解决方法，以创建关于页面为例：&lt;br>
在 source 文件夹下创建 about 文件夹，新建一个 index.md 文件，写下（其中 layout 的值不可修改）：&lt;/p>
&lt;pre>&lt;code>---
title: about
date:
layout: about
---
&lt;/code>&lt;/pre>
&lt;p>&lt;em>假如是创建友链页面的话，记得还要按照“添加数据”这个步骤进行操作。&lt;/em>
总结一下，其实是官方文档没有提到需要创建 index.md 这一点坑了我，我还一直以为是我对文档的理解有误呢。&lt;/p>
&lt;h1 id="部署到github-pages">部署到Github Pages
&lt;/h1>&lt;h2 id="github上的准备">Github上的准备
&lt;/h2>&lt;p>这部分参考知乎专栏上的&lt;a class="link" href="https://zhuanlan.zhihu.com/p/35668237" target="_blank" rel="noopener"
>超详细Hexo+Github博客搭建小白教程&lt;/a>，打开&lt;a class="link" href="https://github.com" target="_blank" rel="noopener"
>github&lt;/a>并登录你的帐号，如果你还没在 github 帐号中添加 ssh key，请参考&lt;a class="link" href="https://gist.github.com/yisibl/8019693" target="_blank" rel="noopener"
>这篇文章&lt;/a>进行添加。接着点击右上角的个人头像，再点击 Your repositories：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog/go_to_repositories.png"
width="868"
height="402"
loading="lazy"
alt="进入项目页面"
class="gallery-image"
data-flex-grow="215"
data-flex-basis="518px"
>&lt;/p>
&lt;p>点击右侧的 New 新建项目。当然，你也可以直接点击&lt;a class="link" href="https://github.com/new" target="_blank" rel="noopener"
>这个链接&lt;/a>新建项目。输入自己的项目名字，后面一定要加.github.io 后缀，README 初始化也要勾上。&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog/new_repositories.png"
width="780"
height="616"
loading="lazy"
alt="新建项目"
class="gallery-image"
data-flex-grow="126"
data-flex-basis="303px"
>&lt;/p>
&lt;p>创建好项目后，点击 Settings，向下拉到最后有个 GitHub Pages，点击 Choose a theme 选择一个主题。然后等一会儿就可看到页面了。&lt;/p>
&lt;h2 id="正式部署到github-pages">正式部署到Github Pages
&lt;/h2>&lt;p>打开&lt;strong>站点配置文件&lt;/strong>，按以下示例进行修改：&lt;/p>
&lt;pre>&lt;code>deploy:
type: git
repository: 你的github项目地址
branch: master
&lt;/code>&lt;/pre>
&lt;p>repository 填写的应是类似于 &lt;a class="link" href="mailto:git@github.com" >git@github.com&lt;/a>:vifly/viflyblog.github.io.git 这样的 ssh 地址。假如你不知道地址，那么可以打开你在 github 上的这个项目，点击右侧的 Clone or download，就会出现所需的地址：&lt;/p>
&lt;p>&lt;img src="https://viflythink.com/Use_GithubPages_and_Hexo_to_build_blog/view_repositories_url.png"
width="1066"
height="463"
loading="lazy"
alt="查看项目ssh地址"
class="gallery-image"
data-flex-grow="230"
data-flex-basis="552px"
>&lt;/p>
&lt;p>最后，发布到 Github Pages：&lt;/p>
&lt;pre>&lt;code>hexo d
&lt;/code>&lt;/pre>
&lt;p>假如一直卡住的话，可中断后加上 -debug 参数再次运行这个部署指令，查看哪里出现问题。&lt;/p></description></item><item><title>Links</title><link>https://viflythink.com/links/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://viflythink.com/links/</guid><description>&lt;p>这里存放了各位友人们的博客站点链接，相信其中总有一些博文能引起你的兴趣。&lt;/p>
&lt;p>列表按友链添加时间先后排序。&lt;/p></description></item><item><title>Search</title><link>https://viflythink.com/search/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://viflythink.com/search/</guid><description/></item></channel></rss>