《抱孙子》记载:东晋时,南粤有民以雄黄驱蛇,见奇效,后世人曰蛇怕雄黄。
蛇:我怕的是广东人,不是他们拿的雄黄!
《抱孙子》记载:东晋时,南粤有民以雄黄驱蛇,见奇效,后世人曰蛇怕雄黄。
蛇:我怕的是广东人,不是他们拿的雄黄!
《诗盗·沉海泥龙睡千秋》:题说众生铭八苦,欲浪乾坤钱却无。挨踢岁月何处寄,观宇督宙忘尘稣。
改自霹雳角色“游海迷迭离人公子”的诗号:
题说众世铭八苦,
玉浪孤灯引夙心。
岁月山河何处寄,
游仙掠海忘离人。
晚上,稣要以穿墙术潜入传说中的鬼楼。
宿舍楼的左半边住男生,右半边住女生,但男生这边的六楼是有人住的,而女生那边的六楼已经荒废一年多。
稣曾经仰慕的学姐就在女生宿舍六楼离奇地香消玉殒,稣一定要探查个明白,并为学姐报仇。这点,学妹也是真心支持的,因为这个穿墙术正是学妹提供的。
宿舍是个 S 型建筑,S 中间有铁门,分割男女两边。男女两边走廊互相平行,但不在同一条直线上,中间的铁门一直是锁着的,这样男女两边都看不见各自那边的走廊。不过稣用穿墙术从六楼中间的铁门穿过去,就能轻易地来到女六楼。
进入之后,才亲眼见证另一边的楼梯入口也特地封起来,所以女生确实也是无法进入六楼的。眼前的六楼简直是另一个世界,出乎意料的破败,走廊上很多灰泥、玻璃渣。稣小心躲避,进入 614,里面的床都被搬走了,地上有好几个打破的热水瓶和一些餐具。
曾经有一个流言:有一天半夜每个女六楼的房间里都有几个女生被“怼”附身,拿起热水瓶往其她女生脸上倒,最后还互相厮杀,死了大半,剩下的全疯了。稣是相信科学的人,怎么可能相信这种怼话?这大概就是一种集体催眠罢了,但究竟是谁有这么大本事呢?难道是医务室的心理医生?
思考时,稣却看到窗户的玻璃上贴着一张照片。靠近一看,拍的居然是食堂的厨房。怎么会有如此奇怪的照片呢?稣想到大家都喜欢在照片背后写备注,于是翻过来,竟看到学姐的字迹,写着:食堂不干净!稣大吃一惊,不干净是这个意思,还是那个意思?难不成真有怼?不可能,一定是在做梦!
啊!稣竟然想不起学姐叫啥名字?只记得学姐的学姐叫桂圆……而学姐好像叫什么香……她还有个舍友姓许,经常打扮得很拉风的,也一样记不起名字了!
吓醒。
走了一天,还好有这个破庙能休息一下,但是环境惨不忍睹,难怪做起噩梦。是不是盖的这些稻草生菇了,又真菌中毒?不管了,天还没亮,继续睡。
嗯?稣又回来了!
在宿舍的床上醒来,就看到上铺的一只手伸出床沿,只是颜色不太对!这手都枯成深色了,吓死人啊!
呵呵,其他舍友都一样……全都成干尸了。要说稣还是因为信仰科学而吓不倒,甚至高中生涯的目标就是考上吓大。果断出门看看其他人,竟然唱起空城计?
澡堂厕所都没活人,其它房门都反锁的无法进入查探情况。宿舍门口的开水房也没人打水。
女生那边的门也是关着的,说明她们都没起来过,没人开门出来。
走去教室,一样空无一人。这究竟是怎么回事?
突然,稣远远地看到操场居然有个高个子自己一个人在踢足球!稣立刻边跑过去边对着他喊:同学,你知道学校怎么了吗?
那位同学把球踢向稣,稣身体向后一仰,本想用胸口停球,不料足球靠近后稣发现它黑乎乎的,赶紧躲闪。
黄言:小开?你刚刚是在模仿尼欧躲子弹吗?
圣小开:言兄,原来是你啊!
黄言:你也是怼吗?
圣小开:什么怼?怎么大家都变成干尸了?
黄言:哦,果然你宿舍也一样。全挂了!
圣小开:你是说,咱俩也都挂了?现在是怼?
黄言:是的。
圣小开:不可能,其他人怎么没有变成怼?而且稣起床后,看过自己的床上,并没有尸体。
黄言:我床上有自己的尸体。
圣小开:你看见了?
黄言:我可以带你去看看。
圣小开:不了。怕怕。你知道是啥导致的吗?
黄言:可能是中子弹,或者声纳武器,也可能是生化武器。
圣小开:开啥玩笑呢?这和平年代的,谁会在学校扔武器?
黄言:并不是只有学校哦,我出去看过了,整个城都是这样。你还记得我写的小说吗?
圣小开:你又想说你化身的主角在山洞里发现希特勒留下的财富?
黄言:这不是重点,你记得看完那小说后,你说了啥?
圣小开:稣很担心有生之年会遇到第三次视觉大战。是这句?
黄言:是的。
圣小开:稣担心的往往会变成现实……
黄言:没错!自从你跳楼后,视界就走向毁灭的边缘。
圣小开:稣不是又复活了吗?
黄言:我听说你被送医后又活了过来,但再也没见过你,料想一定是假的,哪有人从六楼跳下来还不死的?
圣小开:呃!说了你也不信,其实这个视界真的是虚拟的,它们想从稣的意识深处拿到某种机密,所以不断复活稣。
黄言:我信你。个怼。你能有啥机密?说出来吓吓我!
圣小开:呵呵,你以为变出个熟人,稣就放松警惕了吗?稣还能吓醒。
黄言:哈哈,你去未来看看吧!
又是一个早上醒来,原来稣在软件园非正常人类研究中心上班。嗯,昨天一切都还正常,也不知道那个昨天是不是被植入的记忆。总之今天出现了一系列怪异的事情,去买早点没遇到半个人,当然整个的人也没有,到了早餐店才发现没开门。稣想:算了,公司还有点东西可以撑一下肚子。
来到公司才发现更怪异的事,人倒是有,但都是骷髅。真佩服自己当时的淡定,居然没有大呼小叫神马的!不过当时马上想到世界末日,于是看看手机,时间是 2011 年,还早呢。好奇地打电话给家里,他们都表示世界没有抛出任何异常。然后又想:惨了,不会是只有软件园发生灾难吧!稣的弟弟也在软件园,马上打电话给他,他说公司送他们项目组的几个去北京玩了,不知道软件园发生了神马!
接着,发现脑子里还有一个人,可能又是被植入的女朋友,吧?就硬着头皮,往那处走过去,人不在位置,不过那屋子里的其他位置一样坐着几个骷髅,心想好像稣在意的人都没事,真是不幸中的万幸。
为了确保万无一失,稣给她打电话,没人接,不过不接不一定就是挂了,但是稣还不放心,到车库去看了一下,熟悉的车牌号,跑过去,震惊了……车里一个骷髅……车门打不开……过了一会儿,稣马上就冷静了,发现那个骷髅比她大了好几个尺寸,sigh~应该不是!
这时有人叫稣,一转身,居然是黄言!
黄言:都看见了?这视界总是要走向毁灭,躲得了初一,躲不过十五。
圣小开:稣一定要阻止战争!吓醒。
《诗盗·大禹》:治水为命顺逆同,浩然莫测鬼神工。三过涂山生悟空,定海神针串鱼龙。
改自霹雳角色“渊渟无迹静涛君”的诗号:
知水为命顺逆同,
浩然莫测深浅中。
无波沧海掩汹涌,
渊渟不动现鱼龙。
C 和 C++ 都有一些很基本的语句出现不同派系的写法,比如 * 靠左还是靠右,抑或居中?这种还是排版问题,并不影响有效字符数,但下面这个就直接影响有效字符数了!
《对于选择恐惧症患者来说,指针判空究竟要怎么写才不纠结?》
指针判空有两大类写法:
if (p) {}
if (nullptr != p) {}
后者被挺多人推荐的,比如林锐的《高质量C++/C编程指南》。
搜一下,能发现不少网友都挺纠结。这么基础的问题,如果不交代清楚,就是给 C++ 黑很好的攻击理由。
稣刚大学毕业时,是使用 C,并不屑 C++ 的,当时看了不少代码,就是直接 if (p)
和 if (!p)
,所以也坚持这种写法,并认为这样写比较短,对手指好,比较养生。
后来 C++17 出现,稣就坚定地改用 C++,于是也更多遵守 C++ 类型安全的原则,开始认为 if (p)
和 if (!p)
这种写法隐含类型转换,不太好。记得 2017 年以前,其实就看过 Google C++ Style Guide,里面也曾经建议写成 if (nullptr != p)
和 if (nullptr == p)
。
现在,再去看 Google C++ Style Guide,已经没有这样的建议。
C++ Core Guidelines 中有一条:ES.87: Don’t add redundant == or != to conditions,它的理由是:
Reason Doing so avoids verbosity and eliminates some opportunities for mistakes. Helps make style consistent and conventional.
if、while、for 的条件语句本身就是在选择 true 或 false,对于指针来说,会自动与 nullptr 比较。通常来说,if (p)
可以读作 “if p is valid”。
接着,还有一个例子:
1 | if (auto pc = dynamic_cast<Circle>(ps)) { ... } // execute if ps points to a kind of Circle, good |
同样是推荐更简短的写法。但这个例子却有人提出疑问:推荐的写法不会被怀疑是 == 少写了一个 = 吗?
还真不会!这个语句其实不是经典 C++ 的语法,而是 C++17 的 init-statement 语法,这里的 auto 是类型,说明 = 不可能是 == 错误地少写成 =。
对于部分 C 程序员和“经典 C++”程序员会把赋值语句写在 if 条件里的做法,建议是改为分开写。
1 | // p is defined before |
稣会在新工程里坚持使用 C++ Core Guidelines 的建议,但也不反对另一种写法,只要不混合使用。允许有不同派系,但最好别精神分裂。
Windows 自带 OpenSSH 用的是 LibreSSL。要不要也用一下?甚至,直接动态链接到系统自带的 LibreSSL 的 dll,减少 exe 体积!
Chrome 用的是 BoringSSL,要不要……折腾一下?
稣使用 vcpkg,安装几个库,还不是手到擒来?
结果……连装都不让装!这是 LibreSSL 的:
1 | Building libressl:x64-windows... |
这是 BoringSSL 的:
1 | Building boringssl:x64-windows... |
在忍痛 vcpkg remove openssl
后,陷入一阵思考——稣主要通过 Boost.Asio 使用 SSL,所以问题转换为:Boost.Asio 对这几个 SSL 库支持得如何?其中,OpenSSL 是使用多年没有任何问题的,只需要调研其它两个!
先试试 LibreSSL,毕竟是 OpenBSD 的,而且微软也用它。拿 ClipboardSync 代码编译,发现顺利通过!但是运行时抛了异常,说不支持 TLS 1.3……遂查阅官网,说是从 3.2.0 就支持!那就是 Boost.Asio 不对了,果断给它提 issue。
接着尝试 BoringSSL,毕竟是 Google 的,号称重视安全,而且有 Chrome 这个大型流行软件做背书!然而很打脸的是:它居然不支持 SM3!稣当年特地选择用国密标准里的 SM3 做 Hash 算法,就是因为爱国!不支持国密这点岂能忍?立刻 vcpkg remove boringssl
。
Boost.Asio 和稣联合推荐 OpenSSL 为唯一好用又爱国的 SSL 库。
Boost.Beast 是一个 HTTP/WebSocket 库。本文只讨论 WebSocket。
UMU 先是用过 WebSocket++(websocketpp),又用过 libwebsockets,再用的 Boost.Beast,之后就一直使用 Boost.Beast。
2018 年,参与 EOS 开发时,它是用 WebSocket++ 的,跟着学习了一阵子。
2020 年,在金山云时,内部版云游戏用 libwebsockets,跟着学习了一阵子。
做开源版云游戏——鎏光云游戏引擎时,特地学习并使用 Boost.Beast。因为公司其实是要求和内部版有一些差异的,正好之前一直用 Boost.Asio,对它的熟悉可以快速套用在 Boost.Beast,于是果断切到 Boost.Beast。
如果您原本使用裸 TCP,您应该知道 TCP 的流式传输,导致您需要自己分包,即界定一个“消息”的边界。鎏光本来设计为同时支持裸 TCP 和 WebSocket 的,所以还保留着处理分包的代码:
这对 WebSocket 其实并不需要,它的发送和接受已经是都是“消息”,带着长度的。所以如果您原来用 Asio 写 C/S 程序,把它们改为 Beast,是很容易的,而且代码量会缩减不少。
对于工具性的 C/S 程序,建议下次直接用 Beast 写更省事。
另一种适用场景是需要支持浏览器,即同时支持 C/S 和 B/S 模型。
text 和 binary 模式需要区分清楚,如果用于发送音视频,显然应该使用 binary 模式。
stream 的默认接收长度是 16MiB,如果不够可以改长点,0 表示最大的 std::uint64_t。
参考:boost/beast/websocket/stream.hpp
1 | /** Set the maximum incoming message size option. |
前文《防爆破远程桌面密码》提到可以用 Powershell 实现自动加 IP 黑名单到防火墙,这个坑还是得填,毕竟爆破依然在持续……
核心点:
IP 黑名单会持续新增,过去已经加入防火墙的名单也需要保存。所以应该把新增 IP 和防火墙已有 IP 求并集。
如果名单没变,不应该覆盖防火墙规则。
见 Github 仓库:
https://github.com/UMU618/windows-scripts/blob/master/pwsh/add-ip-blacklist-to-firewall.ps1
面向公网开了个远程桌面端口,无论用多少号端口,都会被爆破!
虽然稣的密码很安全,几乎爆破不了,但会在系统日志里留下大量记录,实在很不雅观!
改端口是没用的,因为只有 65535 个,只要机器 IP 被发现,扫描端口很快就能完成。
封 IP 是有用的,虽然爆破者(攻击者)有很多 IP,但一定是有限的,有多少封多少!
打算使用 PowerShell 7 来编写脚本,首先学习 Get-WinEvent
命令:
1 | Get-Help Get-WinEvent -Online |
日志的过滤条件可以用“事件查看器”来协助生成:
当然,以上全部 XML 是 -FilterXml 的参数,比较长,可以用 -FilterXPath 来简化,只需要中间一部分。
条件还可以再加上 LogonType,以缩小范围,其中 3 表示“网络登录”:
1 | Get-WinEvent -LogName 'Security' -FilterXPath '*[System[EventID=4625] and EventData[Data[@Name="LogonType"]=3]]' -MaxEvents 1 | Format-List Message |
以下是完整代码,它会打印出 IP,和这个 IP 的登录失败次数:
1 | $ips = @{} |
目前收集到这些:
1 | 112.184.96.197 |
打开 wf.msc
,新建一个阻止型的防火墙策略,然后加入到“作用域”的“远程 IP 地址”里。
加防火墙也可以用 PowerShell 搞定,这次先偷个懒,下次再说吧!
不同编码规范对函数名的命名格式有不同要求,但主流有以下几类:
PascalCase:比如 Google C++ Style Guide
camelCase:比如 LLVM Coding Standards
snake_case:比如 PPP Styple Guide、K&R Style
哪种适合 Windows 开发呢?
PascalCase 偶尔会遇到和 Win32 API 宏冲突的情况:
1 |
|
以上代码无法编译,因为 SDK 头文件里有这样的定义:
1 |
|
导致以下编译错误:
1 | 1>function_name.cpp(7,5): warning C4003: not enough arguments for function-like macro invocation 'CreateWindowW' |
改成下面这样,才能编译:
1 |
|
以上代码虽然编译通过,但实际上 CreateWindowEx 还是个宏。用 IDA 逆向编译后的 exe,并加载 pdb 后,可以看到:
1 | ; int __cdecl main(int argc, const char **argv, const char **envp) |
虽然没啥危害,但 C++ 20 程序员不喜欢宏!
如果函数名使用 camelCase,则没有机会与 Win32 API 的宏定义冲突。
另一个好处是在做 API Hooking 时,命名可以更短。比如 Hook ShowWindow,那么替代函数可以就叫 showWindow,而用 PascalCase,则可能需要叫 MyShowWindow。
那么是不是把 Google C++ Style 的函数名由 PascalCase 改为 camelCase 就完美了?
更好,并不是完美……camelCase 也有个小问题——只有一个单词时,无法区分是 camelCase,还是 snake_case。比如 size,是函数(camelCase),还是临时变量(snake_case)?
UMU 建议,如果已经在使用 Google C++ Style,应该避免函数名与 Win32 API 一样。如果正在从头制定一套 Coding Style,则可以考虑函数用 camelCase。