sdttttt

普通的魔法师

CBC: Padding Oracle 攻击

最近在研究TLS协议,了解到了TLS1.2时期使用CBC加密导致的 Padding Oracle 攻击(填充神谕攻击),这篇文章稍微讲一下,也顺便总结一下我的理解。 TLS这个协议本身常规的握手就不说了,核心在于他的加密方法CBC. CBC 模式的异或延展性 要理解攻击原理,首先需要明确 CBC(密码块链接)模式的解密公式。对于任意一个密文块 Ci,其解密得到明文 Pi 的过程分为两步: 使用密钥解密当前密文块 Ci,得到一个攻击者不可见的中间状态 (Intermediate State),记为 Ii。 将中间状态 Ii 与前一个密文块 Ci−1 进行异或运算,得到真实的明文 Pi。 公式表达为:Pi=Ii⊕Ci−1 我并不是密码学的专家,对于这些符号的意义,只需要知道C = cipher(密文), P = plain (明文) 即可. 我的初步疑惑在于“修改密文的意义”。实际上,攻击者的目标不是当前密文块 Ci,而是前一个在网络上明文传输的密文块 Ci−1。根据上述公式,如果攻击者篡改了 Ci−1 的某个字节,那么最终计算出的明文 Pi 的对应字节也会发生完全可控的改变。这就赋予了攻击者在不掌握密钥的情况下,操控解密结果的能力。 攻击的实施路径与状态倒推 攻击者将服务器本身当作了一个“神谕(Oracle)”。在早期的协议设计中(如 TLS 1.2 的某些套件),服务器通常采用“先解密、后校验填充、再校验完整性”的处理顺序。如果解密后的数据不符合 PKCS#7 填充规范,服务器会返回一个特定的 Padding Error;如果填充规范正确但后续的 MAC 校验失败。 利用这一逻辑,攻击过程可分为以下几个严谨的步骤: 1. 爆破单字节的中间状态 攻击者截获 Ci−1 和 Ci 后,开始遍历修改 Ci−1 的最后一个字节(从 0x00 到 0xFF),并发送给服务器。 当服务器没有返回 Padding Error 时,意味着当前被篡改的密文解密后,结尾恰好符合 PKCS#7 规范中长度为 1 的填充,即明文的最后一个字节极大概率是 0x01。 ...

2026年3月12日 · sdttttt

博客再次迁移

虽然用NotionNext的时候隐隐约约就知道还是会有这么一天,不过比以前方便多了。 notion因为API改动,NotionNext的blog瘫痪了,我想了半天还是用回老方法吧,也就是使用hugo部署。 不过写了这么多年,我的blog已经文章太多了,所以这次让openclaw干了这活,模型使用的是GLM-5 一开始尝试了直接去web页面上扒取,很遗憾,任务比较复杂,上下文消耗完了,后续我导出了markdown和csv再尝试取做, 这次倒是可以了,虽然不是特别复杂的活,不过这种折腾让openclaw干感觉确实是非常适合…

2026年3月12日 · sdttttt

关于Go语言在操作系统上的调度

最近一直在写Go,对这个语言的体会也是越来越深。 go作为编译类语言,能直接构建对应平台的二进制文件,但是实际上语言本身也是有runtime的。 这点其实和其他语言很不一样,所以这篇文章就来讲讲这个。 首先需要知道,go编译出来的二进制,确实是实打实的纯汇编产物。 中间不会经过任何的指令翻译层,这也意味着不需要模拟任何寄存器的操作。 具体,go是如何在纯指令的汇编中加入rt的: goroutine是什么具体我就不说了,主要讲讲goroutine是如何调度的。 首先go的rt并不是时时刻刻都在运行的,因为go不是虚拟机语言,没有寄存器模拟这种东西。 系统线程在执行指令的时候rt不可能会插入进来。 rt具体是如何介入指令之中的: go编译器在编译go代码的时候会在生成的汇编指令中加入一些安全点,也可以叫做桩。 运行到安全点后,goroutine的上下文就会保存,rt就会开始运行,判断此刻是否要让出资源,或者要不要做GC

2026年1月22日 · sdttttt

下一个想写的项目

最近再配置自己的服务器,有些nginx和docker-compose的配置文件一直在调整。 其中有个很操蛋的事情,就算代码格式化的问题,docker-compose还好,nginx的配置我已经写了接近300行,很多嵌套都相当的乱,因为有lua的部分,但是nginx的配置格式化程序真的很少,vscode我懒得去用,那玩意一打开我的电脑就开始咆哮了。 一些偏门配置文件格式的格式化程序真的很少。 后面我打算写一个能格式化任何代码的lint工具,不过语法词法分析是我的弱项,这块我基本也没接触过,估计后面有时间看看吧。

2025年9月13日 · sdttttt

C草的编译期编程:元模版

最近一直在写time-devourer这个项目,我的多态设计的强迫症又犯了,不可避免和元模版打交道了。这篇文章简单的稍微讲一下我遇到的几个场景。 自动包装COM对象指针,生命周期结束自动调用Release。要求T必须能调用Release方法,且T必须是IUnknown的子类。 头一次写模版元编程给我肘晕了,这就是编译期编程么,害怕. 这里稍微总结一下几个点,最上面的is_com_interface有两个模版参数, 第一个是T, 第二个是void, void没什么意义,主要用来做特化匹配. 下面是重点,typename T只有一个参数,默认外部调用的都是这个模板,例如COMPtr std::void_t<条件…> 这个条件如果成立,就会变成void, 就能用上这个模版了 第一个条件:decltype(std::declval().Release()) std::declvar 这个函数返回一个Type的对象, 用来模拟运行时的情况,然后可以模拟调用运行时的方法. decltype是一个类型提取器,返回值就是一个Type,运行方式sizeof很像,只能在编译期运行,不能在运行时调用。 如果declval模拟的对象的Release方法调用失败了,那就没有返回值,decltype就会报错,就无法匹配到这个模版了。 第二个条件:std::enable_if_t<std::is_base_of_v<IUnknown, T» 这个简单一些,判断T是否是IUnknown的子类,是就enable_if_t 返回 void 不是就enable_if_t 触发SFINAE,退回上面的模版。 template <typename T, typename = void> struct is_com_interface : std::false_type { }; template <typename T> struct is_com_interface< T, std::void_t< decltype(std::declval<T>().Release()), std::enable_if_t<std::is_base_of_v<IUnknown, T>> > > : std::true_type { }; template <typename T> class COMPtr { static_assert(is_com_interface<T>::value, "Type is not a COM object"); T* com_ptr; public: COMPtr() : com_ptr(nullptr) { } COMPtr(std::nullptr_t) : com_ptr(nullptr) { } COMPtr(T* p) : com_ptr(p) { } ~COMPtr() { if (com_ptr) com_ptr->Release(); }; explicit operator bool() const { return com_ptr != nullptr; } T* Get() { return com_ptr; } T* operator->() { return com_ptr; } T** GetAddressOf() { if (com_ptr) { com_ptr->Release(); } com_ptr = nullptr; return &com_ptr; } }; 写了C++才发现,Rust这个语言本身很多的原语设计都是沿袭了C++的设计。 ...

2025年9月10日 · sdttttt

关于Chrome密码管理器的实现逻辑逆向研究

目前存在一个场景:需要填充网页上的账号密码模拟登陆,但是不能触发或者给chrome的密码管理器识别到真实的账号密码。 以前其实就比较好奇这个密码管理器是如何正确抓取网页上的密码的。 经过了一天的研究,大概可以得出几个结论了,我不保证这个逻辑是正确的,但是如果有其他人需要参考我想还是能提供一些帮助的,当然也可能存在其他我没观测到的情况: 在网页加载完成后密码管理器就会根据某种dom查找的算法逻辑找出网页中存在的账号密码input, 然后持续的跟踪这些dom元素的状态信息。 因为存在dom状态跟踪,所以修改input的value时,密码管理器的值也会产生变化。 弹出是否保存密码的时候一般是涉及网页跳转(URL变化) 我这里目前只想到了2种方案来达成目标: 第一种:在触发登陆后,在网页跳转执行前,将账号密码修改为fake值,这样就能在跳转后骗过密码管理器。 第二种:断开密码管理器对dom状态的跟踪,使用重新删除和创建dom的方式来完全规避密码管理器. 这个方法是最直接有效。但是目前大部分的前端应用会使用Vue/React等框架,他们的原理是虚拟dom渲染,这个方法会影响这些响应式运行时实现。

2025年9月5日 · sdttttt

全局描述符

这个东西其实很多,但是文章里不想细讲,我就大概的讲一下,这个篇文章还是偏笔记为主。 总之全局描述符这个东西是继实模式之后保护模式推出的一种内存管理结构。 在实模式下访问内存基本就是靠“段地址+偏移地址=线性地址”这种方式来访问。 保护模式下兼容了原先的内存访问方式,并且加强了安全性,还扩大了寻址范围。 全局描述表(GDT),下对所有管理的内存就会以全局表述符的方式保存。 上图有些字段被拆开zz了,例如段基址被拆分再了三个不连续的内存上。这是因为为了兼容一些遗留问题导致的。具体就别想了。实际使用的时候需要组装成连续的数据。 下面就简单的解释一下每个位的意义: 位31-24:段基地址高8位 存储段起始物理地址的最高字节(bits 24-31),与低16位共同组成32位基地址。 位23:G(Granularity)- 粒度位 0 = 段界限以 1字节 为单位(传统模式) 1 = 段界限以 4KB 为单位(现代OS必选) 位22:D/B(Default Operation Size)- 操作数/栈大小 代码段: 1=32位指令(EIP),0=16位指令(IP) 数据段: 1=32位栈(ESP),0=16位栈(SP) 位21:L(Long Mode)- 64位模式 1= 进入64位长模式(x86-64) 0= 保持32/16位模式 位20:AVL(Available)- 保留位 操作系统自由使用(通常为0) 位19-16:段界限高4位 段界限的最高4位(bits 16-19),与低16位构成20位总界限值。 位15:P(Present)- 存在位 1= 段在内存中(访问时触发缺页异常) 0= 段不在内存(访问时触发#NP异常) 位14-13:DPL(Descriptor Privilege Level)- 特权级 00= 特权级0(内核) 01= 特权级1 10= 特权级2 11= 特权级3(用户程序) 位12:S(Descriptor Type)- 描述符类型 1= 代码/数据段 0= 系统段(TSS/LDT等) 位11-8:TYPE - 段类型(S=1时生效) 值 代码段 数据段 3 1(可执行) 0(向上扩展) 2 1(一致性) 1(可写) 1 1(可读) 1(允许写入) 0 1(已被访问) 1(已被访问) 例:只执行代码段 = 1000b,可写数据段 = 0010b ...

2025年8月9日 · sdttttt

软路由更新日志

最近发现clash(mihomo)核心的内存使用率有明显问题,用几天这玩意就能吃200M以上的内存了,再后面就不得不重启。 并且这个核心的DNS性能也存在部分问题,我不得不使用chinadns-ng来代替clash内部的dns. 以上两点,我在这几天尝试迁移到了全新的代理平台核心,可能有听过这个的名字,没错它就是sing-box 很早的时候我就考虑过迁移到sing-box,只不过哪个时候sing-box的生态还存在一些问题。 我去评价一个代理软件,有几个很重要的点。 机场兼容:一是我不会自建节点,二我也懒得手动把其他软件的配置转成sing-box的,所以提供机场的原生订阅或者订阅转换这个很重要。 分流功能:这个不用多说了,你也不想局域网内的设备偷跑流量吧。 实用面板:这个基本上得靠社区用爱发电. 之前1和3在sing-box上做的并不好,大部分情况下使用起来还是没有流行的核心像clash那么顺手。 不过现在sing-box有了订阅转换,面板的话官方支持了clashAPI,所以直接用clash的面板也没问题。 你以为现在就能愉快的使用sing-box了吗?FALSE**!** sing-box其实上还有一些问题,在配置面上。 目前sing-box的开发处于高速迭代的状态,核心的配置经常会发生变更。并且sing-box的官方文档,不能说是烂,只能说是屎。 非常的混乱,里面有大量的版本特性标签以及弃用标签,你自己都不知道这些配置里,那些配置能在你的核心上用。 抛开混乱的官方文档不谈,只淡sing-box的配置文件,实际上也相比clash的配置更加的复杂。 相比clash的简单设置代理端口以及机场,sing-box需要你设置inbound和outbound口,并且DNS也算是bound的一部分。 不过熟悉了sing-box的配置后还是能感受到它的设计非常的符合直觉。 这个功能之间的模块化相比clash更加先进。 打个比方,你可以把sing-box配置成一个DNS分流工具,就像mosdns一样。 具体还是建议自己上手玩一下。 讲了这么多,我花了2天已经是完全把配置迁移过来了,clash的性能问题在sing-box上完全没有。 并且内存占用对比clash也有所降低,对我这种小内存设备来说简直福音。 不过sing-box的核心是不敢更新的… 目前我用的是1.12版本,这里贴一个我的配置文件吧: { "log": { "level": "debug", "timestamp": true }, "dns": { "servers": [ { "tag": "dns_local", "type": "local" }, { "tag": "dns_proxy", "type": "tls", "server": "8.8.4.4", "detour": "♻️ 自动选择" }, { "tag": "dns_ali", "type": "h3", "server": "dns.alidns.com", "domain_resolver": "dns_resolver" }, { "tag": "dns_nextdns", "type": "tls", "server": "", "domain_resolver": "dns_resolver" }, { "tag": "dns_resolver", "type": "tls", "server": "223.6.6.6" }, { "tag": "dns_fakeip", "type": "fakeip", "inet4_range": "198.18.0.0/15", "inet6_range": "fc00::/18" } ], "rules": [ { "query_type": ["HTTPS"], "action": "reject", "disable_cache": true }, { "domain_suffix": ["lan"], "server": "dns_local", "disable_cache": true }, { "rule_set": [ "geosite-geolocation-cn", "geosite-apple", "geosite-microsoft" ], "strategy": "prefer_ipv6", "server": "dns_ali" }, { "rule_set": ["geosite-apple"], "server": "dns_ali" }, { "rule_set": [ "geosite-google", "geosite-category-porn", "geosite-twitter", "geosite-reddit", "geosite-telegram", "geosite-pixiv", "geosite-category-social-media-!cn", "geosite-category-ai-!cn", "geosite-gfw" ], "query_type": ["A", "AAAA"], "server": "dns_fakeip", "disable_cache": true }, { "rule_set": ["geosite-geolocation-!cn"], "server": "dns_nextdns" } ], "final": "dns_ali", "strategy": "prefer_ipv6", "reverse_mapping": true, "independent_cache": true }, "ntp": { "enabled": true, "server": "time.windows.com", "server_port": 123, "interval": "30m", "detour": "DIRECT" }, "inbounds": [ { "type": "direct", "tag": "dns-in", "listen": "::", "listen_port": 1053 }, { "type": "tun", "tag": "tun-in", "interface_name": "singtun", "stack": "gvisor", "address": ["172.18.0.1/30", "fdfe:dcba:9876::1/126"], "mtu": 9000, "auto_route": true, "strict_route": true, "route_address": ["198.18.0.0/15", "0.0.0.0/0", "fc00::/18", "::/0"] } ], "outbounds": [ ], "route": { "default_domain_resolver": { "server": "dns_ali", "strategy": "prefer_ipv6" }, "rule_set": [ { "tag": "geoip-cn", "type": "local", "format": "binary", "path": "geoip-cn.srs" }, { "tag": "geosite-gfw", "type": "local", "format": "binary", "path": "geosite-gfw.srs" }, { "tag": "geosite-category-porn", "type": "local", "format": "binary", "path": "geosite-category-porn.srs" }, { "tag": "geosite-google", "type": "local", "format": "binary", "path": "geosite-google.srs" }, { "tag": "geosite-twitter", "type": "local", "format": "binary", "path": "geosite-twitter.srs" }, { "tag": "geosite-reddit", "type": "local", "format": "binary", "path": "geosite-reddit.srs" }, { "tag": "geosite-telegram", "type": "local", "format": "binary", "path": "geosite-telegram.srs" }, { "tag": "geosite-geolocation-cn", "type": "local", "format": "binary", "path": "geosite-geolocation-cn.srs" }, { "tag": "geosite-geolocation-!cn", "type": "local", "format": "binary", "path": "geosite-geolocation-!cn.srs" }, { "tag": "geosite-apple", "type": "local", "format": "binary", "path": "geosite-apple.srs" }, { "tag": "geosite-microsoft", "type": "local", "format": "binary", "path": "geosite-microsoft.srs" }, { "tag": "geosite-pixiv", "type": "local", "format": "binary", "path": "geosite-pixiv.srs" }, { "tag": "geosite-category-social-media-!cn", "type": "local", "format": "binary", "path": "geosite-category-social-media-!cn.srs" }, { "tag": "geosite-category-ai-!cn", "type": "local", "format": "binary", "path": "geosite-category-ai-!cn.srs" }, { "tag": "geosite-category-ads", "type": "local", "format": "binary", "path": "geosite-category-ads.srs" } ], "rules": [ { "inbound": "dns-in", "action": "hijack-dns" }, { "action": "sniff", "sniffer": ["dns", "bittorrent"], "timeout": "500ms" }, { "protocol": "dns", "action": "hijack-dns" }, { "protocol": "bittorrent", "outbound": "DIRECT" }, { "domain_keyword": ["sing-box"], "outbound": "🚀 节点选择" }, { "rule_set": [ "geosite-google", "geosite-category-porn", "geosite-twitter", "geosite-reddit", "geosite-pixiv", "geosite-category-social-media-!cn", "geosite-category-ai-!cn", "geosite-gfw" ], "outbound": "🌍 国外媒体" }, { "rule_set": ["geosite-telegram"], "outbound": "📲 电报信息" }, { "rule_set": ["geosite-category-ads"], "outbound": "🍃 应用净化" }, { "rule_set": ["geoip-cn"], "outbound": "🎯 全球直连" } ], "auto_detect_interface": true, "final": "🐟 漏网之鱼" }, "experimental": { "cache_file": { "enabled": true, "store_fakeip": true }, "clash_api": { "external_controller": "0.0.0.0:9999", "external_ui": "ui", "secret": "", "default_mode": "Rule" } } }

2025年6月21日 · sdttttt

BIOS最初的引导

由于操作系统本身时存放在硬盘中,并非能在bios引导后直接在内存中运行,所以才有了BIOS引导操作系统的这个步骤。 在主板上电之后,BIOS需要去找到引导程序的位置,这个位置固定在(0磁头0柱面1扇区)的位置,这个扇区也被称为主引导扇区(MBR)。一个扇区大小固定为512字节。也就是boot_loader的大小也必须在512字节以内。 有趣的是主引导扇区有一个约定,那就是最后两个字节必须是0xAA55, 也就是实际留给bootloader的大小只有510字节,不过这也非常够用了。 上文一直有说到一个词叫做BootLoader,这个其实按照字面意思理解就可以,就是一个启动操作系统的程序。BootLoader主要就写一些控制硬盘的代码,把硬盘里的一些数据给放在内存里。不过内存也不是什么位置都可以放的,有些内存是给显卡用,有些是硬件保留的。留给开发者使用的内存当然也是定死的辣。默认执行完引导扇区的代码后,就会去指向0x7c00该内存地址的指令。所以操作系统都是写入在0x7c00这个内存地址里的。 后面的东西以后在说,不过知道这些之后,恭喜你,至少你已经知道了怎么在计算机上编写一个裸机程序了,配合中断表写个helloworld完全不是问题。

2025年5月14日 · sdttttt

通过函数指针来运行汇编(机器码)

之前已经知道了函数这个玩意在计算机中的本质,本身在栈中存放其实就是一个指针的方式,而指针指向的内容就是在内存中存放的机器码。 解释到这里就已经很明显了,函数其实并不是一定要在编译期载入内存后静态,也就是固定的方式运行的。 我们可以手动开辟内存,输入机器码,然后让指针指向这块内存。从而实现在运行时动态生成函数。 哦呼~想想就有点激动,这个逻辑其实就有点像动态语言,例如PHP,JAVA,Python的解释器的工作方式。 而且这个方式可以不用在语言中编写汇编,可以绕过某些平台无法在C中编写汇编的限制。 首先我们得获取机器码,我这里直接通过反汇编直接获取。 //0x00005555555547a7 <+0>: 55 push rbp //0x00005555555547a8 <+1>: 48 89 e5 mov rbp,rsp //0x00005555555547ab <+4>: b8 01 00 00 00 mov eax,0x1 //0x00005555555547b0 <+9>: 5d pop rbp //0x00005555555547b1 <+10>: c3 ret int ret() { return 1; } 然后把这些机器码写进内存, 之后进行类型强转之后,就可以直接运行。 char ret_code[] = { 0x55, 0x48, 0x89, 0xe5, 0xb8, 0x10, 0x00, 0x00, 0x00, 0x5d, 0xc3 }; void *tmp = mmap( NULL, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0 ); memscp3(tmp, ret_code, sizeof(ret_code)); ret_p p = tmp; int bbb = p(); printf("%d\n", bbb);

2025年5月7日 · sdttttt