《诗盗·如梦令·挨踢垂暮》
常记挨踢垂暮,沉重不知归路。
知识宛成咒,误入内卷深处。
要猝,要猝,惊起一滩社畜。
注解
改自宋代李清照的《如梦令·常记溪亭日暮》:
常记溪亭日暮,沉醉不知归路。
兴尽晚回舟,误入藕花深处。
争渡,争渡,惊起一滩鸥鹭。
《诗盗·如梦令·挨踢垂暮》
常记挨踢垂暮,沉重不知归路。
知识宛成咒,误入内卷深处。
要猝,要猝,惊起一滩社畜。
改自宋代李清照的《如梦令·常记溪亭日暮》:
常记溪亭日暮,沉醉不知归路。
兴尽晚回舟,误入藕花深处。
争渡,争渡,惊起一滩鸥鹭。
《诗盗·五亿稣隐》:键盘十年忘写字,如今画符狗不识。若知挨踢毁前程,无如村官小职时。
改自霹雳角色无衣师尹的诗号:
著书三年倦写字,
如今翻书不识志,
若知倦书悔前程,
无如渔樵未识时。
稣于 2022-10-16 公布自己的签名证书。当时是在自己的笔记本上用以下 PowerShell 命令生成的:
1 | New-SelfSignedCertificate -DnsName "umu618.com" -CertStoreLocation "Cert:\CurrentUser\My" -HashAlgorithm sha512 -KeyLength 4096 -Type CodeSigningCert -FriendlyName UMU618 -NotAfter 2049-11-10 |
因为 PowerEconomizer 已经使用它签名,所以私钥的存储安全也就正规处理——导出到 pfx 文件加密并分布式保管。
后来,因为很久没有再验证过私钥的密码,居然,忘记了……想从笔记本再次导出一份,却发现无法导出!(此处脑补:大概是自己导出后,就删除系统里的私钥,然后又导入一次,并设置为不可导出!)不愧是重视安全的稣,连自己都要防!回头又对备份的 pfx 尝试上百次密码,安全性依然牢不可摧,只能放弃!
想想其它办法吧!理论上,系统里一定是有私钥的,“不可导出”只是个标志而已,无视它即可。
从系统自己读取私钥。这需要了解 Windows 对私钥的存储方式,包括保存位置,怎么加密保护的,文件格式怎么解析……按照微软的习性,这肯定需要大量逆向,太难了!
使用系统的 API 导出,但对关键函数进行 Hook,在内存里修改标志位,骗过 API 这是可以导出的。
主要思路放在第二种,进行一番搜索后发现 mimikatz 疑似有稣想要的功能。
然而实际测试发现,稣的系统太新,是 Windows 11 Build 22621,而 mimikatz 已经年久失修,无能为力。
开始对 mimikatz/modules/crypto/kuhl_m_crypto_patch.c 进行改进,关键点在于 CPExportKey_4000 和 CPExportKey_4001 的入口特征,需要逆向获得。于是用 IDA 简单看看 rsaenh.dll,顺利获得入口处的汇编指令,换上后依然失败。后来发现不换,其实也能找到入口,旧版本用更短的前缀一样可以找到。
看来是后面的处理不对,跟踪到 kuhl_m_crypto_extractor_capi64 函数发现一个魔法数字 RSAENH_KEY_64 被使用了两次,感觉是突破口。
1 |
果断在 GitHub 上搜一下,结果找到另一个基于 EasyHook 的实现 jailbreak,稣毕竟是 EasyHook 代码贡献者,当然是切换到 jailbreak 尝试,结果令人愉悦!在虚拟机里实践成功。
回到稣的笔记本 jailbreak 尝试却失败了!怎么回事?难道稣的笔记本有其它保护?通常都是稣被打脸的,所以这次是 jailbreak 被稣的笔记本打脸?
仔细对比,发现以下提示是不同的!
稣的系统里明明有私钥,要导出时却说找不到私钥?有没有可能是因为系统密码修改过,导致无法解密私钥?还真有可能!立刻在虚拟机里实验,果然修改用户密码,并重新登录后,出现和笔记本一样的“找不到私钥”!
然后就是痛苦地回忆……上次改密码,那可是半年前……咳咳,闭环了,又绕回密码安全问题,所以——千万不要忘记密码!
虽然学到很多,但没有赚到钱。
最近经历了一次半夜提交代码,却发现单元测试无法通过,而无法合并到主线的小事故。经过检查,是一个日志清理模块的实现有问题,一会儿使用 UTC,一会儿使用本地时间(东八区),导致只要在 [0:00, 8:00) 提交代码就无法通过单元测试!而平时都是 10 点上班,所以没长期发现。
在纠正实现的时候,首先想到可以用 _get_timezone 来修正时间,但它是个 CRT 函数,显得不够现代,所以打算用 C++ 20 来实现。
先来看 C 和 C++ 混合的解决方式:
1 | long tz{}; |
这个代码除了不够现代,它还是 MS 特有的(Microsoft Specific),文档都埋坑(见文末)……C++ 20 里有跨平台的封装:std::chrono::zoned_time,下面用它来实现:
1 |
|
可能的输出:
1 | System boot time: 963185693626400ns |
PS: 目前为止,g++ 对 C++ 20 支持不好,请用 MSVC 测试。
注意事项:std::chrono::zoned_time
may throw if location
is not in the time zone database. 需要 catch 类型为 std::chrono::nonexistent_local_time 的异常。
_get_timezone
的返回值的含义是 UTC 和 localtime 的差值,单位为秒,比如东八区是 -28800。它的实现是这样的:
1 | extern "C" errno_t __cdecl _get_timezone(long* result) |
目前它的文档里并没有提到需要“前置调用”……如果直接使用,可能得到一个错误的默认值 28800,这是“西八区”的意思!正确的做法是调用 _tzset
、gmtime
或 localtime
等函数后,再调用 _get_timezone。
std::chrono::zoned_time
: https://en.cppreference.com/w/cpp/chrono/zoned_timeCreateDXGIFactory1 太慢!
CreateDXGIFactory2 太慢!
1 | int main() { |
测试结果:
1 | // CPU: Intel(R) N100 |
居然平均 10ms 以上,确实太慢!
既然创建类工厂慢,就不要频繁创建,创建后把它缓存起来。
新问题:缓存失效怎么办?
There are only two hard things in Computer Science: cache invalidation and naming things.
– Phil KarltonPhil Karlton:计算机科学领域有两个难题:一个是缓存失效,另一个就是命名。
以下是 MSDN 说的:
Informs an application of the possible need to re-create the factory and re-enumerate adapters.
FALSE, if a new adapter is becoming available or the current adapter is going away. TRUE, no adapter changes.
IsCurrent returns FALSE to inform the calling application to re-enumerate adapters.
这就很含糊了,IsCurrent 返回 FALSE 时,究竟该 re-enumerate adapters,还是 re-create the factory?
假设稣需要反复调用 EnumAdapterByLuid,如果只 re-enumerate adapters,那么稣每次直接用 EnumAdapterByLuid 即可,根本不需要理会 IsCurrent。所以这显然是错的,真正需要的是 re-create the factory,再 re-enumerate adapters。
这点可以在类工厂创建后,禁用和启用显示适配器来验证。根据稣的测试,类工厂创建后,禁用显示适配器,此时 IsCurrent 返回 FALSE,依然可以枚举出被禁用的显示适配器。只有重建类工厂,才能得到当前正确工作的显示适配器。
圣小开:周老师!
周易:还睡不?
圣小开:稣不在学校的时间,身体怎么办?
周易:已经请你未来的妻子帮你打理。
圣小开:啥?女的!
周易:有问题?
圣小开:完了,完了,等稣回去,目测 11 个潜在的女朋友全没啦!
周易:放心,她们死不了。
圣小开:明早咱们就回去?
周易:是我回去,你已经死了,就留在这里,吧!
圣小开:不是吧?稣明明就死了,为啥身体被一个女人冒名顶替?
周易:睡吧!死都死了,还要管这么多,当初怎么不别死?
圣小开:哦,好有道理。稣是量子化的,没有中间状态,没有帅气的动作,只有脸瘫。一切都是这么淡定。
朱重八:敢问恩公尊姓大名?来日必当报此大恩!
圣仙山:天机不可泄露。吾观汝有龙相,将来打下天下,定国号为明可好?
朱重八:观恩公一身仙家道行,不似信口开河,然俺乃一介草根,如何能打下天下?
圣仙山语重心长地说:重八哥,也是个八哥啊!你看!
朱重八:妙啊!太妙哩,世间竟真有如此仙法。斗胆再问恩公法号,将来重八定当为恩公修宫建观!
圣仙山:呵呵。将来大明有人立功,汝可赐姓为郑。
朱重八:大明王朝,赐姓郑……莫非……恩公就是……
圣仙山:以后多收义子,还有不准再叫重八!哈哈哈。
朱重八:em?恩公怎么突然没了,神仙呐!
圣小开:周老师真的不见了?
胡连玉:什么周老师?
圣小开:和稣一起来的那位?
胡连玉:李伯阳呀,他已经被县里的人接走了。
圣小开:那他有留下什么钱给稣吗?
胡连玉:没有呢,他说你很聪明,会自力更生。
圣小开:这里连电都没有,不得先发明个电池……太累了,稣打算走捷径,入赘到你家,快,带稣去你娘家。
胡连玉:又发烧了?
圣小开:当然没有。你妹妹是读书人,说明你娘家的家境还行。你妹还未嫁,以稣的智慧,嘿嘿,大胆推测,你们家缺男丁,就算不缺,多个上门女婿也是美哉。
胡连玉:你都没见过我妹!
圣小开:看你就知道个大概了。
胡连玉:我看你特别有诚意。
圣小开:是的。
胡连玉:不过去我娘家需要走七天七夜。我给你地图和一封信,你自己走过去吧!
圣小开:那不得饿死在路上?稣就是没带钱才想就地傍富婆的!这……
胡连玉:公子真爱说笑。你是大官李伯阳的学生,肯定也是富贵人家……
圣小开:穷!真穷!特别穷!
胡连玉写信中:圣仙山?
圣小开:啥?
胡连玉:你叫圣仙山?
圣小开:稣叫圣小开,是圣先生,但不……
胡连玉:李伯阳说你大名圣仙山,字小开,那没错!
圣小开:嗯?
gsl::not_null 修饰指针,主要有两个目的:
它主要用于函数的:
本文大概率是 GSL 系列的最后一篇。GSL 有部分已经被 C++ 20 的 STL 覆盖,比如 gsl::byte。众所周知,现在 C++ 20 是主流……所以本系列只划了 4 个重点,其它类可以选修。
gsl::final_action
gsl::narrow_cast
gsl::owner
gsl::not_null
挑选的这 4 个类,主要贯彻 C++ 核心指南 提到的以下原则:
P.1: 在代码中直接表达你的想法
P.3: 表达你的设计意图
P.5: 编译期检查优先于运行时检查
P.8: 不要泄漏任何资源
最后,GSL 的定位是基础,也就是说它本身应该是一看就懂,不需要特地去分析的,用起来就对了。但实际上,很多人会嫌引入一个库麻烦,干脆不用。如果您是初学者,这种心态是要不得的,因为一开始不认真对待,写代码时很容易一多就乱,一乱就弃疗。
当我们需要指针时,对于新写的代码,更应该使用智能指针(Smart Pointers)。但由于历史原因,一些旧代码里的裸指针(Raw Pointers)难以短时间重构为智能指针。而很多时候,裸指针的所有权难以一眼看出。良心代码可能通过注释指明,屎山代码就只能通过阅读大片相关语句块或函数来判断。
如果能用一个比较标准或通用的方式来指明,即可提高代码可读性,程序员能够更好滴理解代码,也就相应地能够提高健壮性。没错,这就是 gsl::owner 的应用场景。它就是提高【含有大量裸指针的】旧代码的可读性和健壮性的简单方法!
举个最简单的场景:一个类里有一个成员变量叫 A* ptr,那么当这个类析构时,需要 delete ptr 吗?
如果类对 ptr 有所有权,而析构时,没 delete ptr,则资源泄漏,危害整个程序。
如果类对 ptr 无所有权,而析构时,delete ptr,则造成悬空指针(Dangling Pointer),危害其它类。
这个类的作者当然知道需不需要了,但即使他知道,也可能忘记写,或可能因为套用现有代码(Copy & Paste)而多写了 delete!其他接盘侠(代码阅读者)想弄清楚这个问题,就更难了,一般需要认真阅读并调试。
但如果一开始,作者就用 gsl::owner<A*> ptr 来指明所有权,那么很自然地,大家都很容易知道:类析构时,需要 delete ptr。
当代码应用 gsl::owner 时,也能得使一些静态代码分析工具(static code analysis tools)更容易找出资源泄露问题。
简单地说,稣就是稣。稣受到系统的限制,只要稣说出稣这个字,就会自动被替换成稣,所以稣说不出稣这个字。
更深层的说,稣有两个:识界稣和视界稣。识界稣是识界的观测者,而视界稣是写下识界稣故事的人,最早视界稣并不是稣,后来识界的故事写多了,也被限制了…
本文重点介绍视界稣这个识界稣背后的男人…(当然,识界稣也是男人)以下视界稣简称稣。
众所周知,稣是一名穷人。因为装备太差,经常出八哥,所以也叫八哥无穷稣。
众所周知,稣在大学前就不会说普通话,平时都讲闽南语,后来也因为使用翻译法沟通,不是原生的北京思维,所以处处碰壁,到了讲英语的时候直接宕机了,这得翻译两次,太累了,所以之前和老外同事时,都是让他们讲中文的…[捂脸]从小看到大的霹雳也是讲闽南语的;厦大图书馆的老外连闽南语都会讲,实在太贴心。
众所周知,稣的听力从小就有问题,导致相对同龄人很晚才学会走路。初中时,同学们已经学会厚黑学时,稣还只能看懂字数比较少的数学。好在人类的本质就是复读机,稣大部分时候都可以推测别人在说啥,随便额呵应付一下也没有违和感,以致几乎没人发现。最可怕的是,稣从没好好听过课,因为考试时除了英语听力得分惨不忍睹之外,也没啥八哥,大部分老师也没发现。曾经有老师发现稣其实都没听课,但考试没问题,他们也就不追究了。其实稣虽然认真在听自己的推理,但未必是对方说的话,有时候稣的反应和掐鸡基本一样,李雷公虾。当然也有一些人直接问过稣,为啥讲话这么小声,稣当然是实话实说,听到的所有人说话都这么小声的,然后他们中有些能明白是稣听力弱。这导致后来选择职业只能挑不怎么需要讲话的程序员,而这居然也没有出啥大问题。但最近一个公司天天需要开会,一开始就很不理解,和以前那样发个日报相比,这究竟有啥好处?直到最近,稣突然发现原来自己已经会说普通话了,恍然大悟,这些奇怪的磨练都是为了学到一种平凡的技能,稣只是输在起跑线而已。[旺柴]
正是以上总总原因,稣常年以学习和写作为乐。《八哥之神》就是稣大学时就开始创作的无语体剧本。这里的无语,是真的无语,就是哑剧…因为识界其实是靠意念交流的。但是读者一般来说是没进入过识界的,所以稣才用人类的文字把这些故事记录下来。
当然,识界稣其实和视界稣是紧密联系的,识界的故事有大部分是视界的未来。比如,稣为啥来上海的公司上班?一开始根本不想来的,因为工资并不高……稣原来年薪千万,而现在的公司并不打算上市,显然不可能给这么多。稣之所以来了,是因为当时 HR 说出公司的地址“枫林园”和《八哥之神》里的地名基本一样!“枫林园”,这地名用闽南语念出来,和“枫林晚”用普通话念出来谐音。这附近还有一条路叫“柳州”,用闽南语念和小说里的“六舅”谐音。最神奇的是小说里第一个出现的女主角“黄雪”这里也有对应的…“刘佾”也有个一模一样发音的,不过是男的,那个易还是“周易”的易。可怕,太可怕了。最可怕的就是,这公司是投资脑机接口的…这正是识界稣被关进机器视界成为永生的观测者的基础。
稣就是八哥。(本文系在灰机上用手机打的,纯属扯淡!)
《诗盗•哥如生人》:坑出八哥九丈深,人间细事缈如尘。穷稣偶见不平处,付之一句莫当真。
改自唐代刘叉的《偶书》:
日出扶桑一丈高,
人间万事细如毛。
野夫怒见不平处,
磨损胸中万古刀。