Chr_小屋 - x
type: channel
link: https://blog.chrxw.com/tag/x
items: 使用C#源生成器自动完成服务注册
items: 【油猴脚本教程】P1 开发者工具的使用 & JavaScript 基础教程
items: 使用GitHub Action自动编译OpenWrt固件
items: 安卓Frida RPC的一些简单应用
items: 小黑盒逆向分析笔记（五）【未完成】
items: WIFI VFD API 文档
items: 【树莓派】简约令牌时钟
items: 【过期】京东炸年兽自动脚本【1.8】

使用C#源生成器自动完成服务注册
type: item
date: 2024-01-24T11:28:19+08:00
description: 前言 完整项目地址: https://github.com/chr233/XinjingdailyBot 因为项目之前是使用运行时反射来完成服务的动态注册，而源生成器是在编译前完成代码生成并参与编译，完全没有运行时的性能开销 运行时反射 - 旧的方案 标记特性 首先创建一个 Attribute, 用来标注需要扫描的服务类 namespace XinjingdailyBot.Infrastructure.Attribute; /// /// 标记服务 /// 1、如果服务是本身 直接在类上使用[AppService] /// 2、如果服务是接口 在类上使用 [AppService(ServiceType = typeof(实现接口))] /// [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class AppServiceAttribute : System.Attribute { /// /// 服务声明周期 /// 不给默认值的话注册的是AddSingleton /// public LifeTime ServiceLifetime { get; set; } = LifeTime.Scoped; /// /// 指定服务类型 /// public Type? ServiceType { get; set; } /// /// 是否可以从第一个接口获取服务类型 /// public bool InterfaceServiceType { get; set; } /// /// 标记服务 /// /// public AppServiceAttribute(LifeTime serviceLifetime) { ServiceLifetime = serviceLifetime; } /// /// 标记服务 /// /// /// public AppServiceAttribute(Type? serviceType, LifeTime serviceLifetime) { ServiceLifetime = serviceLifetime; ServiceType = serviceType; } /// /// 标记服务 /// /// /// /// public AppServiceAttribute(Type? serviceType, LifeTime serviceLifetime, bool interfaceServiceType) { ServiceLifetime = serviceLifetime; ServiceType = serviceType; InterfaceServiceType = interfaceServiceType; } } /// /// 生命周期 /// public enum LifeTime { /// /// 瞬时 /// Transient, /// /// 范围 /// Scoped, /// /// 单例 /// Singleton } 基于反射的服务扫描器 然后再主项目启动的时候使用反射扫描添加了该 Attribute 的所有 Class 并注册服务 using System.Diagnostics.CodeAnalysis; using System.Reflection; using XinjingdailyBot.Infrastructure.Attribute; namespace XinjingdailyBot.WebAPI.Extensions; /// /// 动态注册服务扩展 /// public static class AppServiceExtensions { private static readonly NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger(); /// /// 注册引用程序域中所有有AppService标记的类的服务 /// /// [RequiresUnreferencedCode("不兼容剪裁")] public static void AddAppService(this IServiceCollection services) { var cls = new string[] { "XinjingdailyBot.Repository", "XinjingdailyBot.Service", "XinjingdailyBot.Command", "XinjingdailyBot.WebAPI", }; foreach (var item i…

【油猴脚本教程】P1 开发者工具的使用 & JavaScript 基础教程
type: item
date: 2022-08-08T23:37:00+08:00
description: P1 开发者工具的使用 与 JavaScript 基础教程 教程索引 视频链接 (上) 视频链接 (下) 开发者工具的使用 F12 或者 菜单 -> 更多工具 -> 开发者工具 调出 切换中文 更改语言后重新打开开发者工具生效 或者 界面简介 元素 HTML 原文 HTML节点的属性 控制台 在任意Tab按ESC可以在下方调出 源代码 浏览网站中的所有资源 调试代码 网络 抓包工具 应用 查看本地储存的内容 Cookie Session LocalStorage JavaScript 基础教程 一些 JavaScript 学习资源 MDN 文档： https://developer.mozilla.org/zh-CN/docs/Web/JavaScript 廖雪峰： https://www.liaoxuefeng.com/wiki/1022910821149312 菜鸟教程： https://www.runoob.com/js/js-tutorial.html uTools 插件 基本特性 单线程 弱类型 / 动态类型 大小写敏感 ' ," 通用 不强制使用分号 (建议统一) 语法很灵活 运行时: v8, NodeJS 基础语法 注释 //单行注释 /* 多行注释 */ 变量与常量 赋值操作符 var let const //定义变量/常量 var a = 1; var b; let c = 1; let d; const e = 4; //a,b,c,d,e 1 undefined 1 undefined 4 console.log("a,b,c,d,e",a,b,c,d,e); //Uncaught SyntaxError: Missing initializer in const declaration const f; //赋新值 a = 2; c = 3; //Uncaught TypeError: Assignment to constant variable. e = 1; //重复定义 //undefined var a = 6; let c = 6; //Uncaught SyntaxError: Identifier 'a' has already been declared let a = 6; //Uncaught SyntaxError: Identifier 'a' has already been declared const a = 6; //常量覆盖 const e = 6; //e 6 console.log("e",e); //在子域中覆盖(ES6引入) var ts = 1; let he = 2; { var ts = "aaa"; let he = "yyy"; //ts,he aaa yyy console.log("ts,he",ts,he); } //ts,he aaa 2 console.log("ts,he",ts,he); var 定义一个 变量 let 定义一个仅在作用域内生效的 变量 const 定义一个 常量，一旦定义无法修改，但是可以覆盖定义 使用相同的操作符可以覆盖定义同名的变量，但是不建议这么使用 使用 let 覆盖定义时不影响父作用域的同名变量 强烈推荐优先使用 let 定义变量，如果值不需要改变，建议使用 const 定义成常量 函数提升 //函数提升 foo(); // "bar" function foo() { console.log("bar"); } //这种写法的函数不会被提升 bar(); // 类型错误：bar 不是一个函数 var bar = function() { console.log("bar2"); }; 作用域 { } 定义代码作用域 (ES6的内容) 使用 let 定义的变量，在子域可以访问父域的变量，但是父域不能访问子域的变量 函数中定义的内容外部无法访问 子域中定义的函数，父域无法访问 //顶级作用域(最外层的作用域) let x = 114; { //子作用域 let b = 514; //x,b 114 514 console.log("x,b", x, b); } //函数内部其实也是一种作用域 function xbz() { let c = 233; //x,c 114 233 console.log("x,c", x, c); //Uncaught ReferenceError: b is not defined console.log("b", b); } xbz(); //匿名函数同理 (() => { let d = 3.14; //x,d 114 3.14 console.log("x,d", x, …

使用GitHub Action自动编译OpenWrt固件
type: item
date: 2022-01-28T22:06:00+08:00
description: 前言 本教程基于 LEDE 项目 首先需要安装Linux虚拟机或者使用WSL虚拟环境，这里使用后者 正文 准备工作 首先自然是配置依赖环境 sudo apt -y update sudo apt -y install build-essential asciidoc binutils bzip2 gawk gettext git libncurses5-dev libz-dev patch python3 python2.7 unzip zlib1g-dev lib32gcc1 libc6-dev-i386 subversion flex uglifyjs git-core gcc-multilib p7zip p7zip-full msmtp libssl-dev texinfo libglib2.0-dev xmlto qemu-utils upx libelf-dev autoconf automake libtool autopoint device-tree-compiler g++-multilib antlr3 gperf wget curl swig rsync 因为要对LEDE固件做一些修改，所以我们需要去Folk一份源码 然后把刚刚Folk的仓库clone到本地 x> 请不要下载Zip存档，这样操作会丢失git配置，导致后续操作无法继续 # PS: 这里换成自己的仓库的地址 git clone https://github.com/chr233/lede !> 请确保处于大小写敏感的文件系统下，否则无法使用配置工具（NTFS和FAT都不行, EXT4可以） 然后使用命令就可以进入配置菜单了 make menuconfig 编译配置菜单说明（部分） Target System (Broadcom BCM27xx) #选择处理器架构 Subtarget (BCM2711 boards (64 bit)) #选择处理器 Target Profile (Raspberry Pi 4B/400/4CM (64bit)) #预制配置文件 Target Images #固件映像设置 ramdisk # 内存盘, 硬件内存充裕的话可以打开，有效提升性能 Compression # 压缩等级(none表示不压缩) Root filesystem archives #根文件系统存档类型 cpio.gz tar.gz Root filesystem images #根文件系统格式 ext4 #适用于大容量闪存,易于修改分区大小,没有恢复出厂设置的功能 squashfs #适用于小容量闪存，不可修改分区大小，有恢复出厂设置的功能 Gzip images #Gzip存档 Image Options Kernel partition size #内核分区大小，建议64M到256M，足够了 Root filesystem partition size #跟文件系统分区大小，根据闪存大小自行设定 Make /var persistent #持久化/var(开启后重启软路由/var下内容会保留) 以下设置一般无需修改 Enable experimental features by default #默认启用实验性功能 Global build settings #全局编译设置 Advanced configuration options (for developers) #高级选项(仅供开发者) Build the OpenWrt Image Builder #编译OpenWrt镜像编译器 Build the OpenWrt SDK #编译OpenWrt SDK Package the OpenWrt-based Toolchain #打包OpenWrt工具链 Image configuration #镜像选项 以下可以根据需要选择要加入的附加组件 i> M代表编译成ipk包，不内置在固件中，可以之后单独安装，*代表内置在固件中 Base system #基本组件 Administration #管理员工具 Boot Loaders #引导程序 Development #开发者工具 Extra packages #额外包 Firmware #固件工具 Fonts #字体 Kernel modules #内核模块 Languages #额外的语言(Python3,PHP,NodeJS等) Libraries #系统库 LuCI #LuCI插件(一般只需修改应用和主题) Collections #合集 Modules #模块 Applications #应用程序 Themes #主题 Protocols #协议支持 Libraries #运行库…

安卓Frida RPC的一些简单应用
type: item
date: 2021-11-19T19:35:00+08:00
description: 前言 本文仅供学习探讨之用，如果侵犯您的权益请联系我删除。 前几天研究某APP未果，中间稍微研究了一些Frida的RPC机制，做一下笔记。 Frida RPC简介 Frida RPC可以将APP中的函数暴露给加载器，方便在外部调用，相比于recv和send，使用起来更加方便。 示例hook脚本 rpc.exports = { add(a, b) { //同步函数 return a + b; }, sub(a, b) { //异步函数 return new Promise(resolve => { setTimeout(() => { resolve(a - b); }, 100); }); } }; 示例Python加载器 import codecs import frida def on_message(message, data): if message['type'] == 'send': print(message['payload']) elif message['type'] == 'error': print(message['stack']) session = frida.attach('iTunes') with codecs.open('./agent.js', 'r', 'utf-8') as f: source = f.read() script = session.create_script(source) script.on('message', on_message) script.load() #调用rpc暴露出来的add和sub方法 print(script.exports.add(2, 3)) print(script.exports.sub(5, 3)) session.detach() Frida RPC使用演示 Java层 Java函数的Hook又分为静态方法和实例方法，静态类直接用Java.use('类名')获取以后直接就可以调用，而实例方法需要先用Java.choose('类名')，在堆中查找到类的实例以后获取该实例，才能调用。 静态方法 实例方法 So层 SO层如果是静态方法也跟Java层的类似，只不过需要获取上下文对象，并作为第一个参数传入。 JNI方法 演示代码 Hook代码 console.log("脚本载入成功"); Java.perform(function () { rpc.exports = { staticmethod() { var n1 = Java.use('com.max.xiaoheihe.utils.n1'); return n1.E(); }, classmethod() { var result = undefined; Java.choose('com.max.xiaoheihe.bean.account.AccountDetailObj', { onMatch: function (instance) { result = instance.getUserid(); }, onComplete: function () { if (result === undefined) { return 'not found'; } } }); return result; }, jnimethod(urlpath, timestamp, nonce) { var currentApp = Java.use('android.app.ActivityThread').currentApplication(); var context = currentApp.getApplicationContext(); var NDKTools = Java.use('com.max.xiaoheihe.utils.NDKTools'); return NDKTools.encode(context, urlpath, timestamp, nonce); }, }; }); 加载器 import frida device = frida.get_usb_device() process = device.attach('小黑盒') with open('demo.js', 'r', encoding='utf-8') as f: script = process.create_script(js.read()) script.load() print('static method: ', script.exports.staticmethod()) print('class method: ', script.exports.classmethod()) prin…

小黑盒逆向分析笔记（五）【未完成】
type: item
date: 2021-11-19T13:28:00+08:00
description: 前言 本文仅供学习探讨之用，如果侵犯您的权益请联系我删除。 上文 [post cid="714"/] [post cid="1347"/] [post cid="1365"/] [post cid="1383"/] 前情提要 距离上一篇文章已经过去一年多了，前两天心血来潮找了最新版本研究了一下，熬了两天夜也没整明白，不打算继续研究下去了，做个笔记记录下分析过程。 @> 本文适用于小黑盒 1.3.191，小黑盒历史版本参考我的仓库 @> 全文评论后可见

WIFI VFD API 文档
type: item
date: 2021-05-07T23:57:00+08:00
description: 前言 项目演示: BV1Sy4y1u7s8 固件源码: WiFi_VFD 配置工具源码: WiFi_VFD_tools 默认连接密码为CHR233，AP模式热点名为WIFI_VFD_xxxxxx，AP密码为CHRXWCHRXW 注意事项 API请求方法不限，推荐使用GET方法，只接受位于Params的参数 未给定的参数均视为 【“”】或【0】 文档性API 所有文档性API均不需要身份验证 / 说明 返回在线文档地址 请求参数 无需参数 返回值示例 online doc: https://blog.chrxw.com/archives/1970/01/01/1569.html /version 说明 返回固件版本号和编译日期 请求参数 无需参数 返回值示例 Firmware version: 1.7.0 Build date: May 7 2021 23:59:18 /test 说明 固定返回 success , 用于检查固件是否正常 请求参数 无需参数 返回值示例 success 功能性API 所有功能性API均需要提供一个名为【k】的连接密码参数，如果密码错误则返回【403】错误 如果设备的连接密码为空，代表不需要身份验证，传入任意的【k】或者不传入【k】都能通过身份验证 设备重启后会自动从闪存读取上一次保存的设置 为了降低闪存的读写损耗，默认不会将修改的设置写入闪存，如果需要保存此次设置，可以传入名为【f】的任意非空值作为参数 例如 http://....../set/mode?m=1&f=1 所有功能性API如果调用成功均返回 success, 后文均省略返回值 功能性API - 通用 /set/mode 说明 设置当前模式 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" m 模式代码 1-3 【1】:时钟模式, 2:文字模式, 3: 进度条模式 必须 f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 /set/wifi 说明 设置WiFi连接，无需【f】参数 如果测试连接成功会自动写入闪存并自动重启 如果测试连接失败则会使用上一次的设置 参数 |参数名|意义|取值范围|说明|默认值| |-|-|-| |k|连接密码|文本|用于身份验证|""| |s|WiFi SSID|文本|WiFi的名称|必须| |p|WiFi 密码|文本|WiFi的密码|必须| /set/password 说明 设置设备的连接密码，默认连接密码为【CHR233】，无需【f】参数 参数 |参数名|意义|取值范围|说明|默认值| |-|-|-| |k|连接密码|文本|用于身份验证|""| |kn|新的连接密码|文本|要设置的连接密码|""| /set/bright 说明 设置显示屏亮度 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" b 显示屏亮度 0-255 显示屏亮度，0:完全关闭显示屏，255:最高亮度 0 f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 /set/reboot 说明 重启设备 参数 |参数名|意义|取值范围|说明|默认值| |-|-|-| |k|连接密码|文本|用于身份验证|""| /set/reset 说明 格式化内置闪存，清除所有设置 参数 |参数名|意义|取值范围|说明|默认值| |-|-|-| |k|连接密码|文本|用于身份验证|""| 功能性API - 模式1 - 时钟模式 调用该模式的API后, 会自动切换到模式2, 但是对模式的修改不会写入闪存 /set/time/config 说明 设置时钟的样式 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" h 24小时 布尔 【""/0】:12小时制, 其他值:24小时制 "" a 显示上下午 布尔 【""/0】:不显示AM/PM, 其他值:显示AM/PM "" w 显示星期 布尔 【""/0】:不显示星期, 其他值:显示星期 "" f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 功能性API - 模式2 - 文字模式 调用该模式的API后, 会自动切换到模式2, 但是对模式的修改不会写入闪存 /set/text/icon 说明 控制显示屏上面一排的图标显示 参数【i】当做二进制数处理，从低到高分别对应从左到右的12个图标, 1代表点亮, 0代表熄灭 参数 参数名 意义 取值范围 说明 默认值 k 连接密码 文本 用于身份验证 "" i 0-4095 整数 从高到低的12个二进制位代表从左到右12个图标的亮灭状态 0 f 写入闪存 布尔 【""/0】:不写入闪存，其他值:写入闪存 0 /set/text/sp…

【树莓派】简约令牌时钟
type: item
date: 2021-04-04T01:17:00+08:00
description: 前言 总共花了4天休息时间做的小玩意，支持显示通用TOTP令牌(6位数字)和STEAM令牌(5位)，同时也是个桌面时钟。 源码获取：GitHub 令牌原理 i> 简单的介绍一下两个令牌的原理，不感兴趣的可以参考下一节 原理 令牌类似于短信验证码，是一种一次性密码OTP，核心原理很简单，服务器和用户事先约定好一个Secret，然后用户通过特殊的算法（一般用Secret + 时间戳或者计数器作为参数），得到一串字符，服务器也通过同样的算法得到一串字符，如果两者相同，那么就证明了我是我。 常见的令牌大多是基于时间的令牌TOTP，也就是用当前的时间戳作为输入，只要保证和服务端时间差不超过令牌有效期（常见的实现是30秒）就能确保通过验证。优点是令牌客户端可以不需要联网，缺点是可能因为时间差导致验证失败。 另一种实现是基于HMAC的令牌HOTP，这种令牌需要和服务器约定一个计数器，客户端刷新一次令牌，就告诉服务器，让两边的计数器同步，这样用户和服务器得到的结果才能一样，才能通过验证。缺点显而易见，就是令牌客户端需要联网。 STEAM的令牌系统类似于TOTP，也是基于时间戳的，只是算法略有区别，最明显的区别是普通TOTP计算得到的是6位数字，STEAM令牌是5位数字或字母。 x> Secret一定要保管好，千万不要向他人透露。 i> 完整代码位于utils/twofa.py TOTP详解 伪代码 #. 演示用的中间结果用【】 表示 0. 服务器和用户事先约定了一个密钥Secret，记为K【MNUHE6DXGEZDGNBVGY======】(Base32编码，也有的实现采用Base64编码） 1. 取当前时间戳/30，取整后记为C【53914423】 2. 使用C作为消息，K作为密钥，哈希方式一般为Sha-1，计算HMAC哈希值H【deb27e0ae7e70fc2d2527e6ed231967e26503f72】(16进制) 3. 取H最后一个字节的低4位，用作偏移，记为O【2】(即16进制C的最后一位) 4. 取H的第O到O+4字节【7e0ae7e7】，丢弃最高位，变为32位无符号整数，记为I【7e0ae7e7】(最高位本来就是0，所以不变) 5. 把I转成10进制数字【17602563747】，最低6位就是结果【643943】 Python实现 import hmac import hashlib from base64 import b32decode from time import time import struct def get_totp_auth_code(secret: str, t: int = None): if not t: t = int(time() / 30) key = b32decode(secret) msg = struct.pack(">Q", t) mac = hmac.new(key, msg, hashlib.sha1).digest() offset = mac[-1] & 0x0f binary = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff code = str(binary)[-6:].zfill(6) return code # 演示DEMO print(get_totp_auth_code('MNUHE6DXGEZDGNBVGY======',53914423)) # 输出结果为 643943 Steam令牌详解 伪代码 #. 演示用的中间结果用【】 表示 0. 服务器和用户事先约定了一个密钥Secret，记为K【Y2hyeHcxMjM0NTY=】(Base64编码） 1. 取当前时间戳/30，先取整【53914423】，然后转换成8字节，前方补0，记为C【000000000336ab37】(16进制) 2. 使用C作为消息，K作为密钥，哈希方式一般为Sha-1，计算HMAC哈希值H【deb27e0ae7e70fc2d2527e6ed231967e26503f72】(16进制) 3. 取H最后一个字节的低4位，用作偏移，记为O【2】(即16进制C的最后一位) 4. 取H的第O到O+4字节【7e0ae7e7】，丢弃最高位，变为32位无符号整数，记为I【7e0ae7e7】(最高位本来就是0，所以不变)，为了方便计算转成10进制为【2114643943】 5. 创建一个字母表G【23456789BCDFGHJKMNPQRTVWXY】(去掉了一些容易混淆的字符) 6. 用I【2114643943】对26取余，记为M【9】，然后将I整除26，重复5次，得到5个M【9,13,7,12,25】(10进制) 7. 把5个M作为下…

【过期】京东炸年兽自动脚本【1.8】
type: item
date: 2021-01-18T10:02:00+08:00
description: 前言 本脚本仅供学习交流之用，使用者如何使用与本文作者无关。 AutoJS下载 & 教程 [post cid="1235" /] 脚本 大部分沿用之前的代码，初步测试没有问题，遇到问题欢迎留言反馈 //JD不定时热更新活动页，请关注脚本更新 //遇到问题请在发布页留言： //https://blog.chrxw.com/archives/2021/01/18/1455.html //Ver 1.8 2021.01.23 //By Chr_(chr@chrxw.com) //========================= //更新说明 //重写了任务完成判断方式,防止偶尔报错 //优化数组访问,应该不会再越界报错了 //修复若干bug //新支持浏览频道任务 //修复加购和参加会员任务的问题（需要打开开关） //修复出现限时任务时报错的问题 //========================= //功能配置 //自动加入会员（设为true启用) let auto_join_vip = false; //自动完成加购任务（设为true启用) let auto_add_cart = false; //延时时间倍率（倍数越大延时越久) let sleep_t = 1.0; //========================= auto.waitFor(); console.show(); toast('\n脚本开始运行\n请切换到任务页\nBy Chr_'); for (let i = 0; i < 10; i++) { //等待切换到任务页 if (className("android.view.View").text("我的爆竹").exists() || className("android.view.View").text("集爆竹").exists()) { break; } rsleep(1); } if (!className("android.view.View").textStartsWith("邀请好友助力").exists()) { advclick(className("android.view.View").text("集爆竹").findOnce()); } toast("\n即将开始工作\n中途需退出请按【音量+】终止"); sleep(1000); for (; ;) { let flag = false; let skip = false; let tasks = className("android.view.View").textStartsWith("邀请好友助力").findOnce().parent().parent().children(); for (let i = 0; i < tasks.length; i++) { if (tasks[i].childCount() == 5) { tasks[i].child(4).click(); rsleep(1); advback(); skip = true; } } if (skip) { continue; } for (let i = 0; i < tasks.length; i++) { if (tasks[i].length < 4) { for (let j = 0; j < tasks[i].childCount(); j++) { log(tasks[i].child(j).text()); } continue; } let desc; let title; try { desc = tasks[i].child(2).text(); title = tasks[i].child(1).text(); } catch (e) { log(e); continue; } let btn = tasks[i].child(3); log(i, desc); if (desc.search("8秒") != -1) { if (checks(btn, title)) { advclick(btn); view(); flag = true; } } else if (desc.search("浏览可得") != -1) { if (checks(btn, title)) { advclick(btn); view2(); flag = true; } } else if (desc.search("关注频道") != -1) { if (checks(btn, title)) { advclick(btn); view3(); flag = true; } } else if (auto_join_vip && des…